纯 MoonBit 实现的 FLAC 无损音频解码器,不依赖任何 C 库或系统编解码器
moon add Xcsa-ee/moonflacmoon run cmd/main -- examples/sample.flac output.wav采样率: 44100 Hz, 声道: 1, 位深: 16, 样本: 2000
已写入: output.wav
解码失败: Xcsa-ee/moonflac.FlacError.BadCrc(kind="CRC-16", ...)import {
"Xcsa-ee/moonflac",
}
// 整流解码
let file = @moonflac.decode(data) // raise FlacError
let wav = @moonflac.wav_encode(
file.channels,
file.info.bits_per_sample,
file.info.sample_rate,
)
// 流式解码 + 定位
let s = @moonflac.FlacStream::new(data)
s.seek(44100) // 跳到第 1 秒
let block = s.next() // Option:每声道一个块的样本# 1. 单元测试(白盒 63 用例,双后端)+ 变异验证(6 判据改错必须变红)
moon test --target wasm-gc
moon test --target js
python3 tools/mutation_check.py
# 2. 差分测试:现场生成语料,与 FFmpeg 逐样本比对
# 本地需要 ffmpeg 与 libFLAC 的 flac(可用 FLAC_FFMPEG / FLAC_BIN 指定路径,
# gen_corpus.py 也接受 --ffmpeg <路径>);CI 使用 apt 安装的 ffmpeg + flac
python3 tools/gen_corpus.py
python3 tools/diff_oracle.py --target wasm-gc
python3 tools/diff_oracle.py --target js
# 3. 兼容性验收报告:xiph 官方测试文件 + 自建语料,
# 三方(本解码器 / FFmpeg / libFLAC)逐样本与 STREAMINFO MD5 比对
git clone --depth 1 --branch 1.5.0 https://github.com/xiph/flac.git /tmp/xiph-flac
python3 tools/compat_report.py \
--xiph /tmp/xiph-flac/test/flac-to-flac-metadata-test-files \
--corpus tools/corpus --out COMPATIBILITY.mdWindows 用户:本机 python3 是 Microsoft Store 的占位符,把上面命令里的 python3 换成 python 即可;其余命令与 Linux 相同(git clone 的目标路径换成任意空目录)。
| 参考对象 | 上游许可 | 参考范围(用到什么) | 没用到什么 |
|---|---|---|---|
| xiph/flac(libFLAC) | 库代码 BSD-3-Clause(其 COPYING.Xiph);flac/metaflac 命令行工具为 GPLv2 | 行为对照:错误分类与边界条件的取舍;以 flac -t 作为独立的第三方校验器(含 STREAMINFO MD5 全量校验) | 未取任何 C 源码,未链接其库,未复用其 API 或数据结构 |
| mackron/dr_libs(dr_flac) | public domain / MIT-0 | 行为对照:帧头损坏后的向后扫描重同步这类容错策略 | 未取源码,未移植其 API |
| FFmpeg | LGPL/GPL(按构建选项) | 仅作差分测试 oracle:解码为 f64le 裸流后逐样本比对,不在解码实现里起作用 | 不参与实现,不进仓库 |
| RFC 9639 | IETF 的 RFC 文本使用条款 | 格式定义与全部强制校验项的规范依据 | — |
| 路径 | 内容 |
|---|---|
| metadata.mbt frame.mbt | 元数据块链与帧头解析 |
| metadata_ext.mbt | SEEKTABLE / VORBIS_COMMENT / CUESHEET / PICTURE / APPLICATION 内容解析 |
| subframe.mbt residual.mbt | 子帧与分区 Rice 残差 |
| flac.mbt | 顶层解码编排与声道去相关 |
| bitreader.mbt crc.mbt | 位级读取(含 UTF-8 形变变长帧号)与 CRC-8/16 |
| stream.mbt incremental.mbt | FlacStream 逐块/seek 与 FlacFeeder 增量输入 |
| wasm_api.mbt wav.mbt | base64 导入导出的 wasm 调用面与 WAV 序列化 |
| flac_error.mbt | 结构化错误类型 |
| cmd/main | 命令行示例 |
| examples/sample.flac | 最小可运行样例(9 KB,44.1 kHz / 16 bit / 单声道 / 2000 样本) |
| tools/ | 差分测试工具链(语料生成 + 逐样本比对 + 兼容性报告 + wasm 冒烟) |
| DEVELOPMENT.md | 开发回顾:架构决策、验证判据、参考边界、AI 参与说明 |
| CHANGELOG.md | 变更记录(按版本) |
pub suberror FlacError {
Truncated(offset~ : Int, need~ : Int, actual~ : Int)
NotFlac
BadMetadata(reason~ : String)
BadFrameHeader(reason~ : String)
BadCrc(kind~ : String, expected~ : Int, actual~ : Int)
BadSubframe(reason~ : String)
BadResidual(reason~ : String)
Md5Mismatch(expected~ : String, actual~ : String)
}pub struct ApplicationBlock {
app_id : String
data : Bytes
}type BitReaderpub struct CueSheet {
media_catalog_number : String
lead_in_samples : Int64
is_cd : Bool
tracks : Array[CueSheetTrack]
}pub struct CueSheetIndex {
offset : Int64
index_number : Int
}pub struct CueSheetTrack {
offset : Int64
track_number : Int
isrc : String
is_non_audio : Bool
pre_emphasis : Bool
indexes : Array[CueSheetIndex]
}pub struct FlacFeeder {
buf : Array[Byte]
base_offset : Int
info : StreamInfo?
blocks : Array[MetadataBlock]
next_frame_byte : Int
meta_done : Bool
finished : Bool
decoded : Int64
eof : Bool
}pub struct FlacStream {
info : StreamInfo
br : BitReader
blocks : Array[MetadataBlock]
frame_offset : Int
decoded : Int64
pending : Array[Array[Int]]
eof : Bool
}pub struct MetadataBlock {
block_type : Int
offset : Int
length : Int
}pub struct ParsedMetadata {
stream_info : StreamInfo
blocks : Array[MetadataBlock]
frame_offset : Int
}pub struct Picture {
picture_type : Int
mime : String
description : String
width : Int
height : Int
depth : Int
colors : Int
data : Bytes
}pub struct SeekPoint {
sample_position : Int64
byte_offset : Int64
frame_samples : Int
}pub struct StreamInfo {
min_block_size : Int
max_block_size : Int
min_frame_size : Int
max_frame_size : Int
sample_rate : Int
channels : Int
bits_per_sample : Int
total_samples : Int64
md5 : Bytes
}fn decode_flac_base64(flac_b64 : String) -> String