AAC 音频编码与 ADTS/ADIF 参考手册
AAC 音频编码与 ADTS/ADIF 参考手册
AAC(Advanced Audio Coding)是音频编码标准;ADTS 和 ADIF 是承载 AAC raw_data_block 的码流/文件格式。AAC 不是网络协议,ADTS 也不是 RTP 的替代品。
本文依据 MPEG-4 Audio 的对象类型与语法概念,结合 ADTS/ADIF 的码流组织方式整理。AAC 编码工具内部的滤波器、量化和熵编码属于编码器实现;本文重点说明能够在码流和封装中观察、解析和验证的字段。
第一章 AAC 标准与编码层次
AAC 编码层次
1 | PCM → AAC 编码器 → raw_data_block → ADTS/ADIF/MP4 等封装 |
AAC 的 Audio Object Type(AOT)决定解码工具和语法能力。工程中最常见的是 AAC-LC(AOT 2);HE-AAC 会结合 SBR,HE-AAC v2 还可能使用 PS。
关键编码参数
| 参数 | 说明 |
|---|---|
| sample rate | 采样率,如 44100/48000 Hz |
| channel configuration | 声道配置,不仅是简单声道数 |
| AOT/Profile | 解码工具集合 |
| bitrate | 目标码率或码率模式 |
| frame length | 常见 AAC-LC 为 1024 samples |
AAC-LD、AAC-ELD 等低延迟规格不能仅凭“同为 AAC”互换,接收端必须支持对应 AOT。
第二章 AAC 码流封装
ADIF 与 ADTS
ADIF 通常在文件开头放一个全局头,适合一次性文件;从文件中间开始解码不方便。ADTS 则由连续帧组成,每帧都有同步头,适合流式传输和逐帧解析。
ADTS 帧结构
1 | ADTS fixed header (28 bits) |
无 CRC 时通常为 7 字节头,有 CRC 时为 9 字节头。frame_length 包含头部和 raw data 全部长度。
ADTS 头字段
| 字段 | 位数 | 说明 |
|---|---|---|
| syncword | 12 | 固定为 0xFFF |
| ID | 1 | MPEG-2/ MPEG-4 标识 |
| layer | 2 | 必须为 0 |
| protection_absent | 1 | 1 表示无 CRC |
| profile | 2 | 通常为 AOT-1,需结合标准解释 |
| sampling_frequency_index | 4 | 采样率索引 |
| private_bit | 1 | 私有位 |
| channel_configuration | 3 | 声道配置 |
| original/copy/home | 3 | 兼容性字段 |
| frame_length | 13 | 当前帧总字节数 |
| adts_buffer_fullness | 11 | 解码缓冲相关字段 |
| number_of_raw_data_blocks_in_frame | 2 | raw block 数减 1 |
adts_buffer_fullness 不能简单解释为“剩余字节数”;其语义依赖 MPEG 音频缓冲模型,工程解析时通常只按规范取值并传递。常见单 raw block 情况下,最后 2 位通常为 0。
采样率索引
ADTS 不直接放 32 位采样率,而是使用索引表。索引 0~12 对应标准采样率,索引 15 表示显式采样率(具体使用需遵循语法和实现支持)。解析器遇到保留索引必须拒绝或明确降级,不能默认为 44100。
AudioSpecificConfig 与 ADTS 的关系
MP4 中常使用 AudioSpecificConfig(ASC),其中 AOT、samplingFrequencyIndex 和 channelConfiguration 的排列与 ADTS 字段相近但不是同一个头。将 ADTS 7 字节直接当 ASC 使用是常见错误。AAC-LC 的 ASC 常可由:
1 | audioObjectType | samplingFrequencyIndex | channelConfiguration |
按 bit 级语法生成。
第三章 帧解析、时间和转换
ADTS 解析流程
- 搜索
0xFFF同步字。 - 检查 layer 是否为 0。
- 根据 protection_absent 确定头长。
- 读取 frame_length 并检查
frame_length >= header_length。 - 检查缓冲区是否包含完整帧。
- 输出 raw_data_block,推进到下一帧。
- 遇到错误时有限距离重同步,防止死循环。
帧时长与时间戳
AAC-LC 常见每帧 1024 samples,帧时长为:
1 | duration = 1024 / sample_rate |
例如 48 kHz 时约 21.333 ms。编码器可能使用 960 samples 或低延迟工具,不能无条件假设 1024。RTP/MPEG-TS/MP4 应使用对应时间基换算 PTS。
错误处理与验证
常见错误
- 把 ADTS frame length 当 raw data 长度。
- 忽略 CRC 导致头长错位。
- 将 profile 字段直接当 AOT,不做减一和版本解释。
- 用 ADTS 头代替 MP4 的 ASC。
- 把每帧固定成 1024 samples,忽略编码规格。
- 只找同步字,不检查长度和后续头合法性。
ADTS 28 字节固定头的位布局
无 CRC 时可按如下位序读取(从 bit 63 到 bit 0):
1 | syncword(12) | ID(1) | layer(2) | protection_absent(1) |
若存在 CRC,CRC 位于头部末尾,头长度增加 2 字节;CRC 覆盖范围应按对应标准条款处理,不能把普通 CRC32 代替。
ADTS 十六进制示例
下面仅展示头部解析形式,数值为示例:
1 | FF F1 50 80 03 3F FC |
解析时应得到:
1 | syncword = 0xFFF |
示例中的字段必须通过逐 bit 计算验证,不能仅凭肉眼按字节分割。
生成 ADTS 头的规则
给定 AOT、采样率索引、声道配置和 raw AAC 长度 N:
1 | header_length = 7 或 9 |
必须检查 frame_length 不超过 13 位可表达范围;如果 raw block 超长,应调整封装或使用其他容器,而不是截断长度字段。
AAC 帧与采样数
AAC-LC 常见一帧 1024 samples,某些编码工具可能为 960 或其他长度。解码器应根据 AOT/工具配置确定输出采样数。首帧和末帧还可能存在 encoder delay 或 padding,封装到 MP4 时应通过 edit list、priming 信息等机制处理,不能简单按文件字节数计算实际听感时长。
ADIF 头的定位
ADIF 通常以 ASCII ADIF 开始,后续包含 copyright、original_copy、home、bitstream_type、bitrate、数目和 program_config_element 等字段。它是全局码流头,解析器不能把 ADIF 文件按 ADTS 的同步字扫描。
解析器伪代码
1 | while (remaining >= 7) { |
生产代码还应检查采样率索引、profile、channel_configuration 和保留位,并对重同步距离设上限。
验证命令
1 | ffprobe -show_packets -show_streams input.aac |
若 ADTS 能播放但转 MP4 失败,通常是 ASC、AOT、采样率或声道配置转换错误。
ADTS 数据构成图
1 | ADTS Frame |
无 CRC 时完整 Header 为 7 字节;有 CRC 时为 9 字节。frame_length 的值包含以上 Header 和 raw data,不能只当作 payload 长度。
字段逐项作用
| 字段 | 作用 |
|---|---|
syncword |
在连续字节流中寻找帧起点,固定为 12 个 1 |
ID |
区分 MPEG-2/MPEG-4 标识 |
layer |
保留字段,必须为 0 |
protection_absent |
决定是否有 CRC,直接影响 Header 长度 |
profile |
指示 AAC 工具集合,常见值需加 1 才得到 AOT |
sampling_frequency_index |
查表得到采样率 |
channel_configuration |
指示声道布局,不能简单等同声道数 |
frame_length |
当前 ADTS 帧总长度 |
adts_buffer_fullness |
MPEG 音频缓冲模型相关字段 |
number_of_raw_data_blocks |
当前帧中 raw block 数量减 1 |
ADTS 与 AudioSpecificConfig 的关系
1 | ADTS:每一帧重复 Header,适合连续传输 |
因此 AAC 的转换链路是:
1 | ADTS = ADTS Header + raw AAC |
从 7 字节头计算字段
ADTS 字段跨越字节边界,解析时应把前 7 个字节视为一个 56 位位串,再按标准位宽取值,或者使用明确的移位和掩码。以 frame_length 为例,它由可变头中连续的 13 位组成,不能直接读取某两个字节;channel_configuration 也跨越前后两个字节。实现中不应把网络字节直接映射到 C 位域结构体,因为位域顺序和对齐由编译器决定。可移植的实现使用 uint8_t 数组,并在单元测试中覆盖字段的最小值、最大值和跨字节边界值。
protection_absent 是解析分支的关键字段:值为 1 时 Header 只有 7 字节,raw data 从偏移 7 开始;值为 0 时 Header 为 9 字节,偏移 7~8 保存 CRC。若错误地固定从偏移 7 解码,带 CRC 的每一帧都会把 CRC 的两个字节当成 AAC 语法,随后所有帧边界都会错位。frame_length 必须大于等于头长,并且不能超过当前输入缓冲区剩余长度。
采样率、声道和 AOT 的一致性
ADTS 头中的采样率索引、声道配置和 profile 共同决定解码器初始化参数。它们不能只在第一帧解析一次后永久忽略,因为实际流可能在编码器重启后改变配置。稳健的解析器会把每帧配置与当前会话配置比较:如果只发生合法的帧长度变化,应继续解码;如果采样率、声道或 AOT 发生变化,应刷新解码器或拒绝不兼容的帧。channel_configuration 为 0 时,声道布局可能由 program_config_element 描述,不能简单当作单声道。
raw_data_block 与播放时长
ADTS 帧不是固定字节大小的 AAC 包。number_of_raw_data_blocks_in_frame 表示一个 ADTS 帧中包含多少个 raw data block 减一。兼容性最好的编码器通常每帧只放一个 raw block,但解析器仍应检查该字段。一个 AAC-LC raw block 常对应 1024 个采样,但这是编码工具的语义,不是 ADTS 长度字段直接给出的字节数。播放时长必须使用采样数和采样率计算,不能用压缩字节数除以码率做精确结果。
由 ADTS 生成 MP4 音频轨
转换器先从有效 ADTS 帧提取 AOT、采样率索引和声道配置,生成 AudioSpecificConfig;然后去掉每帧 ADTS Header,只把 raw AAC 作为 MP4 sample 写入 mdat,并在 stsz 中记录每个 sample 的长度,在 stts 中记录每个 sample 的采样增量。若编码器有 delay 或 padding,还要在 MP4 的编辑列表或编解码器特定字段中保存,否则转换后首尾会出现静音或时长偏差。这个过程说明 ADTS Header 不能直接复制到 MP4。
AudioSpecificConfig 的位级构成
AudioSpecificConfig 常见的起始字段按 bit 串排列,而不是按字节对齐:audioObjectType 通常占 5 位,samplingFrequencyIndex 占 4 位,channelConfiguration 占 4 位。若 AOT 为 31,后面还会出现扩展的 6 位对象类型;若采样率索引为 15,后面会出现 24 位显式采样率。随后可能出现 extensionAudioObjectType、SBR/PS 标志以及依赖具体对象类型的字段。因此不能把 ASC 固定写成两个字节,也不能把 ADTS 的 profile 两位直接原样复制到 ASC。
以 AAC-LC、44100 Hz、双声道为例,AOT=2,采样率索引=4,channelConfiguration=2,最常见的 ASC 是两个字节。解析器应先按 bit 顺序读出 5、4、4 三个字段,再验证 AOT 是否是解码器支持的对象类型。若 ASC 后还有 GASpecificConfig,必须继续按对象类型语法读取 frameLengthFlag、dependsOnCoreCoder、extensionFlag 等标志;不能因为前两个字节已经得到参数就把剩余数据全部忽略。
ADIF 的全局头和无帧同步特性
ADIF 以四个 ASCII 字节 ADIF 作为识别标志。它的头包含版权标志、原始/复制标志、home、bitstream_type、bitrate、program_config_element 数量及每个 program 的配置。ADIF 将解码配置集中放在文件开头,后面是连续 raw_data_stream,因此它不像 ADTS 那样可以从任意帧边界重新同步。解析 ADIF 时如果丢失开头,通常无法可靠判断后续 raw data 的采样率、声道布局和对象类型。
ADTS 重同步和错误策略
流式解析器通常采用“候选同步字、完整头校验、长度校验”的三步判断。发现 0xFFF 后,先检查 layer、采样率索引、channel_configuration 和 frame_length;如果校验失败,只向前移动一个字节继续搜索,而不是一次跳过 7 个字节。这样可以从损坏帧中恢复,但必须限制最大重同步距离,防止恶意输入让解析器长期扫描。连续出现错误时,应向上层报告丢帧数量、最后有效采样时间和恢复位置。
AAC 帧的缓冲和时间线
解码器输入缓冲以 ADTS frame 或 raw sample 为单位,输出缓冲以 PCM sample 为单位。读取端不能把 frame_length 当成固定播放时长;一帧的时长取决于对象类型、采样率和每帧采样数。对 48 kHz AAC-LC,1024 samples 约为 21.333 ms;在 44.1 kHz 下约为 23.220 ms。实时播放应使用累计采样数生成时间线,避免对每帧时长独立取整造成长期漂移。
标准边界
第四章 AudioSpecificConfig 的位级组成
audioObjectType、采样率和声道配置
AudioSpecificConfig(ASC)通常用于 MP4、MPEG-4 音频轨道和流媒体信令。最常见的前两个字节按高位到低位排列为 audioObjectType、samplingFrequencyIndex 和 channelConfiguration,但它不是 ADTS 头的简单复制。audioObjectType 使用 5 bit;当值为 31 时,后面还要读取 escape 扩展值。采样率索引为 15 时,配置中紧接一个 24 位显式采样率;声道配置为 0 时,则由后续的 program_config_element 描述实际声道布局。
1 | audioObjectType 5 bits |
对于 AAC-LC、44.1 kHz、双声道,常见配置为 0x12 0x10。这两个字节只表示解码所需的基础配置,不能把它当成一帧 AAC 原始数据,也不能直接附加 ADTS 同步字节后当作完整音频帧播放。
GASpecificConfig 和扩展对象类型
AAC-LC 等通用音频对象还包含 GASpecificConfig,其中的 frameLengthFlag、dependsOnCoreCoder 和 extensionFlag 会影响后续 raw data 的解释。HE-AAC 可能通过扩展对象类型或 SBR/PS 配置表达输出采样率和核心采样率的关系。解析器应保留原始对象类型和扩展对象类型,不能看到 samplingFrequencyIndex 就忽略 SBR 导致的实际输出采样率变化。
ADTS 头的逐位校验
固定头与可变头的边界
无 CRC 的 ADTS 头长 7 字节,有 CRC 时长 9 字节。固定头中 syncword 占 12 bit,ID 占 1 bit,layer 占 2 bit,protection_absent 占 1 bit;随后是 profile、采样率索引、私有位、声道配置、原始复制和 home。可变头继续给出版权字段、13 位 frame_length、11 位 adts_buffer_fullness 和 2 位 number_of_raw_data_blocks_in_frame。
frame_length 包含头本身和全部 AAC raw data,不能只用它减去 7 字节而忽略 CRC 情况。number_of_raw_data_blocks_in_frame 的值加一才是当前 ADTS frame 中 raw data block 的数量;工程上通常为零,遇到非零值时,解码器必须按照规范处理 block 边界和 CRC,而不是把整段 payload 当成一个独立 AAC-LC frame。
adts_buffer_fullness 的解释
该字段描述发送端传输缓冲模型中的 fullness,不是“解码器当前剩余字节数”的通用运行时计数器。0x7ff 常用于可变码率或未提供该信息的情况;其他值必须结合码率类型和标准定义解释。调试工具可以显示它,但不应据此判断某一时刻接收缓冲区是否即将溢出。
CRC 的保护范围
当 protection_absent 为 0 时,CRC 紧跟在 9 字节头的字段之后。实现应依据对应标准规定的多项式和保护范围计算,而不是把 CRC 字节简单当作 raw data 的前两个字节。若只做帧边界解析,可以跳过 CRC,但应在输出中明确标记“未验证完整性”。
ADIF 头与 program 配置
全局头的特点
ADIF 使用 ADIF 标识和一个全局配置头,后面是连续的 raw_data_stream。它适合完整文件或需要单一全局配置的场景,不适合要求从任意字节启动的传输。解析器应在读取全局字段后记录 bit offset,因为 ADIF 的字段并不一定按字节边界结束,raw_data_stream 之前还可能存在对齐处理。
program_config_element
当 ADIF 或 ASC 通过 PCE 表达声道布局时,不能只把 channel_configuration 的数值当成声道数。PCE 会分别列出 front、side、back、lfe 等元素及其是否成对,解码器需要根据元素数量和标签建立实际通道映射。忽略 PCE 可能造成中央声道、低频声道和环绕声道被错误折叠。
ADTS 解析器的状态机
帧扫描与提交条件
解析器可以维护 SEARCH_SYNC、READ_HEADER、WAIT_PAYLOAD 和 EMIT_FRAME 四个状态。搜索阶段遇到可能的同步字节后,必须等待足够的头长度;读取阶段校验字段和帧长度;等待阶段只在缓存达到完整 frame_length 后提交。提交成功后,游标移动到当前帧末尾,不能从 payload 内部再次搜索同步字节。
1 | while (buffered >= 2) { |
错误帧与时间线
丢弃一帧后,时间线不能简单把下一帧的时间戳减去一个固定字节长度。应根据有效 frame 中的 raw data block 数量和每 block 的采样数推进采样计数;无法确定损失时,向上层报告未知间隔。语音应用可以插入静音,但插入的采样数必须被记录,避免音视频同步器把恢复数据误认为真实编码输出。
AAC 与容器样本的边界
ADTS 转 MP4
把 ADTS 放入 MP4 时,通常去掉每帧 ADTS 头,仅把 raw AAC sample 写入 mdat,并把 ASC 放入对应的 sample entry(例如 esds)中。转换器必须为每个 sample 建立大小和解码时间表;不能把整个 ADTS 文件原样放入普通 AAC sample 后期待所有播放器识别。反向导出 ADTS 时,则根据 ASC 生成每个样本的 ADTS 头,并正确填写 profile、采样率、声道配置和 frame_length。
参数一致性验证
转换前后应比较对象类型、核心采样率、输出采样率、声道配置和每帧采样数。若 ASC 指示 HE-AAC 而导出器按 AAC-LC 写 ADTS profile,某些解码器会把扩展数据当作普通 LC 解释,表现为变调、噪声或无法播放。验证工具应同时检查容器配置和实际 raw_data 语法。
本文不把 AAC 编码器内部的 MDCT、感知模型和量化搜索展开成实现教程;那些内容属于 MPEG-4 Audio 的完整编码语法。本文重点是工程中可以观察到的 AudioSpecificConfig、ADTS、ADIF、raw_data_block、帧长度和时间信息,以及它们在文件转换和流式解析中的关系。
第五章 AAC 标准族与对象类型
MPEG-2 AAC 与 MPEG-4 Audio
AAC 不是单一固定头格式,而是 MPEG 音频标准族中的一组编码工具和对象类型。MPEG-2 AAC 定义 Main、LC、SSR 等配置;MPEG-4 Audio 在此基础上定义更广泛的 Audio Object Type、通用配置、SBR、PS、ER 工具和传输/容器映射。工程中所谓“AAC-LC”通常同时需要说明它由 ADTS、LATM/LOAS、ADIF 还是 MP4 sample 承载。
Audio Object Type
Audio Object Type(AOT)决定后续配置和 raw data 的语法。常见值包括 AAC Main、AAC LC、AAC SSR、AAC LTP、SBR 和 PS,但标准还定义许多其他类型。解析器不能只支持五位 AOT 的直接值;当初始值为 escape 值时,还要读取扩展位形成真实 AOT。
Profile 与 AOT 的区别
ADTS 固定头中的两位 profile 字段只覆盖有限对象类型,且在 MPEG-4 映射中通常保存 audioObjectType - 1。它无法完整表达所有 MPEG-4 Audio 配置。因此复杂对象类型通常依赖 ASC、LATM 或容器信令,不能仅通过 ADTS 两位字段推断完整解码能力。
采样频率与声道配置
采样频率索引
标准索引表覆盖 96 kHz、88.2 kHz、64 kHz、48 kHz、44.1 kHz、32 kHz、24 kHz、22.05 kHz、16 kHz、12 kHz、11.025 kHz、8 kHz 和 7.35 kHz 等值。保留索引不能被当成零采样率继续计算。ASC 中索引为 15 时使用显式 24 位采样率;ADTS 的字段使用规则应依据相应版本和对象类型限制。
channelConfiguration
该字段不是在所有取值下都等于“声道数”。常见单声道和立体声可直接映射,但更多布局与 element 组合有关;值为零时由 Program Config Element 给出。播放器应建立元素到输出扬声器布局的映射,而不是只申请 channelConfiguration × samples 的缓冲区。
核心采样率与输出采样率
HE-AAC 使用 SBR 时,核心 AAC 采样率和最终输出采样率可能不同。ASC 可以显式或隐式信令扩展采样率。时间轴通常应按最终呈现采样数和对应输出采样率解释,解码器内部核心帧长度则按核心配置处理。把二者混用会造成时长减半或加倍。
AudioSpecificConfig 解析流程
顶层条件语法
解析器先读取 AOT,再读取采样率,再读取 channel configuration。若 AOT 直接表示 SBR 或 PS,则要读取扩展采样率并取得基础 AOT;其他情况下还可能在配置尾部通过 sync extension 指示 SBR/PS。随后依据最终基础 AOT 进入 GASpecificConfig 或其他对象类型配置。
1 | audioObjectType = readAudioObjectType() |
每一步都必须保留 bit offset。ASC 不保证以整字节结束,容器中的 descriptor 长度只提供外层字节界限;解析器仍需验证对象配置内部的 bit 条件闭合。
GASpecificConfig
通用音频配置以 frameLengthFlag、dependsOnCoreCoder 和 extensionFlag 开始。dependsOnCoreCoder 为真时还有 core coder delay。channel configuration 为零时出现 PCE。不同对象类型和 extensionFlag 值可能引入 layer、ER 或其他扩展字段,不支持的组合应返回 unsupported config,而不是假设为 AAC-LC。
frameLengthFlag
该标志影响频谱帧长度。常见 AAC-LC 长帧每通道对应 1024 个输出采样,但特定配置可能使用较短变换帧。时间戳计算应来自已解析的对象配置,不能把所有 AAC sample 永久写死为 1024 samples。
sync extension
某些 ASC 在基础配置之后通过特定同步扩展值携带 SBR 或 PS。搜索扩展前必须先完成对象配置并确认剩余 bit 足够;不能在任意 payload 中搜索魔数,因为普通配置比特也可能偶然出现相同模式。
Program Config Element
PCE 的层级
PCE 描述一个 program 使用的前、侧、后、低频、关联数据和耦合元素。每类先给出元素数量,再逐项给出元素是否为 CPE 以及 tag select。PCE 还包含 mixdown 标志、注释长度和 byte alignment。解析器应先校验数量总和,再按顺序读取 tag,避免恶意计数导致超出配置缓冲。
声道数量计算
Single Channel Element 通常贡献一个声道,Channel Pair Element 通常贡献两个,LFE Element 贡献低频声道。Coupling Channel Element 不应直接按普通输出声道计数。PCE 还表达元素位置与顺序,因此只计算总声道数仍不足以完成多声道输出映射。
PCE 与 ADTS
ADTS channel_configuration 为零时,解码配置需要从 raw data 中的 PCE 获得。流式接收端必须保留最近有效 PCE,并在配置改变时重建解码器。仅从 ADTS 7 字节头输出“0 声道”是错误的诊断方式,正确描述应为“声道布局由 PCE 指定”。
PCE 的完整字段顺序
固定计数字段
Program Config Element 以 tag、对象类型、采样率索引和各类元素数量开始。字段按 bit 紧密排列,不做自然字节对齐:
| 字段 | 位数 | 作用 |
|---|---|---|
element_instance_tag |
4 | 当前 PCE 的实例标识 |
object_type |
2 | PCE 内的对象类型/profile 选择,解释范围不同于五位 AOT |
sampling_frequency_index |
4 | PCE 使用的采样率索引 |
num_front_channel_elements |
4 | 前方 SCE/CPE 元素数量 |
num_side_channel_elements |
4 | 侧方元素数量 |
num_back_channel_elements |
4 | 后方元素数量 |
num_lfe_channel_elements |
2 | LFE 元素数量 |
num_assoc_data_elements |
3 | Associated Data 元素数量 |
num_valid_cc_elements |
4 | 有效 Coupling Channel 元素数量 |
这些数量描述的是元素,不是最终声道数。例如一个 front CPE 的数量贡献一项,但通常代表两个声道。解析器先读计数并验证实现上限,再进入依计数循环;不能先按最大数组分配后等待读取失败。
Mixdown 条件字段
计数之后有三组条件信令:
1 | mono_mixdown_present 1 bit |
这些字段描述推荐或可用的混音关系,不会改变随后列出的原始声道元素数量。播放器可以根据输出能力采用 mixdown,但参考解析器应先保存原配置,不应在读取 PCE 时直接丢弃声道。
Front、Side 与 Back 元素数组
三个主要声道区域的每个条目都由一位 is_cpe 和四位 tag_select 组成:
1 | for each front element: |
is_cpe=0 表示引用 SCE,通常贡献一个声道;is_cpe=1 表示引用 CPE,通常贡献两个声道。tag_select 必须与 raw_data_block 中相应元素的 element_instance_tag 匹配。引用键应包含元素类型,不能只按四位 tag 建一个全局字典,否则 tag 相同的 SCE 与 CPE 可能被混淆。
LFE、关联数据与耦合元素
每个 LFE 和 Associated Data 条目只给四位 tag。每个有效 CC 条目给一位 cc_element_is_ind_sw 和四位 tag:
1 | for each LFE: |
LFE 计入输出声道;Associated Data 与 CCE 不应简单加入扬声器声道总数。CCE 对目标元素的影响由 Coupling Channel 语法决定,PCE 在这里主要建立 program 中的有效引用。
Byte Alignment 与注释字段
全部元素列表读取完后,PCE 执行 byte_alignment(),跳过到下一字节边界的对齐位;随后读取八位 comment_field_bytes,再读取相同数量的注释字节。对齐位属于 PCE 语法,不能错误地当作注释长度高位。
1 | align to next byte boundary |
注释字节不应假定为 UTF-8 或以零结尾。解析器按明确长度保存原始字节,显示层再选择字符集和转义策略。comment_field_bytes 最大 255,但仍需检查 PCE 所在 ASC 或 raw_data_block 的剩余 bit 足够。
PCE 声道计数和引用校验
声道总数算法
声道总数可以从 front、side、back 与 LFE 列表计算:
1 | channels = 0 |
这个数是基础 AAC program 的输出声道数。SBR 不一定改变声道数,PS 却可能把核心单声道扩展为最终双声道,因此完整报告仍要区分 core channel count 与 final output channel count。
Tag 唯一性与存在性
PCE 自身可以在实际声道元素之前出现,所以第一次解析时可以先保存“期望引用集合”,等当前配置对应的元素出现后再验证。重复引用同一 (element_type, tag)、引用不存在元素、元素类型与 is_cpe 冲突以及 raw_data_block 出现未纳入 program 的关键元素,都应进入诊断。
配置更新事务
收到新 PCE 时,先在临时对象中完成计数、bit 边界、tag 冲突和实现能力验证,再原子替换活动配置。解析到一半失败时不能污染旧布局。下游音频缓冲的声道数、标签顺序与混音矩阵只能在 frame 边界切换,并应发出 format-change 事件。
第六章 raw_data_block 的元素结构
元素 ID
一个 raw_data_block 由若干语法元素组成,直到 ID_END。常见元素包括 Single Channel Element、Channel Pair Element、Coupling Channel Element、LFE Channel Element、Data Stream Element、Program Config Element 和 Fill Element。每个元素 ID 占固定少量 bit,后续语法依元素类型而不同。
| 元素 | 常用缩写 | 基本作用 |
|---|---|---|
| Single Channel Element | SCE | 单个独立声道 |
| Channel Pair Element | CPE | 一对相关声道 |
| Coupling Channel Element | CCE | 耦合信息 |
| LFE Channel Element | LFE | 低频效果声道 |
| Data Stream Element | DSE | 附加数据 |
| Program Config Element | PCE | program 与声道布局 |
| Fill Element | FIL | 填充和扩展 payload |
解析器必须循环到 END,并处理随后的 byte alignment。若 ADTS 声称一个 frame 有多个 raw data block,还要根据 CRC 和 block 边界逐个解析,不能只寻找第一个 END 后提交整帧。
Element Instance Tag
声道类元素通常携带 instance tag,用于与 PCE 中的 tag select 关联。tag 不是输出声道索引,也不是元素数量;接收端应以“元素类型 + tag”作为引用键,并检查同一配置内是否存在非法冲突。
Individual Channel Stream
global_gain 与 ICS Info
单声道数据或声道对中的各通道包含 global_gain,随后根据 common_window 等条件读取 ICS Info。ICS Info 描述 window sequence、window shape、max_sfb 和 window group。window sequence 决定长窗、长起始、八短窗或长结束语法,直接影响 scale factor band 分组和频谱数据长度。
Window Sequence
长窗通常只有一个 window group,短窗最多通过七个 grouping bit 把八个短窗合并为若干组。max_sfb 的位宽在长窗和短窗模式中不同。解析器如果始终按长窗读取,会在短窗帧进入 section data 时立即错位。
Predictor 和 LTP
特定对象类型和 window sequence 允许预测数据或 LTP 语法。AAC-LC 通常不使用 Main profile predictor,但解析器仍应根据 AOT 决定字段是否存在。把不支持的预测工具静默跳过会导致后续频谱 bit 无法定位,应尽早返回不支持。
Section、Scale Factor 和频谱数据
Section Data
section data 把每个 window group 的 scale factor bands 分成若干区段,每段给出 codebook 和长度。长度使用 escape 形式累加,直到非 escape 值。区段必须连续覆盖到 max_sfb,既不能重叠也不能越界;zero codebook 表示该区段没有频谱系数。
Scale Factor Data
scale factor 使用 Huffman 差分编码,并根据 codebook 类型解释为普通 scale factor、noise energy 或 intensity stereo position。解码器维护不同的累计状态;把所有值都累加到同一个 scale factor 会导致噪声替换或 intensity stereo 错误。
Pulse、TNS 和 Gain Control
长窗下可能出现 pulse data,Temporal Noise Shaping 依据窗组携带滤波器阶数和系数,某些对象类型还允许 gain control。三个工具均由显式 present flag 控制。TNS 阶数、长度和系数压缩位宽存在上限,解析器应逐层验证,防止在频谱解码前失去 bit 同步。
Spectral Data
频谱系数按 section 指定的 codebook 解码。不同 codebook 表示二元组或四元组、带符号或需要额外 sign bit,escape codebook 还会读取扩展幅值。频谱系数数量由 scale factor band 边界和 window group 决定,不是固定的 1024 个 Huffman 符号。
Channel Pair 与立体声工具
common_window
CPE 的 common_window 为真时,两个通道共享 ICS Info,并可能出现 MS mask;为假时两个通道分别携带 ICS。共享 window 不代表两个声道拥有相同频谱系数,只是时间窗和分带结构一致。
MS Stereo
Mid/Side 工具按 scale factor band 指示是否把左右频谱解释为 M/S。mask present 的不同值可表示全不使用、显式 bit mask 或全部使用。解码顺序需要在反量化等正确阶段应用 M/S,不能简单对最终 PCM 做左右相加减来替代标准频域处理。
Intensity Stereo
Intensity stereo 通过特定 codebook 和 scale factor 表达高频声像关系,不传输完整的第二通道频谱。它与 MS stereo 的标志和处理顺序有条件关系。验证器应检查 intensity codebook 只出现在允许的声道和区段。
Fill Element、SBR 与 PS
Fill Element
FIL 首先给出 count,达到 escape 值时再读取扩展计数。payload 可以是真正填充,也可包含扩展类型。解析器必须用 count 限制扩展读取,未知扩展按长度跳过;不能把 FIL 后所有剩余字节都当作 SBR。
SBR 扩展
SBR 通过高频参数重建扩展输出带宽。扩展 payload 包含 header、网格、频率表、包络、噪声、逆滤波和可选扩展数据。配置可以跨帧复用,缺失或损坏 header 时解码器可能需要沿用旧状态或暂时关闭 SBR。容器/ASC 的扩展采样率必须与实际 SBR 信令一致。
Parametric Stereo
PS 在低码率场景用参数重建立体声,通常作为 SBR 扩展的一部分。解码输出声道数可能因此与核心 AAC 的 element 数不同。媒体框架应以最终解码配置报告输出声道,不要仅从核心 channel configuration 分配单声道缓冲。
ADTS 字节级构造
头字段打包
写入 7 字节无 CRC ADTS 头时,字段会跨越字节边界。实现应先验证 profile、采样率索引、声道配置和 13 位 frame length,再按掩码写入:
1 | byte0 = 0xFF |
伪代码只表达字段跨字节关系,正式实现应使用明确的移位掩码并用已知向量逐字节验证。不能把 C bit-field 结构直接写入文件,因为 bit-field 排列和结构 padding 由编译器实现决定。
frame_length 上限
13 位字段限制单个 ADTS frame 的总长度。构造前计算 header_bytes + payload_bytes,超出可表示范围必须分配其他传输方式或调整编码,而不是截断高位。payload 也必须对应整数个 raw data block,不能在任意字节处分割一个 AAC frame 后各自加 ADTS 头。
ADIF 位流解析
固定标识和版权字段
ADIF 从 ASCII ADIF 开始,之后的字段以 bit 紧密排列。copyright_id_present 控制一个 72 位 copyright ID,original_copy、home、bitstream_type 和 bitrate 紧随其后。bitstream_type 影响 program buffer fullness 字段是否出现,不能使用固定字节偏移跳到 PCE。
Program 数量
num_program_config_elements 保存的是数量减一,解析循环次数为字段值加一。每个 program 前可能有 buffer fullness,然后是一份 PCE。解析完所有 PCE 后进入 byte alignment 和 raw data stream。循环计数、PCE 长度和对齐都需要以 bit reader 状态为准。
ADIF 的恢复限制
ADIF 没有每帧同步字和长度,所以头部丢失后很难从中间恢复。文件验证器可以从开头解析并按 AAC 语法推进,但不能仅搜索某个两字节模式认定为新帧。需要随机访问或网络丢包恢复时,通常选择带独立边界的传输或容器。
ADIF Header 的完整条件语法
顶层字段顺序
1 | adif_id 32 bits, ASCII "ADIF" |
num_program_config_elements 是“数量减一”字段,循环次数为值+1;最大字段值15表示16个PCE。循环使用受检整数,且总PCE数受实现资源上限约束。
四字节标识
文件开头
1 | 41 44 49 46 |
ADIF标识必须从所声明流起点出现。与ADTS不同,它不是每帧同步字;后续Raw Data中偶然出现ASCII ADIF不代表新Header。Parser先确认至少四字节,再创建bit reader从bit32继续。
Copyright字段
72位条件区域
copyright_id_present=1时,紧跟9字节/72位copyright_id。它是固定长度字节数据,不保证NUL终止:
1 | copyright_id[0..8] |
读取器按9字节保存,日志转义不可打印字符;不能调用无界字符串函数。Flag为0时,这72位完全不存在,original_copy紧随Flag下一bit。若仍固定跳9字节,后续bitstream_type、bitrate和所有PCE都会错位。
original_copy与home是内容属性标志,不参与AAC频谱解码,但必须按bit保留。转封装是否保留到目标元数据取决于目标格式能力。
Bitstream Type 与Bitrate
CBR和VBR结构差异
bitstream_type通常区分常量/变量比特率模型。其最直接语法影响是:
1 | if bitstream_type == 0: |
不能用bitrate值是否变化来决定是否读取fullness;只看bitstream_type。Fullness是解码缓冲模型字段,不是当前文件剩余字节或帧长度。
bitrate为23位无符号:
1 | 0 <= bitrate <= 2^23 - 1 = 8388607 |
写入128000 bit/s:
1 | 128000 decimal = 0x01F400 |
但该23位字段往往不从字节边界开始,不能直接搜索 01 F4 00。必须通过BitWriter连续写23位。
Program Config Element 的ADIF角色
每个Program一份配置
ADIF在全局Header中放PCE,用Element数组描述对象类型、采样频率和声道元素。Raw Data Block中的SCE/CPE/LFE通过Element Instance Tag与配置关联。Decoder不能只读第一PCE后忽略其他Program。
PCE关键语法:
1 | element_instance_tag 4 |
PCE的2-bit object_type不是任意5-bit MPEG-4 Audio Object Type。对GA配置按该PCE语义映射Main/LC/SSR等,保留值不能当AAC-LC。
17字节合成ADIF Header
配置
构造:无Copyright、original/home=0、bitstream_type=0、bitrate128000、一个PCE、buffer fullness 0x7FFFF;PCE为AAC-LC、44100 Hz、一个Front CPE(双声道)、无其他元素、无注释。
逻辑字段:
1 | adif_id = ADIF |
Bit打包后的17字节:
1 | 41 44 49 46 00 3E 80 00 FF FF E0 A0 80 00 04 00 00 |
这是Header本身,后面紧跟raw_data_stream;它不是完整可播放AAC文件,因为示例未附Raw Data。
合成Header的Bit Offset
ADIF固定部分
| Bit Range | 字段 | 值 |
|---|---|---|
| 0..31 | adif_id | ADIF |
| 32 | copyright_present | 0 |
| 33 | original_copy | 0 |
| 34 | home | 0 |
| 35 | bitstream_type | 0 |
| 36..58 | bitrate | 128000 |
| 59..62 | num_program_config_elements | 0 |
| 63..82 | adif_buffer_fullness | 0x7FFFF |
PCE从bit83开始,并不在字节边界。若Parser在fullness后调用byte_align再读PCE,会错过5bit并破坏配置。
PCE部分
| Bit Range | 字段 | 值 |
|---|---|---|
| 83..86 | element_instance_tag | 0 |
| 87..88 | object_type | 1 |
| 89..92 | sampling_frequency_index | 4 |
| 93..96 | num_front | 1 |
| 97..100 | num_side | 0 |
| 101..104 | num_back | 0 |
| 105..106 | num_lfe | 0 |
| 107..109 | num_assoc_data | 0 |
| 110..113 | num_valid_cc | 0 |
| 114 | mono_mixdown_present | 0 |
| 115 | stereo_mixdown_present | 0 |
| 116 | matrix_mixdown_present | 0 |
| 117 | front[0].is_cpe | 1 |
| 118..121 | front[0].tag | 0 |
| 122..127 | alignment zero bits | 0 |
| 128..135 | comment_field_bytes | 0 |
Header恰好136bit=17字节。byte_alignment()出现在PCE Element数组之后、comment length之前;不是在ADIF全局固定字段后。
Header写入算法
1 | write_adif(config): |
PCE Writer先验证所有Element Count能装入字段、Tag范围0..15、声道总数符合应用上限、sampling index有效、comment length≤255。写入过程中不截断Count高位。
Header解析算法
1 | parse_adif(bitreader): |
只有全部PCE成功、声道和对象类型受支持后才提交配置。解析到一半失败不能覆盖Decoder当前有效配置。
PCE声道数实例
一个Front CPE
PCE数组中:
1 | front element count = 1 |
CPE贡献2个声道,其他数组全零:
1 | channels = 2 |
num_front_channel_elements=1是Element数,不是一个声道。若把它直接加一,会把Stereo错判Mono。
多Program边界
数量减一
字段值2表示三PCE:
1 | for i = 0,1,2 |
bitstream_type=0时每个PCE前各有自己的20-bit fullness;不是全Header只有一个。VBR模式则三个都没有。Parser不能根据第一个PCE结果推断后续PCE长度,逐份执行条件语法。
多个Program可能采样率/对象类型不同,播放器如何选择由应用。容器级验证列出全部,不擅自合并Element Tag命名空间。若同一Program内Tag冲突,按PCE规则报告。
ADIF与Raw Data边界
没有Frame Length
ADIF Header结束后是Raw Data Stream,但没有ADTS式13-bit frame_length。要逐Frame推进,必须解析AAC syntactic elements直到ID_END和对齐,再继续下一Raw Data Block。未知对象类型或Huffman损坏会使边界恢复困难。
因此不能执行:
1 | find FF F? -> next frame |
Raw频谱数据可自然出现相似bit pattern。ADIF更适合完整文件/可靠输入,而非任意包丢失后独立重同步。
ADIF转其他封装
配置提取
可从PCE提取object type、sampling index和声道Element布局,生成适当ASC/PCE表示。若目标ADTS的channel_configuration无法表达自定义布局,需要保留PCE并设置channel configuration 0;不能只写声道总数对应的常见编号。
Frame边界
ADIF到ADTS/MP4必须完整解析raw_data_stream得到每个Access Unit范围和Sample duration。仅删除ADIF Header后把剩余全部作为一个MP4 Sample是错误的。转换还要建立stsz/stts或逐ADTS Header。
反向从ADTS到ADIF时,所有帧配置应稳定/可由选定PCE表达;需要计算/选择bitrate和buffer fullness。丢失原Copyright/PCE注释等元数据无法凭ADTS恢复。
ADIF错误向量
1 | 标识少于4字节或不是ADIF |
验证报告记录每字段bit offset,而不只记录字节offset;ADIF大量字段跨字节,bit位置才可定位错位根因。
解码帧与时间模型
输入帧和输出采样
一个压缩 sample 的字节大小随内容变化,解码时长由对象类型、frame length、SBR 和采样率决定。码率估算必须在足够长时间窗口内累加字节:
1 | average_bitrate = total_frame_bytes × 8 / total_duration_seconds |
用单帧字节数乘固定 fps 只能得到瞬时估算,对 VBR 流波动很大。
编码延迟和 priming
AAC 编码器的滤波器组和预处理会产生编码延迟,文件容器可通过 edit list、priming/padding 元数据或其他约定描述首尾应丢弃的采样。ADTS 裸流通常没有统一的无缝播放元数据;仅用帧数乘 1024 可能比原始 PCM 多出首尾填充。
时间戳累计
推荐维护整数累计采样数,再以有理数换算时间:
1 | pts = base_pts + total_output_samples / output_sample_rate |
不要逐帧把 21.333 ms 四舍五入成 21 ms,否则长时间播放会持续漂移。跨采样率或配置切换时,应建立新的时间段并明确连续性。
AAC 解析器安全与验证
长度和循环限制
ADTS、ADIF、ASC 和 raw_data_block 的每一级长度都要绑定到当前输入边界。Huffman 解码、escape 长度、元素循环、section 循环和 FIL 扩展必须有规范与实现上限。遇到 END 前耗尽 bit 时报告截断,不得读取下一 ADTS frame 的 header 作为当前频谱数据。
配置状态提交
ASC、PCE 或 SBR header 只有完整解析后才更新活动配置。损坏的新配置不应覆盖上一有效配置。配置变化可能改变采样率、声道、frame length 和输出缓存大小,下游必须在 frame 边界重新初始化。
验证矩阵
测试集合应包含 AAC-LC 单声道/立体声、多声道 PCE、不同采样率、CRC ADTS、多 raw data block、显式频率 ASC、HE-AAC、HE-AAC v2、ADIF、截断 frame、非法 section 长度和跨包 ADTS。每个样本应核对配置、frame length、元素列表、输出采样数、时间戳和实际解码结果。
第七章 AAC 编码器处理链
从 PCM 到 AAC 频谱
典型 AAC 编码器先接收等间隔 PCM,按帧维护重叠分析窗口,执行 MDCT 得到频域系数;随后根据听觉模型、瞬态检测和比特预算选择窗序列、Scale Factor、量化步长、立体声工具和码本,最终将元素语法、Huffman 数据和扩展信息打包为 raw_data_block。
1 | PCM |
编码器实现可以使用不同的心理声学模型和码率控制搜索,因此同样配置、同一 PCM 输入不保证输出 bit-exact。解码语法、对象类型约束和最终 PCM 重建则必须遵守标准。
帧重叠
MDCT 使用重叠窗,当前输出依赖相邻帧的频谱与窗状态。解码器不能把每个 AAC frame 当作完全无状态的独立 PCM 块;从中间开始即使配置齐全,也可能需要至少一帧建立 overlap,首帧会受到 priming 和历史状态影响。
第八章 Window Sequence 与滤波器组
ONLY_LONG_SEQUENCE
稳态内容常使用长窗。一个长变换提供较高频率分辨率,适合音调稳定信号,但对突然瞬态的时间定位较差。ICS Info 中的 window sequence 和 shape 决定当前窗及与前一帧的过渡。
LONG_START_SEQUENCE
长开始窗从长窗过渡到短窗,通常在检测到即将出现瞬态时使用。它的窗形前半部与长窗相容,后半部通过平坦/短窗区域完成过渡。解码器必须按 sequence 选择规定组合,不能只看 window_shape 使用一张固定窗表。
EIGHT_SHORT_SEQUENCE
短窗帧包含八个短 MDCT 窗,提高时间分辨率以减少 pre-echo。scale_factor_grouping 的七个 bit 把相邻短窗合成 window group,共享 section 和 scale factor 语法。group length 总和必须等于八。
LONG_STOP_SEQUENCE
长停止窗从短窗返回长窗,与 LONG_START 对称衔接。非法的窗序列跳转可能导致 overlap 不连续;解码器可按当前语法处理,但验证器应检查相邻帧序列是否符合允许转换。
Window Shape
AAC 定义不同分析/合成窗形,例如正弦窗和 KBD 窗。当前帧的 overlap 可能同时依赖前一窗形与当前窗形。window_shape 是状态字段;丢帧隐藏需要决定如何延续该状态。
MDCT 与 IMDCT
变换长度
常见 AAC-LC 长块产生 1024 个频谱系数,短块每个产生较少系数并按八窗组织。具体长度与 frameLengthFlag、对象类型和标准版本有关。代码应由配置选择常量,而不是把 1024 写进所有数组边界。
反变换与 Overlap-Add
解码端对反量化频谱执行 IMDCT、加窗和 overlap-add。前一帧后半与当前帧前半相加得到连续 PCM。内部累积需有足够精度并最终限幅/转换到目标 PCM。丢失 overlap buffer 会在帧边界产生明显爆音。
数值精度
浮点和定点 IMDCT 都可实现兼容解码,但定点需要精确管理缩放、饱和和 headroom。标准符合性测试通常允许定义范围内的数值容差,并不意味着任意低精度近似都可接受。
Scale Factor Band
分带边界
AAC 将频谱线划分为 Scale Factor Band,边界取决于采样率索引和长/短窗。表不是简单等宽划分。max_sfb 表示当前语法使用到的 band 数,必须不超过该采样率/窗类型允许的最大值。
Window Group
短窗组中,同一 scale factor band 跨 group 内多个窗组织 section 和频谱。计算系数数量时要乘 group length,并按短窗 stride 定位。把 group 当作单窗会少读频谱数据并在下个 section 错位。
Zero HCB
使用 ZERO_HCB 的 section 不传输频谱系数,解码后该区段为零,除非受其他工具重建。它不同于 EOB;EOB 属于某些熵编码块的结束符,而 zero codebook 是 section 级选择。
第九章 AAC 量化模型
非线性反量化
AAC 频谱量化不是简单线性乘以量化表。解码通常对量化整数应用约 |q|^(4/3) 的非线性反量化,再依据 global gain 和 scale factor 进行二的幂缩放。符号独立恢复。
Global Gain
global_gain 是通道级基准,实际每 band 缩放还由差分 scale factor、对象工具和窗口状态决定。它不是时域 PCM 音量字段,也不能通过减 6 就保证输出恰好降低 6 dB。
Scale Factor 差分
普通 scale factor 以 Huffman 差分方式更新累计值。Noise 和 intensity section 使用不同累计基准与首值语法。验证器要限制累计范围,防止损坏差分导致移位量极端并触发算术溢出。
码率控制
编码器在 bit reservoir/目标码率约束下迭代选择 global gain、scale factor 和 codebook,使量化噪声低于或接近 masking threshold。此搜索属于编码策略;码流只保存最终参数和量化系数。
AAC Huffman Codebook
频谱码本类别
不同 spectral codebook 编码四元组或二元组,数值范围、是否内含符号、是否使用 sign bit 和 escape 的规则不同。Section Data 的 codebook ID 选择解释方式。解码器必须使用对应维度填充系数,不能所有 symbol 都解成一对值。
Scale Factor 码本
Scale factor 使用独立 Huffman 表,symbol 映射为以零为中心的差值。它不使用 spectral codebook 的 sign/escape 规则。读取失败会改变后续 bit offset,因此应立即终止当前 element。
Sign Bit
某些无符号频谱码本在 Huffman symbol 给出绝对值后,为每个非零分量附加一个 sign bit。零值没有 sign bit。统一为所有分量读取 sign 会使频谱错位。
Escape Codebook
Escape codebook 对达到边界的幅值读取额外 unary/bit 扩展,允许更大系数。解析器限制 unary 长度和最终幅值位数,并检查整数移位溢出。扩展后再应用 sign。
Pulse Data
脉冲位置
Pulse tool 可在长窗频谱的特定位置给量化系数增加幅度。语法给出 pulse 数、起始 SFB、相对 offset 和 amplitude。位置通过累计 offset 得到,必须落在有效频谱范围。
应用顺序
Pulse 通常在频谱 Huffman 解码后、反量化前修改量化系数。符号决定增加或减少方向。放在反量化后直接加浮点幅度会得到错误能量。
条件限制
短窗序列通常不允许 pulse data。发现 present flag 与窗类型矛盾时,应报告非法码流而不是继续读一套不存在的短窗 pulse 语法。
Temporal Noise Shaping
频域滤波
TNS 在频谱域应用预测滤波,控制量化噪声的时间包络。每个 window 可以有若干 filter,语法给出长度、阶数、方向、系数分辨率和压缩标志。滤波区域由 scale factor band 转频谱线边界确定。
系数解量化
码流中的 TNS 系数是有限位索引,需要按分辨率映射为滤波系数。方向决定频谱遍历顺序。阶数为零时没有系数;超过对象类型/采样率允许上限应拒绝。
稳定与边界
实现按标准滤波形式处理,并限制频谱索引在当前窗内。损坏 length 不能让滤波跨入下一短窗。TNS 状态通常是帧内参数,不应错误跨帧复用。
Perceptual Noise Substitution
Noise Codebook
PNS 使用 NOISE_HCB 指示某 band 不传具体频谱,而由解码器生成伪随机噪声并按 noise energy 缩放。首个 noise energy 和后续差分 scale factor 的语法不同。
随机序列
解码器需要标准/实现约定兼容的伪随机生成与通道关联策略,保证能量和立体声相关性正确。为了 bit-exact 回归,随机状态初值必须可控。PNS 输出不应每次播放完全不同到超出符合性容差。
与立体声工具
成对声道的 noise band 可能通过 MS mask 建立相关噪声。处理顺序错误会让噪声声像从中心漂到两侧。验证器应联合检查 codebook、MS mask 和 window group。
Main Profile Prediction 与 LTP
Main Predictor
AAC Main 可在频谱域使用向后自适应预测,ICS Info 携带 predictor reset 和各 SFB 使用标志。预测器具有跨帧状态,随机接入和丢帧需要重置/恢复。AAC-LC 解码器可不实现 Main,但必须拒绝对应 AOT。
Long Term Prediction
LTP 使用过去时域/变换信息预测当前频谱,配置包含 lag、coef 和使用 band 标志。对象类型决定 LTP 字段位于何处。LTP 增加参考状态,不能把 LTP frame 当作完全独立 sample。
Gain Control 与 SSR 边界
Gain Control Data
某些对象类型允许 gain control,按 band/window 给出调整点。AAC-LC 通常不使用该工具。解析器在工具不允许的 AOT 中看到 present flag 应判错。
SSR
Scalable Sample Rate 对象使用不同滤波器组和 gain control 结构,不能由 LC 解码器只忽略几个字段实现。容器 AOT 能正确区分能力,应用应在打开媒体前做支持检查。
Bit Reservoir 与缓冲模型
平均码率和瞬时帧大小
AAC 编码器可以在简单帧少用 bit、复杂帧多用 bit,使每帧字节数变化。ADTS frame_length 给出当前帧实际字节,不能由平均码率固定推导。接收缓冲按峰值 frame 和传输抖动规划。
ADTS Fullness
adts_buffer_fullness 与标准缓冲模型有关。对 VBR 常用全 1 特殊值,CBR 可提供计算值。它不是接收端 socket 当前缓存长度,也不是任意填 payload_size。
Decoder Buffer
容器或 descriptor 可能声明 bufferSizeDB、maxBitrate 和 avgBitrate。验证器可从实际样本统计比较,但编码突发窗口和标准模型比单帧峰值更复杂。字段不一致应报告为描述错误,而不是改写 AAC payload。
ADTS 固定头逐字节实例
示例字段
假设一个无 CRC 的 MPEG-4 AAC-LC ADTS frame,采样率索引为 4(44.1 kHz),双声道,frame length 为 100 字节,fullness 使用 VBR 特殊值,raw data block count 为零。构造器先把 profile 写为 AOT-1,再把 13 位长度拆到 byte3-byte5。
位图
1 | byte0: 11111111 syncword[11:4] |
字段实际跨位数应以标准定义和代码掩码为准;位图用于显示相邻字段跨字节的关系。生成后应重新用独立 parser 读回所有字段,避免写入器和读取器共享同一错误掩码。
长度验证
frame_length=100 表示从 byte0 到 payload 末尾共 100 字节;无 CRC header 为 7 字节,因此 raw payload 为 93 字节。若文件剩余只有 90 字节,该帧截断。下一同步字偶然出现在前 90 字节内也不能把它当成当前帧合法结束。
100 字节示例的完整 Header
按上述字段构造出的七字节头为:
1 | FF F1 50 80 0C 9F FC |
把它重新解析可得到 syncword=0xFFF、ID=0、layer=0、protection_absent=1、profile=1、sampling_frequency_index=4、channel_configuration=2、aac_frame_length=100、adts_buffer_fullness=0x7FF、number_of_raw_data_blocks_in_frame=0。其中 profile=1 并不表示 AOT 1,而是按 ADTS 规则对应 Audio Object Type = profile + 1 = 2,也就是 AAC-LC。
ADTS 固定头的精确位偏移
七字节无 CRC Header
ADTS 的固定头和可变头合计 56 bit。以下偏移从第一个同步字节的最高位开始计数,采用网络位序,即每个字节先读 bit 7:
| Bit 偏移 | 位数 | 字段 | 解释 |
|---|---|---|---|
| 0 | 12 | syncword |
固定为 0xFFF |
| 12 | 1 | ID |
0 表示 MPEG-4,1 表示 MPEG-2 |
| 13 | 2 | layer |
必须为 0 |
| 15 | 1 | protection_absent |
1 无 CRC;0 存在标准规定的错误校验语法 |
| 16 | 2 | profile |
ADTS 配置字段,常见映射为 AOT 减一 |
| 18 | 4 | sampling_frequency_index |
采样频率表索引 |
| 22 | 1 | private_bit |
私有用途,不改变标准采样率表 |
| 23 | 3 | channel_configuration |
三位声道配置,0 表示需由 PCE 描述 |
| 26 | 1 | original_copy |
原始/复制标志 |
| 27 | 1 | home |
home 标志 |
| 28 | 1 | copyright_identification_bit |
版权标识位的一部分 |
| 29 | 1 | copyright_identification_start |
版权标识开始标志 |
| 30 | 13 | aac_frame_length |
整个 ADTS frame 长度,包含头、CRC 结构和 AAC 数据 |
| 43 | 11 | adts_buffer_fullness |
缓冲模型字段,0x7FF 常用于 VBR |
| 54 | 2 | number_of_raw_data_blocks_in_frame |
当前 frame 中 raw_data_block 数量减一 |
这个表比按 byte 记忆掩码更可靠。实现 BitReader 时,连续读取上述位宽即可自然处理跨字节字段;面向高性能的固定字节实现则应使用相同表建立单元测试。
采样频率索引表
AAC 标准配置使用如下索引。ADTS 没有给显式 24 位采样率留出位置,所以索引 15 不能像 ASC 那样紧跟显式频率;ADTS 中应拒绝该值。索引 13、14 保留,也不能当作“未知但继续解码”。
| 索引 | 采样率/Hz | 索引 | 采样率/Hz |
|---|---|---|---|
| 0 | 96000 | 7 | 22050 |
| 1 | 88200 | 8 | 16000 |
| 2 | 64000 | 9 | 12000 |
| 3 | 48000 | 10 | 11025 |
| 4 | 44100 | 11 | 8000 |
| 5 | 32000 | 12 | 7350 |
| 6 | 24000 | 13、14 | 保留 |
| 15 | ASC 中的显式频率转义 | ADTS 中不可直接表达 |
索引决定的是 AAC core 配置频率。存在 SBR 时,最终输出频率还可能由扩展配置决定;只读取 ADTS index 后把它永远称为“播放采样率”并不严谨。
13 位 Frame Length 的拆装
aac_frame_length 的高两位落在 byte 3 最低两位,中间八位落在 byte 4,低三位落在 byte 5 最高三位:
1 | frame_length = ((byte3 & 0x03) << 11) |
反向写入时应只修改对应位,保留 byte 3 的声道与版权标志以及 byte 5 的 fullness 高位:
1 | byte3 = (byte3 & 0xFC) | ((frame_length >> 11) & 0x03) |
13 位最大值是 8191。写入前还需验证 frame_length >= header_and_error_check_bytes,并验证 raw AAC payload 加头后的和没有超过 8191。不能截掉高位继续写,因为那会让下一个同步搜索从当前 payload 中间开始。
11 位 Fullness 的拆装
adts_buffer_fullness 横跨 byte 5 低五位和 byte 6 高六位:
1 | fullness = ((byte5 & 0x1F) << 6) |
VBR 特殊值 0x7FF 的高五位和低六位都为一,所以常见头尾呈现为 ... 9F FC、... 5F FC 或 ... 3F FC;byte 5 的最高三位仍属于 frame length,不能因为看到低位全一就把整个 byte 5 当作 fullness。
ADTS 有 CRC 与多 Raw Block 的结构
单个 Raw Block
当 protection_absent=1 且 number_of_raw_data_blocks_in_frame=0 时,最简单结构是七字节头后直接跟一个 raw_data_block。当 protection_absent=0、仍只有一个 raw_data_block 时,头部语法后还有 16 位 crc_check,常被概括为九字节头:
1 | adts_fixed_header 28 bit |
因此单块模式下:
1 | payload_bytes = aac_frame_length - (protection_absent ? 7 : 9) |
这里的 payload 指供 raw_data_block 解析器读取的区域。CRC 两字节不能送入 AAC 元素层,也不能在转封装到 MP4 时保留下来。
多个 Raw Block
number_of_raw_data_blocks_in_frame 的值是数量减一,字段范围 0~3,所以一个 ADTS frame 最多表示一至四个 raw_data_block。存在多个 block 且启用错误保护时,语法不再只是“固定九字节头”:Header Error Check 中会携带各 block 的位置以及头部 CRC,各 raw block 之后还存在相应错误校验。解析器必须按 adts_frame、header_error_check 和 adts_raw_data_block_error_check 的条件语法读取,不能统一跳过两个字节。
1 | raw_block_count = number_of_raw_data_blocks_in_frame + 1 |
若实现只支持单 block,应在字段非零时返回明确的 unsupported multiple raw_data_blocks,而不是把后续位置/CRC 当成第一个 AAC element。转封装工具也需要决定是把多个 block 拆成多个 MP4 sample,还是拒绝无法可靠拆分的输入。
CRC 算法的实现边界
ADTS 错误保护使用标准定义的保护范围和 CRC 过程。实现不能因为字段宽度是 16 bit 就随意代入常见的 CRC-16/IBM、CCITT-FALSE 等参数;多 block 模式的不同 CRC 字段还保护不同语法区域。若当前工具只做结构解析,可以正确识别并跳过 CRC、将状态标为 present but unchecked。若声称 CRC valid,则必须依据相应 MPEG Audio 规范实现保护位流选择、初值和生成多项式过程,并用符合性向量验证。
真实 AAC-LC ADTS 流实例
编码配置与帧目录
下面的受控测试流使用 MPEG-4 AAC-LC、44.1 kHz、双声道、96 kbit/s 目标码率和无 CRC ADTS 封装。文件总长 987 字节,共五个 frame。每个头都声明 profile=1、频率索引 4、声道配置 2、VBR fullness 0x7FF 和一个 raw_data_block,但各帧实际长度不同。
| 帧索引 | 文件偏移 | 七字节 Header | Frame 长度 | Raw Payload 长度 |
|---|---|---|---|---|
| 0 | 0 | FF F1 50 80 1A 9F FC |
212 | 205 |
| 1 | 212 | FF F1 50 80 23 5F FC |
282 | 275 |
| 2 | 494 | FF F1 50 80 1E 3F FC |
241 | 234 |
| 3 | 735 | FF F1 50 80 1D DF FC |
238 | 231 |
| 4 | 973 | FF F1 50 80 01 DF FC |
14 | 7 |
累计长度验证为:
1 | 212 + 282 + 241 + 238 + 14 = 987 |
每一帧下一偏移都由当前声明长度计算,而不是搜索同步字:0+212=212、212+282=494、494+241=735、735+238=973、973+14=987。最后一个 frame 很小并不自动非法;低复杂度或 flush 帧可能只需很少 payload。结构验证应依靠完整 AAC 语法和边界,而不是人为规定“每帧至少几十字节”。
首帧 Header 逐字节解析
首帧头:
1 | FF F1 50 80 1A 9F FC |
前两字节 FF F1 的二进制为 11111111 11110001。前 12 位是同步字;接下来 ID=0,所以是 MPEG-4;layer=00;最后 protection_absent=1。第三字节 50 = 0101 0000,最高两位 01 给出 profile 1,随后 0100 给出 44.1 kHz 索引,private bit 为零,最后一位是声道配置最高位零。
第四字节 80 = 1000 0000 的最高两位是声道配置低两位 10,与前一字节拼成 010b=2。版权相关位均为零,最低两位为 frame length 高两位。长度按公式计算:
1 | ((0x80 & 0x03) << 11) = 0 |
fullness 的计算为:
1 | ((0x9F & 0x1F) << 6) = 1984 |
最后 0xFC & 0x03 = 0,所以只有一个 raw_data_block。无 CRC,raw payload 从文件偏移 7 开始,长度 212-7=205,下一 frame 必须从偏移 212 开始。
帧时长与累计时间
对于本例 AAC-LC 的 frameLengthFlag=0,每个 raw_data_block 表示 1024 个输出采样时刻。每帧名义时长为:
1 | 1024 / 44100 second |
五帧解码时间轴共推进 5120 个采样,即约 116.099773 ms。输入信号时长比它短时,编码器仍可能产生 priming 和 flush 帧;展示/转封装不能仅凭源滤镜声明的持续时间删除最后一个短帧。精确裁剪应使用编码延迟与尾部 padding 信息。
对于其他 GASpecific 配置,单 raw_data_block 的采样数由对象类型与 frameLengthFlag 等配置决定,不能把 1024 写死为所有 AAC 对象的真理。多 raw block frame 的总采样数为:
1 | samples_in_adts_frame = samples_per_raw_data_block |
VBR 不能由相邻长度差判错
五帧长度从 14 到 282 字节变化,且 fullness 使用 VBR 特殊值。解析器不应由第一帧 212 字节推测后续都固定 212,也不应在短帧后认为编码器切换了码率。平均码率只能在明确时间窗口内按总 payload 或总 frame 字节统计:
1 | transport_bitrate = total_adts_bytes * 8 / duration |
两者相差每帧七字节头产生的开销。样本很短时 priming/flush 对平均值影响很大,因此不能用这个五帧测试向量评估长期编码质量或目标码率准确性。
AudioSpecificConfig 十六进制实例
AAC-LC 44.1 kHz Stereo
常见 12 10 的 bit 开头可以分解为 AOT=2、frequency index=4、channel configuration=2,随后是 GASpecificConfig 的标志位。不能只看两个字节字符串记忆配置,应通过 bit reader 得到每项,并确认配置实际长度包含完整 GASpecificConfig。
显式采样率
frequency index 为 15 时,紧接 24 位显式 Hz 值,使后续 channel configuration 不再位于常见固定 bit offset。硬编码 asc[1] >> 3 取声道只适用于部分普通两字节配置,会在显式频率和扩展 AOT 中失败。
HE-AAC
显式 SBR AOT 配置先给扩展输出采样率,再给基础 AOT;隐式 sync extension 则位于基础配置之后。报告应同时显示 signaled AOT、core AOT、core rate、extension/output rate 和 PS 状态,而不是只写“AAC”。
AudioSpecificConfig 的逐 bit 解析
基础字段读取顺序
ASC 不是固定两字节结构。其开头依次是 audioObjectType、samplingFrequency 和 channelConfiguration,之后由最终对象类型选择具体配置语法:
1 | audioObjectType = getAudioObjectType() |
getAudioObjectType() 先读五位;若结果为 31,再读六位扩展并返回 32 + extension_value。因此遇到 escape AOT 后,采样率字段会比普通配置晚六位。任何直接用 asc[0] >> 3 作为完整 AOT 的写法都无法解析扩展对象类型。
常见两字节 12 10
12 10 的 16 bit 为:
1 | 00010 0100 0010 000 |
前五位 00010=2 表示 AAC-LC;四位 0100=4 表示 44100 Hz;四位 0010=2 表示双声道配置。余下三位依次是 GASpecificConfig 中的 frameLengthFlag=0、dependsOnCoreCoder=0、extensionFlag=0。
由于 dependsOnCoreCoder=0,后面没有 14 位 coreCoderDelay;由于 channelConfiguration!=0,不在此处读取 PCE;由于 extension flag 为零,也没有该 AOT 条件下的扩展字段。到第 16 bit,基础配置恰好闭合。
构造普通 AAC-LC 两字节 ASC 可以使用:
1 | asc16 = (audioObjectType << 11) |
但这个公式只适用于 AOT 小于 31、频率不是显式值、没有 PCE、没有 core coder delay 和其他后续配置的简单情形。通用写入器仍应使用 BitWriter 按条件语法输出。
五字节 12 10 56 E5 00
受控 MP4 AAC-LC 轨道携带的 ASC 为:
1 | 12 10 56 E5 00 |
前 16 bit 仍是上述 AAC-LC 44.1 kHz 双声道配置。剩余 24 bit 为:
1 | 01010110111 00101 0 0000000 |
前 11 位 01010110111 等于 0x2B7,是 MPEG-4 Audio Sync Extension 类型。紧接五位 00101=5 表示扩展对象类型 SBR,再读一位 sbrPresentFlag=0,说明这个配置显式表示当前没有启用 SBR。报告应输出“core AOT 2,44.1 kHz,stereo,sync extension present,SBR absent”,而不是一看到扩展 AOT 5 就把流误报为 HE-AAC。
剩余位是否必须为特定值、是否属于外层 descriptor 对齐,需要在 ASC 的已知 bit 长度范围内判断。MP4 DecoderSpecificInfo 给出字节长度,但 ASC 的有效语法可能不恰好占满最后一个字节;解析器应记录消耗 bit 数和剩余 bit 值。
显式频率实例
假设对象类型 AAC-LC、采样率 12345 Hz、声道配置 2。开头不再能写普通表索引,而是:
1 | audioObjectType 00010 5 bit |
总计至少 40 bit。声道字段从普通配置的 bit 9 移动到 bit 33。硬编码从第二字节某三四位读取 channel configuration 会得到显式频率的一部分,可能造成巨大的声道分配错误。
SBR/PS 显式对象的读取顺序
若最初读出的 AOT 为 5(SBR)或 29(PS),配置需要先保存扩展对象类型和扩展采样频率,再读取真正的核心 AOT:
1 | extensionAudioObjectType = audioObjectType |
最终应同时保存 signaled AOT、core AOT、core sample rate、extension/output sample rate、SBR/PS 状态。覆盖同一个 sample_rate 变量会丢失核心与输出频率之间的关系,进而影响 raw_data_block 时长、SBR 合成和容器时间线解释。
ADTS 与 ASC 的配置映射
从 ADTS 生成基础 ASC
对常见 MPEG-4 AAC-LC 单配置流,可以从 ADTS 的 profile、sampling_frequency_index 和 channel_configuration 生成基础 ASC:
1 | audioObjectType = adts_profile + 1 |
对本例 profile=1、索引 4、声道配置 2,三个 GA 标志取零,结果就是 12 10。映射之前必须确认全部 ADTS frame 的核心字段一致;若中途变化,单一 MP4 sample entry 无法描述整条轨道,应分轨、分段、切换 sample description,或拒绝静默合并。
Channel Configuration 为零
ADTS 的三位声道配置为零时,真正布局由 raw AAC 中的 Program Config Element 给出。此时生成 ASC 也应写 channelConfiguration=0 并在 GASpecificConfig 的规定位置携带相应 PCE,而不是根据解码器输出声道数擅自选择最接近的标准配置编号。PCE 还包含前/侧/后/LFE 元素及其 tag,单一声道数不足以还原布局。
ADTS 表达能力的限制
ADTS profile 只有两位,频率只有索引,声道配置只有三位,不能完整表达所有 MPEG-4 Audio AOT、显式任意频率、复杂 PCE、SBR/PS 和特定错误恢复配置。ASC 到 ADTS 的转换必须先做“可表示性审计”。不可表示时应选择 LATM/LOAS、MP4 等能够保存完整 ASC 的封装,不能把 HE-AAC 强行标成 LC 后认为 payload 已转换。
从 MP4 Raw Sample 写 ADTS
普通 MP4 AAC sample 不含 ADTS Header。转换过程是从 sample entry 的 ASC 建立稳定配置,对每个 sample 根据 stsz 或 fragment trun 得到 raw payload 长度,计算 7 + payload_size,构造 ADTS Header,再原样追加 sample payload。stts/trun 的 duration 用于验证每个 sample 是否对应预期 raw block 时间,但不写入 ADTS Header。
1 | for each mp4_sample: |
若 MP4 sample 大于 8184 字节,单个七字节头无法表示。工具不能用 13 位截断;需要判断能否依据编码 access unit 边界重新组织,或者明确拒绝转换。不能在 raw AAC payload 中任意切字节,因为 AAC element 与 Huffman 数据是 bit 级语法。
从 ADTS 写 MP4
反向转换时,解析每帧边界,去除 ADTS 的固定/可变头和错误校验结构,把每个 raw_data_block 或可可靠拆分的 access unit 写为 sample;ASC 写入 sample entry。MP4 sample 大小不包含七/九字节 ADTS 头,CRC 也不进入 sample。轨道时长从每个 access unit 的实际采样数映射到 media timescale,而不是从 ADTS 文件字节长度估计。
1 | parse ADTS header and error-check syntax |
多 raw block ADTS 若缺乏实现所需的可靠分界支持,就不能简单去掉前七字节后整体作为一个普通 AAC-LC MP4 sample。转换器应支持标准多块语法,或者返回明确限制。
ADTS 流式重同步算法
候选头的验证层次
逐字节找到 0xFF 且下一字节高四位为 0xF 只能说明存在同步候选。完整验证依次包括:至少七字节可用、layer 为零、采样率索引合法、frame length 不小于必需头部且不超过配置上限、CRC/多块条件语法能够落在 frame 边界内、完整 frame 已到达,以及下一预期位置是否出现合理的后继头。
1 | find_candidate(buffer, start): |
候选位于缓存最后一个字节时必须保留该 FF,等待下次输入补齐。不能因为当前没有第二字节就丢弃,否则跨网络分片的合法同步头会永久丢失。
NEED_MORE 与 INVALID 分离
当候选头字段合法但缓存不足 aac_frame_length 时返回 NEED_MORE,并保持候选起点;字段本身非法则返回 INVALID_CANDIDATE,只前进一个字节继续搜索。把两者混在一起会让分片输入被误判损坏,或让真正损坏头无限等待一个不可能到达的巨大长度。
1 | parse_candidate(p): |
后继头提高置信度
当文件或流损坏后重新搜索,可以在 p + frame_length 检查下一合法 ADTS 候选,并比较 MPEG ID、profile、频率索引和声道配置。连续两个或三个一致头能显著降低 payload 内假同步概率。但在文件尾只有最后一帧时不能强制要求后继头;应把“单帧结构合法”和“已由后继头确认”作为不同置信等级。
长度可信与 CRC 失败
若 Header 结构和长度都可信,只是 CRC 验证失败,可以丢弃当前 frame 并直接跳到声明结束位置;若 Header 自身位于错误保护范围且无法信任,或声明长度越界,则从候选后一个字节重新扫描。无论策略如何,都要限制每输入字节的候选次数,避免攻击数据造成平方级回溯。
ADTS 流式解析器实现
环形输入缓冲
同步字可能跨 socket read 边界,因此至少保留最后一个候选字节。找到候选后,在不足 7/9 字节头时返回 NEED_MORE,不移动候选起点。头完整后才读取 frame length,并限制在配置的最大 frame。
False Sync
AAC payload 中可能出现 FF F?。候选头还应验证 layer、frequency index、frame length、profile/ID 组合等;有条件时可检查下一帧位置也存在合法 header。失败只前进一个字节,避免跳过真正同步。
配置变化
ADTS 每帧携带基础 profile/rate/channels。流中这些值变化时,解析器要在 frame 边界通知解码器重配。SBR/PCE 等更深配置仍可能来自 payload,不能只比较 7 字节头。
CRC 模式
protection_absent 为零时等待 9 字节基本头,并依据 raw block 数解析可能的 CRC 结构。若实现不支持 CRC 验证,可仍按正确长度跳过并标记 unchecked;绝不能把 CRC 两字节送进 AAC element parser。
ADTS 写入器实现
从 ASC 映射
写入器从已验证 ASC 取得可由 ADTS 表达的 AOT、频率索引和 channel config。显式频率若不在 ADTS 支持索引、复杂对象类型或 PCE 配置可能需要特定处理或无法无损映射。不能选择最近采样率欺骗接收端。
每帧生成
每个 raw AAC sample 独立计算 frame length,写 header 后追加 payload。VBR 流每帧长度不同但 header 参数大多相同。配置更新后从边界开始用新字段,并确保接收端有方法同步 PCE/SBR 状态。
防止双 Header
输入如果已是 ADTS,直接再加一层 ADTS 会让第一层 payload 以 FF F? 开始,解码器把第二头当 AAC 语法而失败。写入前应明确 API 输入是 raw AAC access unit 还是完整 ADTS frame,不应通过魔数猜测后静默改变。
MP4 AAC Sample Table 映射
一个 Sample 的边界
常见 MP4 AAC 轨道每个 sample 保存一个 raw_data_block/access unit,不含 ADTS header。stsz 给字节大小,stts 给该 sample 的解码时长,stco/stsc 给定位。ASC 存于 sample entry,不在每 sample 重复。
Sample Duration
AAC-LC 常见 duration 为 1024 个 media timescale unit(若 timescale 等于采样率),但对象类型和配置可能不同。写入器应由编码器报告采样数,不要只由扩展名猜 1024。所有 stts count 之和应等于 stsz sample count。
Priming 与 Padding
无缝播放需要记录编码 delay 和尾部 padding。不同 MP4 生态可通过 edit list、sample group或私有/标准元数据表达。转换为裸 ADTS 时这些信息可能丢失,所以导出的总解码采样数不一定与原 PCM 完全相同。
AAC 丢包与错误隐藏
单帧损坏
熵码流 bit 错误可能使当前 element 剩余部分无法定位。解码器通常丢弃整 frame,并用静音、重复频谱或衰减历史进行 concealment。错误隐藏输出必须仍产生正确数量的 PCM sample 以保持时间线。
状态传播
Overlap、predictor、LTP、SBR/PS 等工具有跨帧状态,单帧丢失可能影响后续。恢复策略需更新或衰减状态,而不能仅跳过输入不推进解码器。新的明确配置或随机接入机制可帮助重置。
ADTS 重同步
丢弃损坏 frame 后从声明 frame end 或逐字节候选搜索恢复取决于错误类型。如果 header 长度可信而 payload CRC 失败,可跳到 frame end;header 本身损坏则需搜索。策略应避免在错误 payload 内持续产生大量假帧。
AAC 安全解码边界
输出大小
在完整配置解析前不能仅凭 ADTS payload length 决定 PCM 输出缓冲。输出由 AOT、channel elements、frame length 和 SBR/PS 决定。设置声道数、采样数和总分配上限,并检查乘法溢出。
Huffman 与 Escape
每个 bit read 受当前 frame 边界限制,Huffman 最大码长和 escape unary 有上限。非法码不得回退无限尝试不同 codebook;codebook 已由 section 明确指定。
元素数量
raw_data_block 元素循环必须最终遇到 END,并限制元素数、PCE 元素数、DSE/FIL 长度和 SBR 网格数量。tag 冲突与 channel 总数超过实现能力在分配前失败。
AAC 验证报告
传输层
对 ADTS 列出每帧 offset、header length、frame length、MPEG ID、profile、频率索引、声道配置、fullness、raw block count 和 CRC 状态。对 ADIF 列出头字段、program/PCE 和 raw stream 起点。
配置层
列出 ASC bit 长度、signaled/base/extension AOT、core/output sample rate、channel config/PCE、frameLengthFlag、SBR 和 PS。容器 sample entry、ADTS 与实际 element 配置冲突时并排列出。
元素层
每帧统计 SCE/CPE/LFE/PCE/FIL 等元素、window sequence/group、max_sfb、section/codebook、TNS/PNS/stereo 工具和耗用 bit。解析结束 bit offset 应与 frame payload 边界闭合。
输出层
记录解码 PCM 声道、采样率、每帧 sample 数、累计 duration、concealment 和 priming/padding。能得到 PCM 不等于所有语法合法,容错解码警告应保留在最终报告。
第十章 AAC 传输格式全景
Raw Access Unit
Raw AAC access unit只包含MPEG-4 Audio语法元素,没有同步、长度或完整配置。MP4 sample常使用这种形式,由stsz/trun提供边界、ASC提供配置。独立保存为文件后无法可靠从任意位置识别。
ADTS
ADTS每帧重复基础配置和长度,便于文件/网络重同步,但两位profile和channel_configuration不足以表达所有MPEG-4对象/布局。适合常见AAC-LC及一定兼容范围。
ADIF
ADIF在文件开头集中配置,后续连续raw stream,随机重同步能力弱。它描述一个完整bitstream的program和buffer模型,不是逐帧packet header。
LATM/LOAS
LATM将AudioSpecificConfig和payload复用/复合,支持多个program/layer及高效传输;LOAS在LATM外增加同步层。它们与ADTS header完全不同,不能搜索FFF解析。
MP4
MP4用sample entry/ASC描述配置,用sample table或fragment描述边界与时间。容器可精确表达DTS/duration和priming等,但失去moov/init segment后raw mdat难恢复。
LOAS AudioSyncStream
同步头
LOAS AudioSyncStream通常以固定宽同步字开头,随后是AudioMuxElement长度,单位字节。解析器先验证同步值和长度不超过缓存/实现上限,再在限定payload内解析LATM。长度不包含外部传输包头。
流式重同步
同步字也可能偶然出现在payload中。候选后检查mux length、AudioMuxElement条件语法和后续候选,提高可信度。Header不足时保留跨buffer尾部,不应丢弃首同步字。
Payload 边界
一个AudioSyncStream payload必须严格限制LATM bit reader。LATM内部长度损坏不能读到下一LOAS header。解析完成后若有对齐/padding,按语法验证而不是把剩余全忽略。
LOAS 三字节同步头的精确布局
syncword 与 audioMuxLengthBytes
LOAS AudioSyncStream 的外层头固定 24 bit:11 位同步字 0x2B7,随后 13 位 audioMuxLengthBytes。长度表示紧随其后的 AudioMuxElement 字节数,不包含这三字节头。
1 | bit 0..10 syncword = 0x2B7 |
按三个字节拆装:
1 | syncword = (byte0 << 3) | (byte1 >> 5) |
13 位长度最大 8191。完整 LOAS packet 长度为 3 + mux_len。候选同步后若缓存少于三字节返回 NEED_MORE_HEADER;长度合法但 payload 未到齐返回 NEED_MORE_PACKET。不能把 mux_len 当成含头总长,否则每帧会向后错三字节。
首包 Header 实算
受控 AAC-LC LATM 流的首三个字节为:
1 | 56 E0 D4 |
解析同步字:
1 | (0x56 << 3) | (0xE0 >> 5) |
解析长度:
1 | ((0xE0 & 0x1F) << 8) | 0xD4 |
因此第一个 LOAS packet 总长 215 字节,下一个同步头应位于偏移 215。
真实 LOAS/LATM 包目录
五包边界
同一受控音频编码为 LOAS/LATM 后总长 983 字节,共五包:
| 包索引 | 文件偏移 | LOAS Header | audioMuxLengthBytes |
包总长 |
|---|---|---|---|---|
| 0 | 0 | 56 E0 D4 |
212 | 215 |
| 1 | 215 | 56 E1 16 |
278 | 281 |
| 2 | 496 | 56 E0 EC |
236 | 239 |
| 3 | 735 | 56 E0 E9 |
233 | 236 |
| 4 | 971 | 56 E0 09 |
9 | 12 |
累计验证:
1 | 215 + 281 + 239 + 236 + 12 = 983 |
LOAS 包长与对应 ADTS frame 长并不相同。首包需要携带 StreamMuxConfig,后续包可以复用配置;LATM 的 payload length info 也是 bit 级复用语法。不能拿 ADTS 的七字节开销公式套在 LOAS 上。
首包 AudioMuxElement
第一个 LOAS 头后的字节开头为:
1 | 20 00 12 10 1F E6 6E F0 10 ... |
从第一个 bit 开始解析得到:
| 字段 | 值 | 结束 Bit 偏移 |
|---|---|---|
useSameStreamMux |
0 | 1 |
audioMuxVersion |
0 | 2 |
allStreamsSameTimeFraming |
1 | 3 |
numSubFrames |
0 | 9 |
numProgram |
0 | 13 |
program 0 的 numLayer |
0 | 16 |
ASC audioObjectType |
2 | 21 |
ASC samplingFrequencyIndex |
4 | 25 |
ASC channelConfiguration |
2 | 29 |
frameLengthFlag |
0 | 30 |
dependsOnCoreCoder |
0 | 31 |
extensionFlag |
0 | 32 |
frameLengthType |
0 | 35 |
latmBufferFullness |
255 | 43 |
otherDataPresent |
0 | 44 |
crcCheckPresent |
0 | 45 |
配置到 bit 45 结束,随后进入 Payload Length Info。因为 frame length type 为 0,读取八位长度值 205;该值不是从字节边界开始,而是覆盖 bit 45~52。Raw AAC Payload 从 bit 53 开始。LATM 的核心价值之一正是消除不必要的字节对齐开销,因此解析器不能在读完 StreamMuxConfig 后擅自 round up 到下一个字节。
后续包复用配置
第二至第五包的 useSameStreamMux 都为 1,不再重复 StreamMuxConfig。根据 frame length type 0 的累加规则,payload length info 为:
| 包索引 | useSameStreamMux |
Length 字节序列 | Raw Payload 长度 | Payload 起始 Bit |
|---|---|---|---|---|
| 0 | 0 | 205 |
205 | 53 |
| 1 | 1 | 255 + 20 |
275 | 17 |
| 2 | 1 | 234 |
234 | 9 |
| 3 | 1 | 231 |
231 | 9 |
| 4 | 1 | 7 |
7 | 9 |
长度值为 255 时表示继续读取下一八位并累加,所以 275 编码为 255, 20。解析循环要设置最大累计值和最大 continuation 次数,避免输入持续给出 255 造成整数溢出或无限循环。
随机接入限制
从第二包开始截取流,虽然能识别 LOAS 同步和包长,却只能看到 useSameStreamMux=1,缺少 AOT、采样率、声道和 frame length type。接收器不能凭 payload 外观猜 AAC-LC;必须等待一个携带新 StreamMuxConfig 的包,或者由带外会话描述提供完全一致的配置。重同步成功只恢复了包边界,不等于恢复了解码配置。
LATM AudioMuxElement
useSameStreamMux
AudioMuxElement开头标志决定当前元素是否复用先前StreamMuxConfig。为真时接收器必须已有同一流有效配置;随机加入或配置丢失时无法仅凭payload解码,应等待带新配置的元素。
配置生命周期
收到完整新StreamMuxConfig后在当前元素规定位置生效。解析失败不覆盖旧配置。配置变化可改变program、layer、AOT、采样率、声道和frameLengthType,下游在access unit边界重建。
Payload Length Info
每个subframe/layer的payload长度依据frameLengthType编码。某些类型使用连续255字节累加,某些由固定frame length或CELP/HVXC规则决定。不能一律读取一个8-bit length。
Payload Mux
长度确定后按program/layer顺序读取payload。不同layer可能有不同codec配置。解析器保存 (program,layer,subframe)定位,不把所有字节拼为单AAC-LC frame。
Other Data 与 CRC
StreamMuxConfig可指示otherData和CRC check。Other data长度可能使用分段字段;CRC字段有规定保护范围。实现不消费otherData语义时仍需按长度跳过并报告。
StreamMuxConfig
AudioMuxVersion
audioMuxVersion及可选versionA决定后续taraBufferFullness和ASC长度表达。未知version组合应拒绝。Version字段会改变bit layout,不能按最常见version0固定读取。
Program 与 Layer
numProgram和每program的numLayer字段通常为“减一/最大索引”语义,循环次数需加一。总layer数设置实现上限,防止小LOAS payload声明巨大配置。
ASC 复用
非首program/layer可通过useSameConfig复用先前AudioSpecificConfig。复用对象和顺序按语法,不能任意选择最近相同AOT。被引用配置必须已成功解析。
Frame Length Type 概览
frameLengthType控制每个payload长度和buffer字段,例如固定frameLength、CELP table index等。对AAC常见类型仍要保存latmBufferFullness。未知类型不能跳固定bit后继续。
Subframes
numSubFrames决定一个AudioMuxElement包含的时间子帧数量。每subframe依次携带各layer payload。输出时间按每layer codec frame采样数累计,不按LOAS packet数量固定推进。
StreamMuxConfig 字段级语法
版本字段
StreamMuxConfig 先读 audioMuxVersion;若其为一,再读 audioMuxVersionA。Version A 的非零组合不应交给只实现常见 AAC LATM 路径的解析器继续猜测。audioMuxVersion=1 且 version A 为零时,后面通过 latmGetValue() 读取 taraBufferFullness,并且 ASC 具有显式 bit 长度。
1 | audioMuxVersion 1 bit |
字段 numSubFrames、numProgram 和各 program 的 numLayer 都按“最大索引”循环,即实际数量为字段值加一。解析器在加一和累计总 layer 数时设置上限。
Program、Layer 与配置复用
每个 program 先给三位 numLayer。除第一个 program 的第一个 layer 外,其他 layer 读取 useSameConfig;为真时复用语法规定的先前配置,为假时读取新的 ASC。不能根据 AOT 相同就自行复用,因为采样率、声道、SBR 或对象专用配置仍可能不同。
1 | for prog = 0 .. numProgram: |
Frame Length Type 条件字段
每个 stream 的 frameLengthType 为三位,并改变随后配置与 Payload Length Info。常见 AAC 使用类型 0,随后带八位 latmBufferFullness,payload 长度用一个或多个八位值累计;类型 1 带固定 frameLength,其他值与 CELP、HVXC 等对象的索引或长度规则相关。只实现 AAC 类型 0/1 的工具在遇到其他值时应明确返回 unsupported,不得仍读一个八位长度。
Other Data 与 CRC Check
全部 program/layer 配置之后读取 otherDataPresent。若存在,长度语法受 audioMuxVersion 影响;version 0 使用带 escape 的分段累计,version 1 使用 latmGetValue()。之后读取 crcCheckPresent,为真时还有八位校验值。它们属于 LATM 复用层,不是 ADTS CRC,也不是 AAC raw_data_block 内的填充元素。
配置提交规则
解析新 StreamMuxConfig 时先建立临时的 program/layer 树,完整验证 ASC、frame length type、other data 和 CRC 字段边界后再提交。任何 layer 失败都不能留下半更新配置。useSameStreamMux=1 的 AudioMuxElement 只引用最后一次成功提交的整套配置。
LATM 与 ASC Bit 对齐
taraFullness
特定version使用latmGetValue读取可变字节长度值。先读取长度字节数减一,再拼接对应字节。拼接检查最大字节和整数溢出。
ASC 长度
某些version显式给ASC bit长度,使未知对象配置可按边界跳过;另一些依赖解析对象语法得知结束。Bit长度不一定整字节,完成后准确移动到下一字段。
Fill Bits
当实际ASC解析消耗少于声明bits时,剩余fill/保留bit按规范处理;消耗超过则配置损坏。不要简单round up到byte掩盖错位。
第十一章 Error Resilient AAC 对象
ER 对象类型
MPEG-4 Audio定义ER AAC LC、ER AAC LTP、ER AAC LD等错误恢复对象。AOT不同于普通LC,GASpecificConfig/ER工具有额外字段。普通AAC-LC decoder不能只忽略ER标志。
Error Resilience Flags
配置可有sectionDataResilienceFlag、scalefactorDataResilienceFlag、spectralDataResilienceFlag等,改变相应语法编码和恢复能力。字段存在性依AOT/extensionFlag。Parser按最终AOT选择。
RVLC
Reversible Variable Length Coding可让scale factor数据从两端恢复部分信息。它有专用长度、escape和反向解码语法。普通scale factor Huffman路径不可复用为简单倒序读取。
HCR
Huffman Codeword Reordering把频谱codeword按优先级/segment重排,提高局部错误恢复。需要解析reordered spectral data长度和longest codeword等字段,建立segment后再放回原系数位置。
Error Protection
MPEG-4还定义工具级错误保护/CRC结构。ADTS CRC与对象内部ER不是同一层:前者保护传输frame部分,后者改变codec syntax/恢复。报告分层显示。
AAC Low Delay
AAC-LD
Low Delay对象通过较短transform/更低lookahead等减少算法延迟,配置和frame采样数与普通LC不同。不能按1024 sample固定stts/PTS。它仍有重叠与状态,并非零延迟。
Frame Length
具体对象/配置决定常见frame长度。解码API应报告每access unit输出sample count。容器timescale通常仍为sample rate,但sample duration使用实际长度。
Random Access
低延迟优化不自动使每frame无状态。Overlap、TNS、LTP/其他工具仍影响丢包恢复。会话开始需要完整ASC和初始化策略。
Enhanced Low Delay
AAC-ELD
ELD进一步面向双向通信,组合低延迟滤波器组,并可集成低延迟SBR等工具。AOT和ELDSpecificConfig决定能力。普通HE-AAC SBR parser不能直接解释ELD扩展。
eldSbrPresentFlag
配置可指示ELD SBR及采样率/CRC等参数,声道元素还可能有SBR header。字段布局受channelConfiguration/PCE影响。输出rate和frame duration需由完整配置推导。
延迟预算
容器packetization、声卡period和网络jitter通常大于或接近codec算法延迟。手册区分codec frame/algorithm delay与端到端delay,不能由“AAC-ELD”名称保证某毫秒值。
第十二章 SBR 配置层级
Explicit Signaling
ASC的signaled AOT直接为SBR/PS时,先读取extension sampling frequency,再读核心AOT。Core decoder按核心rate处理,SBR合成提高输出rate。报告两套rate。
Backward Compatible Signaling
基础AOT后可通过sync extension发现SBR/PS,使不识别扩展的decoder仍解核心AAC。搜索/解析只在ASC合法剩余范围内,不能从raw frame任意猜。
In-band Header
FIL extension中的SBR header可更新频率表、limiter、smoothing等配置。Header未出现的frame可复用先前状态。Stream切换/seek时若从无header点开始,扩展解码可能暂不可用。
CRC
SBR extension可有CRC变体,保护扩展payload。失败时核心AAC可能仍可解码,decoder可禁用/隐藏SBR当前frame并保持时间。报告区分core成功与extension失败。
SBR 高频重建
QMF
SBR使用Quadrature Mirror Filter bank把低/高频分带处理。发送端提取包络、噪声、谐波和频带映射参数;接收端从核心低频复制/调整生成高频。它不是简单把sample rate数字翻倍。
Frequency Tables
Header参数生成master/high/low/noise/limiter band表。生成算法含起止频带、scale、alter scale、cross-over等,必须检查表单调、范围和band数上限。
Grid
每frame grid定义时间包络边界和频率分辨率。不同frame class语法不同,可引用前后border。边界必须在允许time slot范围内且递增。
Envelope 与 Noise
包络/噪声以差分Huffman编码,可沿时间或频率方向,coupling模式改变双声道解释。累计值范围检查,解量化后应用到QMF子带。
Harmonic 与 Limiter
Sinusoidal coding可添加谐波,limiter控制重建峰值,smoothing影响时变增益。状态跨frame,丢frame需更新/淡化避免突变。
Parametric Stereo 语法边界
IID 与 ICC
PS主要参数包括Inter-channel Intensity Difference和Inter-channel Coherence,可能还有IPD/OPD相位参数。它们按envelope和parameter band编码,用于从单/少声道QMF信号重建立体声。
PS Header
PS header指示启用的参数、mode和扩展。后续frame可复用header状态。无有效header时不能按默认固定band随意解码。
输出声道
核心element可能为单声道,但PS输出双声道。Framework在解析完整扩展后更新output channel config;buffer size按最终声道。关闭PS fallback时可输出核心mono,但时间/采样率保持对应SBR状态。
Concealment
PS参数丢失时可保持/平滑上帧参数,不能把随机bit作为新参数。Concealment会改变声像但应维持sample count。
第十三章 AAC 多声道元素映射
SCE/CPE/LFE
Channel configuration/PCE指定前、侧、后位置使用SCE或CPE,LFE单独元素。Raw element出现顺序与最终API channel order不一定相同,decoder建立label映射后重排PCM。
Common Window
CPE common_window共享ICS/window group,有利于MS/intensity工具。两个通道仍各有global gain/section/spectral数据的条件结构。Parser保存pair关系,不把CPE拆成无关联两个SCE。
Coupling Channel
CCE可对一个或多个目标元素/通道和band应用coupling gain。它可能在independent/dependent coupling point应用。目标tag必须存在,gain列表数量由target选择与ch_select推导。
Downmix
PCE可携带mono/stereo/matrix mixdown信息。它是建议/配置,不等于decoder必须丢弃多声道。播放器依据输出设备和政策应用,保留原PCM通道用于专业处理。
Channel Order
容器、AAC PCE和操作系统API可能使用不同channel order。使用speaker labels做中间映射。未知/重复tag不应默认为左右顺序。
AAC Dynamic Range Control
DRC Extension
Fill/extension中可携带动态范围控制信息,按band给出control value、program reference level等。它指导播放增益,不改变频谱解码本身。
应用策略
Decoder可输出未应用DRC的PCM和side metadata,或按用户夜间模式应用。报告明确是否已应用,避免测量PCM时误判编码器幅度。
与 Loudness
DRC、容器响度元数据和播放器音量是不同层。重复应用会过度压缩。Muxer转换时保留关联而不把数值直接改写global_gain。
Ancillary Data
Data Stream Element
DSE有element_instance_tag、byte_align_flag和count,count到escape值时扩展。对齐后读取指定data bytes。Unknown ancillary按长度保留/跳过,不交给频谱parser。
Fill Data
FIL可用于码率填充或承载扩展。真正填充bit/byte有规范值,验证器可检查。删除FIL可能同时删除SBR/DRC等extension,必须先解析extension type。
Other Data
LATM otherData与AAC DSE/FIL处于不同封装层。不要把LATM剩余payload当DSE,边界由StreamMuxConfig定义。
第十四章 AAC Encoder Delay 测量
Impulse 方法
输入已知位置impulse,编码解码后寻找主峰,可估计总codec/实现delay。Window ringing和SBR可能分散能量,需用相关/阈值而非只找绝对最大。
Sample Count
记录原PCM frame数、编码AU数、每AU输出数和解码总数。多出的首尾sample由priming/padding解释。仅比较文件秒数舍入不够精确。
容器映射
MP4通过edit/metadata裁掉delay;ADTS裸流通常保留全部decoder输出。转封装前后分别记录raw decode count和presentation count,避免误以为payload损坏。
AAC Bitstream 比较
结构等价
ADTS转MP4再转回时,raw AAC AU payload理应逐帧相同(若不改配置/CRC),ADTS header可因fullness/CRC策略不同。比较先提取AU hash,再比较配置和时间。
解码等价
不同合法编码器输出bitstream不同但可达到相近音质。Decoder conformance用标准测试向量/PCM容差,不以与某编码器字节相同为准。
Metadata 等价
ASC、ADTS profile和PCE/SBR必须表达同一最终配置。仅payload hash相同但ASC声道/采样率错误,容器仍不等价。
AAC Fuzz 测试
Header 变异测试
变异ADTS sync/layer/profile/frequency/channel/frame_length/fullness/raw block和CRC,证明parser不越界且能有限重同步。ASC测试escape AOT、explicit frequency、截断GASpecificConfig和非法PCE数量。
Elements
变异element ID/tag、window sequence/max_sfb、section escape、codebook、TNS order、pulse offset、FIL/DSE length和缺END。每个循环有上限和frame边界。
Huffman
提供无终止码、escape过长、scale factor累计越界、spectral位置超块和ER重排错误。Decoder在首错误停止当前frame,不读下一header。
Extensions
变异SBR grid/table/envelope、PS mode、DRC length、LATM program/layer/subframe和LOAS mux length。核心AAC与扩展错误分层报告。
State
测试配置中途变化、丢frame、重复frame、seek到无SBR header、PCE更新和ER/LD对象切换。旧状态仅在规范允许时复用。
第十五章 raw_data_block 有界解析参考
语法入口与元素循环
AAC 的一个访问单元在语法层通常承载一个 raw_data_block()。ADTS 可以通过 number_of_raw_data_blocks_in_frame 表示同一 ADTS 帧内含有多个块,但实际互操作环境通常限定为一个。解析器不能把“常见为一个”写成“标准永远为一个”,也不能仅靠字节余量猜测块是否结束。块内部由三位 id_syn_ele 标识的语法元素串联,直到 ID_END 为止。
1 | raw_data_block() { |
id_syn_ele |
二进制值 | 元素 | 主要用途 |
|---|---|---|---|
ID_SCE |
000 |
Single Channel Element | 单声道或多声道布局中的独立全频声道 |
ID_CPE |
001 |
Channel Pair Element | 一对相关声道,通常是左右声道 |
ID_CCE |
010 |
Coupling Channel Element | 为若干目标声道提供耦合信息 |
ID_LFE |
011 |
Low Frequency Element | 低频效果声道 |
ID_DSE |
100 |
Data Stream Element | 用户数据或辅助数据,不是 PCM 声道 |
ID_PCE |
101 |
Program Config Element | 显式节目和声道布局 |
ID_FIL |
110 |
Fill Element | 填充字节以及扩展载荷,例如 SBR |
ID_END |
111 |
End | 当前 raw data block 的语法终止符 |
元素实例的 element_instance_tag 只有四位。它用于在 PCE、耦合目标以及实际语法元素之间建立引用,并不直接等于输出声道号。解析器应为当前配置维护“元素类型、实例标签、声道数、输出映射”的联合键;只按标签索引会把 SCE tag 0 与 CPE tag 0 错认成同一对象。
位边界、字节对齐与封装边界
raw_data_block() 的大部分字段按位连续排列,单个元素结束后通常不自动补齐到字节边界。ID_END 后才执行块级字节对齐。若当前 bit offset 已经对齐,则不额外吞掉八位;若未对齐,则丢弃到下一个字节边界的对齐位。外层 ADTS 的 aac_frame_length 给出字节边界,MP4 sample 给出样本边界,LATM 则由 mux slot 长度给出载荷边界。正确实现同时持有语法边界和外层字节边界:语法负责判断正常结束,外层负责阻止越界。
假设读取 ID_END 后位位置为 125,则对齐后位置为 128;若位置本来就是 128,则仍为 128。常见错误是无条件跳过一个字节,使后一块或后一帧从第二字节开始解析。
1 | align_to_next_byte(bitpos): |
标准语法中某些保留位可由解码器忽略,但安全解析器仍应记录非零对齐位,便于区分编码器怪癖和真正的边界偏移。严格一致性测试可要求它们为零;宽容播放模式可以警告后继续,前提是外层长度和后继帧同步都成立。
Single Channel Element 与 LFE
SCE 和 LFE 的顶层布局相同:四位实例标签后跟一个 individual_channel_stream()。差异主要来自节目语义和编码器对带宽、工具使用的约束,而不是一个额外的 LFE 专用头。
1 | single_channel_element() { |
第一参数表示是否使用 CPE 的 common_window,后两个参数与公共 ics_info、公共 scale-factor grouping 的复用有关。实现时不宜用多个布尔形参在不同调用点随意组合,较稳妥的做法是构造显式 IcsContext,其中包含窗口信息来源、是否允许 MS mask、当前声道角色以及配置对象。
Data Stream Element
DSE 用于放置不参与音频频谱重建的数据。其长度字段采用“255 续长”方式:先读取八位 count,若等于 255,再读取另一个八位并相加。data_byte_align_flag 为一时,在载荷前执行字节对齐。
1 | data_stream_element() { |
在进入载荷循环前必须验证 count * 8 <= remaining_bits。不能边读边等待失败,因为上层可能已经把部分载荷提交给业务模块。DSE 内容的语义由应用或注册约定决定;通用 AAC 解码器可以跳过,但不得把它当作音频填零,也不能把其中看似 0xFFF 的字节当成新的 ADTS 同步字。
Fill Element 与续长规则
FIL 的基础长度为四位 count。当 count == 15 时,再读八位 esc_count,最终字节数为 14 + esc_count。这里使用十四而非十五,是 AAC 语法中特别容易写错的一点。
1 | fill_element() { |
若 esc_count 为零,表达式会得到十四;实现必须使用有符号或先分支处理,不能让无符号减法下溢成极大长度。FIL 内可以承载 extension_payload(),包括动态范围控制、数据元素、SBR 数据和填充。扩展解析失败时,解码器只有在仍然知道 FIL 的精确终点时才能跳过扩展并继续;如果内部长度已经不可信,则应丢弃当前访问单元。
Program Config Element 的生效时点
PCE 出现在 raw data block 时可能更新节目配置。它包含 element_instance_tag 引用,随后出现的 SCE/CPE/LFE 才提供对应音频数据。配置更新应在完整 PCE 通过范围、引用和注释长度校验后原子提交,不能读到一半就修改全局声道表。若当前 AudioSpecificConfig 的 channelConfiguration 非零,PCE 通常不是决定基础声道布局的唯一来源;实现需要按对象类型和封装配置规则处理冲突并报告来源。
Coupling Channel Element 的边界
CCE 是高级工具,语法涉及耦合点、目标元素列表、增益元素和一个独立声道流。即使解码器不支持耦合,也必须能有界识别当前访问单元,或者明确拒绝整个对象类型;不能假装 CCE 是 SCE 后跳过未知位数。目标列表中的 cc_target_is_cpe 决定引用 CPE 还是 SCE,CPE 目标还带 cc_l、cc_r,用于选择左右通道。目标数量字段经加一后再参与循环,所有数组都必须在读取后立即与实现上限比较。
1 | coupling_channel_element() { |
耦合可在量化前、TNS 后或频域到时域转换后的不同点应用。工程实现若只支持 LC 常见子集,应在配置协商时拒绝含 CCE 的流,而不是产生“能播但声道能量错误”的静默降级。
元素循环的安全状态机
推荐将元素循环实现为事务式状态机。每个访问单元创建局部 FrameState,保存已见元素、频谱缓冲、PCE 候选、SBR 候选和错误集合。只有读到合法 ID_END、完成字节对齐、且恰好没有越过外层边界后,才把可持续状态提交给解码器。
1 | parse_raw_data_block(reader, au_end): |
MAX_ELEMENTS_PER_AU 不是替代标准语法的任意限制,而是根据支持的最大声道数、DSE/FIL 数量和内存策略制定的资源上限。遇到同类型同标签的重复声道元素时应报告歧义;FIL 和 DSE 可以出现多个,但累计载荷仍需受限。
第十六章 ICS Info 与窗口分组
global_gain 与 Individual Channel Stream 入口
每个 ICS 首先读取八位 global_gain。它为该声道的尺度因子解码提供全局基准,而不是一个可以直接乘到全部 MDCT 系数的线性增益。之后根据 common_window 决定是读取自己的 ics_info(),还是复用 CPE 已经读取的公共窗口信息。
1 | individual_channel_stream() { |
具体条件随对象类型和 ER 语法有所变化。解析器应由已协商的 Audio Object Type 选择语法表,不能仅根据比特流中“下一位看起来合理”来猜测 LC、ER LC 或 LD。
长窗 ICS Info
长窗分支由 window_sequence != EIGHT_SHORT_SEQUENCE 触发。字段包括 window_shape、六位 max_sfb,以及对象类型允许时的 prediction/LTP 信息。
| 字段 | 位数 | 含义 |
|---|---|---|
ics_reserved_bit |
1 | 保留位,应为零 |
window_sequence |
2 | 当前窗口序列 |
window_shape |
1 | 当前窗形选择,并与上一帧窗形共同决定过渡 |
max_sfb |
6 | 本帧实际编码的最高 scale-factor band 数量 |
predictor_data_present |
1 | Main profile prediction 或对象相关扩展是否存在 |
max_sfb 必须小于等于由采样频率索引和窗口类型决定的 num_swb_long。超过表范围时不能截断为最大值,因为后续 section 数据的位位置已经可能由恶意值控制,正确动作是拒绝当前帧。
短窗 ICS Info
八短窗分支使用四位 max_sfb 和七位 scale_factor_grouping。七个 grouping 位描述相邻八短窗是否归入同一个 window group:位为一表示当前短窗和下一短窗同组,为零表示在下一短窗前结束当前组。
1 | num_window_groups = 1 |
例如 scale_factor_grouping = 1101010b,按从最高有效位到最低有效位处理,可得到相应的组长度序列。实现必须明确位序,不能把数值的最低位当作第一对短窗。每个 window group 共享 section 划分和尺度因子,但组内每个短窗仍有自己的频谱系数位置。
四种 Window Sequence
| 值 | 名称 | 作用 |
|---|---|---|
| 0 | ONLY_LONG_SEQUENCE |
稳态长窗 |
| 1 | LONG_START_SEQUENCE |
从长窗过渡到短窗 |
| 2 | EIGHT_SHORT_SEQUENCE |
八个短窗,增强瞬态时间分辨率 |
| 3 | LONG_STOP_SEQUENCE |
从短窗返回长窗 |
窗序列不是每帧独立的装饰字段。解码器要将上一帧 window_shape 保存为状态,并按当前序列组合左右半窗。非法或突变的序列可能导致重叠相加不连续。标准解码通常仍按收到的序列工作,但一致性验证器应检查 ONLY_LONG → LONG_START → EIGHT_SHORT → LONG_STOP → ONLY_LONG 这类合法转换路径,并对缺帧后的状态恢复采用确定策略。
Scale Factor Band 偏移表
AAC 不直接发送每个 SFB 的频率边界。解码器按 sampling frequency index、长短窗和 transform length 选择标准定义的 swb_offset 表。表中最后一个偏移等于窗口的谱线总数;相邻偏移差给出该 SFB 的谱线数。
1 | sfb_width[g][sfb] = swb_offset[sfb + 1] - swb_offset[sfb] |
短窗的存储和遍历尤其容易混淆。比特流按 window group、SFB、组内窗口、SFB 内谱线的顺序编码,输出频谱数组可能按窗口连续存储。解码器需要显式映射,而不能把一个 group 的同一 SFB 系数误写成连续的大频带。
Predictor 与 LTP 条件字段
Main profile 在长窗中可出现 predictor_data_present,随后包含 predictor_reset、可选 reset group number,以及对若干 SFB 的 prediction_used。LTP 对象类型有自己的长时预测字段。两者条件依赖 AOT、采样频率和 max_sfb。若实现只支持 AAC-LC,应在 ASC 阶段确认 AOT,而不是读到 predictor 位才尝试跳过未知长度。
ICS 上下文验证表
| 检查项 | 合法关系 | 失败含义 |
|---|---|---|
| 短窗数量 | 固定为 8 | 窗口分组状态损坏 |
| group length 总和 | 等于 8 | grouping 位序或循环错误 |
max_sfb |
不超过对应 num_swb |
后续 section 会越过表 |
swb_offset[last] |
等于 transform 谱线数 | 选错采样率/窗口表 |
| 公共窗口 | CPE 两声道共享同一 ICS Info | 左右声道 section 解释不一致 |
window_shape |
在已支持集合内 | 无法选择合成窗 |
第十七章 Section、尺度因子与频谱码字
Section Data 的职责
Section Data 把每个 window group 的 SFB 范围划分为若干连续 section,并为每段指定四位 sect_cb。Codebook 决定后续尺度因子语义以及频谱 Huffman 元组的维数、符号和最大绝对值。分段长度采用 escape 累加表示,长窗和短窗使用不同的 sect_len_bits。
1 | for each window_group g: |
短窗 sect_len_bits 较少,escape 值也随之变化。解析器必须在每次相加前做上限判断;恶意流可以给出多个 escape 使整数溢出。sect_len == 0 会令循环永不前进,应明确拒绝。
Codebook 类别
| Codebook | 类别 | 频谱元组 | 符号处理 |
|---|---|---|---|
| 0 | ZERO_HCB | 无 | 对应系数全部为零 |
| 1~4 | 小值四元组 | 4 | 有的表码字含符号,有的另读符号位 |
| 5~10 | 二元组 | 2 | 按表定义恢复正负 |
| 11 | ESC_HCB | 2 | 绝对值达到 16 时追加 escape 序列 |
| 12 | 保留 | 不得使用 | 视为语法错误 |
| 13 | NOISE_HCB | 无普通谱码 | 尺度因子解释为噪声能量 |
| 14 | INTENSITY_HCB2 | 无右声道谱码 | 强度立体声位置,符号约定之一 |
| 15 | INTENSITY_HCB | 无右声道谱码 | 强度立体声位置,另一符号约定 |
“是否另读符号位”应由 codebook 描述表驱动。把所有非零值都统一再读符号,会使本已含符号的码本立刻失步。建议描述结构至少包含 tuple_dimension、lav、unsigned_values、is_escape 和 semantic_kind。
Scale Factor Data 的累积状态
尺度因子并非逐 SFB 发送绝对值。普通频谱段使用 Huffman 解出的差分更新 scale_factor 累加器;噪声段第一次使用九位 PCM 值初始化噪声能量,之后再用差分;强度立体声使用独立 is_position 累加器。
1 | scale_factor = global_gain |
差分累积值必须在标准和实现允许范围内。出现极大正负漂移时不能让数组索引或 pow 查表越界。解码器可用较宽有符号整数保存中间值,验证后再转换为反量化缩放因子。
Spectral Data 的遍历次序
频谱码字按 group、SFB、组内窗口和元组排列。每次 Huffman 解码产生二元组或四元组,直到覆盖该 SFB 在组内所有窗口的谱线。若 SFB 宽度不满足元组维数整除关系,标准表和 band 划分本应保证合法;实现仍应检查最后一个元组不会写出目标频谱数组。
1 | for g in window_groups: |
Escape 值解码
Codebook 11 中,码字先给出基本绝对值。若某分量绝对值等于 16,则后续位串扩展幅度:连续的一位表示指数增长,遇零终止,再读取相应数量的低位形成最终值。规范给出了精确公式和上限约束。安全实现必须限制前导一数量,防止移位超过整数宽度,并在读取低位前验证剩余位数。
1 | decode_escape(sign): |
这里的伪代码表达边界思想;实现应以所采用版本标准中的 escape 公式为准,并用测试向量确认 16、首次扩展值和最大允许值三个边界。
反量化与缩放
Huffman 输出仍是量化整数。AAC 的非线性反量化核心为 sign(q) × |q|^(4/3),随后结合 global_gain、SFB 尺度因子以及对象工具进行幂指数缩放。为了速度可用查表覆盖常见幅度,并对 escape 大值走精确或分段算法;但查表索引必须在验证后的绝对值范围内。
1 | x_quant -> Huffman/escape integer |
浮点和定点实现允许舍入差异,合规验证不应把中间浮点位模式当作标准输出。应使用官方一致性向量或最终 PCM 的允许误差,并单独检查无溢出、无 NaN 和声道能量合理性。
完整的有界解码事务
Section、Scale Factor 和 Spectral 三阶段具有严格依赖:先知道每个 SFB 的 codebook,才能解释尺度因子;先有尺度因子和码本,才能决定频谱读取与缩放。任何阶段失败都应作废整个 ICS 的临时状态。不要在 Section 尚未闭合时预分配由恶意 sect_len 决定的巨大数组,最大数组大小可以从 num_window_groups × max_sfb 直接得出。
第十八章 Channel Pair 与立体声工具
CPE 顶层语法
Channel Pair Element 将两个 individual_channel_stream() 组合为一个相关声道对。四位 element_instance_tag 后是 common_window。为一时,两声道共享一份 ics_info(),并可能发送 Mid/Side 掩码;为零时,左右声道各自发送窗口信息,不能出现公共 MS 掩码。
1 | channel_pair_element() { |
ms_mask_present 的值二是“所有合适的 band 使用 MS”,值零是全部不使用,值一才携带逐 group、逐 SFB 的 ms_used 位。值三保留。解析器若无条件读取矩阵,会在值零和二时立即失去位同步。
ms_mask_present |
含义 | 后随位数 |
|---|---|---|
| 0 | 所有 band 禁用 MS | 0 |
| 1 | 由 ms_used[g][sfb] 指定 |
num_window_groups × max_sfb |
| 2 | 所有允许的 band 启用 MS | 0 |
| 3 | 保留,视为错误 | 不应继续猜测 |
公共窗口保证两声道在同一 SFB 上具有相同时间频率划分,这是逐 band 联合立体声的前提。左右声道仍有独立的 global_gain、section 和尺度因子;“公共窗口”不表示两声道共享全部 ICS 载荷。
Mid/Side 变换
普通左右声道可由中间与侧边信号表示。解码方向的基本关系是将接收到的 M、S 恢复为 L、R;具体归一化与量化缩放应遵循标准定义和实现的数据域。
1 | L = M + S |
MS 只应用于被掩码选中的 SFB,并且要考虑右声道 codebook 是否是 intensity 或 noise。工具应用顺序影响结果:先完成各声道的 Huffman、反量化和必要缩放,再按标准规定在频域执行联合立体声,随后才进入 TNS、滤波器组等阶段。将 MS 在量化整数域随意相加会改变非线性反量化结果。
Intensity Stereo
强度立体声用于高频或空间敏感度较低区域。右声道对应 band 使用 INTENSITY_HCB 或 INTENSITY_HCB2,不再传普通右声道频谱;解码器根据右声道累计的 intensity position 和左声道频谱重建右声道能量。两个 intensity codebook 表达不同的符号关系,MS mask 还会影响最终符号。
实现应把“右 band 是强度类型”作为独立语义,而非把 codebook 14/15 交给通用 Huffman 频谱解码器。右声道该 band 不消费 spectral tuple;如果仍读取码字,后续所有位都会错位。
1 | if right_cb is intensity: |
PNS 与立体声相关性
Perceptual Noise Substitution 用噪声能量代替显式频谱系数。若左右声道相同 band 都采用 PNS,并且 MS 条件指示相关噪声,解码器需要生成相关或共享的随机序列;分别调用两个无关随机源会破坏声像。随机生成器应为每帧、声道、group、SFB 提供可重复状态,以便测试和 seek 后重建,同时不能让输入值直接控制大内存或昂贵循环。
CPE 逐层验证
验证 CPE 时先检查公共窗口和 MS 矩阵维度,再分别验证左右 ICS,最后验证跨声道工具关系。下面的错误不应静默接受:common_window=0 却访问旧 MS 矩阵;MS 矩阵覆盖 max_sfb 之外;右声道 intensity band 对应左声道没有可用谱;PNS 相关标志引用零宽 band;两个声道实际 window group 数不同却强行执行逐 band 联合处理。
声道对到输出布局的映射
CPE 的第一个 ICS 通常对应 pair 的前/左角色,第二个对应后/右角色,但最终扬声器位置来自 channel configuration 或 PCE。元素到输出平面的映射应在配置阶段固定,解码循环只按 mapping 写入;不能把所有 CPE 都假定为普通立体声。例如 5.1 中前方 CPE、后方 CPE 与独立 center、LFE 一起组成输出布局。
第十九章 编码工具的条件语法与应用顺序
Pulse Data 的位级解析
Pulse 工具只适用于允许的长窗情形。它指定一个起始 SFB,以及最多四个脉冲的位置增量和幅度。脉冲位置通过累积偏移定位到量化频谱系数,然后按原系数符号增加或减少幅度。
1 | pulse_data() { |
number_pulse 表示脉冲数减一,所以循环次数是其值加一。第一个位置由 swb_offset[pulse_start_sfb] + pulse_offset[0] 得到,后续位置在前一个位置上继续累加 offset。每一步都要验证不超过长窗谱线总数。短窗出现 pulse present 属于错误;把越界位置钳制到最后一个系数会隐藏损坏并改变音频。
Temporal Noise Shaping 的位级解析
TNS 在频域使用预测滤波改变量化噪声的时间分布。语法按 window 给出 filter 数量、系数分辨率、filter 长度、阶数、方向、压缩标志和量化系数。长窗与短窗的字段位数不同。
1 | for each window w: |
Filter length 按 SFB 或频谱范围逐段从高端向低端分配,不能超过 max_sfb 和对象类型规定的 TNS 最大 band。order 也有 profile、长短窗相关上限。系数量化值需经查表或公式转换为反射系数,再生成预测滤波系数。方向位决定沿谱线正向或反向应用。
TNS 的安全风险不只是越界:接近不稳定的系数会放大浮点异常。实现应使用标准映射,限制阶数,检查中间结果为有限值,并在固定点版本中采用足够宽的累加器和规定饱和策略。
Gain Control
Gain Control 主要与 SSR 等对象相关,LC 常见流通常不出现。语法按窗口序列选择不同数量的调整窗,并发送 alevcode 与 aloccode。通用解析器若声明只支持 LC,遇到 gain_control_data_present=1 应按对象规则判断非法或不支持;不能假设其长度为零后继续。支持 SSR 的实现必须按正确 window sequence 计算循环次数,否则后继频谱边界将错位。
Main Prediction
AAC Main 的频域预测器使用前帧状态减少稳态信号冗余。predictor_reset 可重置一个 predictor group,prediction_used[sfb] 选择参与预测的 band。Seek、丢帧、配置变化和随机访问都影响预测状态。容器给出的随机访问点若不能恢复所需预测历史,解码器应从更早可解码点预滚,而不是输出未初始化预测器产生的 PCM。
Long Term Prediction
LTP 利用跨帧的时域相关性,语法包含 lag、系数以及对长窗或短窗使用情况的选择。它依赖之前合成的时域历史,状态生命周期比单帧频谱更长。刷新配置、采样率变化或显式错误恢复时必须清除相应历史。实现若不支持对应 AOT,应在配置阶段拒绝,不能只忽略 LTP 位,因为该工具会改变必要的码流读取和重建结果。
SBR 扩展载荷
SBR 常通过 FIL 扩展承载,扩展类型决定后续为单声道、声道对、CRC 版本或其他扩展语法。SBR 先解码核心 AAC 的低频信号,再根据 envelope、noise floor、patch 和 limiter 等参数重建高频。隐式 signaling 和显式 signaling 会影响外部报告的采样率与帧输出样本数:核心采样率可能是输出采样率的一半。
解析器需要区分:容器 ASC 中声明的扩展采样率;同步扩展发现的 SBR;帧内扩展载荷是否实际出现;当前帧能否沿用历史 SBR header。把“发现一次 SBR”永久当作所有后续数据都完整,会使 seek 到中间或丢包后使用过期配置。
PS 扩展
Parametric Stereo 常与 HE-AAC v2 结合,在低码率下从一个核心声道和空间参数重建双声道。ASC 中的声道配置、核心解码声道数和最终输出声道数可能不同。因此 API 应分别报告 coded channels 与 presentation channels。PS 参数缺失或损坏时,降级策略可以输出双单声道或核心单声道,但必须明确标记,不能声称完整立体声解码成功。
工具应用顺序
一个稳健 LC/HE 解码链可以用以下依赖顺序理解,实际细节以对应 AOT 标准为准:
1 | 元素与 ICS 语法 |
应用顺序写错往往不会导致解析失败,只会造成音质异常,所以必须用覆盖单项工具和工具组合的标准向量验证。仅用普通 AAC-LC 音乐试听无法证明 SBR、PS、TNS 或 PNS 的实现正确。
第二十章 ADTS、ADIF、LATM 与 MP4 转换边界
转换的本质
封装转换不是“删除前七字节”这么简单。转换器需要识别访问单元边界,提取或构造解码配置,保留样本顺序和时间,处理 CRC、多 raw block、PCE、SBR/PS signaling,并验证目标格式是否能表达源格式的全部语义。只有在 AAC payload 本身不重编码时,才可称为无损重封装。
ADTS 到 AudioSpecificConfig
ADTS 固定头可提供 MPEG 标识、两位 profile、sampling frequency index 和 channel configuration。对常见 AAC-LC、非显式频率、非 PCE 多声道情形,可构造两字节 ASC:AOT 等于 profile + 1,随后写采样率索引和声道配置,再按 GASpecificConfig 写 frameLengthFlag、dependsOnCoreCoder、extensionFlag。
但 ADTS 两位 profile 只能直接表达有限对象类型,不能完整携带所有 MPEG-4 ASC 扩展。若 channel_configuration == 0,目标 ASC 需要获得 PCE;若流使用隐式 SBR,转换器还需通过可靠探测或用户配置确定输出 signaling。无法证明配置时,应停止并要求显式参数,而不是生成貌似合法但错误的 12 10。
ADTS 访问单元提取
1 | for each ADTS frame: |
当存在多个 raw data block 和 CRC 时,附加校验结构并不总是简单的九字节统一头。只支持常见单块模式的转换器必须明确拒绝多块,而不能把剩余所有字节作为一个 MP4 sample。
MP4 到 ADTS
MP4 的 mp4a sample entry 和 esds/DecoderSpecificInfo 提供 ASC,sample table 提供每个 AU 的字节范围和持续时间。转换器从 ASC 获取 AOT、采样率和声道配置,再为每个 sample 构造 ADTS 头。
ADTS 的 aac_frame_length 只有十三位,因此单个目标帧总长度不能超过可表达上限。ASC 使用显式采样频率、escape AOT、PCE 或 SBR/PS 时,还要判断 ADTS 固定头能否忠实表达核心配置。MP4 sample duration 若不是该对象类型每 AU 的预期样本数,不能仅靠 ADTS header 保存原时间轴。
1 | frame_length = adts_header_bytes + sample_size |
ADIF 转换限制
ADIF 提供文件级配置和连续 raw data stream,但没有逐帧同步和长度。将 ADIF 转成 ADTS 或 MP4,必须真正解析 raw_data_block() 才能找出访问单元边界;搜索 0xFFF 不可靠,因为熵编码载荷中可以自然出现相同比特模式。若解析器不支持流中使用的对象工具,就无法安全完成转换。
反向从 ADTS 到 ADIF 时,需要验证所有帧配置一致,选择 constant/variable bitrate 语义,构造 PCE,并去除 ADTS 层。ADTS 中只有 channel_configuration 而没有完整 PCE 时,转换器需按标准默认映射合成等价 PCE;同时不能把 ADTS 的 CRC 当成 raw_data_block 内容保留。
LATM/LOAS 转换
LATM 的 StreamMuxConfig 可以内嵌或复用 ASC,AudioMuxElement 还能表示 program、layer、subframe 和其他数据。常见单 program、单 layer、frameLengthType=0 可以映射成一系列 AAC AU;复杂 mux 需要分别输出轨道或明确拒绝。useSameStreamMux=1 依赖先前配置,随机截取若从该包开始将无法独立解码。
LOAS 三字节同步头的十三位 audioMuxLengthBytes 给出紧随其后的 AudioMuxElement 长度。转换器应先按 LOAS 外层闭合,再在限定范围内解析 LATM;不能在 LATM 内部搜索下一同步字来弥补长度错误。
时间戳换算
普通 AAC-LC 一帧通常对应 1024 个核心采样,LD/ELD、SBR 和特殊 frameLength 配置可能不同。以采样数累计时间可避免逐帧整数舍入:
1 | sample_cursor += samples_per_access_unit |
写 MP4 时把持续时间映射到 track timescale;写实时传输时将时间戳映射到相应 RTP clock。若 SBR 使输出率加倍,应明确时间基准使用核心还是输出采样率,并保证“一帧持续时间”保持一致。例如 1024 个 24 kHz 核心采样经过 2× SBR 输出 2048 个 48 kHz 样本,两者持续时间相同。
转换验证矩阵
| 源 → 目标 | 必须验证的配置 | 必须验证的边界 | 主要不可逆风险 |
|---|---|---|---|
| ADTS → MP4 | AOT、频率、声道、PCE、SBR | frame length 与 AU 数 | ADTS fullness/CRC 不写入 MP4 |
| MP4 → ADTS | ASC 可表达性、样本持续时间 | sample offset/size | 编辑列表和精确时间轴丢失 |
| ADIF → ADTS | PCE、bitstream type | 需完整解析 RDB | 无同步导致错误难恢复 |
| LOAS → MP4 | StreamMuxConfig、program/layer | mux length、subframe | 复杂 mux 不能单轨表达 |
| ADTS → LOAS | 配置一致性 | 每个 ADTS payload | CRC 与 fullness 层被移除 |
转换前后至少比较:访问单元数量;每个原始 AAC AU 的哈希;ASC/PCE 表达的有效配置;累计样本数与持续时间;首尾 priming/padding;随机访问和 seek 行为。只比较“播放器能否发声”不足以证明重封装正确。
第二十一章 实现级错误分类与一致性验证
分层错误模型
错误应按所在层报告。封装错误包括 ADTS 长度、LOAS 长度、MP4 sample 范围;配置错误包括非法 AOT、采样率、声道布局;语法错误包括 section 越界、缺少 ID_END、Huffman 无终止;工具错误包括 TNS 阶数、Pulse 位置、SBR grid;时间错误包括 AU 数、sample duration 和 delay 处理。分层信息能决定是否可重同步以及是否需要清除解码状态。
可恢复与不可恢复错误
ADTS 流中若某帧长度或 CRC 失败,可以在受限搜索窗口内寻找后续候选同步头,并用连续两帧长度闭合验证。raw_data_block 内 Huffman 失步后通常无法在同一 AU 内可靠恢复,因为 ID_END 比特模式可能出现在码字中;应丢弃当前 AU。配置损坏则影响所有后续帧,必须等到新的可靠 ASC、PCE、StreamMuxConfig 或外部配置。
重同步候选评分
一个 0xFFF 只是一条弱证据。候选头还应满足 layer 为零、采样率索引非保留、frame length 至少覆盖头、长度不超过缓存、profile 和声道配置符合策略。若按该长度跳到下一位置也出现合法头,可显著提高可信度。搜索范围应有限,避免在超大损坏缓存上进行二次复杂度扫描。
CRC 的角色
CRC 能检测指定语法区域的比特错误,但不能替代所有结构检查。即使 CRC 正确,长度仍可能由外层截断,配置也可能不受支持;CRC 失败也不意味着可安全搜索帧内的同步字。实现必须依据采用的 ADTS error check 语法计算覆盖范围,不能简单对“整个帧所有字节”套一个通用 CRC-16 后宣称合规。
配置切换
直播或拼接流可能在中途改变采样率、声道数、AOT 或 SBR。所有依赖配置的表和状态都需原子重建:SFB offset、滤波器组、重叠缓冲、预测器、LTP、SBR/PS、声道映射、时间基准。旧帧工作线程和新配置并发时,应以配置版本绑定每个 AU,防止读取已经替换的查表数据。
测试向量分层
基础向量覆盖 mono/stereo、常用采样率、静音、满幅、瞬态和纯音;语法向量分别覆盖八短窗、多个 section、所有常用 codebook、escape、PNS、MS、intensity、TNS 和 pulse;扩展向量覆盖 HE-AAC、HE-AAC v2、LATM、PCE、多声道;损坏向量覆盖每个长度字段截断、保留值、循环上限、CRC 和同步恢复。
结构验证输出
参考解析器应能为每个访问单元输出结构化记录,而不是只打印一句“frame ok”。推荐字段包括:输入 offset、封装长度、头长度、AOT、核心/输出采样率、coded/presentation channels、raw block 数、元素列表、每个 ICS 的 window sequence/group/max_sfb、工具标志、消耗 bit 数、CRC 状态、输出样本数和错误恢复动作。
1 | AU #1842 offset=0x0019A640 size=371 |
与独立工具交叉验证
至少选择两个独立解码器或分析器交叉检查,例如 FFmpeg/ffprobe 与另一实现。比较时区分“结构解析一致”“解码样本数一致”“PCM 数值在容差内”和“元数据报告一致”。不同解码器可能对损坏帧采用不同 concealment 策略,所以错误流的 PCM 不一致不必然说明正常路径错误;首先比较首个诊断位置和状态清理行为。
模糊测试不变量
Fuzz 输入下必须保持:任何读取不超过 AU 边界;任何循环都有由位宽或资源上限推导的终点;任何尺寸乘法不溢出;失败帧不提交部分配置;解析时间与输入长度近似线性;错误消息本身不读取未初始化字段;下一帧恢复不使用失败帧的临时预测、重叠或扩展状态。
最终验收清单
一份可用于生产的 AAC 解析/转换实现,应证明它能区分 transport、configuration、raw syntax 和 presentation 四层;能逐位解释 ADTS、ADIF、ASC 与所支持 LATM;能有界遍历 raw_data_block 和 ICS;能正确处理长短窗、section、scale factor、spectral codebook 及已声明的编码工具;能保持访问单元数量和时间;能在损坏数据上有限失败;并能通过结构、解码、样本数和重封装哈希四类验证。未实现的 AOT 或工具应被显式拒绝,不得以静默跳过冒充兼容。
第二十二章 位读取器、解析器接口与实现范式
位读取器的数据模型
AAC 语法大量使用非字节对齐字段,因此位读取器是整个实现的可信根。读取器至少保存只读缓冲区、缓冲区字节数、当前绝对 bit offset 和一个不可越过的局部 bit limit。嵌套语法不应直接拿到整个文件的末尾,而应继承当前 ADTS frame、MP4 sample 或 LATM payload 的局部终点。
1 | BitReader { |
建立读取器前先验证 byte_length <= MAX / 8,避免换算 bit 数时溢出。read_bits(n) 接受的 n 应有限,例如零到三十二或六十四;先检查 n <= end_bit - bitpos,再进行移位。先计算 bitpos + n 再比较可能在无符号整数上回绕。
跨字节读取规则
AAC 位字段按最高有效位优先。若字节为 0x12 0x10,连续读取五位、四位、四位,依次得到 AOT 2、samplingFrequencyIndex 4、channelConfiguration 的高低组合。一个容易验证的通用读取算法逐位工作:
1 | read_bits(n): |
生产版本可一次装载多个字节优化,但必须与逐位参考版在随机 offset、读取宽度和缓冲区末尾上逐项对拍。优化版不得读取映射缓冲区末尾之外的“额外安全字节”,除非调用契约明确提供 padding;在内存映射文件或网络分片末尾,越界预取仍可能触发访问异常。
子读取器与事务回滚
对于已知长度的 ADTS frame 或 FIL extension,可从父读取器切出子读取器:子读取器的 end_bit 等于字段终点,解析成功后父读取器前进到该终点。解析候选同步头时则适合用 checkpoint:保存 bitpos,验证失败后回滚,不提交任何配置。
1 | checkpoint = reader.bitpos |
回滚只适用于读取位置和临时对象。若解析函数在验证完成前已经修改全局 PCE、SBR header 或预测状态,单纯恢复 bitpos 无法恢复一致性。因此所有语法对象应先写入 frame-local arena 或值对象,成功后一次性提交。
Exp-Golomb 与 AAC Huffman 的区别
H.264 常用 Exp-Golomb,但 AAC 的 section、scale factor 和频谱主要使用各自 Huffman 表及固定宽度字段。不能复用 read_ue() 读取 AAC 的“可变长度字段”。两者都表现为可变长度码,但码树、终止规则和符号映射完全不同。AAC Huffman 解码器应绑定明确的 codebook,并以最大码长限制逐位搜索。
Canonical Huffman 表表示
AAC 实现可把标准码表转换为“码长、码值、输出元组”的静态表,或者构造多级查找表。参考实现应保留一条逐位树遍历路径,便于验证快速表。
1 | decode_huffman(book, reader): |
快速表通常先查看固定数量前缀位,短码直接命中,长码进入子表。peek_bits 也必须服从边界;剩余位不足快速前缀宽度时,应退回逐位路径,而不是用零填充制造一个并不存在的码字。
解析结果与解码结果分离
纯结构解析器可以输出 AAC AU 的语法树或摘要,而完整解码器还要分配频谱和 PCM 状态。两者共享位读取与配置代码,但接口应区分:结构解析成功仅证明语法闭合,不证明音频工具全部可解码;解码成功也不意味着传输时间戳和容器元数据正确。
推荐结果结构:
1 | AccessUnitReport { |
内存布局
AAC-LC 解码需要每声道量化频谱、反量化频谱、时域输出、重叠缓存以及工具状态。最大声道数由支持策略和 PCE 决定。所有平面大小可从固定 transform 长度和最大声道数推导,适合在配置时一次分配并复用;不应根据每帧 section 数量反复分配小对象。SBR/PS 有额外 QMF 和参数历史,应绑定扩展配置版本。
临时语法数组如 sfb_cb[group][sfb]、scale factors 和 TNS filter 可以使用固定上界或从经验证的标准表大小分配。先读取恶意 max_sfb 再按它分配,会在检测其超过 num_swb 前造成资源问题。
流式输入与分片缓存
网络输入可能把七字节 ADTS 头分成多个分片,也可能一次包含多帧。Transport parser 应维护一个小状态机:寻找候选同步、收集完整头、解析 frame length、等待完整帧、把精确 frame span 交给 AAC parser。等待过程中设置最大 frame length 和总缓存上限。
1 | SEARCH_SYNC |
解析器不应要求调用方把输入拼成一个永久增长的连续数组。可使用 ring buffer 或分片 span,但在交给位读取器前,必须保证当前 frame 的逻辑字节可稳定访问。若选择临时拷贝,拷贝长度来自已验证的十三位 frame length。
同步搜索的最坏情况
对每个字节都尝试完整解析后续所有元素可能退化为平方复杂度。同步阶段只做常数时间头验证和最多下一帧头闭合;选定完整候选后才解析 raw payload。失败后至少前进一个字节,并限制单次调用的最大搜索字节数。持续垃圾输入下,CPU 消耗应与输入线性相关,缓存占用不超过策略上限。
解码线程和状态所有权
同一 AAC track 的帧间存在重叠相加、预测、SBR 和 PS 状态,不能简单把相邻帧并行交给共享解码器实例。可并行处理独立轨道,或在语法解析阶段并行但按顺序提交状态相关解码。配置切换必须形成序列屏障:旧配置最后一帧完成后,重建状态,再处理新版本首帧。
固定点实现边界
嵌入式解码器常用固定点完成反量化、TNS、IMDCT 和 QMF。每个阶段应记录 Q 格式、最大输入、累加器位宽、舍入与饱和规则。右移有符号负数的行为在语言和编译器环境中要明确,乘加前强制扩展到宽类型。用无符号溢出模拟模运算并不适用于音频饱和。
1 | Q31 multiply: |
该示例只是接口形式,实际舍入要处理负数对称性并服从实现规范。固定点结果与浮点参考对拍时,使用规定的信噪比、最大绝对误差和能量误差阈值。
错误隐藏接口
语法解析和错误隐藏应分离。Parser 返回“帧不可解码”和已知配置,concealment 模块再决定静音、重复频谱、插值或衰减前帧。隐藏模块不得让失败帧携带的部分 TNS/SBR 参数污染后续状态。若 transport 层不能确定 AU 边界,则连“丢一帧”都无法可靠执行,应先重新同步。
诊断定位
每个错误携带 transport byte offset、AU-relative bit offset、语法路径和必要字段值。例如:
1 | AAC_SECTION_RANGE |
这样的报告可以直接定位编码器和解析器分歧。只返回 invalid data 会迫使调试者从文件头重新数位,也无法支持自动化损坏分类。
单元测试边界集合
位读取器测试包含零位读取、跨一个和多个字节、恰好读到末尾、末尾少一位、非零 begin bit、最大宽度、长度乘八溢出。ADTS 头测试枚举 protection、profile、十五种频率索引、七种非零声道布局、最小/最大 frame length、多 block。ASC 测试包含 escape AOT、显式 24 位频率、PCE、同步扩展和截断在每一个字段的位置。
ICS 测试应生成每种 window sequence、grouping 的边界组合、max_sfb 零和最大、section 单段/多段/escape、所有合法 codebook、非法 12、尺度因子极值、escape 谱值、工具 present 位组合。每个成功向量断言 bitpos 精确到预期终点,每个失败向量断言首个错误位置和未提交状态。
属性测试
写入器与解析器适合做 round-trip 属性测试。随机生成受约束的 ADTS header 或 ASC 对象,序列化后解析,字段应完全相等;再序列化应得到相同规范字节。对允许多种等价编码的结构,则比较语义对象而不是字节。随机截断任一合法输入的每个前缀,解析器必须返回有界的 NEED_MORE_DATA 或 TRUNCATED,不得崩溃或成功越界。
性能测量
性能报告至少分 transport parsing、Huffman/反量化、滤波器组、SBR/PS 和输出转换。用音频秒数与 CPU 时间比表示实时裕量,同时记录峰值内存和最坏帧耗时。平均速度足够并不代表实时安全;短窗、复杂 TNS、SBR 和多声道组合可能形成耗时峰值。
参考实现的验收接口
最终库可以提供三个层级:inspect() 只输出结构;demux() 输出 ASC 与原始 AU;decode() 输出 PCM 和时间信息。三个接口共享同一有界 parser,避免检查工具与实际解码器对同一流给出不同边界。调用者必须提供最大帧长、最大声道数、允许 AOT、CRC 策略和错误恢复策略,库在结果中回显实际采用配置。
通过这种接口划分,AAC 参考手册中的字段、位布局、状态机和验证项能够逐一落到代码责任:外层 transport 决定字节范围,ASC/PCE 决定配置,raw_data_block 决定元素,ICS 决定频谱语法,工具链决定 PCM,时间层决定呈现。任何一层都不能用播放器“听起来正常”替代可审计的结构证据。
ADTS 头构造的逐字段算例
设目标为 MPEG-4 AAC-LC、44.1 kHz、双声道、无 CRC,原始访问单元长 300 字节。七字节 ADTS 头加 payload 后 aac_frame_length=307=0x133。选择 VBR 语义时 adts_buffer_fullness=0x7FF,每个 ADTS frame 只含一个 raw data block,因此最后两位为零。
1 | syncword 0xFFF 12 bit |
按网络位序拼接可得到:
1 | FF F1 50 80 26 7F FC |
逐字节复核如下。FF F 给出十二位同步字;第一字节剩余低四位与第二字节组合表达 ID、layer 和 protection;第三字节的高两位是 profile,随后四位为采样率索引,再跨字节拼出三位声道配置;frame length 跨越第四、第五和第六字节;fullness 又跨第六、第七字节。验证器不应通过本例硬编码常量,而应把七字节重新送入独立 parser,并断言所有字段以及总长度与输入对象相等。
如果加入 CRC,protection_absent 变成零,头部及校验结构长度增加,frame length 也必须随之增加。不能只把第二字节的最低位清零而仍输出七字节,否则 payload 的前两字节会被解码器误当作 CRC。若 payload 长度使总长超过 0x1FFF,写入器应返回不可表达错误,而不是让十三位字段截断。
ASC 与 ADTS 的成对核验
同一常见配置的 ASC 为 12 10。将其逐位展开:
1 | 00010 0100 0010 000 |
前五位 00010 表示 AOT 2;接下来 0100 为采样率索引 4;再后四位 0010 为双声道;最后的 frameLengthFlag、dependsOnCoreCoder、extensionFlag 均为零。与上面的 ADTS 头比较时,应检查 ADTS profile 加一等于 ASC AOT、频率索引相等、声道配置相等。ADTS 没有直接保存三个 GASpecificConfig 标志,因此从 ADTS 合成 ASC 时使用默认值是一项受条件限制的推断,转换报告应记录这一点。
若 ASC 是 HE-AAC 的显式扩展形式,则外层 AOT、扩展采样率和核心 AOT 的关系不能压缩成 ADTS 两位 profile。写 ADTS 时通常表达核心 AAC 配置,SBR 由 payload 扩展或带外知识发现。工具若把外层 AOT 5 直接做减一后塞入两位 profile,会发生截断并生成错误头。
帧目录和累计时间算例
连续五个 AAC-LC AU,每个 1024 个采样,采样率 44100 Hz,总解码采样数为 5120,理想持续时间约为 5120 / 44100 = 0.11609977 秒。若容器 timescale 取 44100,每个样本 duration 直接写 1024,累计时间完全由整数表示;若 timescale 取 1000 而逐帧四舍五入成 23 ms,五帧只有 115 ms,长文件会持续积累误差。
可靠复用整数余数:
1 | total_ticks(n) = round_or_floor_according_to_container( |
这样某些帧可为 23 tick、某些为 24 tick,累计值始终贴近精确有理数。更直接的方式是选用采样率或其整数倍作为 track timescale。SBR 输出两倍采样数时,若输出采样率也加倍,持续时间保持不变;报告中仍应同时给出 core samples、output samples 和 presentation duration。
截断位置验证
对七字节头分别只提供零到六字节,parser 均应返回 NEED_MORE_DATA,不得把缺失位当作零。提供完整头但仅有 299 字节 payload 时,应报告 frame 声明 307 字节、实际只有 306 字节。若紧接着出现另一个合法同步字,也不能擅自缩短前帧去迎合后帧;frame length 是当前帧的权威边界,除非进入明确的损坏恢复模式。
将 frame length 改成六,会小于无 CRC 最小头长,必须在尚未计算 payload range 前拒绝。将 sampling frequency index 改成十五,是保留值;即使系统猜测当前设备固定 44.1 kHz,也不应覆盖码流错误继续。将 layer 改成非零,则该候选不是合法 ADTS 头,适合在同步搜索阶段快速排除。
手册与实现的对应验收
本手册中的每张字段表都应能对应解析器中的一个值对象,每段条件语法都应对应显式分支,每个长度都应形成局部 reader limit,每个状态工具都应标明重置点。实现评审时随机选取一个输出字段,应能反向定位到原始 byte/bit range;随机选取一个输入 bit,应能说明它属于 transport、configuration、element、spectral payload、extension 还是 alignment。满足这种双向可追溯性,才说明参考手册真正转化成了可维护实现,而不只是概念介绍。