音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现

=================================================================

音视频入门基础:AAC专题系列文章:

音视频入门基础:AAC专题(1)------AAC官方文档下载

音视频入门基础:AAC专题(2)------使用FFmpeg命令生成AAC裸流文件

音视频入门基础:AAC专题(3)------AAC的ADTS格式简介

音视频入门基础:AAC专题(4)------ADTS格式的AAC裸流实例分析

音视频入门基础:AAC专题(5)------FFmpeg源码中,判断某文件是否为AAC裸流文件的实现

音视频入门基础:AAC专题(6)------FFmpeg源码中解码ADTS格式的AAC的Header的实现

音视频入门基础:AAC专题(7)------FFmpeg源码中计算AAC裸流每个packet的size值的实现

音视频入门基础:AAC专题(8)------FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础:AAC专题(9)------FFmpeg源码中计算AAC裸流每个packet的duration和duration_time的实现

音视频入门基础:AAC专题(10)------FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现

=================================================================

一、引言

通过FFmpeg命令:

bash 复制代码
./ffmpeg -i XXX.aac

可以获取到ADTS格式的AAC裸流的音频采样频率、声道数、采样位数(注意对于AAC这个采样位数没有意义)、码率等信息:

在vlc中也可以获取到这些信息(vlc底层也使用了FFmpeg进行解码):

所以FFmpeg和vlc是怎样获取到这些信息的呢?除了Bit depth(采样位数)外的信息都是通过解码ADTS格式的AAC的Header(adts_fixed_header + adts_variable_header)获取的。执行FFmpeg命令:./ffmpeg -i XXX.aac时,FFmpeg源码内部会调用adts_aac_probe函数检测该文件是否为ADTS格式的AAC裸流(具体可以参考:《音视频入门基础:AAC专题(5)------FFmpeg源码中,判断某文件是否为AAC裸流文件的实现》)。然后如果检测出该文件为ADTS格式的AAC裸流,会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

二、ff_adts_header_parse函数的声明

ff_adts_header_parse函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavcodec/adts_header.h中:

cpp 复制代码
/**
 * Parse the ADTS frame header to the end of the variable header, which is
 * the first 54 bits.
 * @param[in]  gbc BitContext containing the first 54 bits of the frame.
 * @param[out] hdr Pointer to struct where header info is written.
 * @return Returns 0 on success, -1 if there is a sync word mismatch,
 * -2 if the version element is invalid, -3 if the sample rate
 * element is invalid, or -4 if the bit rate element is invalid.
 */
int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr);

FFmpeg对媒体文件/流进行解复用时,会调用avformat_open_input函数,通过avformat_open_input函数内部的av_probe_input_format3函数来检测该文件是否为ADTS格式的AAC裸流。如果是,FFmpeg源码会继续执行avformat_find_stream_info函数读取部分packet(数据包)以获取码流信息。在avformat_find_stream_info函数内会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

所以ff_adts_header_parse函数的作用就是解码ADTS格式的AAC的Header。

形参gbc:既是输入型参数也是输出型参数。指向已经被初始化的GetBitContext对象。关于GetBitContext结构体可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》。

形参hdr:输出型参数,指向一个AACADTSHeaderInfo对象。AACADTSHeaderInfo结构体声明在libavcodec/adts_header.h中:

cpp 复制代码
typedef struct AACADTSHeaderInfo {
    uint32_t sample_rate;
    uint32_t samples;
    uint32_t bit_rate;
    uint8_t  crc_absent;
    uint8_t  object_type;
    uint8_t  sampling_index;
    uint8_t  chan_config;
    uint8_t  num_aac_frames;
    uint32_t frame_length;
} AACADTSHeaderInfo;

执行ff_adts_header_parse函数后,形参hdr指向的对象的成员变量会得到从AAC Header中解码出来的信息。

返回值:解码成功返回包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。解码失败返回一个负数。

三、ff_adts_header_parse函数的定义

ff_adts_header_parse函数定义在libavcodec/adts_header.c中:

cpp 复制代码
int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr)
{
    int size, rdb, ch, sr;
    int aot, crc_abs;

    memset(hdr, 0, sizeof(*hdr));

    if (get_bits(gbc, 12) != 0xfff)
        return AAC_AC3_PARSE_ERROR_SYNC;

    skip_bits1(gbc);             /* id */
    skip_bits(gbc, 2);           /* layer */
    crc_abs = get_bits1(gbc);    /* protection_absent */
    aot     = get_bits(gbc, 2);  /* profile_objecttype */
    sr      = get_bits(gbc, 4);  /* sample_frequency_index */
    if (!ff_mpeg4audio_sample_rates[sr])
        return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;
    skip_bits1(gbc);             /* private_bit */
    ch = get_bits(gbc, 3);       /* channel_configuration */

    skip_bits1(gbc);             /* original/copy */
    skip_bits1(gbc);             /* home */

    /* adts_variable_header */
    skip_bits1(gbc);             /* copyright_identification_bit */
    skip_bits1(gbc);             /* copyright_identification_start */
    size = get_bits(gbc, 13);    /* aac_frame_length */
    if (size < AV_AAC_ADTS_HEADER_SIZE)
        return AAC_AC3_PARSE_ERROR_FRAME_SIZE;

    skip_bits(gbc, 11);          /* adts_buffer_fullness */
    rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */

    hdr->object_type    = aot + 1;
    hdr->chan_config    = ch;
    hdr->crc_absent     = crc_abs;
    hdr->num_aac_frames = rdb + 1;
    hdr->sampling_index = sr;
    hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];
    hdr->samples        = (rdb + 1) * 1024;
    hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;
    hdr->frame_length   = size;

    return size;
}

四、ff_adts_header_parse函数的内部实现分析

ff_adts_header_parse函数中,首先通过memset让形参hdr指向的对象的成员变量清0:

cpp 复制代码
memset(hdr, 0, sizeof(*hdr));

从《音视频入门基础:AAC专题(3)------AAC的ADTS格式简介》可以知道,syncword为嵌入在ADTS流中的一种编码,用于标识ADTS帧的起始位,其占12位,每个位都必须被设置为1,也就是0b111111111111。所以通过下面的if语句来判断是否读取到了syncword,如果没有读取到,返回宏定义AAC_AC3_PARSE_ERROR_SYNC(-0x3030c0a)。关于get_bits函数的用法可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》:

cpp 复制代码
    if (get_bits(gbc, 12) != 0xfff)
        return AAC_AC3_PARSE_ERROR_SYNC;

如果读取到了syncword,继续往下执行,跳过adts_fixed_header的ID和layer属性(因为跳过了ID属性,所以FFmpeg根本不会区分到底是MPEG-2还是MPEG-4的AAC),关于skip_bits1和skip_bits函数的用法可以参考:《FFmpeg源码:skip_bits、skip_bits1、show_bits函数分析》:

cpp 复制代码
    skip_bits1(gbc);             /* id */
    skip_bits(gbc, 2);           /* layer */

读取adts_fixed_header的protection_absent、profile_ObjectType、samplingFrequencyIndex属性:

cpp 复制代码
    crc_abs = get_bits1(gbc);    /* protection_absent */
    aot     = get_bits(gbc, 2);  /* profile_objecttype */
    sr      = get_bits(gbc, 4);  /* sample_frequency_index */

全局数组ff_mpeg4audio_sample_rates定义在libavcodec/mpeg4audio_sample_rates.h中:

cpp 复制代码
const int ff_mpeg4audio_sample_rates[16] = {
    96000, 88200, 64000, 48000, 44100, 32000,
    24000, 22050, 16000, 12000, 11025, 8000, 7350
};

通过samplingFrequencyIndex属性得到音频采样频率,单位为Hz:

cpp 复制代码
    if (!ff_mpeg4audio_sample_rates[sr])
        return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;

跳过private_bit属性。读取channel_configuration属性,也就是音频声道数:

cpp 复制代码
    skip_bits1(gbc);             /* private_bit */
    ch = get_bits(gbc, 3);       /* channel_configuration */

跳过original_copy、home、copyright_identification_bit、copyright_identification_start属性:

cpp 复制代码
    skip_bits1(gbc);             /* original/copy */
    skip_bits1(gbc);             /* home */

    /* adts_variable_header */
    skip_bits1(gbc);             /* copyright_identification_bit */
    skip_bits1(gbc);             /* copyright_identification_start */

读取aac_frame_length属性,即包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。宏定义AV_AAC_ADTS_HEADER_SIZE的值为7,判断aac_frame_length属性的值是否小于7。ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),所以如果aac_frame_length属性的值小于7,表示ADTS Header格式不正确,返回宏定义AAC_AC3_PARSE_ERROR_FRAME_SIZE(-0x4030c0a):

cpp 复制代码
    size = get_bits(gbc, 13);    /* aac_frame_length */
    if (size < AV_AAC_ADTS_HEADER_SIZE)
        return AAC_AC3_PARSE_ERROR_FRAME_SIZE;

跳过adts_buffer_fullness属性,读取number_of_raw_data_blocks_in_frame属性:

cpp 复制代码
    skip_bits(gbc, 11);          /* adts_buffer_fullness */
    rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */

将profile_ObjectType属性的值加1赋值给hdr->object_type。所以MPEG版本为MPEG-4时,如果hdr->object_type为1,表示AAC的规格为AAC Main;hdr->object_type为2,表示规格为AAC LC;hdr->object_type为3,表示规格为AAC SSR;hdr->object_type为4,表示规格为AAC LTP:

cpp 复制代码
hdr->object_type    = aot + 1;

将音频声道数赋值给hdr->chan_config:

cpp 复制代码
hdr->chan_config    = ch;

将protection_absent属性的值赋值给hdr->crc_absent。所以hdr->crc_absent为0时,表示CRC校验存在,当hdr->crc_absent为1时,CRC校验不存在:

cpp 复制代码
hdr->crc_absent     = crc_abs;

将number_of_raw_data_blocks_in_frame属性的值加1赋值给hdr->num_aac_frames。所以该ADTS音频帧中有hdr->num_aac_frames个原始数据块:

cpp 复制代码
hdr->num_aac_frames = rdb + 1;

将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:

cpp 复制代码
    hdr->sampling_index = sr;
    hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];

规格为AAC LC和AAC LTP的AAC,一帧音频数据中采样的次数只允许为1024次。将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples。所以hdr->samples为该ADTS音频帧中采样的次数:

cpp 复制代码
hdr->samples        = (rdb + 1) * 1024;

通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:

cpp 复制代码
hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;

将整个ADTS音频帧的长度(包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节)赋值给hdr->frame_length:

cpp 复制代码
hdr->frame_length   = size;

五、FFmpeg源码对AAC裸流解封装过程中,对Bit depth的处理

从《音视频入门基础:AAC专题(3)------AAC的ADTS格式简介》中可以知道,Bit depth只对PCM数字信号有意义。非PCM格式,如AAC这种有损压缩格式,Bit depth是没有意义的。

FFmpeg源码对AAC裸流解封装过程中会调用avformat_find_stream_info函数,而avformat_find_stream_info函数中会调用aac_decode_init函数对AAC解码器进行初始化。在aac_decode_init函数内部强制让AAC的采样格式赋值为AV_SAMPLE_FMT_FLTP:

cpp 复制代码
static av_cold int aac_decode_init(AVCodecContext *avctx)
{
//...
    avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//...
}

所以使用FFmpeg获取AAC裸流信息时,显示的这个fltp没有任何意义:

相关推荐
「QT(C++)开发工程师」2 小时前
【FFmpeg应用场景概述】
ffmpeg
yunhuibin2 小时前
ffmpeg面向对象——参数配置秘密探索及其设计模式
学习·设计模式·ffmpeg
Rookie也要加油4 小时前
WebRtc一对一视频通话_New_peer信令处理
笔记·学习·音视频·webrtc
heidyxlw8 小时前
局域网视频
音视频
Mr数据杨8 小时前
我的AI工具箱Tauri版-VideoClipMixingCut视频批量混剪
音视频
!学习使我快乐!8 小时前
检测场景变化并将视频按场景分开
音视频
青柠视频云12 小时前
青柠视频云——视频丢包(卡顿、花屏、绿屏)排查
服务器·网络·音视频
华清远见IT开放实验室16 小时前
【项目案例】物联网比较好的10+练手项目推荐,附项目文档/源码/视频
物联网·音视频
superconvert1 天前
主流流媒体的综合性能大 PK ( smart_rtmpd, srs, zlm, nginx rtmp )
websocket·ffmpeg·webrtc·hevc·rtmp·h264·hls·dash·rtsp·srt·flv