はじめに
こんにちは、hirataraです。
本稿はFFmpeg APIの解説の後編となります。前編をご覧になっていない方は、前編からお読みください。
前回の記事
サンプル: サムネイル画像を作る 後編
前編では、動画から最初のフレームを取り込むプログラムを作成しました。後編では、このフレームをFFmpeg APIを使って書き出します。
フレームを静止画としてファイルに書き込む
それでは、取得したフレームを書き出す処理を書いてみます。今回のサンプルでは動画ではなく画像として書き出すので、実はlibswscaleを使ってフォーマットを変更した物を直接ファイルに出力するだけでもこの仕様を満たすことができます。しかし、FFmpeg APIのエンコーダの利用方法の説明を兼ねるため、ここではあえて"image2"フォーマットを利用して動画と同じようにエンコーダ経由で画像を出力してみます。
main
ルーチンより、フレームの保存処理を次のように呼びます。save_frame()
は、これから自分で実装する処理です。
/** * フレームを画像として保存する * @param[in] frame 保存するフレーム * @param[in] filename 保存先のPATH */ void save_frame(const AVFrame *frame, const char *filename); int main(int argc, char *argv[]){ /* ... フレーム読み込み処理(前編で実装済) ...*/ /* フレームの保存 */ save_frame(frame, argv[2]); /* ... 後略 ...*/ }
save_frame
内の処理は、次のような流れとなります。
- 出力するフォーマットの情報を生成
- ストリームを開き、動画コーデックを割り当てる
- コーデック、ファイルを開き、書き込み準備をする
- ヘッダを書き込む
- パケットを符号化し、書き込む
- 書き込むパケットが無くなるまで5を続ける
- 利用したメモリを解放し、終了する
出力するフォーマットの情報を生成
読み込みの時、AVFormatContext
構造体は、既存の動画ファイルを読み込みフォーマットやコーデックを自動判別することで作られました。しかし、書き込みの際は自分で作りたい動画のフォーマットを決定し、AVFormatContext
構造体を設定しなければなりません。
以下のコードで、フォーマットを準備しています。
/* 画像出力のフォーマットを準備 */ AVOutputFormat *format = guess_format("image2", NULL, NULL); if(format == NULL) error("can't find image2 format."); /* フォーマットコンテキストを作成 */ AVFormatContext *formatCtx = av_alloc_format_context(); if(formatCtx == NULL) error("can't alloc format context."); formatCtx->oformat = format; av_strlcpy(formatCtx->filename, filename, sizeof(formatCtx->filename));
guess_format
はlibavformat/avformat.hで宣言されている関数で、出力フォーマットを得るために利用します。第1引数がフォーマット名、第2引数がファイル名、第3引数がMIME名となっており、これらの情報から判断して1番ふさわしい物を返してくれます。今回は"image2"フォーマットを使うとすでに決めているので、この値を渡しています。
続くav_alloc_format_context
では、フォーマット情報を格納するためのAVFormatContext
構造体を確保しています。この構造体のoformat
フィールドにAVOutputFormat
を、filename
フィールドにはファイル名をセットします。
なお、ファイル名をセットするために使っているav_strlcpy
はlibavutil/avstring.hで宣言されており、文字列のコピーを行うユーティリティ関数となっています。filename
フィールドには、文字列の先頭文字へのポインタではなく、文字配列のコピーを実際に入れる必要があるため、この関数を利用してコピーを行っています。