:AAC格式与解析)
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_INPUT2解码出来的 PCM 直接送 AudioTrack结果声音是绿色噪音3跨进程传 AAC 数据对端死活同步不上这些坑共同的根源没搞清 AAC 的字节格式。AAC 不是单一格式——它有四种装法每种存元信息的位置都不一样。一个.aac文件里看到的字节跟 MP4 里 esds box 的字节跟 RTMP 的 AAC 包里的字节长得都不同。1. AAC 的四种装法四种容器选哪种一眼看清容器元信息位置能从中间播放吗实战用途raw AAC全无必须靠外面带❌MP4 内部 mdatADTS-AAC⭐每帧前 7-9 字节头✅.aac文件 / RTMP / HLS-TSAAC in MP4esds box 里一份 AudioSpecificConfig2 字节✅需先读 esdsMP4 容器、视频音轨ADIF文件开头一份❌已淘汰实战经验序号要点1自己存盘 → 用 ADTS-AAC.aac直接能播2视频音轨 → 走 MP4esds raw 帧3直播流RTMP / HLS-TS→ ADTS-AAC4看到陌生 .aac 文件第一件事hexdump -C file.aac #124; head -1看头FF F1/FF F9开头 ADTS否则是 ADIF 或 raw2. ADTS 头每个字节都装了啥一个标准 ADTS 帧 7 字节头 AAC 压缩数据带 CRC 是 9 字节头。56 个 bit 的字段2.1 字节 1-2关键syncword字段位数含义syncword12 bit 0xFFF固定 12 个 1找帧起点的标志ID1 bit0MPEG-41MPEG-2layer2 bit固定 00protection_absent1 bit1无 CRC头 7 字节0有 CRC头 9 字节2.2 字节 3-4关键profile 采样率索引 声道字段位数含义profile2 bit0Main1AAC LC ⭐ 最常见2SSR3LTPsampling_frequency_index4 bit采样率索引查表见下private_bit1 bit私有 0channel_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 时 0x7FFVBR 时是缓冲状态number_of_raw_data_blocks_in_frame2 bit0 1 个 block最常见每 block 1024 samples2.4 采样率索引表必查index采样率Hz备注096000—188200—264000—348000视频音轨 ⭐444100音乐 ⭐532000—624000—722050—816000通话912000—1011025—118000窄带语音12-15保留—2.5 声道配置表value声道数布局0自定义头里再带11Mono22L R最常见33C L R44C L R 后中55C L R LS RS665.1FLFRCLFEBLBR787.13. 怎么从 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_rateaac_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 跟 AudioSpecificConfigASC里的 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_length3切出 AAC payloadframe_length - 7带 CRC 时 -94送解码器FAAD2 / FFmpeg avcodec / Android MediaCodec— 用采样率 声道初始化5拿 PCM 输出一次解 1024 samples × 声道数6指针前移p frame_length回到 ① 找下一帧4.1 用 FFmpeg 解码C 代码// 1. 找 AAC 解码器constAVCodec*codecavcodec_find_decoder(AV_CODEC_ID_AAC);AVCodecContext*ctxavcodec_alloc_context3(codec);// 2. 配置参数 (从 ADTS 头来的, 上一步抠出来的)ctx-sample_rateh.sample_rate;ctx-ch_layout.nb_channelsh.channels;ctx-profileFF_PROFILE_AAC_LOW;// AAC LCavcodec_open2(ctx,codec,NULL);// 3. 喂数据AVPacket*pktav_packet_alloc();pkt-data(uint8_t*)adts_frame_data;// 含 ADTS 头的整帧pkt-sizeh.frame_length;avcodec_send_packet(ctx,pkt);// 4. 拿 PCMAVFrame*frameav_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 代码MediaCodecdecoderMediaCodec.createDecoderByType(audio/mp4a-latm);MediaFormatformatMediaFormat.createAudioFormat(audio/mp4a-latm,sampleRate,channels);// AAC LC 用 csd-0 AudioSpecificConfig 2 字节// (从 ADTS 头转换: profile/sr_index/channel 拼出来)byte[]csdmakeAudioSpecificConfig(profile,srIndex,channels);format.setByteBuffer(csd-0,ByteBuffer.wrap(csd));decoder.configure(format,null,null,0);decoder.start();// 喂 raw AAC (注意: MediaCodec 默认要的是 raw, 不是 ADTS)// 必须先把 ADTS 头剥掉再送intidxdecoder.dequeueInputBuffer(-1);ByteBufferbufdecoder.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 帧字节数stereoMediaCodecS16 (16 bit int)InterleavedL0 R0 L1 R1...1024 × 2 ch × 2 4096AudioTrack 输入S16Interleaved同上FFmpeg AVFrameFloat32Planardata[0]L*, data[1]R*每平面 1024 × 4 4096 (× 2 平面)OpenSL ESS16Interleaved4096AAudio outputFloat32 / S16Interleaved用setSharingMode视配置互转套路// FFmpeg Planar Float → MediaCodec S16 Interleavedfor(inti0;i1024;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*21]R;}少做这一步 → 左右声道错乱、或者满屏噪音。6. 跟 MP4 里的 AAC 有啥区别AudioSpecificConfigMP4 里的 AAC 不带 ADTS 头而是把所有元信息浓缩到2 字节的 AudioSpecificConfigASC存在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, 01024 sample, 1960 bit 14: depends_on_core_coder // 1 bit, 0 bit 15: extension_flag // 1 bit, 0ADTS → ASC 转换公式实战常用uint8_tasc[2];asc[0](aot3)|((sr_index1)0x07);// AOT 5bit sr 高 3bitasc[1]((sr_index0x01)7)|(channels3);// sr 低 1bit ch 4bit// AAC LC: aot 2 (注意! ADTS 的 profile1 对应 AOT2)知道这个就能在ADTS-AAC 文件和MP4 里的 AAC之间互转。7. 实战代码解析一个 .aac 文件完整可跑的最小拆帧器#includestdio.h#includestdint.hstaticconstintsr_table[16]{96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350,0,0,0};intmain(intargc,char**argv){FILE*fpfopen(argv[1],rb);fseek(fp,0,SEEK_END);longsizeftell(fp);fseek(fp,0,SEEK_SET);uint8_t*bufmalloc(size);fread(buf,1,size,fp);fclose(fp);longoffset0;intframe_count0;while(offsetsize-7){// 找 syncword 0xFFFif(buf[offset]!0xFF||(buf[offset1]0xF0)!0xF0){offset;continue;}intprofile((buf[offset2]6)0x03)1;intsr_idx(buf[offset2]2)0x0F;intch((buf[offset2]0x01)2)|((buf[offset3]6)0x03);intlen((buf[offset3]0x03)11)|(buf[offset4]3)|((buf[offset5]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);offsetlen;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: profile2 sr44100 ch2 len415# Frame 1 0x19f: profile2 sr44100 ch2 len412# ...# Total frames: 834740 行 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 F1或0xFF F9开头ADTS看到字节流以其他字节开头大概率 raw要从外面拿 ASC8.3 profile 偏移坑来源AAC LC 编号ADTS profile 字段1AudioSpecificConfig AOT2FFmpegFF_PROFILE_AAC_LOW1MediaCodecMediaCodecInfo.CodecProfileLevel.AACObjectLC2互转代码记得1/-1。8.4 1024 vs 960 samplesAAC 默认每帧 1024 samples。但有种AAC LDLow Delayprofile 是 480 / 512AAC 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 帧 syncword12 bit7 字节—— ADTS 头标准长度带 CRC 是 91024 samples—— AAC LC 一帧定长per channel剩下的所有解析都是从这三个数字推出来的。