FFmpeg.wasm:ブラウザ内でFFmpegを実行するWebAssembly技術の解説

FFmpeg.wasm Browser

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の2つの動作モード

シングルスレッドモード(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ヘッダー(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 ヘッダーを設定する必要があります:
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ヒープの上限 約 1〜2 GB(ブラウザとOSに依存します)
Wasmメモリモデル 32ビットアドレッシング、単一モジュールの上限は 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を回収(GC)させる
  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 オンラインプレーヤーを試す