BW64 与 ADM:沉浸式音频交付的中文技术参考
写给正在为民乐、戏曲和现场录音准备三维声母版的工程师,也写给刚刚被要求交一个"沉浸式母版"、却发现问题全在元数据上的音乐人。
简短回答
ADM(Audio Definition Model,音频定义模型,ITU-R BS.2076)是一套开放的元数据模型,它说明文件里每一条轨道究竟是什么——是一个固定的扬声器声道、一个会移动的对象,还是一组 Ambisonics 分量——以及这些轨道如何组合成一个节目。它以 XML 的形式装在 BW64 容器(ITU-R BS.2088)里;BW64 是 RIFF/WAVE 的 64 位扩展,存在的唯一理由就是突破传统 WAV 的 4 GB 上限。沉浸式交付之所以频繁被退回,原因几乎从来不是"不好听",而是音频和元数据是两样东西、必须严格一致,而文件格式本身并不强制它们一致。fmt 里的声道数与 chna 表对不上、某个 audioTrackUID 没有任何对象引用、区块(block)的时间轴往回跳、XML 里写了一个从未真正打进磁盘的声床——这些在波形编辑器里全都是静默的,在渲染器面前全都是致命的。ADM 的验收是引用完整性检查,不是听音检查。
一、从中国民族管弦乐团说起:一个罕见地"可复现"的声床
大多数关于基于声道(channel-based)与基于对象(object-based)的教学都会举交响乐团做例子,然后立刻遇到一个麻烦:真实的乐团座位其实是有弹性的,指挥、厅堂、曲目都会让它变。
中国民族管弦乐团在这一点上是个例外,而且是一个对录音师非常友好的例外。现代民族管弦乐团的编制与座位并不是历史自然长成的,它是二十世纪中叶被有意设计出来的:以拉弦、弹拨、吹管、打击四组为骨架,参照西方管弦乐团的声部分层重新排布,并在长期的乐团实践、教学与曲目写作中固定下来。今天你走进任何一个专业民族乐团的排练厅,看到的基本都是同一张座位图:
- 拉弦组在指挥左前方向右铺开,高胡在最内侧,二胡分一、二声部,中胡在其后,大提琴(或革胡)与低音提琴压在右侧外缘;
- 弹拨组在指挥正前方偏右成排——柳琴、琵琶、扬琴、中阮、大阮、三弦、古筝,这一组几乎是民族乐团独有的、横向铺得非常宽的一片声源;
- 吹管组在中后区,梆笛、曲笛、新笛在前,笙组居中,唢呐与管子在最后一排、通常还被垫高;
- 打击组在最后,排鼓、大鼓、锣、镲、云锣沿后墙一字排开。
这张图有三个性质,恰好就是 ADM 里"声床(bed)"这个概念需要的性质:位置是标称的、是文档化的、是可重复的。声源的方位角与俯仰角不依赖于某一次演出的临时安排,你可以在录音开始之前就把整个几何写进元数据,并且下一次录同一个乐团时它依然成立。
这就是为什么民族管弦乐团是一个异常干净的基于声道的案例。你不需要给每一件乐器一个对象。你需要的是一个 7.1.4(BS.2051 的 System D,4+5+0,或更常见的扩展布局)声床,把整个乐团的空间像"厅堂的一张照片"那样装进去:拉弦组的宽度落在前方水平面上,弹拨组的横向铺陈落在前方与两侧之间,打击组的能量落在后方与上层的早期反射里。声床里的第 3 条轨道就是中置声道,它在任何回放系统上都是中置声道,不需要任何 XML 来解释自己。
对比一下反面:一支临时凑起来的丝竹小组、一次在录音棚里各自分开补录的拼贴,几何是任意的、不可复现的,把它硬写成声床只是在元数据里撒谎。那种素材应该走对象。
声床适合"几何本身就是作品一部分、并且被规定下来"的合奏;对象适合"几何是这一次演出的偶然事实"的合奏。民族管弦乐团罕见地属于前者。
在 ADM 里,声床使用 DirectSpeakers 类型定义,typeLabel 为 0001。
二、京剧:舞台几何被写进了行当规矩里
京剧是这份文档里的对象案例,而且是一个非常硬的对象案例。
2.1 文场与武场:侧台上的固定点
京剧的乐队不在乐池里,也不在观众看不见的地方。文场(京胡、京二胡、月琴、三弦,有时加笛、笙)与武场(单皮鼓与板、大锣、铙钹、小锣)坐在舞台的侧边,位置是固定的、约定俗成的:鼓佬(司鼓)是整场演出的调度中枢,锣鼓紧挨着他。从录音的角度看,这是一组静止的对象——不是声床,因为它们不占据"厅堂的整张照片",而是集中在一个明确的方位上;也不是随便什么位置,因为那个方位是剧种规矩的一部分,不是这一晚的临时安排。
在 ADM 里,静止对象就是只有一个 audioBlockFormat 的 audioObject:一个方位角、一个俯仰角、一个距离,rtime 为 00:00:00.00000,duration 覆盖整个节目。它依然是对象而不是声道,因为渲染器需要根据实际存在的扬声器布局去决定怎么放它,而不是假设"左侧一定有一只音箱"。
2.2 边走边唱:真正的移动声源
京剧演员在台上是移动的,而且是边移动边唱。这不是"我们假装它在动"的示范素材——这是移动声源的真实录音问题。一段【西皮流水】可能从下场门起唱、走到台口、再回到台中;一个圆场是演员绕着台面跑一圈,同时锣鼓在侧台不动。声源的方位角在几十秒内扫过一百多度,而伴奏的方位角一动不动。
这正是 Objects(typeLabel 0003)存在的理由:一条单声道信号,加上随时间变化的位置元数据,由渲染器决定用哪些真实扬声器、以什么比例重放。轨迹的来源可以是无线话筒配合的位置记录、可以是排练时按舞台调度手写的关键帧、也可以是事后按视频逐帧标注——但它必须以一串连续的 audioBlockFormat 存在,每一块都带 rtime 和 duration,前一块的 rtime + duration 严格等于下一块的 rtime。
2.3 检场与武戏
京剧的舞台几何之所以能被写成元数据,还因为检场的存在。检场人负责在演出中搬桌椅、递砌末、铺跳板,而"一桌二椅"在什么位置代表什么空间,是有成规的。舞台不是自由的空地,它是一套被编码过的坐标系。桌子摆在台中偏后是公堂,桌椅的方位决定了演员站位,演员站位决定了声源位置——这条链条对录音师意味着:你可以在开录之前就画出对象轨迹的骨架。
武戏把这件事推到极端。一出带打出手、翻扑、起霸的武戏,演员在台上高速位移,还有上下的分量——起范儿、上桌、翻下,声源的俯仰角是真实变化的。如果你要找一个"教科书上的移动对象"在现实中的对应物,武戏比任何合成示例都更接近。它同时也是最容易暴露元数据错误的素材:轨迹一旦有一块时间倒退,渲染器的行为是未定义的——有的保持上一位置,有的直接静音,你在立体声折叠里往往什么都听不出来。
三、唢呐与锣鼓:走在街上和院子里的乐队
红白喜事的鼓吹班、庙会的行香队、秧歌与舞龙的锣鼓,是另一类沉浸式素材:整个合奏在移动。
唢呐班沿街行进时,乐队本身就是一个移动的声源群:唢呐在前,笙管居中,锣鼓在后,队形在拐弯时被拉长又压扁。在院落里(尤其是北方四合院、南方天井院这种硬质围合空间)绕行时,直达声的方位在变,而反射声的方位几乎不变——墙面是固定的。
这对元数据有两个直接后果:
- 每一件乐器(或每一个小组)是一个对象,而不是一个声床。它们的相对几何在变,绝对几何更在变,任何"固定扬声器位置"的描述都是错的。
- 厅堂/院落的反射应当另行处理。常见做法是一个基于场景(scene-based)的高阶 Ambisonics(HOA,typeLabel
0004)层承载环境,叠加若干移动对象承载乐器。HOA 用球谐分量编码整个声场,任何单个分量都不对应一个方向,方向是线性组合的结果——这恰好适合"围合空间里到处都是反射"的情况。
ADM 还定义了 Matrix(0002,用于 M/S、Lt/Rt 这类矩阵信号)与 Binaural(0005,已经做好的耳机双声道)。一次行进录音的完整交付里,这五种类型可能出现三种,而 chna 表必须把每一条物理轨道正确地指向它自己的那一种。
四、编钟、庙宇与院落:本来就是二维阵列的乐器
如果要在全世界范围内找一件"在物理上就是一个二维声源阵列"的乐器,编钟大概是最直白的答案。
编钟是一组按大小依次悬挂在钟架上的青铜钟,分层悬挂、层内左右排开。也就是说:同一件乐器的不同音高,在物理上处于不同的高度和不同的水平位置。低音的大钟在下层、靠外,高音的小钟在上层、靠内。演奏者用钟槌与撞钟木在架前移动着敲击。这不是"我们用元数据模拟出来的高度",这是真实存在的、可以用卷尺量出来的高度差。
一支立体声话筒对着编钟,会把这个二维阵列压成一条水平线——这是立体声录音一直以来在做的、并且不得不做的妥协。沉浸式录音第一次让这个妥协变得没有必要:上层的小钟可以真的出现在上层扬声器里。
同样的道理适用于寺庙大殿与院落的声学环境。中国传统木构大殿的内部是一个高、深、有大量硬质与半硬质反射面的空间,梵呗、法器(磬、木鱼、铙钹)在其中的混响是有明确方向性的;四合院的天井是一个上方开口的方形空间,声音在四面墙之间往返,而顶部是敞开的——这意味着水平面上有强反射、上半球却近乎没有。这类空间的上层声道内容与水平层内容差异极大,恰恰是沉浸式格式能表达而立体声不能表达的部分。
录这类空间时,一个务实的分层是:
- 声床:厅堂/院落的整体声场(若用固定阵列拾音);
- 对象:法器、领腔、移动的行列;
- HOA 层:如果用了 Ambisonics 话筒,作为环境层单独存在。
三者在 ADM 里是三个 audioPackFormat,由同一个 audioProgramme 下的若干 audioContent 组织起来。
五、三类音频的正式定义
把上面的例子收拢成规范语言:
基于声道(channel-based):每条轨道对应一个固定的扬声器位置。立体声是;5.1 是;7.1.4 声床是。位置烘焙在声道身份里。ITU-R BS.2051 为先进声音系统定义了这些布局,记法是上层 + 中层 + 下层的扬声器数:System A 是 0+2+0(立体声),System B 是 0+5+0(5.1),System D 是 4+5+0,System J 是 4+7+0,System H 是 9+10+3,也就是 22.2。ADM 类型定义为 DirectSpeakers,typeLabel 0001。
基于对象(object-based):一条单声道(或多声道包)信号,加上随时间变化的位置元数据。渲染器根据房间里实际存在的布局决定重放方式。轨道本身不预设任何扬声器。typeDefinition 为 Objects,typeLabel 0003。
基于场景(scene-based):实践中就是高阶 Ambisonics,把整个声场编码为一组球谐分量。typeDefinition 为 HOA,typeLabel 0004。
这不是分类学游戏。一个基于声道的交付物即使剥掉元数据也还能勉强活着——声道顺序对了就能放。一个基于对象或基于场景的交付物没有元数据就毫无意义:音频只是一堆无差别的单声道流,只有 ADM 才知道它们是什么。这个不对称,就是 BW64 和 ADM 存在的全部理由,也是沉浸式验收比立体声验收难得多的全部理由。
六、BW64 为什么存在:4 GB 的算术
传统 WAV 是 RIFF 文件。RIFF 给每个块(chunk)加一个 32 位的大小字段。32 位能表示 4 294 967 296 字节,也就是 4 GB,这既是整个文件的上限,也是 data 块的上限。
对立体声来说这个上限根本不存在。48 kHz / 24 bit 立体声的数据率是
2 × 48 000 × 3 = 288 000 字节/秒
4 294 967 296 ÷ 288 000 ≈ 14 913 秒 ≈ 4 小时 8 分。没人会撞到。
沉浸式素材完全是另一回事。用上面第一节的民族管弦乐团配置举例:12 声道的 7.1.4 声床,加 12 个对象(四组的近场话筒、独奏、指挥席参考),共 24 条轨道,96 kHz / 24 bit:
24 × 96 000 × 3 = 6 912 000 字节/秒 4 294 967 296 ÷ 6 912 000 ≈ 621 秒 ≈ 10 分 21 秒
一首《瑶族舞曲》放得下,一部民族交响套曲的完整一轨放不下。
再看京剧。一出折子戏做 32 轨、48 kHz / 24 bit:
32 × 48 000 × 3 = 4 608 000 字节/秒 4 294 967 296 ÷ 4 608 000 ≈ 932 秒 ≈ 15 分 32 秒
一出《三岔口》勉强、一出《打渔杀家》必然超。全本大戏更不用说。
极端一点,128 轨、96 kHz / 24 bit:
128 × 96 000 × 3 = 36 864 000 字节/秒 4 294 967 296 ÷ 36 864 000 ≈ 116.5 秒
不到两分钟。撞到上限时,WAV 写入器要么写出截断的文件,要么写出一个大小字段已经悄悄回绕的文件——后者更糟,因为它看起来能打开。
EBU 先用 RF64(EBU Tech 3306)解决了这个问题,ITU 随后把这项工作推进为 ITU-R BS.2088《用于国际交换带元数据的音频节目素材的长格式文件格式》,即 BW64。BS.2088 对机制的表述很直白:"文件前四个字节使用 ID BW64 而不是 RIFF",原来的 32 位字段变成逃逸标志——"如果字段中的 32 位值为 0xFFFFFFFF,则使用 ds64 块中的 64 位值。"
BW64 并没有把 RIFF 的大小字段加宽;它在那些字段里填入0xFFFFFFFF作为哨兵值,把真正的 64 位大小放进一个必须位于文件最前面的ds64块。
把它理解成一条谱系的第三代最省事:RIFF/WAVE 给出分块容器;广播波形格式(BWF,EBU Tech 3285)加上 bext 块——制作者、时间码参考、编码历史,把 WAV 变成广播交换格式;BW64 保留这一切,加上 64 位寻址,再加上承载 ADM 的那几个块。一个小于 4 GB 的 BW64 文件,除了那四个字节的签名之外,在任何方面都与 WAV 读取器兼容——很多工具认,也有一些工具死活不认。
七、块布局,逐字节讲清楚
按 BS.2088,一个 BW64 文件预期至少包含 <ds64-ck>、<fmt-ck>、<chna-ck>、<axml-ck>(<bxml-ck> 与 <sxml-ck> 是元数据的替代载体)以及 <wave-data>。
7.1 ds64 —— 64 位大小表
必须是 BW64 签名之后的第一个块,因为读取器必须先知道真实大小,才能往下走。它的字段是 64 位量的 32 位半:
| 字段 | 字节 | 含义 |
|---|---|---|
ckID | 4 | 'ds64' |
ckSize | 4 | 本块大小 |
bw64SizeLow / bw64SizeHigh | 4 + 4 | 整个文件的 64 位大小 |
dataSizeLow / dataSizeHigh | 4 + 4 | data 块的 64 位大小 |
dummyLow / dummyHigh | 4 + 4 | 保留 / 兼容 |
tableLength | 4 | 后随的 ChunkSize64 条目数 |
table[] | 可变 | 其他超大块的 64 位大小 |
table[] 比看上去重要。一个描述几千个对象、带采样精度自动化的 axml 块,本身就可能逼近甚至超过 4 GB;table[] 是除 data 之外任何块声明 64 位大小的唯一途径。如果你的 axml 超了 4 GB 而 ds64 表里没有对应条目,文件就是坏的——而且是那种"波形能放、元数据读一半"的坏法。
7.2 fmt —— 实体(essence)的描述
标准 WAVE 格式块:采样格式、采样率、声道数、位深、块对齐。这是关于 data 里到底交织了多少条声道的唯一权威陈述。下游一切都是关于这些声道的元数据,而元数据是会撒谎的。
7.3 chna —— 声道分配表与那 40 个字节
chna 是物理轨道与 ADM 之间的桥。它的开头是:
ckID—— 4 字节,'chna'ckSize—— 4 字节numTracks—— 2 字节,文件中的轨道数numUIDs—— 2 字节,随后的audioTrackUID条目数
然后是一个定宽 audioID 条目的平坦数组。每个条目恰好 40 字节:
| 字段 | 字节 | 内容 |
|---|---|---|
trackIndex | 2 | 从 1 开始的物理轨道号 |
UID | 12 | audioTrackUID 值,如 ATU_00000001 |
trackRef | 14 | audioTrackFormatID 引用,如 AT_00031001_01 |
packRef | 11 | audioPackFormatID 引用,如 AP_00031001 |
pad | 1 | 补齐到偶数对齐 |
2 + 12 + 14 + 11 + 1 = 40。 自己数一下那三个示例字符串:ATU_00000001 是 12 个 ASCII 字符 = 12 字节;AT_00031001_01 是 14 个字符 = 14 字节;AP_00031001 是 11 个字符 = 11 字节。字段宽度不是建议,是定宽 ASCII;写出 13 个字符 UID 的写入器产生的不是"稍微不寻常的表",是损坏的表。
顺带算一下整块大小。上面那个 24 轨的民族管弦乐团文件,若每轨一个 UID:
负载 =numTracks(2) +numUIDs(2) + 24 × 40 = 4 + 960 = 964 字节 连同ckID与ckSize共 8 字节,整个chna块 = 972 字节
还要注意 ID 约定:0x0FFF 及以下的值引用 ADM 的通用定义(common definitions)——预先定义好的标准声道与包格式;0x1000 及以上表示自定义定义,必须出现在 axml 块里。AP_00010003 按通用定义就是 5.1,不需要任何 XML;AP_00031001 是自定义对象包,没有 XML 就是一条悬空引用。
轨道 UID 是文件给某一条物理轨道的"身份编号",它之所以存在,是因为一条轨道可以在节目进行中合法地改变自己承载的内容。
这也是 numUIDs 可以大于 numTracks 的原因:EBU 的指南指出,当"一条轨道的音频元素在文件过程中可能被以不同方式定义……则每一种定义都会有一个不同的 UID"。同一个 trackIndex 因此可以出现在多个条目里。假设"一轨一 UID"的验收工具会把正确的文件判为损坏。对京剧这类素材这一点很实际:一条无线话筒轨道,上半场跟老生、下半场跟花脸,是完全合法的两个 UID。
7.4 一个中文特有的陷阱:汉字绝不能进 chna
这是本文里唯一一条只对中文使用者成立、而且几乎必然会咬人一口的规则。
ADM 的 axml 里可以放汉字,chna 里绝对不可以。
axml 是 UTF-8 编码的 XML,名称类字段——audioProgrammeName、audioContentName、audioObjectName、audioPackFormatName——放中文完全合法、完全正确,而且应该放中文:
<audioObject audioObjectID="AO_1002" audioObjectName="武场 · 大锣">chna 完全相反。它不是文本,它是定宽字节网格。而 UTF-8 里一个常用汉字占 3 个字节。把这两件事放在一起,算术立刻就崩了:
- 一个"按字符数"而不是"按字节数"填充的写入器,想往 12 字节的 UID 槽里写
ATU_0000000弹:11 个 ASCII 字符(11 字节)+ 1 个汉字(3 字节)= 14 字节,它自己数是 12 个字符,于是心满意足地写了出去,溢出 2 字节。 - 更糟的情况:写
ATU_民乐0001——4 + 3 + 3 + 4 = 14 字节,但只有 10 个字符;按字符补齐到 12 个字符再补 2 个 ASCII,就是 16 字节,溢出 4 字节。 - 如果有人把段名写进
packRef(11 字节):AP_弹拨0001= 3 + 6 + 4 = 13 字节,溢出 2 字节。
溢出的后果不是"这一条坏了",而是从这一条开始,后面每一条都坏了。读取器按固定的 40 字节步长走:条目 n 的起点在负载偏移 4 + 40 × (n − 1)。如果第 1 条实际占了 44 字节,读取器仍然从偏移 44 处(即 4 + 40)开始读第 2 条——它读到的是第 1 条最后 4 个字节接上第 2 条的前 36 个字节。trackIndex 变成两个随机字节,UID 变成一截乱码,packRef 指向不存在的包。一个汉字,毁掉整张表的后半段。
而且 ckSize 也对不上了:24 条本该是 964 字节负载,溢出 4 字节后写入器可能写成 968,也可能仍然写 964 而把最后 4 字节截掉。两种都会让严格的解析器直接拒绝文件,让宽松的解析器读出一堆无意义的引用。
配套的两条实务建议:
chna的三个 ID 字段只允许[A-Za-z0-9_],长度硬编码为 12 / 14 / 11,用字节长度校验,不要用len(字符串)。Python 里是len(s.encode('utf-8')),不是len(s);C 里strlen恰好是字节数,反而不容易错。axml必须真的是 UTF-8。 国内工具链里另一个高频事故是 XML 声明写着encoding="UTF-8",实际却是 GBK/GB18030 落盘(尤其是把 XML 拿到本地文本编辑器里手改过之后)。这样的文件在解析器眼里是非法字节序列,通常直接抛异常。另外不要写 UTF-8 BOM——有些解析器会把 BOM 当成 XML 声明之前的字符而报错。中文名称写在 XML 里是对的;中文名称写在 ID 里、或者用错编码,是两种最常见的中文特有失败。
7.5 axml —— ADM 本体
一份 UTF-8 的 XML 文档,包含 <audioFormatExtended> 树。这就是 ADM。它是文本,可读、可 diff,而且几乎所有有意思的失败都住在这里。
7.6 data —— 交织的 PCM
就是普通的交织采样,和 WAV 一模一样。data 里没有任何东西知道"对象"是什么。
7.7 顺序,以及那个所有人都踩的坑
ds64 永远第一。fmt 在 data 之前。axml 可以合法地放在 data 之后,而且经常就是放在后面——正如 BS.2088 所指出的,录音过程中"XML 元数据的长度很可能是未知的"。axml 在文件尾部的文件不是畸形文件;但一个只扫描文件前若干兆字节的读取器会报告"没有 ADM"。国内工单里"我的文件没有元数据"这类报告,相当大一部分就是这一条。
八、ADM 对象模型:一张引用图
ITU-R BS.2076 把模型分成两半。格式部分(format part)"描述音频的技术性质,以便正确解码或渲染",在任何音频存在之前就可以写好。内容部分(content part)描述"对白的语言、响度等",只有在信号存在之后才能补全。搞清楚一个元素属于哪一半,就知道某个错误是在制作的哪个阶段引入的。
自顶向下的层级:
audioProgramme—— 一个完整的交付呈现。引用一个或多个audioContent。audioContent—— 具有编辑意义的组成部分:唱腔分层、伴奏分层、一个语言版本。引用一个或多个audioObject。audioObject—— 编辑意图与技术格式的接合点。它携带起始时间与时长,引用零或多个audioPackFormat、零或多个嵌套audioObject、零或多个audioTrackUID。内容在这里遇上实体。audioPackFormat—— 一组属于同一整体的声道:一个 7.1.4 声床、一个立体声对、一组给定阶数的 HOA。引用audioChannelFormat,也可以嵌套其他包。audioChannelFormat—— 一条声道随时间的行为。包含一个或多个audioBlockFormat。audioBlockFormat—— 原子。对Objects而言是位置(azimuth/elevation/distance或笛卡尔X/Y/Z)、增益、尺寸、扩散度,加上rtime与duration。静止对象是一块;移动对象是一串。audioTrackUID—— 叶子,也是唯一与物理轨道对应的元素。可带sampleRate与bitDepth属性,并引用一个audioTrackFormat与一个audioPackFormat。
audioStreamFormat 与 audioTrackFormat 位于声道格式与轨道 UID 之间,描述流的编码方式。BS.2076-3 指出,对 PCM 而言它们实际上是冗余的——"应省略 audioStreamFormat 与 audioTrackFormat"——但读取器"应当注意,既有的(基于 ITU-R BS.2076-2 及更早版本的)PCM ADM 文件中可能包含"它们。两种形态都合法。坚持只接受其中一种的验证器,在另一种面前就是错的。
ADM 是一张引用构成的图,不是一份嵌套的文档;每一次严重的交付失败,都是这张图上断掉的一条边。
引用悬空时到底会发生什么? 没有统一行为,这正是麻烦所在。渲染器在解析一个指向 XML 中不存在的包的 audioPackFormatIDRef 时,可能中止解析并拒绝文件;可能跳过该对象、照常渲染其余部分,产出一个悄悄少了一层的混音;也可能回落到通用定义,发现这是一个 0x1000 以上的自定义 ID 而无法匹配,于是代之以静音。三种结果都是"文件打开了"。只有一种能靠听发现,而且前提是你恰好知道那里本该有什么。这就是 ADM 验收必须自动化的原因:人耳听不出一个自己从未被告知存在过的缺席。
九、渲染:BS.2127 把 ADM 落到 BS.2051 布局
这是最容易被误解的一环:ADM 文件本身不是声音,它是一份关于如何生成声音的说明。
ITU-R BS.2127 定义了参考 ADM 渲染器:给定一个 ADM 文件和一个目标 BS.2051 布局(0+5+0、4+5+0、4+7+0、9+10+3……),它规定了如何把每一个对象的位置元数据变成一组具体的扬声器增益。它的开源同源实现 EBU ADM Renderer(EAR,EBU Tech 3388)是拿到可复现结果的实际途径。
对本文的例子来说,含义很具体:
- 民族管弦乐团的 7.1.4 声床落到 4+5+0 是直接映射;落到 0+5+0 时上层四路必须被下混,下混规则不是你决定的,是渲染器决定的;
- 京剧演员那条扫过一百多度的轨迹,在 4+7+0 上是连续的,在 0+2+0 上会被折叠成一个左右摇摆的幅度包络——听感差异巨大,但元数据完全相同;
- 行进唢呐班的 HOA 环境层在不同布局上的解码方式不同,阶数越高、扬声器越少,损失越明显。
因此一句在交付说明里必须写清楚的话是:"这个文件在什么布局上、用什么渲染器、听到的是什么。"只写"沉浸式母版"没有信息量。
十、按 BS.1770-5 测量沉浸式内容的响度
这里必须把"已公布的"和"流传的"分开。
10.1 规范规定了什么
ITU-R BS.1770 定义 K 计权:一个模拟刚性球体的高架("head")滤波器,后接一个 RLB 高通。该曲线在 1 kHz 处的增益为 +0.698 dB(线性 1.0836)。测量使用 400 ms 的门限块、75% 重叠;绝对门限丢弃低于 −70 LUFS 的块;相对门限由通过绝对门限的那些块的均值计算,再偏移 −10 LU。请注意:相对门限来自幸存块的均值,不是未加门限的总均值——这个区别被实现搞错的频率高到值得单独强调。
核心算法中的声道加权为:L、R、C 为 1.0(0 dB),Ls、Rs 为 1.41(约 +1.5 dB),LFE 不计入。基础算法的适用范围是"一到五个声道"。BS.1770-5 在其附件中把范围扩展到 BS.2051 的先进声音系统——任意位置的扬声器与高度声道——以及基于对象的音频,而后者必须先被渲染才能被测量。
版本这件事要说明白:BS.1770 共有六个版本——-0(2006)、-1(2007)、-2(2011)、-3(2012)、-4(2015)、-5(2023 年 11 月)。目前生效的是 BS.1770-5;BS.1770-4(2015 年 10 月)已被取代,尽管市面上大多数已部署的表头仍然标着 -4。写交付说明时请写 -5。
相关的 EBU 文档:EBU Tech 3341 定义表头行为(瞬时 momentary 400 ms、短时 short-term 3 s);EBU Tech 3342 定义响度范围(LRA),它使用 −20 LU 的相对门限,不是 −10 LU——把 LRA 的门限写成 −10 是一个常见的实现 bug。EBU R 128 规定广播节目目标 −23 LUFS,那是广播归一化实践,不是音乐流媒体规范。
真峰值必须在过采样信号上测量(BS.1770 要求至少 4×,8× 更好)。采样峰值不等于真峰值,采样间峰值可以超过任何一个采样点的最大值。
沉浸式响度不是文件的属性,而是某一次渲染的属性;换一个目标布局,数字就变。
这是与立体声最实质的差别。一个立体声母版只有一个响度值;一个基于对象的母版有多少个渲染目标就有多少个响度值。诚实的写法是同时写明三件事:测的是哪个布局、用的是哪个渲染器、用的是哪个版本的表。
10.2 音乐方面已经公布的
面向基于声道的立体声音乐:Spotify 公布的整体目标是 −14 LUFS,真峰值上限 −1 dBTP;若母版响于 −14 LUFS,则收紧到 −2 dBTP。AES TD1008 对音乐的建议是 −16 LUFS;文件中的 −18 LUFS 适用于以语言为主的内容(新闻、谈话、广播剧),把 −18 当作音乐目标是一个常见且严重的错误。
关于归一化方向也要说准:Spotify 页面写明"对较轻的母版施加正增益,使响度达到 −14 dB LUFS",同时又写明"我们会考虑轨道的余量,为有损编码保留 1 dB 余量以维持音质"。所以"安静的母版不会被提上来"是错的,"安静的母版一定会被提到目标"也是错的。结论仍然成立:过度限制换来的是平淡,不是响度。
10.3 未公布的
没有任何一家音乐流媒体服务公布过沉浸式交付的整体响度目标。 论坛与厂商培训材料里流传着若干数字,其中有些看起来合理、有些大概是对的。它们都不是公布的规范,本文不会把它们当规范复述。
同样地,Apple Music、YouTube Music、Amazon Music、TIDAL、Deezer 连立体声的归一化目标都没有公布。常被引用的数字(Apple 约 −16、YouTube Music 约 −14、Amazon 约 −14、TIDAL 约 −14、Deezer 约 −15)属于"广为流传,但服务方并未公布",只能在明确标注的独立段落里提及,绝不可据以计算增益变化。
10.4 关于专有系统与国内的三维声标准
Dolby Atmos 是杜比实验室的专有授权技术,Sony 360 Reality Audio 是建立在 MPEG-H 之上的专有系统。它们的交付要求由各自的所有者制定,与 ITU 或 EBU 的时间表无关。本文不构成对上述任何公司要求的陈述,也不主张任何隶属、认证或背书关系。授权方公布了要求的,请阅读并引用授权方自己的最新文档。
国内使用者还应知道另一条路径:三维菁彩声(Audio Vivid)。据世界超高清视频产业联盟(UWA)的说明,它由广播电视行业标准 GY/T 363-2023《三维声编解码及渲染》定义(2023 年 2 月 3 日发布),并作为附件 4 被纳入 ITU-R BS.2493(2025 年 1 月 17 日发布);它同时支持基于声道、"声床 + 对象"以及基于 HOA 的声场三种实现方式(来源:theuwa.com)。这与本文的主题并不冲突:BW64/ADM 是制作与交换阶段的母版格式,编解码标准是分发阶段的事。母版做对了,往哪条分发链路走都还有得选;母版的引用图是断的,走哪条链路都救不回来。
十一、诚实的现实:国内的音乐棚基本没有沉浸式监听
这份文档如果不说这一段就是不诚实的。
在中国,具备规范沉浸式监听条件的房间,主要集中在广播电视机构与影视后期公司——超高清频道的制作链条、少数电影混录棚、几家有专门投入的后期机构。音乐录音棚里极少见:一个正经的 7.1.4 监听环境要求房间高度、上层吊装位置、十二路以上校准过的通路和一次严肃的声学处理,绝大多数音乐棚不具备其中任何一项。做民乐、戏曲、独立音乐的人,多数是在立体声监听或耳机上完成一个自己听不全的沉浸式母版。
这不是一个可以靠意志力解决的问题,也不必假装它不存在。它带来的真实结论是:在这种条件下,你能控制的价值不在"混得多好听",而在"交得多正确"——正确的交付、正确的元数据,以及验证一个你无法充分试听的文件的能力。
在没有监听的条件下,一份可执行的验收流程是这样的:
- 先做结构检查,再谈听感。 结构检查是确定性的、可自动化的、几秒钟就能跑完,而且它一旦失败,后面所有听感判断都没有意义。
- 用 BS.2127/EAR 渲染出多个目标布局:至少 0+2+0(立体声折叠)、0+5+0、以及交付规格要求的那个布局。在你实际拥有的监听上听立体声折叠,用它来抓"对象根本没进来""某一层全是静音""轨迹反了方向"这类粗大错误——这些错误在折叠里通常是听得出来的。
- 对每一条声道做电平与静音统计。 声明为声床的 12 条轨道里如果有 4 条是数字静音,这可能是合法的(一个混音完全可以留空后上方声道),但必须由人来做这个判断,而不是被默默交出去。
- 把 XML 单独存档。 你可以对 XML 做 diff,对一个二进制母版你只能重新解析。半年后有人问"上次那个版本的对象轨迹是不是不一样",能回答这个问题的是存档的 XML。
- 对每一个交付物做校验和并保留清单。
在这一步上,mazufa.com 提供一个免费的浏览器端 BW64/ADM 检查工具,容器与元数据的解析全部在你自己的设备上完成、不上传任何音频,因此可以用在合同上不允许离开机房的素材上。
十二、常见 QC 失败与检测方法
12.1 fmt 与 chna 的声道数不一致
fmt.nChannels 说 16,chna.numTracks 说 14。文件从最前面两个块开始就自相矛盾。通常的成因是元数据写好之后又改了轨道数的一次导出,或者某个转码工具丢了轨道却没重写 chna。
检测:解析两个块,比较整数。这是整条流水线里最便宜的一项检查,却能拦下惊人比例的失败。同时验证 chna 中每一个 trackIndex 都落在 1 … fmt.nChannels 之内。
12.2 孤立的轨道 UID
chna 里存在、却没有任何 audioObject 引用的 UID;或者 XML 里的 audioTrackUIDRef 在 chna 里找不到对应条目。前者意味着有音频不会被渲染,后者意味着元数据在等一条不存在的轨道。
检测:分别从 chna 与 axml 构造 UID 集合,取对称差,应为空集。EBU Tech 3392 把意图说得很清楚:"若 audioObject 引用了某个 audioPackFormat,它也应引用相应的 audioTrackUID。"
12.3 区块时间缺失或非单调
BS.2076 的表述毫不含糊:"当一个 audioChannelFormat 内存在多于一个 audioBlockFormat 时……rtime 与 duration 均应存在。"EBU Tech 3392 进一步收紧为连续性规则——"一个 audioBlockFormat 的 rtime + duration 应等于下一块的 rtime"——对象的第一块从 00:00:00.00000 开始,任何块不得短于一个采样,各块时长之和应与父 audioObject 的时长一致。
检测:对每个 audioChannelFormat,按文档顺序遍历各块,断言 rtime[n] + duration[n] == rtime[n+1]。失败分三类:空隙(渲染器行为未定义,有的保持上一位置,有的静音)、重叠(两块打架)、逆序(自动化在节目中途往回跳)。京剧的圆场轨迹和武戏的翻扑轨迹是这类错误的高发区,因为关键帧往往是人工标注的。
12.4 采样率或位深不符
audioTrackUID 可带 sampleRate 与 bitDepth 属性。当它们与 fmt 矛盾时,你就有了关于同一份实体的两种说法。EBU Tech 3392 的立场是这些属性"若可从音频实体获得则应被忽略"——但并非每个渲染器都遵守,而一个一处写 48 000、一处写 96 000 的文件会被不同工具解释成不同的东西。另外要确认采样率就是交付规格要求的那一个:ADM 写好之后再做采样率转换,会让所有以采样为单位表达的 rtime 与 duration 全部失效。
检测:把 fmt 与 XML 中每一个 sampleRate/bitDepth 属性比对,任何不一致都标出来,哪怕规范上可以容忍。
12.5 立体声/双耳折叠缺失或不同步
多数沉浸式交付要求在母版之外附一个立体声(常常还有双耳)折叠版本。反复出现的问题不是"缺失"——缺失是显而易见的——而是漂移:折叠版是从旧版本渲染的、错了几帧、或者起始时间码不同。一个比母版早 40 ms 的立体声文件,能通过"文件是否存在"的检查,却会在同步试听里当场露馅。
检测:逐采样比较时长,比较 bext 里的起始时间码,并把折叠版与母版的空渲染做互相关。任何不能从当前母版按字节推导出来的折叠版都应视为可疑,重新渲染而不是重新检查。
12.6 XML 里有一个文件里没有的声床
XML 声明了一个 7.1.4 的 DirectSpeakers 包——十二条声道——但实际只打进了 5.1 子集,或者高度声道在导出时被静音、打成了数字静音。引用图完好无损,音频不是。
检测:结构检查抓不到这一类。你需要实体分析:对 DirectSpeakers 包声称的每一条声道,测量该轨道里究竟有没有信号。声明的声床声道上出现数字静音不自动等于错误——一个合法的混音完全可能留空某个后上方声道——但它永远值得一次人工判断。
12.7 chna 条目字节长度异常
见 7.4 节。任何一个条目不是恰好 40 字节,整张表从那里开始就报废了。
检测:用 (ckSize − 4) / 40 验证是否为整数,并与 numUIDs 比对;同时对三个 ID 字段做字节级正则校验,只允许 [A-Za-z0-9_]。中文工具链请把这一项当作必测项,不是选测项。
容器可以完全合法、XML 可以完全良构,而交付物依然是错的;检查一条空声床声道的唯一办法,是去看采样。
十三、交付检查清单
按顺序做。结构检查在前,因为它们快,而且一旦失败,后面的一切都作废。
容器
- 前四个字节是
BW64。若是RIFF,这就是一个普通 WAV,不可能超过 4 GB。 ds64是签名之后的第一个块,其 64 位大小与磁盘上文件与data的实际大小一致。- 任何超大的非
data块,在ds64的table[]里有对应的ChunkSize64条目。 - 明确地去定位
axml,包括data之后的位置。不要靠部分扫描就下"没有 ADM"的结论。
实体
fmt的采样率与位深与交付规格完全一致。ADM 写好之后不再做采样率转换。fmt.nChannels等于chna.numTracks。chna中每个trackIndex在范围内且从 1 开始。
chna 完整性
- 每个条目恰好 40 字节,定宽字段正确填充。
- 三个 ID 字段仅含
[A-Za-z0-9_],无汉字、无全角字符、无 BOM 残留,且按字节校验长度。 0x1000及以上的每个packRef都能在axml中解析到自定义定义。- 重复的
trackIndex是有意为之(一条轨道合法地改变定义),而不是重复写入错误。
ADM 图
- 每一个
audioContentIDRef、audioObjectIDRef、audioPackFormatIDRef、audioChannelFormatIDRef、audioTrackUIDRef都能解析。悬空边为零。 - 两个方向上都没有孤立的
audioTrackUID。 - 没有循环引用或自引用元素。
- 每个
audioChannelFormat的typeLabel与其父audioPackFormat一致。 axml是真正的 UTF-8(不是声称 UTF-8 的 GBK),且中文名称只出现在 name 类字段里。
时间
- 多块的声道格式,每一块都同时带
rtime与duration。 - 各块连续且单调;时长之和等于父
audioObject的时长。 - 对象从
00:00:00.00000开始。
内容
- 声明的声床的每一条声道里确实有该有的东西;数字静音要查清楚。
- 对象数量与声床配置符合交付规格。
bext起始时间码正确,且在整套交付物之间一致。
渲染与折叠
- 立体声与双耳折叠是从当前母版重新渲染的,不是沿用旧版。
- 时长与起始时间码与母版逐采样一致。
- 响度是在指名的布局上、用指名的渲染器、按 BS.1770-5 测得的,三者都写进交付说明。
可复现性
- 每个交付物都做校验和,保留清单。
- 工程与 ADM XML 与 BW64 分开归档。能 diff 的 XML,在半年后比只能重新解析的二进制值钱得多。
十四、结语
BW64 与 ADM 是开放、已公布、可以免费阅读的标准。在这个行业的这个角落里这并不常见,值得好好利用:你可以自己去读 BS.2088 和 BS.2076,用四十行代码解析一个 chna 块,去核实某个厂商的导出器实际写了什么,而不是相信它的对话框声称写了什么。沉浸式音频至今仍是不必要退回率最高的环节,而其中绝大多数是引用完整性错误——一个验证器一秒钟之内就能抓住。
对做民乐、戏曲和现场录音的人来说,还有一层额外的意义:这些音乐的空间性是真实存在的,不是后期加上去的。编钟的高度差、京剧演员的圆场、行进唢呐班在院墙之间的移动——它们本来就在那儿,只是过去被立体声压平了。把它们正确地写进 ADM,是这套标准最值得做的一件事。
Mazufa 的发行是免费的——没有上传费、没有订阅费、没有单发行收费——仅从收到的版税中扣除 5%,且每一份完整的申请都由人工审核。
参考资料
ITU-R 建议书
- ITU-R BS.2088-2 (11/2025),《用于国际交换带元数据的音频节目素材的长格式文件格式》(BW64)—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-2-202511-I!!PDF-E.pdf
- ITU-R BS.2076-3 (02/2025),《音频定义模型》—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2076-3-202502-I!!PDF-E.pdf
- ITU-R BS.2076-2 (10/2019),《音频定义模型》—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2076-2-201910-S!!TOC-HTM-E.htm
- ITU-R BS.1770-5 (11/2023),《测量音频节目响度与真峰值电平的算法》—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.1770-5-202311-I!!PDF-E.pdf
- ITU-R BS.2051-2 (07/2018),《用于节目制作的先进声音系统》—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2051-2-201807-S!!PDF-E.pdf
- ITU-R BS.2127-1 (11/2023),《用于先进声音系统的音频定义模型渲染器》—— https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2127-1-202311-I!!PDF-E.pdf
- ITU-R BS.2127 页面 —— https://www.itu.int/rec/R-REC-BS.2127
EBU 技术文档
- EBU Tech 3306,《RF64:音频数据的扩展文件格式》—— https://tech.ebu.ch/docs/tech/tech3306.pdf
- EBU Tech 3285(及其增补),《广播波形格式规范》—— https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s7.pdf
- EBU Tech 3392,《ADM 广播制作规范子集》—— https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3392.pdf
- EBU Tech 3388,《用于下一代音频广播的 ADM 渲染器》—— https://tech.ebu.ch/docs/tech/tech3388.pdf 与 https://tech.ebu.ch/publications/tech3388
- EBU Tech 3343,《按照 EBU R 128 进行制作与实现的实用指南》—— https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3343v2_0.pdf
- EBU R 128、EBU Tech 3341(表头)、EBU Tech 3342(响度范围)—— https://tech.ebu.ch
- EBU ADM 指南 —— CHNA 块 —— https://adm.ebu.io/reference/excursions/chna_chunk.html
- EBU ADM 指南 —— BW64 与 ADM —— https://adm.ebu.io/reference/excursions/bw64_and_adm.html
- EBU ADM 指南 —— audioTrackUID —— https://adm.ebu.io/reference/adm_elements/audio_track_uid.html
- EBU ADM Renderer (EAR) 文档,BW64 I/O —— https://ear.readthedocs.io/en/latest/BW64.html
- libbw64 参考实现 —— https://github.com/ebu/libbw64
- libadm 参考实现 —— https://github.com/ebu/libadm
AES
- AES TD1008,《互联网音频流媒体与点播分发的响度建议》—— https://www.aes.org/community/technical-council/technical-document-aestd1008/
服务方文档
- Spotify,《响度归一化》—— https://support.spotify.com/us/artists/article/loudness-normalization/
国内三维声
- 世界超高清视频产业联盟(UWA),Audio Vivid(菁彩声)说明页 —— https://www.theuwa.com/new-detail/633
- 《三维菁彩声(Audio Vivid)技术白皮书》 —— http://www.theuwa.com/upload/ueditor/file/20220830/1661852920141636/1a375b861818888a3f619bd8ae65e2cd.pdf
最后审阅于 2026 年 9 月。标准会被修订;引用条款号之前,请先到上述 ITU 与 EBU 页面确认当前版本。