音频导入面板比纹理简单,但选错的代价出现在两个很具体的地方:内存峰值和循环接缝的爆音。三种压缩模式的取舍由两个变量决定——数据放内存还是流式、解码 CPU 花在哪。先把数据量算清,循环点的谜就有了一半答案。
数据量公式
未压缩 PCM 的字节数是严格的:
$$B = f_s \times \frac{\text{bit depth}}{8} \times N_{ch} \times t$$
以 44.1 kHz、16 bit、立体声为例:每秒 $44100 \times 2 \times 2 = 176.4$ KB,三分钟音乐约 31.8 MB。这是 WAV(PCM 模式)全量进内存的代价。OGG(Vorbis q5 约 160 kbps)同样三分钟约 3.6 MB——十倍差距,这就是长音频没人用 WAV 的原因。
三种模式与解码后端
WAV / PCM 模式。 不做压缩,可选 16 bit 整型或 IEEE float 采样格式。没有解码这一步,播放时数据原样喂给混音器(AudioServer 那篇的软件混音器直接读采样)。CPU 成本最低的格式,代价是全量内存。短音效(开枪、UI 音)默认选它:几百 KB 一个,播放零解码延迟,多路并发混音也不怕。
OGG / Vorbis 模式。 解码用 stb_vorbis(单头文件库,无外部依赖,和引擎"自带一切"的品味一致)。默认流式播放:磁盘上的 .ogg 按需解码进环形缓冲(AudioStream 那篇拆这个缓冲),内存占用只有几十 KB 的解码窗口,和音频长度无关。音乐、环境长音的标准选择。解码 CPU 成本中等——Vorbis 是按频域逆变换的,比 MP3 略贵但在现代 CPU 上可忽略。
MP3 模式。 解码用 minimp3。数据量和 OGG 同级,CPU 还便宜一点,看起来是双赢——但有一个结构性缺陷,下面细说。4.x 还新增了第四种:QOA(Quite OK Audio),4:1 压缩、解码极廉价(只做斜率预测,无频域变换),定位是"大量短音效想省内存又不想付解码成本"的中间档,音频格式里的 ASTC。
循环点之谜:为什么 MP3 循环不准
无缝循环要求解码器输出恰好覆盖 $[0, N)$ 个采样的波形,多一个少一个采样,首尾相接处就产生爆音或漂移。三种格式的精度差异来自编码原理:
- WAV:原始采样序列,循环点想设哪设哪,精度到 1 采样。Godot 的 WAV 导入器读
smplchunk 的循环点标记。唯一限制是体积。 - OGG:Vorbis 按采样寻址。编码器在首尾加的窗函数会被解码器正确处理(stb_vorbis 处理 granule 定位),解码输出长度和原始波形逐采样对齐。所以标题说"循环点的坑在 OGG 上不存在"——引擎层面它是精确的,唯一要做的是源音频本身首尾波形对齐(那是音频制作问题,不是格式问题)。
- MP3:按帧编码,每帧固定 1152 采样。输入采样数如果不是 1152 的整数倍,编码器会在首帧前填 encoder delay、尾帧补 padding——这段假数据进了比特流,但每帧多少 padding 只有编码器自己知道(LAME 把它写进 Info tag,很多解码器懒得读)。minimp3 老老实实解出全部帧,结果就是首尾各多一截静音或假采样,循环点偏移几十毫秒,爆音随之而来。想做无缝循环,MP3 从原理上就被排除了。
一句话总结实践选择:短音效 WAV,循环音乐 OGG,MP3 留给"一次性播放且不在乎首尾"的语音/背景素材。
并发与加载成本
还有一个容易漏的账:OGG/MP3 的流式解码发生在音频线程(混音线程),多路流同时播,CPU 在音频线程上叠加。实测 Vorbis 解码约占单核的百分之几每路,十路背景音乐同播会顶到音频线程的预算。Voice 多的游戏(大量 NPC 语音)要么预算内控制并发路数,要么长语音提前 AudioStreamWAV 化预解码。WAV 多路并发则只有混音成本,解码成本为零。