技术博客

音频导入:WAV/OGG/MP3 的解码后端与循环点之谜

第 107 / 149 篇 · 2025-11-12 · godot · 资源管线音频工程实践

音频导入面板比纹理简单,但选错的代价出现在两个很具体的地方:内存峰值和循环接缝的爆音。三种压缩模式的取舍由两个变量决定——数据放内存还是流式、解码 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 导入器读 smpl chunk 的循环点标记。唯一限制是体积。
  • 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 多路并发则只有混音成本,解码成本为零。

音频导入:内存、解码与循环精度的三角 WAV / PCM 内存:全量(3min ≈ 32MB) 解码:无,混音器直读 循环:按采样精确(smpl) 适合:短音效、多路并发 OGG / Vorbis 内存:流式,几十 KB 窗口 解码:stb_vorbis,音频线程 循环:按采样精确 适合:循环音乐、长环境音 MP3 / minimp3 内存:流式,同 OGG 级 解码:CPU 最省 循环:帧对齐,首尾有 padding 适合:一次性语音,不做循环 MP3 循环偏移的物理根源 帧固定 1152 采样,首尾填 encoder delay + padding;解码器多输出假采样 → 循环点漂移几十 ms → 爆音 选择流程 短音效 WAV → 循环音乐 OGG → 大量短音效且省内存 QOA(4.x)→ MP3 只给不循环的素材