马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
=================================================================
音视频入门底子: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裸流音频信息的实现
=================================================================
一、弁言
通过FFprobe下令:
- ffprobe -of json -show_packets XXX.aac
复制代码 可以表现AAC裸流每个packet(也称为数据包或多媒体包)的信息,这些信息包罗该packet的size:
这个“size”实际是AVPacket结构体中的成员变量size,为AVPacket的成员变量data指向的缓冲区的巨细,也就是AAC裸流中某个packet的巨细(单元为字节)。该值通过fftools/ffprobe.c中的show_packet函数打印出来:
- static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
- {
- //...
- print_val("size", pkt->size, unit_byte_str);
- //...
- }
复制代码
本文陈诉这个“size”值是怎样被盘算出来的。如果想直接看结论,可以跳到本文的末了,直接看“总结”。对于AAC裸流,size值是通过adts_aac_read_packet函数盘算出来的。
二、adts_aac_read_packet函数的界说
adts_aac_read_packet函数界说在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/aacdec.c中:
- static int adts_aac_read_packet(AVFormatContext *s, AVPacket *pkt)
- {
- int ret, fsize;
- retry:
- ret = av_get_packet(s->pb, pkt, ADTS_HEADER_SIZE);
- if (ret < 0)
- return ret;
- if (ret < ADTS_HEADER_SIZE) {
- return AVERROR(EIO);
- }
- if ((AV_RB16(pkt->data) >> 4) != 0xfff) {
- // Parse all the ID3 headers between frames
- int append = ID3v2_HEADER_SIZE - ADTS_HEADER_SIZE;
- av_assert2(append > 0);
- ret = av_append_packet(s->pb, pkt, append);
- if (ret != append) {
- return AVERROR(EIO);
- }
- if (!ff_id3v2_match(pkt->data, ID3v2_DEFAULT_MAGIC)) {
- av_packet_unref(pkt);
- ret = adts_aac_resync(s);
- } else
- ret = handle_id3(s, pkt);
- if (ret < 0)
- return ret;
- goto retry;
- }
- fsize = (AV_RB32(pkt->data + 3) >> 13) & 0x1FFF;
- if (fsize < ADTS_HEADER_SIZE) {
- return AVERROR_INVALIDDATA;
- }
- ret = av_append_packet(s->pb, pkt, fsize - pkt->size);
- return ret;
- }
复制代码 该函数的作用是:获取一个ADTS音频帧的数据,赋值给形参pkt指向的packet。
形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext对象。
形参pkt:输出型参数,指向一个AVPacket对象。实行adts_aac_read_packet函数后,pkt->data指向的的缓冲区会得到整个ADTS音频帧的数据,pkt->size会增至该ADTS音频帧的巨细。
三、adts_aac_read_packet函数的内部实现分析
宏界说ADTS_HEADER_SIZE的值为7,表现不包罗CRC校验的ADTS Header的长度:
- #define ADTS_HEADER_SIZE 7
复制代码
adts_aac_read_packet函数内部,起首通过av_get_packet函数从AVIOContext输入缓冲区或文件形貌符中统共读取ADTS_HEADER_SIZE(7)个字节数据,也就是读取该音频帧的Header,追加到原来pkt->data指向的的缓冲区的尾部。关于av_get_packet函数的用法可以参考 《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》。如果实际读取到的巨微小于7个字节,返回AVERROR(EIO)表现IO错误:
- ret = av_get_packet(s->pb, pkt, ADTS_HEADER_SIZE);
- if (ret < 0)
- return ret;
- if (ret < ADTS_HEADER_SIZE) {
- return AVERROR(EIO);
- }
复制代码
由《音视频入门底子:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS音频帧的adts_fixed_header中的syncword属性占12位,每个位都必须被设置为1。通过下面的if语句判断syncword属性的值是否精确,如果不精确,实行大括号内剖析id3v2 header的流程。ID3v2是一系列元数据,内里存储了一些跟歌曲干系的信息(比如:演唱者、歌曲名、备注等)。关于AV_RB16宏界说的用法可以参考:《FFmpeg源码:AV_RB32、AV_RB16、AV_RB8宏界说分析》:
- if ((AV_RB16(pkt->data) >> 4) != 0xfff) {
- // Parse all the ID3 headers between frames
- //...
- }
复制代码
获取adts_variable_header中的aac_frame_length属性,即该ADTS音频帧的总长度(包罗ADTS Header、错误校验和AAC原始数据块,单元为字节),赋值给变量fsize。由《音视频入门底子:AAC专题(3)——AAC的ADTS格式简介》可以知道,ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),以是如果从上面得到的该ADTS音频帧的总长度小于7,表现ADTS Header格式不精确,返回AVERROR_INVALIDDATA:
- fsize = (AV_RB32(pkt->data + 3) >> 13) & 0x1FFF;
- if (fsize < ADTS_HEADER_SIZE) {
- return AVERROR_INVALIDDATA;
- }
复制代码
通过av_append_packet函数,增长形参pkt指向的packet的巨细至aac_frame_length个字节,让pkt->data指向的的缓冲区得到整个ADTS音频帧的数据。关于av_append_packet函数的用法可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》:
- ret = av_append_packet(s->pb, pkt, fsize - pkt->size);
复制代码
四、总结
1.AAC裸流每个packet的size值(该packet的巨细,单元为字节)实际上是通过ADTS音频帧(adts音频压缩数据包)的adts_fixed_header中的aac_frame_length属性获取的。
2.如果过细地观察,会发现AAC裸流每个packet的size值都不一样,这是由于ADTS音频帧存贮的是压缩后的音频数据。而WAV音频文件一样寻常存贮的是PCM,也就是无压缩的原始音频数据,以是在存贮的是PCM数据的环境下,WAV音频文件每个packet的size值都是一样的。各位同砚可以把本文跟《音视频入门底子:WAV专题(7)——FFmpeg源码中盘算WAV音频文件每个packet的size值的实现》举行对比,以加深对音频帧size值的明白。
3.FFmpeg源码内部得到AAC裸流每个packet的size值是通过adts_aac_read_packet函数,但是解码ADTS Header获取内里信息(音频采样频率、声道数、采样位数等)是通过ff_adts_header_parse函数,关于ff_adts_header_parse函数可以参考:《音视频入门底子:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现》。可以看到这两个函数有部分功能是重复了,重复获取了ADTS音频帧的巨细。FFmpeg对AAC裸流解封装时,会重复解码ADTS Header导致性能丧失。也就是说:FFmpeg为了架构的通用性、扩展性、兼容性和代码的可读性造成了代码的冗余和重复,以是为了极致的性能,比如更快的解封装速率,很多开辟者会选择不依靠任何第三方库本身写剖析代码,大概改FFmpeg的源码实现优化。各位同砚可以实验改FFmpeg源码,使得对AAC裸流举行解复用时,只解码一次ADTS Header,从而进步解复用速率。
|