WebCodecsのVideoEncoderでエンコード【mp4ファイル生成解説】
2025/05/03

mp4動画ファイル生成について、プログラマー向けに解説します。 大まかな流れとしては、各フレーム画像をエンコードし、mp4フォーマットに合わせてまとめます。 本記事ではその最初の工程、各フレーム画像をエンコードする処理について、詳述します。
なお、実際にこれを用いて作られたWebアプリケーションがこの記事などにありますので、 プログラマー以外の方も、ぜひお試しください。

プログラミングブログ記事一覧
[PR]
mp4動画ファイル生成の工程
当ブログにおける、mp4動画ファイルを生成する工程は、以下の4ステップです。
本記事では「1.WebCodecsのVideoEncoderで各フレーム画像をエンコード」を解説します。
WebCodecsのVideoEncoderで各フレーム画像をエンコード
最新Webブラウザーには、WebCodecs API という、動画を扱う方法が用意されています。 本記事では、その中の VideoEncoder を用いて、画像をエンコードする方法を解説します。
コードと補足
以下に、JavaScriptのプログラムのソースコードを提示します。
基本的には、コードを見てもらえればわかると思いますが、 補足が必要な箇所は後述します。
呼び出し方法
プログラムの呼び出し方法は以下です。
- initVideoEncoder呼び出し
- 以下をフレーム数回ループ
- canvasに画像を生成
- addFrame呼び出し
- finishVideoEncoder呼び出し
中身を順次解説します。
1.initVideoEncoder呼び出しでVideoEncoder初期化
まず、initVideoEncoder で VideoEncoder を初期化します。
//***************************************************************** // initVideoEncoder : ビデオエンコーダーを生成し初期化 // 引数 // oCanvas .. 対象のcanvasエレメントオブジェクト // 注意 // async のため 呼び出し側で await する //***************************************************************** async function initVideoEncoder ( oCanvas ) { // エラー発生時に取得できるように try ~ catch する try { // ビデオエンコーダー生成 oVideoEncoder = new VideoEncoder({ // エンコード済データの処理 output: ( oVideoFrame ) => { // バイナリデータ長のバッファを用意 let aData = new Uint8Array( oVideoFrame.byteLength ); // データをコピー oVideoFrame.copyTo( aData ); // エンコードデータ配列に追加 aaFrame.push( aData ); }, // エンコードがエラーになったときの処理 error: (e) => { // エラーをコンソールに表示 console.error( 'Encoder error:' , e ); } }); // 初期設定 await oVideoEncoder.configure({ codec: 'avc1.42E01E', // コーデック width: oCanvas.width, // 横幅 height: oCanvas.height, // 縦幅 bitrate: 5_000_000, // ビットレート framerate: 30, // フレームレート(fps) avc: { format:'annexb' }, // annexbフォーマット指定 }); } // エラー発生時の処理 catch (e) { // エラーメッセージ表示 alert( 'サポート対象外のWebブラウザーです' ); // エラー throw e; } }
initVideoEncoder
initVideoEncoder は、VideoEncoder を初期化する、当ブログ自作関数です。
WebCodecs の VideoEncoder のインスタンスを生成し、configure() で初期化します。 この configure が非同期のため、initVideoEncoder を async 関数として、await で処理完了を待てるようにしています。
output
output には、エンコードされたデータオブジェクトを受け取る関数を記述します。 引数は VideoFrame オブジェクトです。
VideoFrameはエンコードされたデータを保持していますが、使用者側でメモリーコピーして保存しておく必要があります。 そのための copyTo メソッドが用意されているので、必要なメモリーを確保して、そこにコピーします。
そして、後の自作処理で使えるように、フレームごとのデータとして、当ブログ自作変数の aaFrame 配列に push で追加しておきます。
configure設定内容
configureの設定内容を以下に補足します。
codec
コーデックは 'avc1.42E01E' としました。'avc1' は h.264 コーデックの識別子です。 h.264 は mp4 のコーデックです。
'42E01E' は、スマートフォンなど処理能力に制限のあるデバイスを想定した設定です。
width,height
映像サイズはそれぞれ16の倍数にする必要があります。 拡張仕様があるようで、300x300 のサイズで動く環境があることも確認していますが、 基本的な仕様上16ピクセルの倍数と規定されているので、そうしておくのが良いと思います。また、環境により、最大サイズに制限があります。 ここでは、手元で確認した範囲で最小の、640 ピクセルを最大としています。
bitrate
ビットレートは、生成AI(Gemini2)に、このコード生成をお願いしたときに記載されていた数値、そのままにしています。 5_000_000 は 5Mbps です。1.5Mbps程度でも良いとは思いますが、自作される場合は調整してください。
framerate
フレームレートは 30fps をデフォルトとしています。なお、この記事などでは、mp4動画以外に、gifアニメーションも生成できます。 その場合、1フレームの長さの単位が 10ms のため、30fps は表現できません。 近い値としてフレーム長 30ms なら 33.33fps になります。
format:'annexb'
avc の format で annexb を指定します。ここは試行錯誤しましたが、明示的に annexb を指定することで、必要な情報が取得できるようになりました。 この詳細は NAL Unit に関するこちらの記事で解説しています。
2.canvas画像をaddFrame呼び出しでエンコード
//***************************************************************** // addFrame : フレームをエンコードして追加 // 引数 // oCanvas .. 対象のcanvasエレメントオブジェクト // iElapsedTime .. 動画開始からの経過時間(マイクロ秒) // 注意 // async関数 //***************************************************************** async function addFrame( oCanvas , iElapsedTime ) { // ビデオフレームオブジェクトの生成 let oFrame = new VideoFrame( oCanvas , { timestamp:iElapsedTime , duration:33333 } ); // エンコード await oVideoEncoder.encode( oFrame ); // ビデオフレーム終了 oFrame.close(); }
addFrame
addFrame は、今の canvas の画像をフレームに追加する、当ブログ自作関数です。
initVideoEncoder で初期化した後に、「canvas に画像を生成して addFrame を呼ぶ」を、動画フレーム数回繰り返す想定で作られています。
本関数内部では、VideoFrame を生成して、 VideoEncoder.encode を呼び出し、終了を待って close します。
duration
durationには、このフレームを表示する長さをマイクロ秒で指定します。 30fpsの場合 33333.3333… ですが、整数値ですので、近い値を設定すれば問題ありません。
3.finishVideoEncoder呼び出しで終了
//***************************************************************** // finishVideoEncoder : 終了処理 // 注意 // async関数 //***************************************************************** async function finishVideoEncoder() { // 途中の処理があれば強制的に完了させる await oVideoEncoder.flush(); // ビデオエンコーダー終了 oVideoEncoder.close(); }
finishVideoEncoder
finishVideoEncoder は、動画エンコード処理を終了する、当ブログ自作関数です。 強制的に完了させ、終わり次第終了します。
aaFrame 配列を次の処理へ
一連の処理で aaFrame 配列にエンコードデータが入ります。 これを、この記事の、「エンコード結果から NAL Unit を取得する処理」へ渡します。
実践コード
本記事のコードは、読みやすく整形して開示しています。 実際のコードは、設定変更に対応するなど、より実践的で、視認性は劣ります。
もし参考にされたい場合は、 この記事など、各mp4クリエイター記事のソースから、import 先を調べてみてください。 プログラムを読める方に限定するため、ここではあえて不親切に紹介させていただきますが、コードの再利用は、悪意の無い範囲でしたら、まったく問題ありません
既存ツールについて
mp4動画を作成するツールとしては、ffmpegやmp4boxといった、既存のものがあります。 これらを利用しなかった理由は、エンコードにおいて、特許などの権利関係が不明だったからです。
実際には問題ないかもしれませんが、本記事のように、WebCodecsを用いて、Webブラウザーの機能を利用すれば、 エンコードの権利に関する問題は回避できるので、利用して自作することにしました。 実際、本記事の内容は、ほぼ生成AI(Gemini2)が作ってくれましたので、簡単でした。
ただし、これ以外の部分が当初想像していたよりかなり多く、大変でした。 それは次回以降の解説に続きます。
[PR]
まとめ
本記事では、mp4動画ファイルを生成する工程のうち、WebCodecsのVideoEncoderで各フレーム画像をエンコードする部分について解説しました。 残り3ステップも順次解説します。
補足
- WebCodecs API は比較的新しい技術のため、未対応のWebブラウザーもまだ多く使用されているかもしれません。
- oVideoEncoder と aaFrame は、実践ではオブジェクトのメンバ変数として定義されていますが、本記事ではグローバル変数と読み替えてください。
- 本記事に記載のプログラムソースコードは、悪意のない範囲で自由に使用・改変していただいて問題ありません。ただし、ご自身の判断と責任でお願いします。
- 画像内のラスタライズ文字フォントにOpen Font LicenseのNoto Sans Monoを使用しております。
- ※各社の登録商標または商標について「®」「™」等の表記はしておりません。
- (本記事公開後)本記事公開後に公開された記事へのリンクを追加しております。
- (本記事公開後追記) マイクロ秒を指定する箇所について、当ブログの実装はミリ秒単位で扱っているため、例えば duration には33333ではなく33000を設定しています。
カテゴリー:プログラミング解説,mp4ファイル生成解説
[PR]
