author: hjjdebug
date: 2025年 12月 05日 星期五 09:33:57 CST
descrip: ffmpeg 与 aac 文件解码
文章目录
- [1. aac 文件对应的 demuxer 对象](#1. aac 文件对应的 demuxer 对象)
- [2. aac 文件对应的 parser对象](#2. aac 文件对应的 parser对象)
-
- [2.1. 对parser的访问:](#2.1. 对parser的访问:)
- [2.2 aac_parse_init](#2.2 aac_parse_init)
- [2.3 ff_aac_ac3_parse](#2.3 ff_aac_ac3_parse)
-
- [2.3.1 ff_adts_header_parse 函数](#2.3.1 ff_adts_header_parse 函数)
- [3. aac 文件对应的decoder对象](#3. aac 文件对应的decoder对象)
-
- [3.1 aac_decode_init](#3.1 aac_decode_init)
- [3.2 aac_decode_frame](#3.2 aac_decode_frame)
测试文件. 1.aac, 是一个单通道aac 格式音频文件.
测试程序. 随便写一个简单的测试程序吧,
能调用到avcodec_send_packet(),avcodec_receive_frame()就行
用附带的demux-decoding 程序就很好.
以前用c写代码,2个人合作常说, 你写个函数让我调一调, 我就省事了.
现在写代码会说, 你写个类,让我调一调. 或者给我个对象,让我调用调用.
对象好啊, 不仅可以包含成员,还可以包含函数, 一块打包了. 接口更简单了.
针对aac 文件格式的解码, ffmpeg 已经给我们提供了几个对象.
至于编码, 那是反方向, 以后再说.
1. aac 文件对应的 demuxer 对象
对象名称为 ff_aac_demuxer ,类型 AVInputFormat
注意: AVInputFormat 不是一个AVClass 类 但它可以包含一个私有的AVClass 类指针
例如 ff_pcm_s16le_demuxer 原始pcm 格式demux 就包含一个私有AVClass 指针,
.priv_class = &pcm_demuxer_class
.priv_data_size = sizeof(PCMAudioDemuxerContext)
其中在PCMAudioDemuxerContext中定义的选项sample_rate, ch_layout 可以从外部去设置,
例如从命令行设置.
好在ff_aac_demuxer 不需要包含私有类对象, 其.priv_data_size, .priv_class 均为0
demuxer 对象可以用ffmpeg -h的帮助系统列出对象属性
例如: $ ffmpeg -h demuxer=aac
显示如下:
cpp
Demuxer aac [raw ADTS AAC (Advanced Audio Coding)]:
Common extensions: aac.
解释一下它的运行过程:
-h 对应着ffmpeg options 中的全局选项, demuxer=aac 是它的参数.
-h 选项会执行show_help() 函数, 带demuxer=aac参数会进一步调用到show_help_demuxer("aac")
show_help_demuxer(char *name) 函数如下, 执行之则输出了上述结果.
cpp
void show_help_demuxer(const char *name)
{
const AVInputFormat *fmt = av_find_input_format(name); //关键函数,由名称找到对象,后面只是打印对象的属性
printf("Demuxer %s [%s]:\n", fmt->name, fmt->long_name);
if (fmt->extensions)
printf(" Common extensions: %s.\n", fmt->extensions);
if (fmt->priv_class) //如果有子类,还会打印子类的属性, ff_aac_demuxer 就没有子类对象了.
show_help_children(fmt->priv_class, AV_OPT_FLAG_DECODING_PARAM);
}
干脆给出 ff_aac_demuxer 对象定义吧.
在libavformat/aacdec.c中定义,
cpp
const AVInputFormat ff_aac_demuxer = {
.name = "aac",
.long_name = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),
.read_probe = adts_aac_probe,
.read_header = adts_aac_read_header,
.read_packet = adts_aac_read_packet,
.flags = AVFMT_GENERIC_INDEX,
.extensions = "aac",
.mime_type = "audio/aac,audio/aacp,audio/x-aac",
.raw_codec_id = AV_CODEC_ID_AAC,
};
aacdec.c 文件很小,内容是比较简单的.
但是请注意, 这个对象是不能直接从外边访问的, 因为它的符号类型不是全局的,而是私有的.
$ nm libavformat.so|grep ff_aac_demuxer
0000000000309080 d ff_aac_demuxer
是小写的'd',而不是大写的'D'
本来.o文件符号还是公有的,链接成.so文件时被脚本链接成了私有符号,不向外公开了.
不过gdb 功能倒是很强大, 它不管你是全局数据还是私有数据, 一样都可以打印出来.甚慰!
它的内存地址也能一目了然,不用推算了. 为我们调试提供了方便.
那外部怎样访问这个对象呢? 只能通过已经提供的接口.
例如:
const AVInputFormat *av_demuxer_iterate(void **opaque)
int i=0;
while ((fmt = av_demuxer_iterate(&i) //就能访问到所有的demuxer.
av_demuxer_iterate 函数可以访问demuxer 对象地址数组,根据传入参数i,访问指定对象.
例如:avformat_open_input() 就通过
av_probe_input_format2() 把探测到的格式保存到了 s->iformat 成员变量中
找的过程需要枚举每一种格式,找到对象的依据是使用探测数据看是否符合你的格式要求,
你找到对象后,要保留对象地址以被以后使用.
av_find_input_format(name)
根据名称找demuxer 对象是另一个接口,也会调用iterate函数
aacdec.c文件主要功能是提交格式探测函数 和 读文件头和读包函数
文件格式探测:
static int adts_aac_probe(const AVProbeData* p)
先找frame 的开头0xfff(12个1) 再从固定位置读取本frame的长度
偏移之后又能遇到0xfff, 再读取frame_size, 再继续向下找.
在探测的数据中能够找到3个为合格.
读文件头:
static int adts_aac_read_header(AVFormatContext* s)
该函数没干什么事,只是创建了一个音频流. 并且把数据读指针同步到0xfff开始的地方.
读包函数:
static int adts_aac_read_packet(AVFormatContext* s, AVPacket* pkt)
就是把从包头0xfff 到frame_size的数据都读出来.
这三个函数都声明为static 了, 因而也都是私有的,外部函数不能直接调用它们.
$ nm libavformat.so|grep adts_aac_probe
000000000004031c t adts_aac_probe
nm libavformat.so|grep adts_aac_read_header
00000000000405ea t adts_aac_read_header
对这三个函数的使用, 也只能通过ff_aac_demuxer对象来调用.
不过你可以用gdb 在这些函数上下断点, 就能轻松知道它的调用栈,方便在ffmpeg环境下调试代码,了解它们的功能.
AAC文件是由一个个ADTS frame构成的, 每个frame都有7字节frame 头和数据构成.
demuxer 只是访问了头部中56bits的12个bit同步位和frame_length(13bits).
数据体还没涉及, 内容真的很少.
2. aac 文件对应的 parser对象
对象名称为 ff_aac_parser , 类型 AVCodecParser
注意: AVCodecParser 并不是一个AVClass 类, 意味着它没有什么外部可设置项,
干脆把对象copy过来吧
对象在libavcodec/aac_parser.c 文件中
cpp
const AVCodecParser ff_aac_parser = {
.codec_ids = { AV_CODEC_ID_AAC },
.priv_data_size = sizeof(AACAC3ParseContext), //可以有私有数据,但私有数据不是avclass对象
.parser_init = aac_parse_init,
.parser_parse = ff_aac_ac3_parse,
.parser_close = ff_parse_close,
};
它的parser_parse 函数ff_aac_ac3_parse 在libavcodec/aac_ac3_parser.c中
其中调用到ff_adts_header_parse() 在libavcodec/adts_header.c 中
2.1. 对parser的访问:
是通过枚举来进行的, 给不同的i,将返回不同的对象地址.
while ((parser = av_parser_iterate(&i)))
av_parser_init 函数介绍
AVCodecParserContext *av_parser_init(int codec_id)
该函数功能是根据codec_id 找到parser, 找的过程就是前面的那个枚举,
对每一个对象,进行如下判断:
if (parser->codec_id[0] == codec_id) goto find;
按图索骥, 那么多对象,总要给点线索,才能给你返回正确的东西.
然后创建一个AVCodecParserContext, 返回.
其中codec_id 可以由demuxer 提供, 当你找到demuxer 时,其中包含codec_id 信息.
2.2 aac_parse_init
该函数是parser 的 parser_init 函数指针, 可以直接下断点调试跟踪
这个函数太简单,就是给私有结构s赋值. 抄过来以观其貌.
cpp
static av_cold int aac_parse_init(AVCodecParserContext *s1)
{
AACAC3ParseContext *s = s1->priv_data;
s->header_size = AV_AAC_ADTS_HEADER_SIZE;
s->sync = aac_sync;
return 0;
}
2.3 ff_aac_ac3_parse
该函数是parser 的 parser_parse 函数指针, 可以直接下断点调试跟踪
首先分析一下里面的 s->sync 指针函数, 就是初始化是赋值的 aac_sync() 函数, 其中又调用了
ff_adts_header_parse 函数
2.3.1 ff_adts_header_parse 函数
该函数完整解析了头部56bits 数据, 7bytes 头部.
代码如下, 功能是试图获取hdr 数据, 成功返回frame_size, 失败返回错误码(负数)
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; //一般是1
hdr->sampling_index = sr;
hdr->sample_rate = ff_mpeg4audio_sample_rates[sr]; //采用率,一般是44100或48000
hdr->samples = (rdb + 1) * 1024; //
hdr->bit_rate = size * 8 * hdr->sample_rate / hdr->samples; //比特率的计算
hdr->frame_length = size; //本frame 的大小
return size;
}
理解了get_bits() 函数,上面代码就能读懂了.
这里给一个7byte的header 实例:
FF F1 4C 00 B8 1F FC
-
hdr 参数
从7bytes 头中提取的参数, num_aac_frames 一般就是1个frame, samples 采样点数就是1024
还包含了通道配置ch, 确定了channel 个数或者说ch_layout
采样率(sample_rate),一般是44.1k或48k
object_type, 大概是确定了采样点格式.
上面3要素是播放所必需的.
frame_length说明了本压缩数据的长度,它代表了本帧的压缩数据长度,它能解出1024个采样点.
于是我们可以计算出比特率
-
比特率的计算:
hdr->bit_rate = size * 8 * hdr->sample_rate / hdr->samples;
hdr->sample_rate: 采样率,每秒的采样点数, 通常是44.1k,或48k
hdr-> samples: 数据块中的采用点数, 1024个采样点
二者相除,代表1秒采集了多少块,
size 是本块数据的byte 大小,
* 8 变成bit大小
1秒采集n块,一块size大小, 2者相乘再乘8就是比特率大小.
ff_aac_ac3_parse 函数比较长,就不copy 了, 跟踪它还可以看懂,
基本功能是找到帧头后,记录下帧长度,设置循环退出条件,并设置输出数据
里边代码有一些过渡冗余,好几次调用header_parse, 只是为了健壮性.
总的来讲, parser 函数也只是涉及到帧头的解析, 并没有真正解码帧数据.
对数据的解压缩,那要看decoder 了.
3. aac 文件对应的decoder对象
由codec_id 可以找到decoder, codec_id 就是demuxer中使用的AV_CODEC_ID_AAC.
用法:
AVCodec *dec = avcodec_find_decoder(AV_CODEC_ID_AAC);
我们还是看看它的真身在哪里吧,调试知:
源码位于: libavcodec/aacdec.c
其对象名称为: ff_aac_decoder, 对象类型 AVCodec, 严格说是FFCodec,
AVCodec是FFCodec 的子类 .p 部分, public 部分.
cpp
const FFCodec ff_aac_decoder = {
.p.name = "aac",
CODEC_LONG_NAME("AAC (Advanced Audio Coding)"),
.p.type = AVMEDIA_TYPE_AUDIO,
.p.id = AV_CODEC_ID_AAC,
.priv_data_size = sizeof(AACContext),
.init = aac_decode_init,
.close = aac_decode_close,
FF_CODEC_DECODE_CB(aac_decode_frame), //最重要函数!!!
.p.sample_fmts = (const enum AVSampleFormat[]) {
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE
},
.p.capabilities = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
CODEC_OLD_CHANNEL_LAYOUTS_ARRAY(aac_channel_layout)
.p.ch_layouts = aac_ch_layout,
.flush = flush,
.p.priv_class = &aac_decoder_class,
.p.profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
};
下面介绍2个重要函数: 这些函数都在libavcodec/aacdec_template.c文件中,3480行代码
3.1 aac_decode_init
如果你单步跟踪下去,连子函数也单步跟踪,会累到手抽筋, 没看出所以然. 反正在做一堆设置.
3.2 aac_decode_frame
碰到了一堆新概念,新名称,翻一翻代码,这是具体解压缩数据的地方.
一般我们无需知道具体算法, 有需求再研究. 已经兵临城下.