Audio container toolkit in pure MoonBit: Ogg page/packet read and write, CRC-32 verification, WAV parsing and encoding, and an Ogg-to-WAV transcoding pipeline
作者:WangYF0829(王轶凡)|创建日期:2026-10-13|用途:moonmux 项目文档
| 项 | 状态 |
|---|---|
| MP4 / ISOBMFF 写入(Opus-in-MP4,新 mp4/ 包:ftyp/moov/mdat、stbl 表、dOps) | 已完成 |
| remux 命令:Ogg Opus → .m4a | 已完成 |
| info 识别 MP4 | 已完成 |
| CRC-32 查表优化(输出与 RFC 3533 逐字节一致) | 已完成 |
| WAV 全格式兼容:IEEE float 32/64、WAVE_FORMAT_EXTENSIBLE、24-in-4 容器;新增 encode_wav_ex | 已完成(v0.4.0) |
| 流式页读取:增量 PageReader(分块喂入、按需取页、失步重同步) | 已完成(v0.4.0) |
| to-ogg 命令:MP4(Opus-in-MP4)→ Ogg Opus,与 remux 互逆 | 已完成(v0.5.0) |
| 性能基准 scripts/bench.mjs:verify/info/dump/remux/to-ogg/demux 端到端计时,CI 运行并写 job summary | 已完成(v0.6.0) |
| 确定性变异测试 scripts/fuzz.mjs:真实 Ogg/Vorbis/MP4/WAV × 6 类变异,CI 400 次/固定种子 | 已完成(v0.6.0) |
| WAV 块保真:LIST/fact 等扩展块原样保留,WavFile::to_bytes 逐字节还原(含 24-in-4 容器与 channelMask) | 已完成(v0.7.0) |
| 流式写接口 WavWriter:增量追加 PCM、可选扩展块,finish 生成 WAV | 已完成(v0.7.0) |
| 测试 / 示例 / 文档 / CHANGELOG / v0.7.1 发布(mooncakes + GitHub Release) | 已完成 |
| 环节 | 生态现状 |
|---|---|
| 读容器(demux) | 有:isobmff(MP4 解析)、moontsinspect(TS 检查) |
| 解码 | 有:moonopus、moonvorbis、mp3.mbt、moon-wav |
| 播放 | 有:moon_rodio |
| 写容器(mux) | 没有 |
| 转码管线 | 没有 |
| 模块 | 内容 | 状态 |
|---|---|---|
| ogg/ | 页 / 段 / 包解析与序列化;Ogg 专用 CRC-32;多逻辑流交织;物理流链接与拆分;按大小攒页;granule 二分定位;结构体检(分级 + 坏页重同步);页级补丁;增量页读取 PageReader(v0.4.0,流式/失步重同步) | 已完成 |
| codec/ | 编解码头解析(OpusHead / Vorbis ID)、时长换算、VorbisComment / OpusTags 读写 | 已完成 |
| manifest/ | demux/mux 的流目录清单(流号、包数、链序号、逐包粒度)的解析与生成 | 已完成 |
| wav/ | WAV(RIFF)读写:PCM 8/16/24/32、IEEE float 32/64、WAVE_FORMAT_EXTENSIBLE、24-in-4 容器读取;整数 PCM 编码与 encode_wav_ex(float/Extensible);v0.7.0 块保真(LIST/fact 扩展块、channelMask)+ WavFile::to_bytes + WavWriter | 已完成(v0.4.0 全格式 / v0.7.0 块保真) |
| pipeline/ | Ogg(Opus / Vorbis)→ WAV 转码管线 | 已完成 |
| mp4/ | MP4/ISOBMFF:写侧 ftyp/moov/mdat/stbl 五表/dOps(Opus-in-MP4,v0.3.x);读侧 inspect + demux_mp4_opus 解封装回 Ogg(v0.5.0,读写闭环) | 已完成(v0.3.x 写 / v0.5.0 读) |
| cmd/main/ | 命令行工具:info、demux、mux、cat、split、extract、dump、tags、tag、seek、verify、to-wav、chop、remux、to-ogg | 已完成 |
moon add WangYF0829/moonmuximport {
"WangYF0829/moonmux/ogg",
"WangYF0829/moonmux/wav",
}fn main {
// WAV:编码 → 解析往返
let bytes = @wav.encode_wav(
{ channels: 1, sample_rate: 8000, bits_per_sample: 16 },
b"\x00\x00\x01\x00",
)
match @wav.WavFile::parse(bytes) {
Some(f) => println("wav ok: " + f.format.sample_rate.to_string())
None => println("wav parse failed")
}
// Ogg:页起始识别
println("ogg page: " + @ogg.is_page_start(b"OggS", 0).to_string())
}| 退出码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 数据校验未通过(输入不是合法 Ogg、被截断、清单与目录不符、verify 的 error / CRC 失败) |
| 2 | 用法或 I/O 错误(参数错、文件读不到、拒绝覆盖非空目录等) |
| 项 | 上限 | 原因 |
|---|---|---|
| 单文件大小 | 2 GiB | MoonBit Int 是 32 位,页偏移与长度都用它;@fs 也只有整文件 API |
| MP4 写侧 | 仅 Opus-in-MP4、单音轨、单 chunk | 只补 Opus;Vorbis-in-MP4 非主流且需解析 Vorbis setup,明确不做;remux 只接受 Ogg Opus 输入 |
| MP4 读侧 | 仅解封装 Opus-in-MP4(单音轨、mdhd timescale=48000) | to-ogg 是 remux 的逆操作;注释不随 MP4 保存,输出使用最小 OpusTags |
| demux 包数 | 999999 | 包文件名固定 6 位,超过后字典序错乱、mux 读回顺序不对 |
| to-wav PCM 预算 | 256 MiB(MOONMUX_MAX_PCM_MB 可调大) | 整条链路没有流式写接口,PCM 必须攒齐再落盘;实测峰值约为预算的 3–4 倍。外部解码器一次返回整段 PCM,恶意伪造的 granule 仍可能造成超出预算的瞬时分配 |
| chop Opus 时间戳 | 页精度,约 ±1 页 | 不重写 OpusHead 的 pre-skip;裁剪后首个数据页可能多带不到一页的前导样本 |
| to-wav 速度 | Opus 约 1.5× 实时;Vorbis 约 80× 实时 | 纯 MoonBit 解码器;Vorbis 路径明显更慢,长文件请先 split |
| chained 拆分 | 整段都由单页流组成时无法拆分 | 与「一段内交织两条单页流」字节相同,规格上不可判定(保守不切) |
| 裁剪流时长 | 上界 | 容器层拿不到首个音频页的样本数,精确值需解码 |
irm https://cli.moonbitlang.cn/install/powershell.ps1 | iexmoon versionmoon build
moon testmoon build --release --target native
node scripts/bench.mjs --runs 5| 操作 | 输入 | 最小耗时 | 吞吐 |
|---|---|---|---|
| verify(CRC + 结构体检) | 1.02 MiB | 21.7 ms | 47 MB/s |
| info --json | 1.02 MiB | 17.6 ms | 58 MB/s |
| dump(页级转储) | 1.02 MiB | 18.2 ms | 56 MB/s |
| remux(Ogg → MP4) | 65 KiB | 16.9 ms | — |
| to-ogg(MP4 → Ogg) | 66 KiB | 18.7 ms | — |
| demux(拆包写目录) | 65 KiB | 87.8 ms | — |
moon build --target native
node scripts/fuzz.mjs --iterations 400 --seed 20261109# 查看容器信息(自动识别 Ogg 与 WAV)
# Ogg:格式、流号、页数、包数、负载字节、各流末粒度;能识别音频流时再给编码/声道/采样率/时长
# WAV:声道、采样率、位深、PCM 字节数、帧数与时长
moonmux info input.ogg
moonmux info input.wav
moonmux info input.ogg --json # 单行 JSON,便于脚本处理
# 注:时长为「末粒度 − 首粒度 − pre-skip」,对裁剪过的流是上界
# 结构体检:逐页 CRC + 页序号连续性 + BOS / EOS 完备性 + 段表一致性
# 用于判断文件是容器损坏还是编码问题,并给出损坏的具体位置
moonmux verify input.ogg
moonmux verify input.ogg --json # 完整 findings(文本只铺前 10 条)
# 解复用:按逻辑流拆出包,每条流一个子目录(s1/、s2/……)
moonmux demux input.ogg ./packets/
# 复用:把包重新封装成 Ogg
# 目录里有子目录 → 每个子目录一条逻辑流,按 Ogg 规则交织(BOS 页置顶、数据页按粒度升序)
# 目录里没有子目录 → 整个目录一条逻辑流
moonmux mux ./packets/ output.ogg
# 链接:把多个 Ogg 文件首尾相接成一个物理流
# 流号不冲突时逐页原样透传(字节级无损);冲突时把后一个文件相撞的流换新流号
moonmux cat a.ogg b.ogg c.ogg output.ogg
# 拆分:把链接流按链接点切回各自独立的 Ogg 文件(输出 part1.ogg、part2.ogg……)
# 页原样沿用,字节与 CRC 一字节不改 —— 非链接流得到的 part1 与输入完全相同
moonmux split chained.ogg ./parts/
# 抽流:把第 n 条逻辑流单独取出来(页级原样,可与输入逐字节对账)
moonmux extract input.ogg 2 single.ogg
# 页级转储:偏移 / 流号 / 页序 / 粒度 / 标志 / 段数 / 负载
moonmux dump input.ogg
moonmux dump input.ogg --hex # xxd 兼容,可 `| xxd -r` 还原
# 注释:读取与改写(KEY= 表示删除该键)
moonmux tags input.ogg
moonmux tag input.ogg tagged.ogg TITLE=月光 ARTIST=王轶凡
# 定位:按毫秒找到不晚于该时刻的页(打印页下标、字节偏移与该页起始时间)
# 链接流可用第 3 个参数指定第几段(默认第 1 段)
moonmux seek input.ogg 2500
moonmux seek chained.ogg 500 2
# 转码:Ogg(Opus / Vorbis)→ WAV;默认 16 位,可选 24 位
moonmux to-wav input.ogg output.wav
moonmux to-wav input.ogg output24.wav 24
# 重新封装:Ogg(Opus)→ MP4 / ISOBMFF 音频容器(.m4a,v0.3.x 新增)
# 不做转码:Opus 数据包原样搬运,只重建容器;info 可识别产物
moonmux remux input.ogg output.m4a
moonmux info output.m4a
# 反向解封装:MP4(Opus)→ Ogg(v0.5.0 新增,remux 的逆操作)
# 音频包逐字节还原;注释未随 MP4 保存,输出最小 OpusTags
moonmux to-ogg output.m4a back.ogg
moonmux verify back.ogg
# 裁剪:按毫秒区间裁剪(页精度,头页保留,时间轴归零)
moonmux chop input.ogg clip.ogg 1000 3000moon build --target nativemoonmux/
├── moon.mod 模块配置
├── moon.pkg 根包
├── moonmux.mbt 模块级描述
├── ogg/ Ogg 容器
│ ├── crc.mbt CRC-32(RFC 3533 参数)与页校验
│ ├── page.mbt 页的解析与序列化
│ ├── packet.mbt 包重组与页切分
│ ├── mux.mbt 多逻辑流的分组与交织
│ ├── chain.mbt 物理流链接与拆分(chained bitstream)
│ ├── verify.mbt 结构体检(段表 / 页序号 / BOS·EOS)
│ └── stream.mbt 字节流 ↔ 页序列
├── wav/ WAV 容器
├── pipeline/ 转码管线(唯一引入三方解码器的包)
├── cli/ 命令行参数解析(纯逻辑)
├── cmd/main/ 命令行入口
└── testdata/ 真实音频样本| 示例 | 命令 | 说明 |
|---|---|---|
| WAV 往返 | moon run examples/wav_roundtrip | 最小库用法:encode_wav → WavFile::parse |
| Ogg 信息 | moon run examples/ogg_info | 用 ogg/ + codec/ 公开 API 读取容器信息 |
| Ogg → MP4 | moon run examples/remux_ogg_to_mp4 | mp4/ 写—读对称:remux 后 inspect 读回 |
| MP4 → Ogg | smoke.sh / verify_mp4.sh 的 4e/6 段 | to-ogg 后 verify + 音频包逐字节比对 |
source scripts/moonmux.bash # 子命令与文件路径补全| 项目 | 参考了什么 | 没有照搬什么 |
|---|---|---|
| Xiph oggz / liboggz | 子命令的语义(rip/dump/validate/chop 的职责划分)、多流合并按呈现时间 | 不做 Skeleton,不解析 Theora/FLAC 等头;dump 不可逆 |
| libogg | 页大小与 flush 时机(4096 阈值、首页/EOS 强制刷出) | 不把 BOS 顺序交给调用方;写侧自己维护不变量 |
| mutagen(Python) | 注释改写时用 padding 保持旧包等长、复用旧页布局 | 不做 high/low 区间公式 |
| ffmpeg | 作为行为对照(转码产物用独立工具回读验证) | 不用时间驱动的分页策略 |
| libopusfile | seek 在 granule 上插值二分、80 ms pre-roll | 只定位到页,不解码 |
| Rust hound(Apache-2.0) | WAV fmt 标签 1/3/0xFFFE、Extensible 字段布局与 SubFormat GUID、24-in-4 容器语义、float 32/64、未知块跳过 | 不复制代码;v0.7.0 额外保留 channelMask 与扩展块以支持逐字节往返(hound 不暴露这些字段) |
| RustAudio ogg crate | 增量页读取状态机语义:先同步捕获模式、按段表等数据、CRC 失步后重同步 | 不复制代码;不实现其异步/seek 组合器 |
| hyperfine / cargo bench | 基准的「多次运行取最小值 + 表格化输出」呈现方式 | 不引入第三方依赖、不设跨机器阈值(CI 只记录) |
| cargo-fuzz / AFL++ | 「种子 + 确定性变异 + 退出码/超时断言」的变异测试模式 | 不使用其 fuzz 引擎;无覆盖率引导 |
Install
Download zipAudio container toolkit in pure MoonBit: Ogg page/packet read and write, CRC-32 verification, WAV parsing and encoding, and an Ogg-to-WAV transcoding pipeline