SWF 音频格式与采样率
一只 SWF 可以装进 MP3、ADPCM、Nellymoser 等不同声音;作品页上的格式和采样率就是这样来的。
Flash 保存计划 · 最近整理于 2026年7月30日
本文目录 · 11 节
拆一只老 SWF 的声音,先会碰到两条路:有些声音是可以反复调用的资源,有些则切成许多小块,跟着时间轴一帧帧往前走。两条路都用四位格式编号说明编码,再用两位 SoundRate 记下采样率档位。
这些是 SWF 内部的记录,不一定保留作者最初导入的 WAV、AIFF 或 MP3 外形。本馆把其中六类编码整理成下面这些名称:
| 本馆显示 | SWF 格式编号 | 规范中的含义 | 最低 SWF 版本 |
|---|---|---|---|
pcm_be |
0 | 未压缩 PCM,原生字节序 | 1 |
adpcm |
1 | SWF ADPCM | 1 |
mp3 |
2 | MPEG Audio Layer III 帧 | 4 |
pcm_le |
3 | 未压缩 PCM,小端序 | 4 |
nellymoser |
4、5、6 | Nellymoser Asao | 10、10、6 |
speex |
11 | Speex 语音编码 | 10 |
作品页上的一行声音信息
提取器会沿着两条路一起找:从 DefineSound 读取 SoundFormat、SoundRate,也从 SoundStreamHead 或 SoundStreamHead2 读取真正描述流数据的 StreamSoundCompression、StreamSoundRate。流头里另有一组供播放器参考的 playback 字段,这里不用它。
同一文件若出现多种编码,页面会全部列出,例如 adpcm / mp3。采样率为了不把一行摘要撑得太长,只保留最高档位。所以看到 mp3 · 44.1 kHz,可以读成“这里找到了 MP3,声音声明中最高是 44.1 kHz”,而不是“全片每一段都是 44.1 kHz MP3”。这个数字也来自文件头,不是把声音解码后重新测了一遍。
这也解释了一个看似奇怪的页面:它可以显示“音频:有”,资源计数里的“声音”却是 0。资源计数只数 DefineSound;跟着时间轴走的流音频不需要这个 Tag,照样能播。
声音怎样待在 SWF 里
DefineSound:可复用的事件声音
DefineSound(Tag 14)把声音定义为 SWF 字典中的一个资源,内容包括声音 ID、编码、声明采样率、采样位数、声道标志、样本数和音频负载。StartSound 再通过 ID 播放或停止它,还可以附加循环、入点、出点和音量包络。
Adobe 把它叫作 event sound。声音先被定义和下载,之后由时间轴、按钮或脚本触发;一旦开始,可以离开时间轴自己继续播放。
SoundStreamHead + SoundStreamBlock:跟随时间轴
流式声音先由 SoundStreamHead(Tag 18)或 SoundStreamHead2(Tag 45)声明实际编码与采样率,后续 SoundStreamBlock(Tag 19)把音频包穿插在 SWF 帧之间。播放器可以在取得前几个数据块后开始播放,并以音频为基准保持动画时间轴同步。
这里的 stream 容易让人想到外部网址,其实音频通常就在 SWF 里,只是被切成 Tag,夹在影片帧之间。流头要走在第一个数据块前面;如果老文件坏到只剩一串数据块,我们也就失去了判断编码的可靠入口。
pcm_be:格式 0,原生字节序 PCM
PCM 直接保存量化样本,不做有损编码。格式 0 的完整名称是 uncompressed, native-endian:16 位样本跟着创建平台的原生字节序写,也让播放平台按自己的字节序读。8 位样本只有一个字节,倒没有这个麻烦。
馆内沿用 pcm_be 这个短名,但格式 0 本身没有告诉我们作者机器究竟是哪一种字节序。Ruffle 也把它视为“字节序未知的未压缩音频”,提醒之后按小端序尝试。碰到真正按大端序写的 16 位老文件,结果就可能是一片杂音。
PCM 的好处是解码简单,没有编码损失;代价是占空间。SWF 若采用 CWS 或 ZWS,外层还会再压这些样本——声音编码与 SWF 整体压缩方式 是上下两层,可以同时存在。
adpcm:差分压缩
ADPCM 的全名是自适应差分脉冲编码调制。与其逐个保存完整样本,它更关心声音怎样变化,用预测值和变化量来编码。SWF 有自己的一套 ADPCM 记录,每个差分码可用 2、3、4 或 5 位。它有损,但结构不复杂,从 SWF 1 就一路用过来。
这里也没有一个可以直接另存的 .adpcm 容器。事件声音从 SWF ADPCM 头开始,流式 ADPCM 则每个 SoundStreamBlock 都有自己的头;解码器得逐块重读预测状态。Ruffle 为这种布局单独准备了块级解码路径。
SWF 4 以后,Adobe 更推荐 MP3,不只因为同码率下的音质,还因为 MP3 有专门的流式时序与 seek 补偿。不过翻看早期 Flash 动画和按钮音效,ADPCM 仍是常客。
mp3:嵌入的 MPEG Audio 帧
编号 2 从 SWF 4 起表示 MP3。里面确实能找到标准 MPEG Audio Layer III 帧,不过 SWF 没有把它们原封不动地塞进来:DefineSound 会在前面加一个 SeekSamples,用来跳过编码器造成的初始延迟;流式 MP3 块还带着样本数和 seek 信息,好让时间轴跳转后重新对齐。
这就是为什么从 DefineSound 或 SoundStreamBlock 截下一段字节,通常还不能直接改名成 .mp3 播放。导出工具得先拆掉 SWF 包装,再把散在各块里的音频帧按顺序拼回来。
MP3 还有自己的帧内采样率,可表达 8、11.025、12、16、22.05、24、32、44.1、48 kHz 等值;SWF 外层则只给四个档位,而且不允许 MP3 使用最低的 5.5 kHz 档。本馆目前读取外层字段,没有逐个拆 MP3 帧头,遇到特殊或畸形文件时,两边的数可能对不上。
Ruffle 的 MP3 路径能处理 SeekSamples、576/1152 样本帧和流式同步,但它要在构建时启用。损坏帧或错误的样本计数,也仍可能造成无声、失步。
pcm_le:明确的小端序 PCM
编号 3 也是未压缩 PCM,但 16 位样本固定为 little-endian(最低有效字节在前);8 位时与格式 0 完全相同。它从 SWF 4 起可用。
这等于给格式 0 的历史包袱收了个尾:字节序写死以后,各平台更容易得到一致结果。pcm_le 仍只管样本怎样排;采样率、8/16 位和单/双声道要看声音头里的其他位。
nellymoser:面向低码率语音的 Asao
Nellymoser Asao 是一套面向低码率语音传输的单声道有损编码,在 SWF 里占了三个编号:
- 4:Nellymoser 16 kHz,SWF 10 起;
- 5:Nellymoser 8 kHz,SWF 10 起;
- 6:一般 Nellymoser,SWF 6 起。
作品页把三者合并成 nellymoser,这样比较好找同一家族的作品,但具体编号也随之藏进了摘要背后。Nellymoser 本身不采用声音头里的 SoundRate、采样位数和声道位,所以旁边那个 kHz 数字并不是它的真实解码参数。
截至本页更新时间,Ruffle 能认出三个编号,但通用解码分派只为编号 6 准备了可选解码器;编号 4、5 还接不上。馆里能叫出它的名字,和播放器已经能让它发声,是两件不同的事。
speex:固定 16 kHz 的单声道语音
Speex 是 Xiph.Org 面向语音的开放有损编码,从 SWF 10 起使用编号 11。它在 SWF 里的配置很固定:16 kHz、单声道,在播放器内部解码成 16 位样本;声音头里的 SoundRate、SoundSize 和 SoundType 都不会参与解码。
于是,页面偶尔出现的 speex · 44.1 kHz 看起来有点怪,却不矛盾。Speex 仍是 16 kHz;44.1 kHz 可能来自那个对 Speex 不起作用的两位字段,也可能来自同文件里的另一段声音。
Ruffle 目前会保留 Speex 编号,通用音频解码分派里还没有接上它。
采样率:四个声明档位
SWF 声音头使用两位记录采样率。本馆按提取器的整数映射显示为:
| 两位值 | 本馆保存(Hz) | 页面显示 |
|---|---|---|
| 0 | 5,512 | 5.512 kHz |
| 1 | 11,025 | 11.025 kHz |
| 2 | 22,050 | 22.05 kHz |
| 3 | 44,100 | 44.1 kHz |
Adobe 的文档常把它们简写成 5.5、11、22、44 kHz。44.1 kHz 是个很熟悉的数字,却不自动等于“音质好”:编码、码率、位深、声道和原始素材都会左右听感,采样率和码率也不是一回事。
本馆的 audio_sample_rate 取所有已识别声音头中的最高档。多种声音共存时,这一行不会告诉你最高值属于哪一种;Nellymoser 和 Speex 虽然不用这个字段,提取器目前仍会把它收进汇总;MP3 的帧内采样率也尚未逐帧核对。想做无损导出或更细的分析,仍得回到具体声音块。
听不见时,从哪里查起
| 格式 | Adobe Flash 历史支持 | Ruffle 当前源码中的处理 |
|---|---|---|
pcm_be |
SWF 1 起 | 可解码;未知字节序按小端假定 |
adpcm |
SWF 1 起 | 事件与流式解码路径均存在 |
mp3 |
SWF 4 起 | 可选 MP3 功能;含 SWF seek/流同步处理 |
pcm_le |
SWF 4 起 | 可解码,字节序明确 |
nellymoser |
ID 6 自 SWF 6;ID 4/5 自 SWF 10 | ID 6 有可选解码路径;ID 4/5 当前未分派 |
speex |
SWF 10 起 | 可识别格式编号;当前通用解码分派未处理 |
表里说的是解码器这一层。实际播放还会被文件损坏、时间轴控制、样本计数、浏览器音频策略和 Ruffle 构建配置影响。遇到“画面正常但没声音”,格式和流结构是很好的起点,却不是全部答案。
导出也是如此。MP3 外面有 SeekSamples 和样本计数,一条音轨可能跨过许多数据块;ADPCM 又有自己的块头。作品页告诉我们该往哪条解码路径走,但真要把声音完整取出来,还是得把这些 Tag 一块块接好。