動画の変換や圧縮、切り出し、フィルター処理で使われるFFmpegは、もともとPCやサーバー上で動かすネイティブアプリケーションです。ところが現在は、FFmpegをWebAssemblyへコンパイルすることで、ブラウザの中でも動画の読み込み、Filter Graphの処理、再エンコード、MP4の生成までかなりの範囲を実行できます。

ただし『FFmpegがブラウザで動く』ことと、『PC版FFmpegと同じ条件で動く』ことは別です。特にSingle-threadとMulti-threadでは、使えるCPUだけでなく、SharedArrayBuffer、Web Worker、cross-origin isolation、HTTPレスポンスヘッダーまで関係します。Browser KittyのFFmpeg Filter Builderでも、持ち運びやすいSingle-thread版と、HTTP(S)配信を前提にしたMulti-thread版を分けています。

ST / MTを理解するための5つの要素

WebAssembly

FFmpegをブラウザへ持ち込む

FFmpegのソースコードをWebAssembly向けにビルドし、ブラウザのサンドボックス内で実行します。どのCodec・Filter・外部ライブラリを含めるかはビルド構成で決まります。

Worker

重い処理をUIから分離する

Single-thread版でもFFmpeg WASMをWeb Worker側で動かせます。『Single-thread』は、ページ全体が1スレッドしか使えないという意味ではありません。

SharedArrayBuffer

複数Workerで同じメモリを共有する

Multi-thread版では、複数のWorkerから同じWebAssemblyメモリへアクセスして同期するためSharedArrayBufferを使います。EmscriptenのPthreads実装を支える重要な仕組みです。

Isolation

MT版は配信条件まで変わる

SharedArrayBufferを使う一般的なWebページではcross-origin isolationが必要です。COOPとCOEPをHTTPレスポンスで設定するため、単にHTMLをダブルクリックするST版とは配布条件が変わります。

Trade-off

速さ以外のコストも見る

Multi-thread化はCPUをより広く使える一方、Worker、同期、共有メモリ、配信設定が増えます。処理内容によって速度差も変わるため、用途に合わせてST / MTを選ぶ必要があります。

FFmpegがブラウザに入っているのではなく、WebAssembly版を動かしている

ChromeやEdgeがffmpegコマンドをそのまま提供しているわけではありません。FFmpegのソースコードをEmscriptenなどでWebAssemblyへコンパイルし、JavaScriptからその実行環境を操作します。ブラウザがユーザーのPC上の任意パスを自由に読むことはできないため、入力もFile APIなどを通じてユーザーが明示的に選択したデータを渡します。

WebAssembly化してもFFmpegの全機能が自動的に入るわけではありません。Decoder、Encoder、Muxer、Filter、外部ライブラリのどれを含めるかで、WASMのサイズと対応機能が変わります。つまり『デスクトップFFmpegで動くコマンドならブラウザでも必ず動く』とは限りません。

ブラウザ版FFmpegの大まかな流れ
FFmpegソースコード Emscripten WebAssembly Browser / Worker

ブラウザのサンドボックスとFile APIの条件の中でFFmpegを実行する

Single-threadでも、動画処理をUIのメインスレッドから外せる

Single-threadという名前から『ブラウザの画面も含めて1本のスレッドですべて動く』ように見えますが、そうとは限りません。FFmpeg WASMそのものをWeb Workerで動かせば、UIを管理するメインスレッドとは分離できます。重い動画処理をWorker側へ置き、進捗だけをメイン側へ返す構成にできます。

ここでいうSingle-threadは主に、FFmpeg側の処理が複数のPthreadsを使わない構成だと考えると分かりやすくなります。ST版にも、SharedArrayBufferやcross-origin isolationが不要で配布しやすいという明確な利点があります。

Multi-thread化の中心にあるのがSharedArrayBuffer

FFmpeg内部で複数スレッドを使うには、各Workerが独立したコピーを持つだけでは足りません。複数の実行コンテキストが同じWebAssemblyメモリを共有し、同期できる必要があります。そのためEmscriptenのPthreadsではSharedArrayBufferが使われます。

SharedArrayBufferは複数のWorkerから同じメモリ領域を参照でき、Atomicsと組み合わせて同期できます。この仕組みによって、ブラウザ上でもNative側のマルチスレッドに近い実行モデルを作れます。ただし、Worker作成や同期にもコストがあるため、スレッド数を増やせば比例して速くなるわけではありません。

SharedArrayBufferを使うと、HTMLの配信方法まで変わる

現在の主要ブラウザでSharedArrayBufferをWebページから使う一般的な構成では、Secure Contextとcross-origin isolationが必要です。cross-origin isolationは、代表的にはCross-Origin-Opener-Policy: same-originとCross-Origin-Embedder-Policy: require-corp(またはcredentialless)をHTTPレスポンスで設定して成立させます。ページ上ではcrossOriginIsolatedで状態を確認できます。

ここがST版との大きな違いです。HTTPレスポンスヘッダーはfile://でHTMLを直接開いたときには付けられません。そのため、Browser KittyのFFmpeg Filter Builderでは標準ST版はfile://でも使える単一HTMLとして残し、MT版はHTTP(S) + cross-origin isolationを前提にしています。Cross-Origin-Resource-Policy: same-originもBrowser Kittyでは追加していますが、これはcrossOriginIsolated成立そのものの必須条件ではなく、同一オリジン用途へ絞る追加の制限です。

Browser KittyでのST / MTの使い分け
観点Single-threadMulti-thread
FFmpeg側1スレッド構成複数Pthreadsを利用
SharedArrayBuffer不要必要
配信file:// またはHTTP(S)cross-origin isolation付きHTTP(S)
持ち運び単一HTMLを直接開きやすいサーバー側または代替手段が必要
主な利点互換性・配布しやすさCPU並列性を使える

Multi-threadなら必ず速い、ではない

動画の再エンコードや複雑なFilter GraphはCPU負荷が大きいため、複数コアを使えるMT版が有利になる場面があります。ffmpeg.wasmの公開ベンチマークにも、特定の変換でMulti-thread版がSingle-thread版より高速だった例があります。ただし、これは特定環境・特定バージョン・特定処理での結果であり、すべての処理が同じ比率で速くなることを意味しません。

処理には並列化しやすい部分としにくい部分があり、Worker間同期、メモリ管理、スレッド起動にもコストがあります。またMT版はWorkerや共有メモリを使うぶん、メモリ使用量が増えやすくなります。ブラウザ内の動画処理では、CPUより先に端末メモリが実用上の上限になることもあります。

Browser Kittyでは、MTが必要なアプリだけをcross-origin isolatedにする

cross-origin isolationはサイト全体へ一律に付ければよいとは限りません。COEPを有効にすると、別オリジンの画像、iframe、スクリプトなどの読み込み条件が変わり、広告や外部埋め込みにも影響し得ます。そこでBrowser Kittyでは、Azure Static Web Appsのルート設定で/apps/ffmpeg-filter-builder.htmlだけにCOOP / COEPを付け、他のツールやポータルページは従来どおりにしています。

FFmpeg Filter BuilderのGitHub Pages用MTページは、サーバー側で必要なヘッダーを自由に付けられない場合に備えて、crossOriginIsolatedがfalseのときだけ同一originのcoi-serviceworkerを補助的に読み込む構成です。一方Browser Kitty本番ではサーバー側ヘッダーで最初からcrossOriginIsolated === trueにできるため、そのfallbackに依存しません。

ブラウザ版FFmpegはNative FFmpegの置き換えではなく、別の使いどころがある

ブラウザ版FFmpegの強みは、インストールせずに使えること、選択した動画をアプリからサーバーへアップロードせずに処理できること、GUIや固定Recipeと組み合わせやすいことです。数本の動画へその場で処理をかけたい用途では、CLIを用意するより扱いやすい場合があります。

一方、大量ファイルのバッチ処理、長時間・高解像度動画、幅広いCodecや外部ライブラリ、最大限の速度が必要な処理ではNative FFmpegが適しています。WebAssemblyによってNative FFmpegと同じ実行環境になるのではなく、『これまでブラウザでは難しかった処理を、ブラウザの制約の中へ持ち込める』と考える方が実態に近いです。

ST / MTを選ぶときに見る順番
持ち運び優先? 重い再エンコード? COOP / COEPを設定可能? ST / MTを選択

速度だけでなく、配布方法・互換性・メモリまで含めて選ぶ

Browser Kittyで試す

FFmpeg Filter Builder

FFmpeg Filter Graphをノードで組み、Previewで確認してH.264/AAC MP4を書き出す。Browser Kittyでは高速なMulti-thread版を利用します。

ツールを開くツールの詳細を見る

注意点

  • Single-thread版でもWorkerへ処理を分けられるため、『ST = UIが必ず固まる』ではありません。UIスレッドとFFmpeg内部のスレッド数は分けて考える必要があります。
  • SharedArrayBufferが必要だからといって、サイト全体へCOOP / COEPを付ける必要はありません。外部埋め込みへの影響を確認し、必要なルートだけに設定する方法もあります。
  • 完全ローカル処理とオフライン起動は別です。MT版はHTTP(S)配信が必要でも、選択した動画そのものを外部サーバーへ送らずに処理できます。
  • 大きな動画ではCPUだけでなくメモリも確認します。MT化によって高速化しても、端末メモリが先に限界になる場合があります。

よくある質問

Single-thread版は、動画処理中に画面が操作できなくなりますか?

必ずしもそうではありません。FFmpeg WASMをWeb Workerで実行すれば、UIを管理するメインスレッドとは分離できます。Single-threadは主にFFmpeg内部で複数Pthreadsを使わない構成を指します。

SharedArrayBufferがあればMulti-thread版はどこでも動きますか?

ブラウザの機能だけでは足りません。通常はSecure Contextとcross-origin isolationが必要で、HTTP(S)配信時にCOOP / COEPなどの条件を満たす必要があります。また、Permissions-Policy: cross-origin-isolatedで明示的に禁止されている場合は利用できません。既定値はselfなので、通常のトップレベルページでは追加設定は不要です。実際の対応状況はブラウザと配信環境の両方で確認します。

COOP / COEPに加えてCORPも必須ですか?

crossOriginIsolatedを成立させる直接の組み合わせは、一般的にはCOOP: same-originとCOEP: require-corpまたはcredentiallessです。CORPは個々のリソースをどのオリジンから利用できるか制限する別のヘッダーで、Browser KittyではFFmpeg Filter Builderへ追加の制限としてsame-originを設定しています。

Multi-thread版ならSingle-thread版より必ず速いですか?

必ずではありません。重い再エンコードなどで有利になる場合がありますが、並列化しにくい処理やWorker同期のコストもあります。端末のCPU・コア数・メモリ、入力動画、Filter Graphによって差は変わります。

Multi-thread版は完全ローカル処理ではないのですか?

いいえ。最初にアプリHTMLを取得する通信と、選択した動画の処理場所は別です。Browser Kitty版ではHTMLはWebから取得しますが、入力動画、Preview、Full Renderはブラウザ内で処理し、選択した動画をアプリから外部サーバーへアップロードしません。

参考資料

SharedArrayBufferとcross-origin isolation、Emscripten Pthreads、FFmpeg WebAssemblyの性能・構成については以下の公式資料・プロジェクト資料を参照しています。Browser Kitty固有のST / MT配信構成はFFmpeg Filter Builderの公開リポジトリに基づきます。