FFmpeg WebAssembly 教學

什麼是 FFmpeg.wasm?

FFmpeg.wasm 是將知名的開源影音處理工具 FFmpeg 透過 Emscripten 編譯為 WebAssembly 的版本。它讓瀏覽器能夠在不依賴後端伺服器的情況下,直接在用戶端執行複雜的影片解碼、轉碼、合併等操作。

專案連結:github.com/ffmpegwasm/ffmpeg.wasm

傳統架構 vs FFmpeg.wasm 架構

傳統架構(後端轉碼):
用戶 → 上傳影片 → 伺服器 FFmpeg → 輸出 → 用戶下載
問題:伺服器 CPU 資源消耗大、頻寬成本高、隱私疑慮

FFmpeg.wasm 架構:
用戶 → 瀏覽器內 FFmpeg.wasm → 輸出 Blob → 用戶下載
優點:零伺服器消耗、隱私保障、離線可用

技術核心:WebAssembly 是什麼?

WebAssembly(Wasm)是一種低層級的二進位格式,設計目標是讓編譯語言(C、C++、Rust 等)的程式碼在瀏覽器中接近原生速度執行。

FFmpeg 的原始碼是 C 語言,Emscripten 工具鏈可以將 C 程式碼編譯成 .wasm 二進位模組,配合 JavaScript 膠水程式碼(ffmpeg-core.js)在瀏覽器環境運行。

FFmpeg.wasm 的兩種執行模式

單執行緒模式(Single-threaded)

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';

const ffmpeg = new FFmpeg();
await ffmpeg.load({
  coreURL: await toBlobURL(
    'https://unpkg.com/@ffmpeg/[email protected]/dist/esm/ffmpeg-core.js',
    'text/javascript'
  ),
  wasmURL: await toBlobURL(
    'https://unpkg.com/@ffmpeg/[email protected]/dist/esm/ffmpeg-core.wasm',
    'application/wasm'
  ),
});
  • 不需要 SharedArrayBuffer
  • 不需要 特殊 HTTP Header(COOP/COEP)
  • 所有操作在單一 JS 執行緒(Worker)中進行
  • 適合大多數靜態網站部署(Cloudflare Pages、GitHub Pages 等)

多執行緒模式(Multi-threaded)

await ffmpeg.load({
  coreURL: 'ffmpeg-core-mt.js',  // 多執行緒版 core
  wasmURL: 'ffmpeg-core-mt.wasm',
  workerURL: 'ffmpeg-core-mt.worker.js',
});
  • 使用 SharedArrayBuffer 在多個 Web Worker 之間共享記憶體
  • 需要伺服器設定以下 HTTP Headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • 速度顯著提升(可利用多核心 CPU)
  • 但設定較為複雜,部分 CDN 不支援

虛擬檔案系統(Emscripten FS)

FFmpeg.wasm 沒有直接存取本地磁碟的能力。它使用 Emscripten 的虛擬檔案系統(VFS) 模擬磁碟操作:

// 將 ArrayBuffer 寫入虛擬 FS
await ffmpeg.writeFile('input.ts', new Uint8Array(buffer));

// 執行 FFmpeg 命令(在虛擬 FS 中操作)
await ffmpeg.exec(['-i', 'input.ts', '-c', 'copy', 'output.mp4']);

// 讀取輸出
const data = await ffmpeg.readFile('output.mp4');

// 清理釋放記憶體(重要!)
await ffmpeg.deleteFile('input.ts');
await ffmpeg.deleteFile('output.mp4');

關鍵: 虛擬 FS 中的資料存在於 JavaScript 堆(Heap)中,必須在使用完畢後手動刪除,否則會佔用瀏覽器記憶體直到頁面關閉。

記憶體限制與最佳實踐

這是 FFmpeg.wasm 最大的先天限制:

項目 限制
JS Heap 上限 約 1-2 GB(依瀏覽器和 OS 而定)
Wasm 記憶體模型 32-bit 定址,單一模組上限 4 GB
實際可用 通常 500 MB - 1.5 GB

建議的使用場景

  • ✅ 720p 以下,30 分鐘以內的影片
  • ✅ 主要是 concat(片段合併),非重新編碼
  • ✅ 公開串流的技術研究與測試
  • ❌ 4K/1080p 長片(記憶體不足)
  • ❌ 複雜的濾鏡和重新編碼(速度過慢)

記憶體管理最佳實踐

// 1. 分批下載,避免同時持有所有 ArrayBuffer
const BATCH = 5;
for (let i = 0; i < segments.length; i += BATCH) {
  const batch = await downloadBatch(segments.slice(i, i + BATCH));
  // 立即寫入 VFS,可回收 JS 側的 ArrayBuffer
  for (const [idx, buf] of batch.entries()) {
    await ffmpeg.writeFile(`seg${i + idx}.ts`, new Uint8Array(buf));
  }
}

// 2. 執行完成後立即清理 VFS
await ffmpeg.deleteFile('output.mp4');

與後端 API 的比較

面向 FFmpeg.wasm 後端 API
伺服器成本 高(CPU 密集)
隱私性 100%(本地處理) 需信任伺服器
處理速度 慢(單核 Wasm) 快(多核 + GPU)
大檔案支援 受瀏覽器記憶體限制 幾乎無限制
部署複雜度 極簡(靜態資源) 需要伺服器維護
最適場景 小型轉換、研究工具 生產環境大批量處理

實際應用:本站實作方式

本站的 HLS 研究工具 採用以下組合:

  • @ffmpeg/ffmpeg 0.12.x(單執行緒,無需 COOP/COEP)
  • @ffmpeg/core 0.12.x(對應的 Wasm Core)
  • 透過 unpkg CDN 動態載入,首次載入約 15-30 秒(Wasm 模組約 30MB)
  • 執行 ffmpeg -f concat -safe 0 -i concat.txt -c copy output.mp4 完成 TS 合併

延伸閱讀

想立即測試您的 M3U8 連結嗎?

🚀 立即測試 M3U8 線上播放器