一、引言

从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以显示WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的size:

693ed79c59f94ce6af9b7593d45124df.png

这个“size”实际是AVPacket结构体中的成员变量size,为WAV音频文件中某个packet的大小(单位为字节),通过fftools/ffprobe.c中的show_packet函数打印出来:

static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...
    print_val("size",             pkt->size, unit_byte_str);
//...
}

本文讲述这个“size”值是怎样被计算出来的。如果想直接看结论,可以跳到本文的最后,直接看“总结”。

二、FFmpeg源码中计算WAV音频文件每个packet的size值的实现

(一)ff_pcm_default_packet_size函数

size值其实是通过源文件libavformat/pcm.c中的ff_pcm_default_packet_size函数计算出来的:

int ff_pcm_default_packet_size(AVCodecParameters *par)
{
    int nb_samples, max_samples, bits_per_sample;
    int64_t bitrate;

    if (par->block_align <= 0)
        return AVERROR(EINVAL);

    max_samples = INT_MAX / par->block_align;
    bits_per_sample = av_get_bits_per_sample(par->codec_id);
    bitrate = par->bit_rate;

    /* Don't trust the codecpar bitrate if we can calculate it ourselves */
    if (bits_per_sample > 0 && par->sample_rate > 0 && par->ch_layout.nb_channels > 0)
        if ((int64_t)par->sample_rate * par->ch_layout.nb_channels < INT64_MAX / bits_per_sample)
            bitrate = bits_per_sample * (int64_t)par->sample_rate * par->ch_layout.nb_channels;

    if (bitrate > 0) {
        nb_samples = av_clip64(bitrate / 8 / PCM_DEMUX_TARGET_FPS / par->block_align, 1, max_samples);
        nb_samples = 1 << av_log2(nb_samples);
    } else {
        /* Fallback to a size based method for a non-pcm codec with unknown bitrate */
        nb_samples = av_clip(4096 / par->block_align, 1, max_samples);
    }

    return par->block_align * nb_samples;
}

从《音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现》中可以知道:

par->bit_rate为从WAV Header解码出来的音频码率,单位为bits/s。

par->bits_per_coded_sample为从WAV Header解码出来的音频采样位数。

par->channels为从WAV Header解码出来的声道数量。

par->sample_rate为从WAV Header解码出来的音频采样频率,单位为Hz。

par->block_align为从WAV Header解码出来的“区块对齐”,即每个采样点所需的字节数。

ff_pcm_default_packet_size函数中,首先计算出“最大采样”:

max_samples = INT_MAX / par->block_align;

将拿到的音频采样位数保存到变量bits_per_sample中;把拿到的音频码率(单位为bits/s)保存到变量bitrate中:

bits_per_sample = av_get_bits_per_sample(par->codec_id);
bitrate = par->bit_rate;

如果满足条件:从WAV Header中解码出来的音频采样位数、音频采样频率、声道数量都大于0,不使用从WAV Header中解码出来的音频码率,而是根据公式:音频码率 = 采样位数*采样频率*声道,计算:

    /* Don't trust the codecpar bitrate if we can calculate it ourselves */
    if (bits_per_sample > 0 && par->sample_rate > 0 && par->ch_layout.nb_channels > 0)
        if ((int64_t)par->sample_rate * par->ch_layout.nb_channels < INT64_MAX / bits_per_sample)
            bitrate = bits_per_sample * (int64_t)par->sample_rate * par->ch_layout.nb_channels;

宏PCM_DEMUX_TARGET_FPS定义在源文件libavformat/pcm.c中:

#define PCM_DEMUX_TARGET_FPS  10

关于av_clip、av_clip64用法可以参考:《FFmpeg源码:av_clip、av_clip64宏定义分析》、《FFmpeg源码:av_log2函数分析》。

nb_samples为一帧音频数据中采样的数量(次数)。

情况一:如果音频码率大于0,计算上述音频码率(单位为bits/s) ‌÷ 8 ‌÷ 10 ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,然后求该值是2的多少次幂,保存到变量nb_samples中;

情况二:如果音频码率不大于0,计算4096  ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,保存到变量nb_samples中:

if (bitrate > 0) {
        nb_samples = av_clip64(bitrate / 8 / PCM_DEMUX_TARGET_FPS / par->block_align, 1, max_samples);
        nb_samples = 1 << av_log2(nb_samples);
    } else {
        /* Fallback to a size based method for a non-pcm codec with unknown bitrate */
        nb_samples = av_clip(4096 / par->block_align, 1, max_samples);
    }

最后返回“区块对齐” × 一帧音频数据中采样的次数:

return par->block_align * nb_samples;

(二)wav->max_size

从《音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现》中可以知道,FFmpeg源码通过wav_read_header函数解码WAV Header,该函数最后会调用set_max_size函数:

/* wav input */
static int wav_read_header(AVFormatContext *s)
{
//...
    WAVDemuxContext *wav = s->priv_data;
    set_max_size(st, wav);
    return 0;
//...
}

set_max_size函数定义在源文件libavformat/wavdec.c中。可以看到该函数内部会调用ff_pcm_default_packet_size函数,把“区块对齐” × 一帧音频数据中采样的次数的结果赋值给变量max_size。如果max_size小于0,wav->max_size=4096,否则wav->max_size=“区块对齐” × 一帧音频数据中采样的次数:

static void set_max_size(AVStream *st, WAVDemuxContext *wav)
{
    if (wav->max_size <= 0) {
        int max_size = ff_pcm_default_packet_size(st->codecpar);
        wav->max_size = max_size < 0 ? 4096 : max_size;
    }
}

(三)AVPacket结构体得到size值

对于WAV音频文件,FFmpeg源码通过源文件libavformat/wavdec.c的wav_read_packet函数读取一个packet:

static int wav_read_packet(AVFormatContext *s, AVPacket *pkt)
{
//...
    WAVDemuxContext *wav = s->priv_data;
//...
    left = wav->data_end - avio_tell(s->pb);
//...

    size = wav->max_size;
    if (st->codecpar->block_align > 1) {
        if (size < st->codecpar->block_align)
            size = st->codecpar->block_align;
        size = (size / st->codecpar->block_align) * st->codecpar->block_align;
    }
    size = FFMIN(size, left);
    ret  = av_get_packet(s->pb, pkt, size);
    if (ret < 0)
        return ret;
    pkt->stream_index = 0;

    return ret;
}

由《音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现》中可以知道,wav->data_end为该WAV文件的总大小(单位为字节)。avio_tell(s->pb)为读取到该WAV音频文件的第几个字节了(关于avio_tell函数用法可以参考:《FFmpeg源码:avio_tell函数分析》)。所以wav_read_packet函数中,变量left的值等于该WAV音频文件中还剩下多少个字节没被读取:

left = wav->data_end - avio_tell(s->pb);

让变量size拿到wav->max_size的值,也就是“区块对齐” × 一帧音频数据中采样的次数的结果:

size = wav->max_size;

如果“区块对齐” × 一帧音频数据中采样的次数的结果小于“区块对齐”,size的值等于“区块对齐”;否则size的值等于“区块对齐” × 一帧音频数据中采样的次数的结果:

if (st->codecpar->block_align > 1) {
    if (size < st->codecpar->block_align)
        size = st->codecpar->block_align;
    size = (size / st->codecpar->block_align) * st->codecpar->block_align;
}

让size的值取上述得到的size值和“该WAV音频文件中还剩下多少个字节没被读取”中的最小值,这是因为读取WAV音频文件到最后,剩下还未被读取的数据的字节数是不满一个packet的大小的:

size = FFMIN(size, left);

最后通过av_get_packet函数(关于该函数用法可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet函数分析》),增加该packet大小至size个字节,也就是让pkt->size增至size字节,从而设置AVPacket结构体中的size成员变量:

ret  = av_get_packet(s->pb, pkt, size);

三、总结

1.区块对齐(每个采样点所需的字节数)是从WAV音频文件的WAV Header中解码出来的。

2.nb_samples为一帧音频数据中采样的次数。如果音频码率大于0,计算音频码率(单位为bits/s) ‌÷ 8 ‌÷ 10 ‌÷ “区块对齐”的结果,将该结果裁剪到1到“最大采样”的范围内,然后求该值是2的多少次幂,这个最终计算的得到结果就是nb_samples。

3.WAV音频文件每个packet的size值一般为:区块对齐 × nb_samples。如果读取到WAV音频文件的最后,size值为剩下的还未被读取的不满一个packet大小的字节数。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部