99%之谜:为什么我的 ffmpeg.wasm 应用在终点线卡住了

发布: (2026年5月8日 GMT+8 18:29)
5 分钟阅读
原文: Dev.to

Source: Dev.to

Cover image for The 99% Mystery: Why My ffmpeg.wasm App Stalls at the Finish Line

我一直在构建 VideoSnap,这是一款使用 ffmpeg.wasm 在浏览器中完整处理视频的工具。长期以来,我一直被一个特定且令人沮丧的 bug 纠缠:所谓的“99 % 陷阱”。
用户上传文件后,进度条平稳上升,达到 99 %……随后一切就停住了。UI 变得无响应,风扇转个不停,几分钟后下载才出现,好像什么都没发生过。

99 % 不是 FFmpeg——而是交接

ffmpeg.wasm 中,进度条跟踪 FFmpeg 的执行。当进度达到 99 % 时,转码的繁重工作实际上已经完成。

“卡住”发生在交接阶段:当你调用 engine.readFile() 将处理后的视频从 WebAssembly 虚拟内存(MEMFS)拉取到 JavaScript 堆时。

“内存重叠”问题

  • 峰值: 在 99 % 时,WASM 内存同时保存你的 500 MB 输入文件 以及 新生成的 500 MB 输出文件 → 1 GB 的 WASM 内存被占用。
  • 请求: 你调用 engine.readFile()。JavaScript 现在尝试分配一个 新的 500 MB Uint8Array 来复制该数据。
  • GC 风暴: 浏览器现在要管理接近 1.5 GB–2 GB 的大块连续内存。这会触发一次 “Stop‑The‑World” 垃圾回收事件。主线程在引擎尝试碎片整理内存以寻找 500 MB 空洞时被锁住。你看到的 UI 卡顿正是这种 GC 抖动的表现。

“手术式”修复:打破重叠

一旦我明白卡顿是由 MEMFS 中输入文件和输出文件 simultaneous existence 引起的,解决办法就显而易见:在搬动大箱子之前先清理桌面。

// Optimized handover logic

// 1. FFmpeg is done. Before reading the output, delete the input file
await engine.deleteFile('input.mp4');

// 2. Now the WASM memory has breathing room; read the result
const data = await engine.readFile('output.mp4');

// 3. Immediately delete the WASM copy of the output
await engine.deleteFile('output.mp4');

// 4. Create a Blob from the JS buffer
const blob = new Blob([data.buffer], { type: 'video/mp4' });

通过重新排列这些删除操作,我消除了浏览器在最需要内存的关键时刻出现的大规模内存重叠。99 % 的卡顿并不会凭空消失——浏览器仍然需要时间来分配大型 JS 缓冲区——但此清理削减了关键的几秒 GC 抖动,并防止标签页在处理大文件时窒息。

Why I Didn’t Use WORKERFS or OPFS

  • WORKERFS: 挂载文件而不复制它们,听起来很完美,但它使用同步 I/O 桥,使 FFmpeg 运行显著变慢。我用内存换取了巨大的速度惩罚,这不值得。
  • OPFS (Origin Private File System): 将数据直接流式写入磁盘,是未来的方向,但它需要一个自定义构建的带有 WASMFS 支持的 FFmpeg 核心——官方的 @ffmpeg/ffmpeg 包并未开箱即用提供。

要点:了解你的交接

在构建高性能 WebAssembly 应用时,请记住:管道中最危险的环节是数据交接

在 WASM “世界”和 JS “世界”之间移动大量数据会迫使浏览器分配巨大的连续内存块。如果在发起请求之前不先清理内部状态,就会招致一次 GC 风暴。

VideoSnap 现在显著更稳定,并不是因为算法更快,而是因为内存生命周期被精确管理。

我是 VideoSnap 的创建者,专注于写下在浏览器中构建高性能工具的乱象与真实经验。关注我,获取更多深度解析。

0 浏览
Back to Blog

相关文章

阅读更多 »

Bun 在 6 天内移植到 Rust

概览 - 测试覆盖率:在 Rust 重写版中,Bun 现有的测试套件在 Linux x64 glibc 上的通过率为 99.8%。 - 代码库基本保持不变,但 Ru...