remotionの動画レンダリング技術の調査

2023年12月28日
きっかけ
その昔自分の結婚式のプロフィールムービーを作る機会があり、ヘッドレスブラウザを使って動画を作成しました。
同様にヘッドレスブラウザを用いてプログラマブルに動画を作成できるremotionの動画レンダリング技術が気になり、調べてみました。

準備

requestAnimationFrame

requestAnimationFrameは、Web標準のAPIです。引数にコールバックを渡すことで、「ブラウザが再描画可能なタイミング」でコールバックが実行されます。

requestAnimationFrameを使わずに再描画処理をloop処理で行うと、描画可能でないタイミングで再描画処理が行われてしまう可能性があります。即ち画面上で表示されない再描画処理のiterationが生じてしまう可能性があります。

また、同じフレーム内に複数のrequestAnimationFrameのコールバックがenqueueされた場合、それらのコールバックは同じフレームで実行されます。以下はmozillaのdocsからの引用です。timestampはひとつ前の描画フレームの終了時刻を指します。

When multiple callbacks queued by requestAnimationFrame() begin to fire in a single frame, each receives the same timestamp even though time has passed during the computation of every previous callback’s workload.

remotionのレンダリング方法

以下では、remotion v4.0.81の実装を基にremotionの動画レンダリング方法を読解します。

シーケンス図

以下がremotionによるレンダリング時のシーケンス図です。1

特徴的な点として、以下が挙げられます

  • ヘッドレスブラウザがフレームを描画できる状態かどうかはrequestAnimationFrameを用いて判断する。
  • スクリーンショットを撮るAPIはJavaScriptには存在しない2ため、CDP(Chrome DevTools Protocol)のPage.captureScreenshotを用いる
    • ブラウザが描画できる状態でPage.captureScreenshotを実行するため、以下要領で実行タイミングを決定する
      • Reactを実行しているヘッドレスブラウザ上でglobal変数(remotion_renderReady)に描画できる状態かどうかのフラグを持たせる
      • waitForReady関数からremotion_renderReadyをpollingする。trueになったタイミングでscreenshotを撮る。React側でrequestAnimationFrameによるcontinueRenderコールバックを実行できるようにするため、pollingのループはrequestAnimationFrameで行う
sequenceDiagram
    participant renderer
    participant puppeteer
    participant browser
    participant remotion_renderReady
    participant react

    loop screenshot a frame
      Note over renderer,react: set the next frame
        renderer->>puppeteer: set the next frame
        puppeteer->>browser: remotion_setFrame
(Runtime.callFunctionOn) browser->>remotion_renderReady: delayRender remotion_renderReady->>remotion_renderReady: set false remotion_renderReady->>browser: done browser->>react: set frame context browser->>remotion_renderReady: continueRender
with requestAnimationFrame browser->>puppeteer: done puppeteer->>renderer: done par rerendering the next frame react->>react: update frame context and
rerender user-defined components remotion_renderReady->>remotion_renderReady: set true
with renderable timing end Note over renderer,react: wait ready state renderer->>puppeteer: wait ready puppeteer->>browser: onRaF
(Runtime.callFunctionOn) loop until remotion_renderReady being true browser->>remotion_renderReady: check value
with requestAnimationFrame remotion_renderReady->>browser: end browser->>puppeteer: done puppeteer->>renderer: ready Note over renderer,react: take screenshot renderer->>puppeteer: take screenshot puppeteer->>browser: Page.captureScreenshot(CDP) browser->>puppeteer: screenshot end

まとめ

remotionは、requestAnimationFrameとReactのContextを用いることにより、frameごとに正確な動画レンダリングを可能にするとても実用的なフレームワークです。
この記事では扱いませんでしたが、videoやaudioを埋め込む場合も、レンダリングした動画上で正確にそれらが再生されるような機構を設けています。


  1. v2以前ではPuppetterがdependenciesに入っていましたが、v3以降では互換性を保ったまま内製化しています。この記事では内製化したものを"puppeteer"と表記しています ↩︎

  2. getDisplayMediaというAPIは存在しますが、これはStreamを返すものです。これを使う場合最終的に描画可能になったタイミングのフレームを継ぎはぎする必要があるので、描画可能になったタイミングでスクリーンショットを撮る実装の方がシンプルです。 ↩︎

プロフィール
profile image
情報科学科(学士)と数学科(修士)を持つソフトウェアエンジニアのブログです。