一、概述
AAC(Advanced Audio Coding),中文名:高级音频编码。出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,目的是取代MP3格式。
2000年,MPEG-4标准出现后,AAC重新集成了其特性,加入了SBR技术和PS技术,为了区别于传统的MPEG-2 AAC又称为MPEG-4 AAC。
本文从aac编码的基础概念出发展开,后面包含aac编码规格、AAC格式的文件怎么组成的。
二、aac编码规格
aac有9种规格(Profile),以适应不同场景
MPEG-2 AAC LC:低复杂度规格(Low Complexity) 注:比较简单,没有增益控制,但提高了编码效率,在中等码率的编码效率以及音质方面,都能找到平衡点。
MPEG-2 AAC Main:主规格
MPEG-2 AAC SSR:可变采样率规格(Scaleable Sample Rate)
MPEG-4 AAC LC:低复杂度规格(Low Complexity)—现在的手机比较常见的MP4文件中的音频部份就包括了该规格音频文件
MPEG-4 AAC Main:主规格 注:包含了除增益控制之外的全部功能,其音质最好
MPEG-4 AAC SSR:可变采样率规格(Scaleable Sample Rate)
MPEG-4 AAC LTP:长时期预测规格(Long Term Predicition)
MPEG-4 AAC LD:低延迟规格(Low Delay)
MPEG-4 AAC HE:高效率规格(High Efficiency)—这种规格适合用于低码率编码,有Nero ACC 编码器支持
最早是基于MPEG-2标准,称为:MPEG-2 AAC。后来MPEG-4标准在原来基础上增加了一些新技术,称为:MPEG-4 AAC。
流行的Nero AAC编码程序只支持LC,HE,HEv2这三种规格,编码后的AAC音频,规格显示都是LC。HE其实就是AAC(LC)+ SBR技术,HEv2就是AAC(LC)+ SBR + PS技术;
HE:HE-AAC v1(又称AACPlusV1,SBR),用容器的方法实现了AAC(LC)+SBR技术。SBR其实代表的是Spectral Band Replication(频段复制)。简要叙述一下,音乐的主要频谱集中在低频段,高频段幅度很小,但很重要,决定了音质。如果对整个频段编码,若是为了保护高频就会造成低频段编码过细以致文件巨大;若是保存了低频的主要成分而失去高频成分就会丧失音质。SBR把频谱切割开来,低频单独编码保存主要成分,高频单独放大编码保存音质,“统筹兼顾”了,在减少文件大小的情况下还保存了音质,完美的化解这一矛盾。
HEv2:用容器的方法包含了HE-AAC v1和PS技术。PS指“parametric stereo”(参数立体声)。原来的立体声文件文件大小是一个声道的两倍。但是两个声道的声音存在某种相似性,根据香农信息熵编码定理,相关性应该被去掉才能减小文件大小。所以PS技术存储了一个声道的全部信息,然后,花很少的字节用参数描述另一个声道和它不同的地方。
三、aac封装格式
AAC有两种封装格式:ADIF和ADTS
- ADIF:全称 Audio Data Interchange Format,音频数据交换格式,该格式一般应用在将音频通过写文件方式存储在磁盘里的场景,不能进行随机访问,不允许在文件中间开始进行解码;只有拿到整个文件时才能开始进行渲染播放;
- ADTS:全称 Audio Data Transport Stream,音频数据传输流,该格式的特征是用同步字节进行将 AAC 音频截断,然后可以允许客户端在任何地方进行解码播放,适合网络传输场景;简单来说,ADTS可以在任意帧进行解码,每一个帧都有头信息,但ADIF却只有一个统一的头,所以必须得到所有的数据后才能解码。一个帧就能单独解码。
两种Header的形式也不同,目前一般编码后和抽取出的基本都是ADTS格式音频流。
✨3.1 ADIF 格式
这个格式比较少见,简单认识一下即可,下图是ADIF格式的序列,由adif_header、byte_alignment、raw_data_stream三部分组成。byte_alignment是用来做字节对齐的,也就是说,ADIF格式由一个ADIF头信息(adif_header) 和 原始数据流(raw_data_stream) 构成。
下面看看ADIF头信息包括哪些内容,如下图:
✨3.2 ADTS 格式
ADTS 格式的AAC音频流是由一个个的ADTS帧组成的,下图是ADTS序列的语法,整个序列由若干个adts_frame
组成。
每个ADTS帧都是由ADTS头部
和AAC音频数据
组成,下图是adts_frame的语法,adts_fixed_header
(固定头部信息)和adts_variable_header
(可变头部信息)都属于ADTS头部数据,raw_data_block
表示AAC音频原始数据块。
下图是网络上对AAC音频流总结的一张图片,可以帮助我们宏观地认识AAC音频流的ADTS格式。
✨3.2.1 ADTS头部
ADTS头部有两部分,分为固定头部(adts_fixed_header
)、可变头(adts_variable_header
):
✨3.2.1.1 固定头部(adts_fixed_header
)
ADTS固定头部是 解码器识别音频格式和参数的关键部分,它告诉解码器:
- 这是一个合法的ADTS帧(通过syncword)
- 音频是MPEG-4还是MPEG-2
- 使用哪种AAC Profile
- 采样率和声道数是多少
- 是否有CRC校验等附加信息
字段解析:
- syncword:占用12bit;
所有的bit位都是1。总是0xFFF,代表一个ADTS帧的开始,作为分界符,用于同步每帧起始位置。 - ID:占用 1 bit;
表示MPEG版本,0代表MPEG-4, 1代表MPEG-2,一般用 0,因为都是属于 MPEG 的规范.。 - layer:占用 2 bit;
固定为00
,保留字段 - protection_absent:占用 1 bit;(用于判断是否禁用CRC校验)
设置 1 表示没有CRC,整个ADST头为7字节;0 表示有CRC,整个ADST头为9字节。关于CRC是什么及它的作用:CRC(Cyclic Redundancy Check,循环冗余校验)是一种错误检测机制,用于在数据传输或存储过程中检测比特级错误(比如信号干扰导致的位翻转)。在ADTS中,CRC字段是可选的,由protection_absent位控制:protection_absent = 0 → 存在CRC字段(16位校验码,紧跟在固定头部之后)。protection_absent = 1 → 无CRC校验,节省2字节开销。音频流(如网络传输、无线广播)可能因噪声或干扰导致数据损坏,CRC能发现错误帧并触发重传或丢弃,避免播放杂音,它就是一个音频帧的“封条”,确保数据完整性。 - profile_ObjectType:占用 2 bit,表示使用的AAC规格(profile);
该字段的解释取决于ID位的值。如果ID等于1,则该字段包含与ISO/IEC 13818-7中定义的ADTS流中的配置文件字段相同的信息,也就是MPEG-2的规格;当ID为0是表示的是MPEG-4的规格,该字段的值等于 Audio Object Type 的值减1。字段取值如下面图片的表格
sampling_frequency_index
:占用 4 bit;
表示采样率下标索引,字段取值及解释如下图: 例如0对应96000HZ
- private_bit:占用 1 bit,作为私有位,用户自定义,编码时设置为0,解码时忽略;
详细解释参见 ISO/IEC 11172-3, subclause 2.4.2.3 (Table 8) - channel_configuration:占用 3 bit;
通道配置即声道数,一般 2 表示立体声双声道。更多的值参考下图:
original_copy
:占用 1 bit,原始/复制标志,0表示当前帧是拷贝是非首次发行而经过复制再编码的版本,1表示是原始是第一次发行的原始母带/母盘home
:占用 1 bit,该位纯粹保留给广播或版权系统使用,实际编码器/播放器都会填 0,表示“无特殊家用标志”,1表示家用,编码时设置为0,解码时忽略,对解码、播放、版权均无影响,可忽略
✨3.2.1.2 可变头部(adts_variable_header)
ADTS 可变头部提供了 帧级别的动态信息,结合固定头部,解码器就能完整解析每一帧的格式和内容,它的关键作用包括:
- 定位帧边界(通过
aac_frame_length
)。 - 控制解码器缓冲(通过
adts_buffer_fullness
)。 - 支持版权标记(通过
copyright_identification_*
位)。 - 支持多 AAC 数据块打包(通过
number_of_raw_data_blocks_in_frame
)。
字段解析:
- copyright_identification_bit:占用 1 bit,版权标识位,与下一字段配合使用,(0表示不包含版权标识,1标识包含)编码时设置为0,解码时忽略;
- copyright_identification_start:占用 1 bit,版权标识开始标志,编码时设置为0,解码时忽略;
- frame_length:占用 13 bit,当前 ADTS 帧的长度,包括 ADTS 头(固定+可变)和 AAC 原始流,单位byte;
- adts_buffer_fullness:占用 11 bit,解码器缓冲区占用程度,用于码率控制,0x7FF 表示码率可变的码流,0x000 表示固定码率的码流;
实际的AAC文件中,这个字段会出现不等于0x7FF、0x000的情况,下面是GPT的回复:adts_buffer_fullness字段表示AAC解码器中的缓冲区当前的填充量。这个字段的值代表了解码器输入缓冲区中未使用的字节数,可以用来衡量解码器缓冲区的剩余空间。理想状态下,这个值应该保持在一个合适的范围内,以确保解码器能够持续地接收和处理音频数据,而不会发生溢出或欠流的情况。
- number_of_raw_data_blocks_in_frame:占用 2 bit;
该字段表示当前ADST帧中所包含的AAC帧的个数减一。为了最大的兼容性通常每个ADTS frame 包含一个AAC frame,所以该值一般为0。一个AAC原始帧包含一段时间内1024个采样及相关数据
四、 AAC格式音频文件解析 (编译器+代码实践)
现在较常使用的AAC文件格式是使用ADTS帧来保存的,所以后面就以ADTS格式的AAC文件的解析过程进行展开,通过实践巩固加强对ADTS帧结构的认识。
✨4.1 aac文件解析(编译器+ffmpeg命令)
✨4.1.1 编译器查看ADTS头各字段
我使用的是一个在线网页的Hex16进制编辑器来查看aac文件的 HexEd.it — 基于浏览器的十六进制编辑器 如下图 我们可以对他们进行转为二进制然后按位来看
没有CRC的情况下,文件开头的7个字节是ADTS帧头部,这里7个字节的数据是:
FF F1 50 80 03 9F FC
我们将这7个字节转为二进制来看
字节 | 二进制(共56位) |
---|---|
FF | 11111111 |
F1 | 11110001 |
50 | 01010000 |
80 | 10000000 |
03 | 00000011 |
9F | 10011111 |
FC | 11111100 |
然后按 ADTS 结构解析:
前7字节的二进制是这样的:11111111 11110001 01010000 10000000 00000011 10011111 11111100 然后下面就按每个字段的位宽进行查看 转为十进制然后比对
字段名 | 位(bit) | 值(二进制) | 十进制 | 含义 |
---|---|---|---|---|
syncword | 12 | 111111111111 | 0xFFF | 同步字正确 |
ID | 1 | 0 | 0 | MPEG-4 |
layer | 2 | 00 | 0 | 保留,必须为0 |
protection_absent | 1 | 1 | 1 | ✅ 无 CRC 校验 |
profile_ObjectType | 2 | 0 1 | 1 | ✅ AAC LC(Low Complexity) |
sampling_frequency_index | 4 | 010 0 | 4 | ✅ 采样率 = 44100Hz(查表) |
private_bit | 1 | 0 | 0 | ✅ 未使用 |
channel_configuration | 3 | 010 | 2 | 立体声(2 声道) |
original_copy | 1 | 0 | 0 | 当前帧是 拷贝 |
home | 1 | 0 | 0 | 未使用 |
copyright_identification_bit | 1 | 0 | 0 | 无版权标识 |
copyright_identification_start | 1 | 0 | 0 | 非版权序列起始 |
aac_frame_length | 13 | 0000000011100 | 28(字节) | 帧总长度 |
adts_buffer_fullness | 11 | 11111111111 | 0x7FF=2047 | 0x7FF码率可变的码流 |
number_of_raw_data_blocks_in_frame | 2 | 00 | 0 | 每帧含 1aac数据块 |
这样看可能更直观点:
这个是固定头部的各字段:
bit 0-11 : syncword 111111111111
bit 12 : ID 0
bit 13-14 : layer 00
bit 15 : protection_absent 1
bit 16-17 : profile 01
bit 18-21 : sf_index 0100 → 44.1 kHz
bit 22 : private_bit 0
bit 23-25 : channel_config 010 → 2 声道
bit 26 : original_copy 0
bit 27 : home 0
这个是可变头部的各字段:
bit 28 : copyright_id_bit 0
bit 29 : copyright_start 0
bit 30-42 : aac_frame_length 0000000011100 → 28字节帧长度(头+后面数据)
bit 43-53 : adts_buffer_fullness 11111111111 → 0x7FF码率可变的码流
bit 54-55 : raw_blocks 00 → 每帧含 1aac数据块
可以得到以下结论:
这是一帧 ADTS-AAC LC 数据,格式为 MPEG-4、44.1 kHz、立体声、无 CRC;
帧总长度 28 字节,采用 VBR 码率,且 每帧仅含 1 个 AAC 原始数据块。
✨4.1.2 FFmpeg检查分析是否正确
可以通过FFmpeg检查ADTS帧结构是否和分析的一样
ffmpeg -v error -i input.aac -c copy -f adts NUL
这个命令可以看看“这个文件是不是标准的 ADTS”,若文件有错误,FFmpeg 会在 stderr 直接报 “ADTS syncword missing” 或 “CRC mismatch”。无任何报错即说明头信息与你分析的一致,文件可被正常识别。结果证明是标准的ADTS结构。
ffprobe -v error -show_entries stream=sample_rate,profile,channels,channel_layout -select_streams a:0 input.aac
命令片段 | 作用 |
---|---|
ffprobe | FFmpeg 套件里的“媒体信息探测器”,只读文件、不解码输出。 |
-v error | 把日志级别设为 error ,只打印真正的错误,忽略 info/debug 信息,输出更干净。 |
-show_entries stream=sample_rate,profile,channels,channel_layout | 告诉 ffprobe:只把 stream(流)级别 里的 sample_rate 、profile 、channels 、channel_layout 四个字段打印出来,别的都不显示。 |
-select_streams a:0 | 在所有流里只选 第 0 条音频流(a:0 = 第 1 条音频流)。 |
input.aac | 要探测的文件。 |
通过ffprobe进行输出结构化信息,可以看到和我们解析的是一样的,aac规格为LC的(对应profile_ObjectType字段),采样率为44100(对应profile_ObjectType字段),channels=2和stereo立体声2声道(对应channel_configuration字段)
✨4.2 C语言代码解析AAC文件
/**
* 最简单的视音频数据处理示例
* Simplest MediaData Test
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本项目包含如下几种视音频测试示例:
* (1)像素数据处理程序。包含RGB和YUV像素格式处理的函数。
* (2)音频采样数据处理程序。包含PCM音频采样格式处理的函数。
* (3)H.264码流分析程序。可以分离并解析NALU。
* (4)AAC码流分析程序。可以分离并解析ADTS帧。
* (5)FLV封装格式分析程序。可以将FLV中的MP3音频码流分离出来。
* (6)UDP-RTP协议分析程序。可以将分析UDP/RTP/MPEG-TS数据包。
*
* This project contains following samples to handling multimedia data:
* (1) Video pixel data handling program. It contains several examples to handle RGB and YUV data.
* (2) Audio sample data handling program. It contains several examples to handle PCM data.
* (3) H.264 stream analysis program. It can parse H.264 bitstream and analysis NALU of stream.
* (4) AAC stream analysis program. It can parse AAC bitstream and analysis ADTS frame of stream.
* (5) FLV format analysis program. It can analysis FLV file and extract MP3 audio stream.
* (6) UDP-RTP protocol analysis program. It can analysis UDP/RTP/MPEG-TS Packet.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data ,int* data_size){
int size = 0;
if(!buffer || !data || !data_size ){
return -1;
}
while(1){
if(buf_size < 7 ){
return -1;
}
//Sync words
if((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){
size |= ((buffer[3] & 0x03) <<11); //high 2 bit
size |= buffer[4]<<3; //middle 8 bit
size |= ((buffer[5] & 0xe0)>>5); //low 3bit
break;
}
--buf_size;
++buffer;
}
if(buf_size < size){
return 1;
}
memcpy(data, buffer, size);
*data_size = size;
return 0;
}
int simplest_aac_parser(char *url)
{
int data_size = 0;
int size = 0;
int cnt=0;
int offset=0;
//FILE *myout=fopen("output_log.txt","wb+");
FILE *myout=stdout;
unsigned char *aacframe=(unsigned char *)malloc(1024*5);
unsigned char *aacbuffer=(unsigned char *)malloc(1024*1024);
FILE *ifile = fopen(url, "rb");
if(!ifile){
printf("Open file error\n");
return -1;
}
printf("-----+- ADTS Frame Table -+------+\n");
printf(" NUM | Profile | Frequency| Size |\n");
printf("-----+---------+----------+------+\n");
while(!feof(ifile)){
data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);
unsigned char* input_data = aacbuffer;
while(1)
{
int ret=getADTSframe(input_data, data_size, aacframe, &size);
if(ret==-1){
break;
}else if(ret==1){
memcpy(aacbuffer,input_data,data_size);
offset=data_size;
break;
}
char profile_str[10]={0};
char frequence_str[10]={0};
unsigned char profile=aacframe[2]&0xC0;
profile=profile>>6;
switch(profile){
case 0: sprintf(profile_str,"Main");break;
case 1: sprintf(profile_str,"LC");break;
case 2: sprintf(profile_str,"SSR");break;
default:sprintf(profile_str,"unknown");break;
}
unsigned char sampling_frequency_index=aacframe[2]&0x3C;
sampling_frequency_index=sampling_frequency_index>>2;
switch(sampling_frequency_index){
case 0: sprintf(frequence_str,"96000Hz");break;
case 1: sprintf(frequence_str,"88200Hz");break;
case 2: sprintf(frequence_str,"64000Hz");break;
case 3: sprintf(frequence_str,"48000Hz");break;
case 4: sprintf(frequence_str,"44100Hz");break;
case 5: sprintf(frequence_str,"32000Hz");break;
case 6: sprintf(frequence_str,"24000Hz");break;
case 7: sprintf(frequence_str,"22050Hz");break;
case 8: sprintf(frequence_str,"16000Hz");break;
case 9: sprintf(frequence_str,"12000Hz");break;
case 10: sprintf(frequence_str,"11025Hz");break;
case 11: sprintf(frequence_str,"8000Hz");break;
default:sprintf(frequence_str,"unknown");break;
}
fprintf(myout,"%5d| %8s| %8s| %5d|\n",cnt,profile_str ,frequence_str,size);
data_size -= size;
input_data += size;
cnt++;
}
}
fclose(ifile);
free(aacbuffer);
free(aacframe);
return 0;
}
int main()
{
simplest_aac_parser((char*)"./audio_chn0.aac");
return 0;
}
这块代码是雷霄骅大佬的AAC音频码流解析里面的,保存后进行gcc simplest_mediadata_aac.cpp编译,运行结果:
五、个人思考
通过Hex16进制编译器查看aac文件,然后转为2进制根据每个字段的位宽进行分析字段的值和属性,这个方法可以更好的去认识ATDS的固定头部和可变头部,同时也可以借助ffmpeg和前人的一些代码进行输出结构最后验证。
参考链接:
视音频数据处理入门:AAC音频码流解析_声码流-CSDN博客
【网络通信 — 直播】音频流编码 — AAC 基础_直播流音频编码格式-CSDN博客