- 音视频
- 视频处理
- 音频处理
【免费下载链接】mediabunny
Pure TypeScript media toolkit for reading, writing, and converting video and audio files, directly in the browser.
@mediabunny/flac-encoder是 mediabunny 的官方 FLAC 编码扩展包:由于目前没有任何浏览器的 WebCodecs 实现支持 FLAC 编码,该扩展通过 mediabunny 的自定义编码器(Custom Coder)API 注册一个可靠的 FLAC 编码器,底层封装了快速且针对体积优化的 libFLAC WASM 构建。读完本文你将掌握:如何安装并注册该扩展、如何用canEncodeAudio做特性检测、如何把任意音频文件在浏览器或 Node 端转码为 FLAC,以及该扩展从 Worker 线程、C 桥接到 libFLAC 的完整实现原理与从零构建 WASM 的方法。
为什么需要 FLAC 编码扩展
FLAC 是一种广泛使用的无损音频编码格式,但 WebCodecs 规范中的AudioEncoder目前在各浏览器实现里都只覆盖了有损编码(如 AAC、Opus、MP3),没有任何浏览器原生支持 FLAC 编码。这意味着如果你希望在前端直接产出无损音频文件,就需要一个 polyfill 级别的编码器。
mediabunny 为此提供了@mediabunny/flac-encoder扩展包(当前仓库内版本为 1.60.0,见 packages/flac-encoder/package.json)。它没有重新造轮子,而是选择了业界成熟的开源实现:
- libFLAC:Xiph 基金会维护的官方 FLAC 参考实现,编码质量与兼容性有保障;
- WASM 构建:编译产物是经过
-Oz、-flto、-msimd128等优化标记处理的、体积精简的单文件 WASM; - Custom Coder API:以插件形式接入 mediabunny,注册后即可被编码管线自动调度,与原生编码器使用方式完全一致。
扩展包通过 npm 的peerDependencies声明依赖mediabunny@^1.0.0,且sideEffects: false,可以安全参与 tree-shaking;同时声明"browser": { "worker_threads": false },确保在浏览器打包工具中不会引用 Node 的 worker_threads 模块。
安装与引入
该扩展与 mediabunny 一起安装(peer 依赖关系):
npm install mediabunny @mediabunny/flac-encoder如果你的应用不使用打包工具,也可以直接通过 script 标签引入官方发布包中的构建产物:
<script src="mediabunny.js"></script> <script src="mediabunny-flac-encoder.js"></script>这会暴露两个全局对象Mediabunny与MediabunnyFlacEncoder;配套的mediabunny-flac-encoder.d.ts声明文件可为这些全局变量提供类型。构建后的发行文件可从项目的 releases 页面下载。
注册编码器:一行代码接入
在应用启动时调用一次注册函数即可:
import { registerFlacEncoder } from '@mediabunny/flac-encoder'; registerFlacEncoder();注册之后,mediabunny 在需要编码 FLAC 时会自动使用该编码器,无需再手动指定。
更严谨的写法是先做特性检测——如果将来某个浏览器原生支持了 FLAC 编码,就不应覆盖它:
import { canEncodeAudio } from 'mediabunny'; import { registerFlacEncoder } from '@mediabunny/flac-encoder'; if (!(await canEncodeAudio('flac'))) { registerFlacEncoder(); }registerFlacEncoder本身是幂等的:从 packages/flac-encoder/src/encoder.ts 可以看到,它用模块级registered标志保证只向 mediabunny 的registerEncoder注册一次。而registerEncoder内部会把编码器类推入customAudioEncoders数组并清空canEncodeAudioMemo能力缓存(见 src/custom-coder.ts),因此注册后能力检测结果会立即刷新。
完整示例:文件转 FLAC
下面这个示例把用户选择的任意受支持音频/视频文件转换为 FLAC 文件并拿到其二进制数据,这也是官方文档给出的标准用法:
import { Input, ALL_FORMATS, BlobSource, Output, BufferTarget, FlacOutputFormat, canEncodeAudio, Conversion, } from 'mediabunny'; import { registerFlacEncoder } from '@mediabunny/flac-encoder'; if (!(await canEncodeAudio('flac'))) { registerFlacEncoder(); } const input = new Input({ source: new BlobSource(file), // file 来自文件选择器(如 <input type="file">) formats: ALL_FORMATS, }); const output = new Output({ format: new FlacOutputFormat(), target: new BufferTarget(), }); const conversion = await Conversion.init({ input, output, }); await conversion.execute(); output.target.buffer; // => ArrayBuffer,即完整的 FLAC 文件内容流程解读:
BlobSource把 File/Blob 包装为解码源,ALL_FORMATS让解码器自动嗅探容器格式(MP4、Matroska、WAV、OGG 等);FlacOutputFormat声明输出为 FLAC 容器,BufferTarget让编码结果沉淀为内存中的ArrayBuffer;Conversion.init完成解码器、编码器与封装器的装配,execute()执行全流程转码。
如果想落盘,只需把BufferTarget换成 blob 目标并触发浏览器下载,即可得到一个标准的.flac文件。更多 mediabunny 用法参见官方指南。
FlacOutputFormat 的可选配置
FlacOutputFormat构造函数接受一个可选的配置对象,定义见 src/output-format.ts:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
appendOnly | boolean | false | 只追加新数据到文件末尾,适合边编码边直播推送 FLAC 的场景。开启后 STREAMINFO 块不会写入精确的 min/max 块大小、帧大小与总采样数,因此不要用它产出需要长期保存的干净文件 |
onFrame | (data: Uint8Array, position: number) => unknown | 无 | 每写一个 FLAC 帧时回调,data是该帧原始字节,position是它在文件中的字节偏移。可用于流式传输、帧级进度上报或自定义封装 |
构造函数会对参数做运行时校验(非对象或非布尔类型会抛TypeError),错误配置会在编码开始前暴露,而不是在写出坏文件之后。
支持矩阵与位深选择
FlacEncoder的静态supports方法(packages/flac-encoder/src/encoder.ts)决定了该编码器接受哪些配置:
- 编码格式:
flac; - 声道数:1~8 通道(
numberOfChannels >= 1 && numberOfChannels <= 8); - 采样率:白名单
[8000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000],覆盖 CD 与高解析度音频的常用采样率。
输出位深是自动选择的(见encode()中对首个 AudioSample 格式的 switch 分支):
| 输入 AudioSample 格式 | FLAC 输出位深 |
|---|---|
u8/u8-planar/s16/s16-planar | 16 bit |
s32/s32-planar/f32/f32-planar | 24 bit |
因此在把 float32 PCM 编码为 FLAC 时,扩展会自动采用 24 bit,最大化动态范围。
实现原理:从 TS 到 libFLAC 的调用链
整个编码链路可以分成四层,读者可按需深入对应源码:
1. 编码器外层(主线程)——FlacEncoder继承CustomAudioEncoder(抽象基类定义于 src/custom-coder.ts),实现init/encode/flush/close四个抽象方法。编码过程在独立 Web Worker 中执行,主线程通过带自增id的请求-响应消息协议(sendCommand)与 Worker 通信,并支持用 Transferable 转移 ArrayBuffer 避免拷贝。Worker 若发生错误(如 CSP 拦截),会被视为致命错误:所有挂起的 Promise 被 reject、Worker 被 terminate(packages/flac-encoder/src/encoder.ts)。
2. Worker 消息层—— packages/flac-encoder/src/encode.worker.ts 加载 Emscripten 模块(createModule()),通过cwrap绑定 C 侧导出函数,并把命令分发为init/encode/flush三类(协议类型定义于 packages/flac-encoder/src/shared.ts)。Worker 同时适配浏览器self.addEventListener与 Nodeworker_threads的parentPort,因此同一份代码在两端都能跑。文件末尾还留了一个setInterval(() => {}, 1000),这是为了阻止 Firefox 因空闲而随机回收 Worker(对应上游 issue 的规避措施)。
3. C 桥接层—— packages/flac-encoder/src/bridge.c 是连接 JS 与 libFLAC 的薄层,内部维护一个EncoderContext:
init_encoder(channels, sampleRate, bitsPerSample):创建FLAC__StreamEncoder,设置通道数、采样率、位深,并把压缩级别固定为 5(#define COMPRESSION_LEVEL 5,libFLAC 的默认压缩级别,是压缩率与速度的均衡点),随后初始化流并返回上下文指针;send_samples:把 JS 传来的 interleaved int32 数据按32 - bits_per_sample位右移对齐后,交给FLAC__stream_encoder_process_interleaved;write_callback:libFLAC 回调出口。samples == 0的写入被识别为 STREAMINFO 等元数据,收集成header_buffer;真正的音频帧则追加进连续输出缓冲区,同时记录每帧的字节数与采样数(FrameInfo),供 JS 侧把整块输出精确切分成单个 EncodedPacket;finish_encoder:结束当前流,然后利用 libFLAC“finish 后保留配置”的特性重新初始化流,以便继续接收下一批采样。
4. 底层编解码—— libFLAC 的 WASM 静态库,负责实际的线性预测、固定预测、残差编码与 CRC 校验。
从主线程视角看,编码后的每个 FLAC 帧都会以EncodedPacket(类型恒为'key',因为 FLAC 每一帧都可独立解码)加上decoderConfig(包含 codec、声道数、采样率与description头)通过onPacket回调交给 muxer(见 packages/flac-encoder/src/encoder.ts)。
测试验证:注册、转码与时间戳
仓库在 test/node/flac-encoder-extension.test.ts 中为扩展提供了三组 Node 端测试,可作为行为契约的参考:
- Custom coder registration:注册前
canEncode('flac')为false,调用registerFlacEncoder()后变为true,验证了扩展与能力检测机制的联动。 - FLAC encoding:用程序生成的 48kHz 立体声正弦波(2 秒)经
AudioSampleSource送入FlacOutputFormat,写出 Buffer 后再用Input回读:断言轨道 codec 为flac、采样率与声道数不变,解码得到的 packet 数大于duration * sampleRate / 4096(FLAC 默认块大小 4096 采样,2 秒 48kHz 约 24 帧),且计算时长约等于 2 秒——即转码后可以被 mediabunny 完整无损回读。 - FLAC with huge timestamps:把起始时间戳设为 1e9 秒级别,输出到 MP4 容器后首个 packet 的时间戳仍精确保持原值,验证了大时间戳场景下的正确处理。
从零构建 WASM(可选)
仓库为了方便使用,已把构建好的 WASM 产物直接包含在仓库中(见packages/flac-encoder/build/),日常开发无需自行编译。但如果你需要定制 libFLAC 参数(如修改压缩级别、启用特定优化),可以按官方流程从源码重建:
前置条件:安装 Emscripten 与 CMake,并克隆 libFLAC 源码。随后在 mediabunny 仓库根目录、确保 Emscripten 环境已 source 的前提下执行:
export FLAC_PATH=/path/to/flac export MEDIABUNNY_ROOT=$PWD cd $FLAC_PATH mkdir -p build && cd build emcmake cmake .. \ -DBUILD_PROGRAMS=OFF \ -DBUILD_CXXLIBS=OFF \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTING=OFF \ -DWITH_OGG=OFF \ -DBUILD_SHARED_LIBS=OFF \ -DENABLE_MULTITHREADING=OFF \ -DINSTALL_MANPAGES=OFF \ -DCMAKE_C_FLAGS="-DNDEBUG -Oz -flto -msimd128" emmake make # 编译 JavaScript 与 libFLAC API 之间的桥接层 cd $MEDIABUNNY_ROOT/packages/flac-encoder emcc src/bridge.c \ $FLAC_PATH/build/src/libFLAC/libFLAC.a \ -I$FLAC_PATH/include \ -s MODULARIZE=1 \ -s EXPORT_ES6=1 \ -s SINGLE_FILE=1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s ENVIRONMENT=web,worker \ -s FILESYSTEM=0 \ -s MALLOC=emmalloc \ -s SUPPORT_LONGJMP=0 \ -s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \ -s EXPORTED_FUNCTIONS=_malloc,_free \ -msimd128 \ -flto \ -Oz \ -o build/flac.jsCMake 阶段的关键选项作用如下:
| 选项 | 作用 |
|---|---|
-DBUILD_PROGRAMS=OFF/-DBUILD_EXAMPLES=OFF/-DBUILD_TESTING=OFF | 只编译静态库,不产出 CLI 程序、示例与测试 |
-DWITH_OGG=OFF | 关闭 OGG 容器支持(FLAC 编码不需要) |
-DENABLE_MULTITHREADING=OFF | 禁用多线程,减小 WASM 体积并避免线程同步开销 |
-DCMAKE_C_FLAGS="-DNDEBUG -Oz -flto -msimd128" | 优化级别 Oz(压缩体积)、启用链接时优化与 SIMD128 指令集 |
emcc阶段则把 C 桥接层与 libFLAC 静态库链接为单个build/flac.js文件:-s SINGLE_FILE=1把 WASM 二进制内联进 JS,-s MODULARIZE=1 -s EXPORT_ES6=1使其以 ES Module 形式导出,-s ALLOW_MEMORY_GROWTH=1允许堆按需增长,-s FILESYSTEM=0去掉无用文件系统 API,-s MALLOC=emmalloc使用轻量内存分配器。
构建完build/flac.js后,在仓库根目录运行npm run build,即可连同 mediabunny 其余部分一起产出完整 JS 包(对应的 esbuild 内联 Worker 处理见 scripts/esbuild/inlined-workers.ts)。
注意事项与常见问题
- 重复加载检测:扩展在模块加载时通过
Symbol.for('@mediabunny/flac-encoder loaded')检测自身是否被重复引入,若检测到会通过Logging._error告警,提示检查是否有多个依赖导入了不同版本(见 packages/flac-encoder/src/index.ts)。多版本共存会导致编码器异常,应保证依赖收敛。 - CSP 与 Worker:编码在 Worker 中执行,若站点 Content-Security-Policy 限制了
worker-src或内联脚本,可能导致 Worker 加载失败;此类错误会被编码器捕获并作为致命错误向上抛出,需在部署时放行。 - 不要在
appendOnly模式下追求精确元数据:该模式牺牲 STREAMINFO 的准确性换取流式写入能力,只适合直播场景。 - Node 端可用:由于 Worker 层同时支持
worker_threads,该扩展不仅在浏览器中可用,也能在 Node 服务端(配合 mediabunny 的 server 扩展或直接使用)执行 FLAC 编码。
小结
@mediabunny/flac-encoder以"注册即用"的方式补齐了浏览器生态缺失的 FLAC 编码能力:上行通过 mediabunny 的 Custom Coder API 无缝融入现有转码管线,下行以体积优化过的 libFLAC WASM 提供可靠的无损编码,并自动根据输入 PCM 格式选择 16/24 bit 输出位深。配合canEncodeAudio特性检测,你可以在任何环境(浏览器、Node)中安全地编写"原生优先、扩展兜底"的 FLAC 编码逻辑。若需深入了解插拔式编解码机制,可继续阅读 mediabunny 的自定义编码器文档与扩展实现源码。
- 音视频
- 视频处理
- 音频处理
【免费下载链接】mediabunny
Pure TypeScript media toolkit for reading, writing, and converting video and audio files, directly in the browser.
相关推荐
Mediabunny MP3 编码扩展 @mediabunny/mp3-encoder:基于 LAME WASM 的浏览器与服务器端 MP3 编码方案
Mediabunny MP3 编码扩展 @mediabunny/mp3 encoder:基于 LAME WASM 的浏览器与服务器端 MP3 编码方案 Medi
音视频视频处理音频处理Iosevka CV Influences 全解析:OpenType 字符变体影响范围与文档生成机制
Iosevka CV Influences 全解析:OpenType 字符变体影响范围与文档生成机制 在 Iosevka 中,每一个字符变体(Character
音视频视频处理音频处理@mediabunny/mp3-encoder 实战指南:基于 LAME WASM 的浏览器端 MP3 编码扩展
@mediabunny/mp3 encoder 实战指南:基于 LAME WASM 的浏览器端 MP3 编码扩展 本指南围绕 Mediabunny 官方扩展包
音视频视频处理音频处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考