動画から1フレームを得る(load_frame関数の実装)
ここまでで、前処理が完了しましたので、いよいよ動画を読み込む処理を見て行きましょう。main()関数からは、先ほど確保したフレームを出力先として利用し、次のように呼び出すこととします。
/**
* 動画から最初のフレームを読み込む
* @param[out] output_frame 読み込んだフレームの出力先
* @param[in] filename 動画ファイルのPATH
*/
void load_frame(AVFrame *output_frame, const char *filename);
/*main関数 */
int main(int argc, char *argv[]){
/* ... FFmpegの初期化(略) ...*/
/* ... サムネイルの受け取り場所を確保(略) ...*/
/* フレームのロード */
load_frame(frame, argv[1]);
/* ... 後略(後編で作成) ...*/
}
load_frame()の処理は、おおよそ次のような流れで実行していきます。
- 動画ファイルのフォーマットをFFmpegに解析させる
- 動画ファイルの中から、動画ストリームを探し出す
- コーデックを開き、読み込みの準備を行う
- パケットを読み込む
- パケットからフレームを復元する
- 目的のフレームが得られるまで、4と5を繰り返す
- 利用したメモリを解放し、終了する
動画ファイルのフォーマットをFFmpegに解析させる
動画ファイルを読み込むには、当然対象となる動画ファイルのフォーマットやコーデックを正確に把握し、それに合致した手順で読み込まなくてはなりません。FFmpegでは、動画ファイルからこれらの情報を予想するための関数が提供されています。
以下のコードを見てください。
/* ファイルのヘッダを読み、フォーマットを得る */ AVFormatContext *formatCtx; ret = av_open_input_file(&formatCtx, filename, NULL, 0, NULL); if(ret != 0) error("can't open input file."); /* ファイルの中身からストリーム情報を得る */ ret = av_find_stream_info(formatCtx); if(ret < 0) error("can't find stream info.");
構造体AVFormatContextは「libavformat/avformat.h」で定義されています。この構造体には、対象となるファイルの入出力フォーマットとI/Oにかかわる情報が保存されています。具体的には、ファイル名や入出力ストリーム、メタ情報として動画のタイトルや著者など、となります。
av_open_input_file()も「libavformat/avformat.h」に宣言があります。この関数では、動画ファイルのヘッダ部を読み込むことで、動画フォーマットを判定します。開いたファイルハンドルや判明したフォーマット情報は、すべてformatCtxへ保存されます。
続くav_find_stream_info()も「libavformat/avformat.h」で宣言される関数で、パケットを先読みして動画ファイルに含まれるストリームの情報を判別します。動画ファイルには動画だけではなく、音声など、種々のストリームが含まれています。この関数を呼ぶことで、含まれるストリームとその種類が分かるようになります。
動画ファイルの中から、動画ストリームを探し出す
今回のサンプルではサムネイルを作成するため、実際に読み込みたいのは動画情報を含むストリームです。複数ある(かもしれない)ストリームの中から、サムネイル作成の対象とするフレームをあらかじめ見つけておきます。
この部分のコードは、次のようになります。
/* ビデオストリームを探す */ int streamIndex = -1; for(i = 0; i < formatCtx->nb_streams; i++){ if(formatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO){ /* ビデオストリームを発見 */ streamIndex = i; break; } } if(streamIndex < 0) error("can't find video stream."); AVCodecContext *codecCtx = formatCtx->streams[streamIndex]->codec;
formatCtx->nb_streamsにはこの動画ファイルが含んでいるストリームの本数が含まれており、formatCtx->streamsには実際のストリーム(AVStream)が含まれています。これらを利用し、ストリームを0から順番に調べています。ビデオ情報を含んでいるかどうかは、streams[i]->codecに含まれる、ストリーム内のコーデック情報(AVCodecContext)を参照し、コーデックの種類で判定しています。コーデックの種類はcodec->codec_typeに含まれるenum CodecTypeという列挙型です。ストリームが動画を含む場合、コーデックの種類はCODEC_TYPE_VIDEOとなります。
目的のストリームが見つかったら、次の処理のためにそのコーデック情報をcodecCtxへ保存しておきます。
enum CodecTypeは「libavcodec/avcodec.h」で定義されており、他に以下の種類があります。CODEC_TYPE_UNKNOWNCODEC_TYPE_VIDEOCODEC_TYPE_AUDIOCODEC_TYPE_DATACODEC_TYPE_SUBTITLECODEC_TYPE_ATTACHMENT
CODEC_TYPE_NBが定義されていますが、これはコーデック種類の個数を表すのに利用されています(_NBはnumberの略だと推測されます)。例えば、「ffmpeg.c」ではCODEC_TYPE_NBを次のように利用しています。static AVCodecContext *avctx_opts[CODEC_TYPE_NB]; ... for(i=0; i<CODEC_TYPE_NB; i++){ avctx_opts[i]= avcodec_alloc_context2(i); }
