=================================================================
音视频入门基础: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的实现
音视频入门基础:AAC专题(11)——AudioSpecificConfig简介
音视频入门基础:AAC专题(12)——FFmpeg源码中,解码AudioSpecificConfig的实现
音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
=================================================================
一、引言
对于携带Audio Specific Config的媒体文件,比如音频压缩编码格式为AAC的FLV文件,FFmpeg一般通过解码其Audio Tag中Audio Specific Config获取其音频信息。而通过《音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件》生成的AAC裸流文件和TS流中的AAC是没有Audio Specific Config的,只有ADTS Header,这时就得通过解码ADTS Header获取其音频信息(音频压缩编码格式的profile、音频采样率、音频声道数、码率等):
本文讲述FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现。
二、音频压缩编码格式
具体获取方法可以参考:《音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现》
三、音频压缩编码格式的profile
音频压缩编码格式还有附带的profile(规格)。比如,如果音频压缩编码格式为AAC,根据《ISO14496-3-2009.pdf》第124页,还有AAC Main、AAC LC、AAC SSR、AAC LTP这几种规格:
FFmpeg获取AAC裸流的音频压缩编码格式的profile,是根据ADTS Header中的profile_ObjectType属性获取的。由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占2位的profile_ObjectType属性,表示AAC的规格。
由《音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现》可以知道,FFmpeg源码中通过ff_adts_header_parse函数解码ADTS格式的AAC的Header。而ff_adts_header_parse函数中,通过下面语句,将profile_ObjectType属性的值加1赋值给hdr->object_type:
hdr->object_type = aot + 1;
然后在parse_adts_frame_header函数中,将hdr->object_type赋值给ac->oc[1].m4ac.object_type:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...
size = ff_adts_header_parse(gb, &hdr_info);
if (size > 0) {
//...
ac->oc[1].m4ac.object_type = hdr_info.object_type;
//...
}
//...
}
之后,通过aac_decode_frame_int函数将ac->oc[1].m4ac.object_type的值减1赋值给AVCodecContext的profile,这样AVCodecContext的profile就会得到原本的profile_ObjectType属性:
static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,
int *got_frame_ptr, GetBitContext *gb,
const AVPacket *avpkt)
{
//...
// The AV_PROFILE_AAC_* defines are all object_type - 1
// This may lead to an undefined profile being signaled
ac->avctx->profile = ac->oc[1].m4ac.object_type - 1;
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:profile = avcodec_profile_name(enc->codec_id, enc->profile)拿到上一步中得到的AVCodecContext的profile。最后再在dump_stream_format函数中将profile打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...
profile = avcodec_profile_name(enc->codec_id, enc->profile);
//...
}
所以FFmpeg获取AAC裸流文件的音频压缩编码格式的profile,获取的是ADTS Header中的profile_ObjectType属性:
四、音频采样率
FFmpeg获取AAC裸流的音频采样频率,是根据ADTS Header中的samplingFrequencyIndex属性获取的。 由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占4位的samplingFrequencyIndex属性,表示音频采样频率:
ff_adts_header_parse函数中,通过下面语句,将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:
hdr->sampling_index = sr;
hdr->sample_rate = ff_mpeg4audio_sample_rates[sr];
然后在parse_adts_frame_header函数中,将hdr->sample_rate赋值给ac->oc[1].m4ac.sample_rate:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...
size = ff_adts_header_parse(gb, &hdr_info);
if (size > 0) {
//...
ac->oc[1].m4ac.sample_rate = hdr_info.sample_rate;
//...
}
//...
}
之后,通过aac_decode_frame_int函数将ac->oc[1].m4ac.sample_rate赋值给AVCodecContext的sample_rate:
static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,
int *got_frame_ptr, GetBitContext *gb,
const AVPacket *avpkt)
{
//...
if (ac->oc[1].status && audio_found) {
avctx->sample_rate = ac->oc[1].m4ac.sample_rate << multiplier;
avctx->frame_size = samples;
ac->oc[1].status = OC_LOCKED;
}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:av_bprintf(&bprint, "%d Hz, ", enc->sample_rate)拿到上一步中得到的AVCodecContext的sample_rate。最后再在dump_stream_format函数中将其打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...
switch (enc->codec_type) {
case AVMEDIA_TYPE_AUDIO:
av_bprintf(&bprint, "%s", separator);
if (enc->sample_rate) {
av_bprintf(&bprint, "%d Hz, ", enc->sample_rate);
}
//...
}
//...
}
所以FFmpeg获取AAC裸流文件的音频采样率,获取的是ADTS Header中的samplingFrequencyIndex属性:
五、音频声道数
FFmpeg获取AAC裸流的音频声道数,是根据ADTS Header中的channel_configuration属性获取的。 由《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header中存在一个占3位的channel_configuration属性,表示音频声道数:
ff_adts_header_parse函数中,通过下面语句,将音频声道数赋值给hdr->chan_config:
hdr->chan_config = ch;
然后在parse_adts_frame_header函数中,将hdr->chan_config赋值给AVCodecContext的ch_layout:
static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...
size = ff_adts_header_parse(gb, &hdr_info);
if (size > 0) {
//...
if (hdr_info.chan_config) {
ac->oc[1].m4ac.chan_config = hdr_info.chan_config;
if ((ret = set_default_channel_config(ac, ac->avctx,
layout_map,
&layout_map_tags,
hdr_info.chan_config)) < 0)
return ret;
if ((ret = output_configure(ac, layout_map, layout_map_tags,
FFMAX(ac->oc[1].status,
OC_TRIAL_FRAME), 0)) < 0)
return ret;
}
//...
}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:av_channel_layout_describe_bprint(&enc->ch_layout, &bprint)拿到AVCodecContext的ch_layout对应的音频声道数目。最后再在dump_stream_format函数中将音频声道数目打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...
switch (enc->codec_type) {
case AVMEDIA_TYPE_AUDIO:
av_channel_layout_describe_bprint(&enc->ch_layout, &bprint);
//...
break;
}
//...
}
所以FFmpeg获取AAC裸流文件的音频声道数,获取的是ADTS Header中的channel_configuration属性:
六、Bit depth
FFmpeg获取AAC裸流的Bit depth(又叫位深度、位元深度、采样深度、采样位数、采样格式),获取到的值是没有意义的。当音频压缩编码格式为AAC时,FFmpeg会强制把Bit depth设置为fltp。这是因为对于有损压缩编解码器(如MP3和AAC),Bit depth是在编码期间计算的,并且可以因采样而异,Bit depth只对PCM数字信号有意义。具体可以参考:《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》。
可以看到在aac_decode_init函数中(该函数定义在libavcodec/aacdec_template.c),强制把音频采样格式设置成了AV_SAMPLE_FMT_FLTP:
static av_cold int aac_decode_init(AVCodecContext *avctx)
{
//...
avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//...
}
所以如果音频压缩编码格式为AAC,通过FFmpeg获取到的音频采样格式固定为fltp,该值没有意义:
七、音频码率
通过解码ADTS Header无法直接获得音频码率,但是可以通过里面的属性间接计算出音频码率。
ff_adts_header_parse函数中,将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples。FFmpeg源码内部强制默认AAC(AAC Main、AAC LC、AAC SSR、AAC LTP)的samples是1024。hdr->samples为该ADTS音频帧中采样的次数:
hdr->samples = (rdb + 1) * 1024;
通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:
hdr->bit_rate = size * 8 * hdr->sample_rate / hdr->samples;
然后ff_aac_ac3_parse函数中,通过下面代码得到实际的以bps为单位的音频码率,赋值给AVCodecContext的bit_rate:
int ff_aac_ac3_parse(AVCodecParserContext *s1,
AVCodecContext *avctx,
const uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size)
{
//...
if (got_frame) {
//...
int bit_rate;
if (avctx->codec_id != AV_CODEC_ID_AAC) {
//...
}
else
{
AACADTSHeaderInfo hdr, *phrd = &hdr;
int ret = avpriv_adts_header_parse(&phrd, buf, buf_size);
if (ret < 0)
return i;
bit_rate = hdr.bit_rate;
}
/* Calculate the average bit rate */
s->frame_number++;
if (!CONFIG_EAC3_DECODER || avctx->codec_id != AV_CODEC_ID_EAC3) {
avctx->bit_rate +=
(bit_rate - avctx->bit_rate) / s->frame_number;
}
}
//...
}
然后在dump_stream_format函数中,通过avcodec_string函数中的语句:bitrate = get_bit_rate(enc)拿到AVCodecContext的bit_rate。最后再把它除以1000,得到以kb/s为单位的音频码率,打印出来:
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...
bitrate = get_bit_rate(enc);
if (bitrate != 0) {
av_bprintf(&bprint, ", %"PRId64" kb/s", bitrate / 1000);
//...
}
所以FFmpeg获取AAC裸流文件的音频码率,是根据ADTS Header中的属性计算出来的: