一、引言
通过FFmpeg命令:
./ffmpeg -i XXX.aac
可以获取到ADTS格式的AAC裸流的音频采样频率、声道数、采样位数、码率等信息:
在vlc中也可以获取到这些信息(vlc底层也使用了FFmpeg进行解码):
所以FFmpeg和vlc是怎样获取到这些信息的呢?它们其实是通过解码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中:
/**
* 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中:
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中:
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:
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函数分析》:
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函数分析》:
skip_bits1(gbc); /* id */
skip_bits(gbc, 2); /* layer */
读取adts_fixed_header的protection_absent、profile_ObjectType、samplingFrequencyIndex属性:
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中:
const int ff_mpeg4audio_sample_rates[16] = {
96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000, 7350
};
通过samplingFrequencyIndex属性得到音频采样频率,单位为Hz:
if (!ff_mpeg4audio_sample_rates[sr])
return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;
跳过private_bit属性。读取channel_configuration属性,也就是音频声道数:
skip_bits1(gbc); /* private_bit */
ch = get_bits(gbc, 3); /* channel_configuration */
跳过original_copy、home、copyright_identification_bit、copyright_identification_start属性:
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):
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属性:
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:
hdr->object_type = aot + 1;
将音频声道数赋值给hdr->chan_config:
hdr->chan_config = ch;
将protection_absent属性的值赋值给hdr->crc_absent。所以hdr->crc_absent为0时,表示CRC校验存在,当hdr->crc_absent为1时,CRC校验不存在:
hdr->crc_absent = crc_abs;
将number_of_raw_data_blocks_in_frame属性的值赋值给hdr->num_aac_frames。所以该ADTS音频帧中有hdr->num_aac_frames个原始数据块:
hdr->num_aac_frames = rdb + 1;
将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:
hdr->sampling_index = sr;
hdr->sample_rate = ff_mpeg4audio_sample_rates[sr];
将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples:
hdr->samples = (rdb + 1) * 1024;
通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:
hdr->bit_rate = size * 8 * hdr->sample_rate / hdr->samples;
将整个ADTS音频帧的长度(包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节)赋值给hdr->frame_length:
hdr->frame_length = size;