CodeZine(コードジン)

特集ページ一覧

FFmpeg APIで、さまざまな動画を操る - 前編

自作アプリケーションに動画の読み込み処理を組み込む

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/06/20 14:00
目次

コーデックを開き、読み込みの準備を行う

 読み込みたいストリームに対するコーデックの情報が手に入ったので、そのコーデックを開いて動画のデコードが行える状態にします。

 コードは、次のようになります。

コーデックを開く
/* 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_decoderavcodec_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:ブロックを作って必ずパケットを解放するようにしています。

 後は、whileav_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に飛び、パケットを解放してもう一巡してフレームが復号されるのを待ちます。

なぜフレームが復号されないことがあるのか
 パケットには動画1フレームが含まれていいます。それにも関わらず、なぜ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フレームは手前のフレームだけで復号できるフレームです。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2008/06/25 23:40 手順に不具合がありサンプルがビルドできませんでしたので、手順を修正しました。

  • 2008/06/25 17:30 サンプルファイルを一部修正しました。

バックナンバー

連載:FFmpeg APIで、さまざまな動画を操る

著者プロフィール

  • hiratara(ヒラタラ)

    1977年に苫小牧市で生まれる。北海道大学理学部数学科卒。小学生の頃、両親に買い与えられたMZ-2500でプログラミングを始めた。学生時代、CGIの自作に没頭し、それ以降WEB開発の魅力に憑かれる。社会人になっても数学好きは変わらず、専門書を買い集めるのが最近の趣味。 id:hirataraに...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5