コーデックを開き、読み込みの準備を行う
読み込みたいストリームに対するコーデックの情報が手に入ったので、そのコーデックを開いて動画のデコードが行える状態にします。
コードは、次のようになります。
/* codecを探す */ AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id); if(codec == NULL) error("can't find codec(decoder)."); /* codecを開く */ ret = avcodec_open(codecCtx, codec); if(ret != 0) error("can't open codec(decoder).");
まず先に、AVCodecContextについて説明しておきましょう。この構造体は「libavcodec/avcodec.h」に定義されており、ビットレートや時間の単位、画像のサイズ、その他コーデックに渡すフラグなど、コーデックに関する情報を保持しています。この中にenum CodecID型のcodec_idというフィールドがあり、これが実際のコーデックの識別子となります。
この識別子をavcodec_find_decoderに渡すと、デコードに必要なコーデックが返ってきます。ここで_decoderとなってることから推測できるかと思いますが、エンコードに必要なコーデックを入手するためのavcodec_find_encoder関数も存在します(後編で利用)。
デコーダを入手できたら、次はこのデコーダをavcodec_open関数に渡します。この関数を呼ぶと、codecに適切な初期化処理を行ってcodecCtxへセットし、利用可能な状態にしてくれます。
なお、avcodec_find_decoderもavcodec_openも「libavcodec/avcodec.h」で宣言されており、実際の定義は「libavcodec/utils.c」あります。
パケットを読み込む
動画ファイルのデータを読み込むには、av_read_frameを利用します。この関数を呼び出すと、AVPacket構造体にファイルから1パケットが読み込まれます。動画の場合、パケットにはフレームが1つ含まれています。
ただし、av_read_frameで入手できるパケットは、動画だけではなく音声などのストリームから得られた物も含んでおり、これらのパケットが時系列の順に読み込まれていきます。今回欲しいのは動画の情報だけですので、あらかじめ見つけておいた動画ストリーム(streamIndex)から得られたパケットだけを利用します。packet.stream_indexに、出元のストリーム番号が入っているため、これを元に動画ストリームかどうかを判定しています。
なお、av_read_frameで入手したパケットは、自分で解放する必要があるとドキュメントに書かれています。パケットを解放するには、av_free_packetを利用します。これを必ず呼び出す必要があるので、LAST:ブロックを作って必ずパケットを解放するようにしています。
後は、whileでav_read_frameがパケットを返さなくなるまでループをし続ければ、ファイル全体を読み込むことができます。
ここまでのコードは以下です。
/* ファイルからパケットを読み込む */ AVPacket packet; while(av_read_frame(formatCtx, &packet) >= 0){ /* 動画ストリーム以外は飛ばす */ if(packet.stream_index != streamIndex) goto LAST; /*... パケットの復号処理 ...*/ LAST: /* パケットを解放 */ av_free_packet(&packet); }
ちなみに、av_read_frameは「libavformat/avformat.h」に宣言されています。av_free_packetも同様に「libavformat/avformat.h」にありますが、こちらはインライン関数として直接定義されています。AVPacket構造体の定義もこのファイル内にあります。
パケットからフレームを復元する
AVPacketに含まれているフレームは符号化されているため、このままでは利用できません。これをコーデックで復号することで、参照可能なフレームを得ます。
得たフレームは、最終的にはoutput_frameに保存しますが、その前に一時領域で受け取ります。パケットの読み込みループに入る前に、次のように一時領域を用意します。
/* データを受け取るフレームの作成 */ AVFrame *frame = avcodec_alloc_frame(); if(frame == NULL) error("can't allocate a frame to store data.");
init_frameでは、画像のバッファを自前で確保しましたが、ここで用意したフレームではコーデックが画像領域の面倒を見てくれるため、AVFrame構造体の領域だけ確保しています。例えば、"mpeg4"や"h264"などのデコーダでは、復号したフレームは「libavcodec/mpegvideo.h」の
MpegEncContext構造体のpictureフィールドに格納されており、これらの領域は「libavcodec/mpegvideo.c」が確保・解放の管理をしています。この一時領域へ、パケットを復号してできたフレームを格納していきます。パケットの読み出しループの中で、次のようなコードで処理をします。
int isFinish = 0; while(パケット読み出し){ /* ... 前略 ... */ /* パケットからフレームを復号する */ avcodec_decode_video(codecCtx, frame, &isFinish, packet.data, packet.size); /* 復号がまだの場合は次のパケットまで処理を飛ばす */ if(! isFinish) goto LAST; /* ... 後略 ... */ }
avcodec_decode_videoは「libavcodec/avcodec.h」で宣言されている関数で、パケットからフレームを復号します。引数がちょっと多いので解説します。
| 引数 | 説明 |
| codecCtx | コーデックの情報 |
| frame | ここにフレームが出力される |
| &isFinish | ここに結果が出力される。0だと、フレームが出力されなかったことを意味する。 |
| packet.data | 入力バッファ |
| packet.size | 入力バッファのサイズ |
avcodec_decode_videoの結果、isFinishに0が代入されていれば、フレームが生成できなかったのでLASTに飛び、パケットを解放してもう一巡してフレームが復号されるのを待ちます。
avcodec_decode_videoを読んでもフレームが復号されないことがあるのでしょうか?それは、動画の圧縮技術に関係があります。MPEGでは圧縮のためにフレーム間の変化を予測をして符号化しますが、これを前フレームからの予測だけではなく、後フレームからの予測でも行うことがあります。
例えば、1-2-3-4という4フレームがあった場合、MPEGによる圧縮では、2と3のフレームを1と4を元にして圧縮することがあります。そうすると、符号化の順番の関係で、ファイルにはこのフレームが1-4-2-3という順で符号化されて記録されることがありえるのです。このとき、動画ファイルを前から読み込むと1の次は4のフレームが含まれたパケットが返ってきますが、次に返さなければいけないのは2のフレームです。そのため、
avcodec_decode_videoを呼んで4のフレームを復号してもその場では返すことができず、次の2のフレームが出てくるのを待つという動きをします。この例の2と3のように、前後のフレームを元に符号化されるフレームをBフレーム(Bi-directional Predicted Frame)と呼びます。また、1や4のような、Bフレームの元となるフレームをIフレーム(Intra-coded Frame)やPフレーム(Predicted Frame)と呼びます。Iフレームは他のフレームがなくても復号できるフレームで、Pフレームは手前のフレームだけで復号できるフレームです。
