コンテンツにスキップ

ffmpeg による動画エンコード

MP4 や WebM などの動画形式へ出力する場合も、静止画と同じ端末エミュレーターと SVG レンダラーを使用します。 指定されたサンプリング間隔ごとに端末状態を静止画 SVG として生成し、PNG へラスタライズしたうえで、ffmpeg(オープンソースの動画・音声処理ツール)の標準入力へ順番にパイプ転送します。 中間ファイルを作らずメモリ上で完結させることで、ディスク I/O のオーバーヘッドを排除しています。

端末エミュレーターの単一方向再生

Section titled “端末エミュレーターの単一方向再生”

一定のフレームレート(FPS)で動画フレームをサンプリングする際、フレームごとに録画の先頭(時刻 0)から再エミュレーションを実行することはありません。 エミュレーターのインスタンスを 1 つだけ保持し、サンプリング時刻の進行に合わせて未処理のイベントだけを順方向へ適用します。

イベントの探索インデックスも常に前方へ進めます。 後ろのフレームを生成する際に過去の全イベントを走査し直す無駄を省き、全体の処理時間を録画長に対して線形に抑えています。

シグネチャによる SVG の再生成抑止

Section titled “シグネチャによる SVG の再生成抑止”

エミュレーターをサンプリング時刻まで進めた直後に、画面全体のビジュアルシグネチャ(セル内容やカーソル状態のハッシュ値)を取得します。 このシグネチャをキーとして、直近に生成した SVG 文字列を最大 128 件キャッシュします。

CLI アプリケーションの出力は、キー入力待ちや処理待ちなどで画面が静止している時間が長いため、多くのサンプルが同一画面を指します。 シグネチャが一致した場合は XML の組み立てを行わず、キャッシュ内の同一の string インスタンスを返却します。

順序を維持した並列ラスタライズ

Section titled “順序を維持した並列ラスタライズ”

SVG から PNG へのラスタライズは CPU バウンドな重い処理です。 そのため、利用可能な CPU コア数に合わせて最大 8 スレッドで並列にラスタライズを実行します。

一方で、動画エンコーダーへ渡す画像フレームの時系列順序を崩すわけにはいきません。 そこで、非同期ラスタライズのタスクを容量制限付きの FIFO キューで管理し、先頭のタスクが完了した順に 1 つのライターから ffmpeg の標準入力へ書き込みます。 この制御により、並列処理の高速性を活かしつつ、動画全体の全フレームを一括してメモリ上に抱え込むリスクを回避しています。

オブジェクト同一性による二段キャッシュ

Section titled “オブジェクト同一性による二段キャッシュ”

生成された PNG 画像は SVG 文字列よりもメモリフットプリントが大きくなるため、PNG のキャッシュサイズは 16 件に制限しています。

このキャッシュのキーには、SVG の文字列比較ではなくオブジェクト参照(同一性)を用います。 前段の SVG キャッシュが同一画面に対して全く同じ string インスタンスを返す仕組みになっているためです。 これにより、巨大な XML 文字列のハッシュ計算や全比較を行うことなく、SVG の再生成と PNG の再ラスタライズを同時に防ぐ 二段キャッシュ が機能します。

image2pipe による単一プロセス転送

Section titled “image2pipe による単一プロセス転送”

1 本の動画を生成するにあたり、起動する ffmpeg プロセスは 1 つだけです。 すべての画像フレームは、image2pipe(連続した画像ストリームを標準入力などのパイプ経由で一括受信する ffmpeg の入力フォーマット)を用いて渡します。 指定オプションは -f image2pipe -vcodec png -i pipe:0 です。

子プロセスの標準出力および標準エラー出力は、プロセス起動直後から非同期ループで読み捨てます(drain)。 パイプバッファが溢れて ffmpeg がブロックする事態を防ぎつつ、エンコード失敗時には標準エラー出力に残されたエラーメッセージを診断ログとして回収します。 処理の中断(キャンセル)が発生した場合は、プロセスツリー全体へ終了シグナルを送信してリソースを解放します。

事前プローブによる SVG デコーダーの確認

Section titled “事前プローブによる SVG デコーダーの確認”

ffmpeg はビルド構成によって組み込まれているライブラリが異なります。 形式一覧に SVG が表示されていても、内部の SVG デコーダー(libxml2 や librsvg など)が欠落している環境が存在します。

そのため console2svg では、起動時に最小限の SVG を実際に PNG へ変換するプローブ(試験実行)を行い、SVG デコードが可能かどうかを判定して結果をキャッシュします。 メモリ内動画パイプラインにおいては、複数フレームの SVG をパイプ上で安全に切り分けられない制約があるため、変換エンジンに同梱の resvg または rsvg-convert を優先して割り当て、ffmpeg は動画エンコードの工程に専念させます。

MP4 コーデックの選択と寸法の偶数補正

Section titled “MP4 コーデックの選択と寸法の偶数補正”

MP4 コンテナへ出力する際は、システムで libx264 が利用可能であれば優先し、存在しない場合は mpeg4 へフォールバックします。 WebM や GIF の場合は、コンテナの標準コーデックへ委ねます。

MP4 のピクセルフォーマットには、幅広い動画再生環境で互換性を持つ yuv420p(輝度信号 Y と 2 つの色差信号 U/V を用い、色差情報を縦横 2 ピクセルごとに間引いて圧縮するフォーマット)を指定します。 yuv420p 仕様では画像の幅と高さが偶数でなければならないため、次の ffmpeg フィルターを適用します。

pad=ceil(iw/2)*2:ceil(ih/2)*2:0:0

このフィルターは、奇数のピクセル幅または高さを切り上げて右端・下端に最大 1 ピクセルの余白を補完します。 基準となる左上の座標系をずらさずに、エンコーダーの要件を満たす安全な動画を出力します。