音视频修炼之基础理论(五):AAC格式与解析
2026/9/8 18:46:26 网站建设 项目流程

AAC 格式与解析 —— ADTS 头 / 元信息提取 / 声道分布 / 完整解码流程

第 2 篇音频基础讲了"为啥要 AAC",这一篇讲"AAC 长啥样、怎么拆开、怎么解码"。重点:ADTS 头 7 个字节里都装了啥、采样率/位深/声道这些元信息怎么从字节流里抠出来、解码后左右声道在内存里怎么排、完整解析流程。读完之后你能从 0 写一个 ADTS 拆帧器。


本文速览

章节阅读重点
0. 引言:为啥要懂 AAC 字节细节把握本节核心概念和使用场景
1. AAC 的四种装法把握本节核心概念和使用场景
2. ADTS 头每个字节都装了啥把握本节核心概念和使用场景
3. 怎么从 ADTS 头抠出元信息(C 代码示例)对照代码和运行效果落地
4. 完整解码流程重点看数据/调用如何流转
5. 解码后左右声道怎么排把握本节核心概念和使用场景
6. 跟 MP4 里的 AAC 有啥区别(AudioSpecificConfig)按场景做技术取舍
7. 实战代码:解析一个 .aac 文件对照代码和运行效果落地
8. 常见坑定位常见故障和规避方式
9. 一句话总结收束要点,形成记忆锚点

0. 引言:为啥要懂 AAC 字节细节

很多人写音频程序时遇到过这种事:

序号要点
1拿到一个.aac文件,FFmpeg 能播,自己写代码调用 MediaCodec 报MEDIA_ERROR_INVALID_INPUT
2解码出来的 PCM 直接送 AudioTrack,结果声音是绿色噪音
3跨进程传 AAC 数据,对端死活同步不上

这些坑共同的根源:没搞清 AAC 的字节格式。AAC 不是单一格式——它有四种装法,每种存元信息的位置都不一样。一个.aac文件里看到的字节,跟 MP4 里 esds box 的字节,跟 RTMP 的 AAC 包里的字节,长得都不同。


1. AAC 的四种装法

四种容器选哪种,一眼看清:

容器元信息位置能从中间播放吗实战用途
raw AAC全无(必须靠外面带)MP4 内部 mdat
ADTS-AAC每帧前 7-9 字节头.aac文件 / RTMP / HLS-TS
AAC in MP4esds box 里一份 AudioSpecificConfig(2 字节)✅(需先读 esds)MP4 容器、视频音轨
ADIF文件开头一份已淘汰

实战经验

序号要点
1自己存盘 → 用 ADTS-AAC(.aac直接能播)
2视频音轨 → 走 MP4(esds + raw 帧)
3直播流(RTMP / HLS-TS)→ ADTS-AAC
4看到陌生 .aac 文件第一件事hexdump -C file.aac | head -1看头,FF F1/FF F9开头 = ADTS,否则是 ADIF 或 raw

2. ADTS 头每个字节都装了啥

一个标准 ADTS 帧 =7 字节头 + AAC 压缩数据(带 CRC 是 9 字节头)。56 个 bit 的字段:

2.1 字节 1-2(关键:syncword)

字段位数含义
syncword12 bit= 0xFFF,固定 12 个 1,找帧起点的标志
ID1 bit0=MPEG-4,1=MPEG-2
layer2 bit固定 = 00
protection_absent1 bit1=无 CRC(头 7 字节),0=有 CRC(头 9 字节)

2.2 字节 3-4(关键:profile + 采样率索引 + 声道)

字段位数含义
profile2 bit0=Main,1=AAC LC ⭐ 最常见,2=SSR,3=LTP
sampling_frequency_index4 bit采样率索引(查表见下)
private_bit1 bit私有,= 0
channel_configuration3 bit声道配置(查表见下)

2.3 字节 5-7(关键:帧长度)

字段位数含义
original/copy1 bit忽略
home1 bit忽略
copyright_id_bit1 bit忽略
copyright_id_start1 bit忽略
aac_frame_length13 bit整帧长度 = 7 (header) + AAC payload,跳到下一帧靠这个
adts_buffer_fullness11 bitCBR 时 = 0x7FF,VBR 时是缓冲状态
number_of_raw_data_blocks_in_frame2 bit0 = 1 个 block(最常见),每 block = 1024 samples

2.4 采样率索引表(必查)

index采样率(Hz)备注
096000
188200
264000
348000视频音轨 ⭐
444100音乐 ⭐
532000
624000
722050
816000通话
912000
1011025
118000窄带语音
12-15保留

2.5 声道配置表

value声道数布局
0自定义头里再带
11Mono
22L + R(最常见
33C + L + R
44C + L + R + 后中
55C + L + R + LS + RS
665.1(FL+FR+C+LFE+BL+BR)
787.1

3. 怎么从 ADTS 头抠出元信息(C 代码示例)

实战代码——给一段 ADTS 字节流,抠出采样率、声道、帧长:

typedefstruct{intprofile;// AAC profileintsample_rate;// 采样率intchannels;// 声道数intframe_length;// 整帧长度inthas_crc;// 是否有 CRC}AdtsHeader;staticconstintaac_sample_rates[16]={96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350,0,0,0};intparse_adts(constuint8_t*p,AdtsHeader*h){// ① 检查 syncword 0xFFFif(p[0]!=0xFF||(p[1]&0xF0)!=0xF0){return-1;// 不是 ADTS 帧起点}// ② protection_absent: byte 1 bit 0h->has_crc=!(p[1]&0x01);// ③ profile: byte 2 bit 7-6 (注意 +1, 因为 AAC LC 在 ADTS 里是 1, ASC 里是 2)h->profile=((p[2]>>6)&0x03)+1;// ④ sampling_frequency_index: byte 2 bit 5-2intsr_index=(p[2]>>2)&0x0F;h->sample_rate=aac_sample_rates[sr_index];// ⑤ channel_configuration: byte 2 bit 0 + byte 3 bit 7-6h->channels=((p[2]&0x01)<<2)|((p[3]>>6)&0x03);// ⑥ aac_frame_length: byte 3 bit 1-0 + byte 4 全 + byte 5 bit 7-5 (共 13 bit)h->frame_length=((p[3]&0x03)<<11)|(p[4]<<3)|((p[5]>>5)&0x07);return0;}

几个易踩的坑

说明
profile 偏移ADTS 里的 profile 跟 AudioSpecificConfig(ASC)里的 AOT 差 1。ADTS 的 1 = ASC 的 2 = AAC LC
位深 AAC 不存AAC 是有损压缩,输入端是 16 bit PCM、输出端可能是 16 bit S16 或 Float32,但 ADTS 头里没有"位深"字段——位深只在解码器输出格式里有
frame_length 跨字节13 bit 跨 3 个字节,移位别错

4. 完整解码流程

6 步串起来:

序号要点说明
1找 syncword 0xFFF12 个连续 1 bit,定位 ADTS 帧起点
2解 ADTS 头(7 字节)— 抠出采样率索引、声道、profile、frame_length
3切出 AAC payload=frame_length - 7(带 CRC 时 -9)
4送解码器(FAAD2 / FFmpeg avcodec / Android MediaCodec)— 用采样率 + 声道初始化
5拿 PCM 输出一次解 1024 samples × 声道数
6指针前移p += frame_length,回到 ① 找下一帧

4.1 用 FFmpeg 解码(C 代码)

// 1. 找 AAC 解码器constAVCodec*codec=avcodec_find_decoder(AV_CODEC_ID_AAC);AVCodecContext*ctx=avcodec_alloc_context3(codec);// 2. 配置参数 (从 ADTS 头来的, 上一步抠出来的)ctx->sample_rate=h.sample_rate;ctx->ch_layout.nb_channels=h.channels;ctx->profile=FF_PROFILE_AAC_LOW;// AAC LCavcodec_open2(ctx,codec,NULL);// 3. 喂数据AVPacket*pkt=av_packet_alloc();pkt->data=(uint8_t*)adts_frame_data;// 含 ADTS 头的整帧pkt->size=h.frame_length;avcodec_send_packet(ctx,pkt);// 4. 拿 PCMAVFrame*frame=av_frame_alloc();while(avcodec_receive_frame(ctx,frame)>=0){// frame->data[0] = 左声道 PCM (Planar Float32)// frame->data[1] = 右声道 PCM// frame->nb_samples = 1024process_pcm(frame);}

4.2 用 Android MediaCodec 解码(Java 代码)

MediaCodecdecoder=MediaCodec.createDecoderByType("audio/mp4a-latm");MediaFormatformat=MediaFormat.createAudioFormat("audio/mp4a-latm",sampleRate,channels);// AAC LC 用 csd-0 = AudioSpecificConfig 2 字节// (从 ADTS 头转换: profile/sr_index/channel 拼出来)byte[]csd=makeAudioSpecificConfig(profile,srIndex,channels);format.setByteBuffer("csd-0",ByteBuffer.wrap(csd));decoder.configure(format,null,null,0);decoder.start();// 喂 raw AAC (注意: MediaCodec 默认要的是 raw, 不是 ADTS)// 必须先把 ADTS 头剥掉再送intidx=decoder.dequeueInputBuffer(-1);ByteBufferbuf=decoder.getInputBuffer(idx);buf.put(adts_frame,7,frame_length-7);// 跳过 7 字节 ADTS 头decoder.queueInputBuffer(idx,0,frame_length-7,pts,0);

关键差异:FFmpeg 接受带 ADTS 头的整帧,MediaCodec 默认接受剥掉 ADTS 头的 raw AAC + csd 元信息。这俩别搞混。


5. 解码后左右声道怎么排

不同解码器的输出格式不一样,实战必须 query 一下

解码器输出格式排布1 帧字节数(stereo)
MediaCodecS16 (16 bit int)InterleavedL0 R0 L1 R1...1024 × 2 ch × 2 =4096
AudioTrack 输入S16Interleaved同上
FFmpeg AVFrameFloat32Planardata[0]=L*, data[1]=R*每平面 1024 × 4 = 4096 (× 2 平面)
OpenSL ESS16Interleaved4096
AAudio outputFloat32 / S16Interleaved(用setSharingMode视配置

互转套路

// FFmpeg Planar Float → MediaCodec S16 Interleavedfor(inti=0;i<1024;i++){int16_tL=(int16_t)(frame->data[0][i*4]*32767);// 左int16_tR=(int16_t)(frame->data[1][i*4]*32767);// 右interleaved[i*2]=L;interleaved[i*2+1]=R;}

少做这一步 → 左右声道错乱、或者满屏噪音。


6. 跟 MP4 里的 AAC 有啥区别(AudioSpecificConfig)

MP4 里的 AAC 不带 ADTS 头,而是把所有元信息浓缩到2 字节的 AudioSpecificConfig(ASC),存在esdsbox 里:

ASC 字节 (5 bit + 4 bit + 4 bit + 1 bit + 1 bit + 1 bit = 16 bit = 2 字节): bit 0-4: AOT (Audio Object Type) // 5 bit, AAC LC = 2 bit 5-8: sampling_frequency_index // 4 bit, 跟 ADTS 同表 bit 9-12: channel_configuration // 4 bit bit 13: frame_length_flag // 1 bit, 0=1024 sample, 1=960 bit 14: depends_on_core_coder // 1 bit, = 0 bit 15: extension_flag // 1 bit, = 0

ADTS → ASC 转换公式(实战常用):

uint8_tasc[2];asc[0]=(aot<<3)|((sr_index>>1)&0x07);// AOT 5bit + sr 高 3bitasc[1]=((sr_index&0x01)<<7)|(channels<<3);// sr 低 1bit + ch 4bit// AAC LC: aot = 2 (注意! ADTS 的 profile=1 对应 AOT=2)

知道这个,就能在"ADTS-AAC 文件"和"MP4 里的 AAC"之间互转。


7. 实战代码:解析一个 .aac 文件

完整可跑的最小拆帧器:

#include<stdio.h>#include<stdint.h>staticconstintsr_table[16]={96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350,0,0,0};intmain(intargc,char**argv){FILE*fp=fopen(argv[1],"rb");fseek(fp,0,SEEK_END);longsize=ftell(fp);fseek(fp,0,SEEK_SET);uint8_t*buf=malloc(size);fread(buf,1,size,fp);fclose(fp);longoffset=0;intframe_count=0;while(offset<size-7){// 找 syncword 0xFFFif(buf[offset]!=0xFF||(buf[offset+1]&0xF0)!=0xF0){offset++;continue;}intprofile=((buf[offset+2]>>6)&0x03)+1;intsr_idx=(buf[offset+2]>>2)&0x0F;intch=((buf[offset+2]&0x01)<<2)|((buf[offset+3]>>6)&0x03);intlen=((buf[offset+3]&0x03)<<11)|(buf[offset+4]<<3)|((buf[offset+5]>>5)&0x07);printf("Frame %d @ 0x%lx: profile=%d sr=%d ch=%d len=%d\n",frame_count,offset,profile,sr_table[sr_idx],ch,len);offset+=len;frame_count++;}printf("Total frames: %d\n",frame_count);free(buf);return0;}

跑一下:

gcc parse_aac.c-oparse_aac ./parse_aac music.aac# Frame 0 @ 0x0: profile=2 sr=44100 ch=2 len=415# Frame 1 @ 0x19f: profile=2 sr=44100 ch=2 len=412# ...# Total frames: 8347

40 行 C 代码就把 ADTS-AAC 文件拆完了。


8. 常见坑

8.1 syncword 找错位置

0xFFF这 12 bit 在数据里其实不算稀有——某些 AAC payload 里也可能出现这串 bit。

解决方案:找到候选 syncword 后,用 frame_length 跳到下一帧再确认——如果下一个位置也是 0xFFF,才算真起点。常见叫"双校验"。

8.2 ADTS / raw AAC 混淆

直播流里 AAC 通常带 ADTS 头,但有些转推系统会剥掉头只传 raw + esds。

要点说明
看到字节流以0xFF F10xFF F9开头ADTS
看到字节流以其他字节开头大概率 raw(要从外面拿 ASC)

8.3 profile 偏移坑

来源AAC LC 编号
ADTS profile 字段1
AudioSpecificConfig AOT2
FFmpegFF_PROFILE_AAC_LOW1
MediaCodecMediaCodecInfo.CodecProfileLevel.AACObjectLC2

互转代码记得+1/-1

8.4 1024 vs 960 samples

AAC 默认每帧 1024 samples。但有种"AAC LD"(Low Delay)profile 是 480 / 512,"AAC ELD"是 480 / 512。看到帧长不对立马查 profile。

8.5 LATM / LOAS

广播电视用的另一种 AAC 封装,不是 ADTS 也不是 MP4。看到0x56 0xE0开头基本就是 LATM。RTMP / HLS 里基本不会碰到,DVB-T 流里要小心。


9. 一句话总结

AAC =裸压缩流 + 一份元信息。元信息要么放在每帧头部(ADTS),要么放在容器里(MP4 esds),要么完全在外面(raw)。搞清楚拿到的字节是哪种装法,是写音频代码的第一步

记住三个数字:

说明
0xFFF—— ADTS 帧 syncword(12 bit)
7 字节—— ADTS 头标准长度(带 CRC 是 9)
1024 samples—— AAC LC 一帧定长(per channel)

剩下的所有解析都是从这三个数字推出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询