音视频入门基础: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没有任何意义:

相关推荐
TechVoyager_824635 分钟前
多形态输入与12 通道音频:IT6615 这颗 HDMI 发射器的接口宽度
音视频·音频芯片·芯片选型·视频发射
Raymond168840 分钟前
AI配音工具长文本合成架构技术解析:全局音色锚定与声学状态跟踪
人工智能·架构·实时互动·视觉检测·音视频·语音识别
hflmwl87 小时前
Wise Data Recovery找回误删的文档照片音乐和视频
音视频·datarecovery·找回误删的文档照片音乐和视频·找回误删的文档照片音乐视频
技灵AI7 小时前
AI 视频转场提示词怎么写?让前后画面衔接更自然
人工智能·prompt·aigc·音视频
youandyouyou9 小时前
跨区培训的屏幕共享怎么做才稳?同屏同步与弱网优化实践
音视频·网络通信
开开心心就好9 小时前
课堂随机点名工具,绿色版免安装双击即用
java·服务器·前端·ffmpeg·音视频·perl·symfony
美狐美颜SDK开放平台10 小时前
直播APP集成美颜SDK,如何梳理第三方组件的数据权限?
开发语言·人工智能·深度学习·音视频·第三方美颜sdk
小鹿软件办公10 小时前
不依赖 FFmpeg 也能录屏?ShareX 22 新增录屏等十余款新工具
ffmpeg·sharex·免费录屏工具
l143723326711 小时前
批量视频硬字幕擦除多少钱?影响报价的关键因素全解析
音视频
重生六道11 小时前
B站视频时长统计:一段JS脚本搞定分P/合集总时长(附倍速计算)
开发语言·javascript·音视频