SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

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

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

コーデック、ファイルを開き、書き込み準備をする

 変数の設定がすべて終わったので、次はコーデックとファイルを開きます。コードは次のようになります。

コーデックとファイルを開く
/* codecを開く */
ret = avcodec_open(codecCtx, codec);
if(ret != 0) error("can't open codec(encoder).");

/* 出力ファイルを開く */
if (! ( format->flags & AVFMT_NOFILE )) {
    ret = url_fopen(&formatCtx->pb, formatCtx->filename, URL_WRONLY);
    if (ret < 0) error("can't open output file.");
}

 avcodec_openについては、読み込みの時に使った物と同じ関数です。この関数でエンコーダも開くことができます。

 続く出力先のファイルを開く処理では、url_fopen関数を利用しています。この関数はlibavformat/avio.hで宣言されています。開いたファイルの情報はAVFormatContext構造体内に保管されます。

出力ファイルを開かない場合もある?
 実は、今回の出力フォーマットである"image2"フォーマットでは、ファイルを開く必要はありません。"image2"フォーマットでは、出力ファイル名を"image%d.jpeg"等と指定し、image0.jpeg、image1.jpeg、...、と複数ファイルに連続をしてサムネイルを書き込むことができます。そのため、通常の動画ファイルのように1つのファイルを開いてそこにデータを流し込むという構造には、うまく適合しないのです。
 
 "image2"フォーマットのflagsフィールドにはAVFMT_NOFILEフラグが立っているので、url_fopenは実際には飛ばされます。

ヘッダを書き込む

 動画データを書き出し始める前に、ファイルのヘッダ部を書き込みます。この処理は、libavformat/avformat.hで宣言されているav_write_header関数を呼ぶだけです。

ヘッダの書き込み
/* ファイルへ、ヘッダの書き込み */
av_write_header(formatCtx);

パケットを符号化し、書き込む

 ここから、いよいよパケットの書き出しループとなります。この部分のコードの概要は、次のようになります。

パケット書き出しループ
/* エンコードに利用するバッファを用意する */
int buf_size = codecCtx->width * codecCtx->height * 4;
uint8_t *buf = av_malloc(buf_size);

/* ファイルにコンテンツを書き込む */
while(1){
    /* コーデックでデータを符号化 */

    /* 符号化したデータをパケットに詰め込む */

    /* パケットを書き出す */
}

 ループに入る前に、符号化したデータを入れるバッファを用意しています。1フレームのデータの大きさは、圧縮しない状態では最大で「縦×横×4 byte」となるので、想定される最大値を用意しています。4というのは、赤、緑、青、不透明度、の4データです。

 バッファが用意できたらループに入ります。書き出すフレームがなくなるまで、延々とループさせます。

コーデックでデータを符号化

 まず、対象フレームを符号化します。符号化の結果は、先ほど用意したバッファに保存します。

フレームの符号化処理(ループ内)
/* フレームを符合化する */
int out_size = avcodec_encode_video (
    codecCtx, buf, buf_size, frame
);

/* 符号化がまだの場合は、次のフレームへ */
if (out_size == 0){
    continue;
} else if (out_size < 0){
    error("can't encode frame.");
}

 読み込みにはavcodec_decode_videoを利用しましたが、書き込みではavcodec_encode_videoを利用します。読み込みのときと同様の理由で、フレームを渡しても符号化が先送りにされてバッファにデータが入って来ない場合があるので、データが来ない場合は次にフレームに処理を移しています。

符号化したデータをパケットに詰め込む

 パケットデータを作成するコードは次です。

パケットデータの作成(ループ内)
/* 書き込むパケットを準備 */
AVPacket packet;
av_init_packet(&packet);

packet.stream_index= stream->index;
packet.data= buf;
packet.size= out_size;

/* codecのtime baseによるptsをstreamのtime baseによるものに修正 */
packet.pts= av_rescale_q(
    codecCtx->coded_frame->pts, 
    codecCtx->time_base, stream->time_base
);
if(codecCtx->coded_frame->key_frame)
    packet.flags |= PKT_FLAG_KEY;

 まず、libavformat/avformat.hで宣言されているav_init_packet関数を利用し、AVPacket構造体を初期化します。その後、packetに対してstream_index(書き込み先のストリームID。ここでは最初に開いたストリーム)、data(書き込むデータ。ここでは符号化したデータ)、size(書き込みバイト数)をセットしています。

 その後、PTSとキーフレームフラグを指定しています。

 まず、PTS(Presentation Time Stamp)の指定部分です。PTSとは、そのフレームを画面に表示するタイミングを指定するための値ですが、これをパケットに指定する必要があります。このとき、AVFrameが持っているPTSの時間単位(time_base)と書き出そうとしているストリームの時間単位が異なることがありますので、これを単位変換しなければなりません。

 time_baseフィールドはAVRational構造体となっていますが、これは分数(有理数)を表す構造体です。av_rescale_qはlibavutil/mathematics.hで宣言される関数で、64ビット整数とAVRationalに対して単純にA×B÷Cという算術演算を行います。

 PTSと時間単位の関係は、次の等式で表されるはずです。

  • フレームのPTS × コーデックの時間単位 = パケットのPTS × ストリームの時間単位

 よって、これを変形すると、

  • パケットのPTS = フレームのPTS × コーデックの時間単位 ÷ ストリームの時間単位

 となります。コードを見てみると、確かにこのとおりの計算を行っています。

 次にセットしているキーフレームフラグとは、文字通りキーフレームであることを示すフラグです。キーフレームとは、MPEG圧縮で言えばIフレームのことで、復号に他のフレームを必要としないフレームのことです。フレームがキーフレームの場合は、このフラグをパケットにも立てておきます。

パケットを書き出す

 最後にこのパケットを書き出します。読み込みにはav_read_frameを使いましたが、書き込みにはav_interleaved_write_frameを使います。av_write_frameという関数もありますが、av_interleaved_write_frameだとパケットの書き出し順が正しくなるのを保証してくれるとのことですので、こちらの方が安心でしょう。

パケットの書き出し(ループ内)
/* パケットを書き込む */
int ret = av_interleaved_write_frame(formatCtx, &packet);
if(ret != 0) error("can't write frame.");

/* 1フレームだけ書き込めればいいので、終了 */
break;

 なお、今回は先頭フレームを書き出すだけですので、パケットを1つ書き込んだらこれ以上処理を続ける必要がないのでbreakでループを抜けることにしました。通常の動画を書き出す場合は、ここまでのループを、書き出すフレームがなくなるまで続けることになります。

次のページ

この記事は参考になりましたか?

FFmpeg APIで、さまざまな動画を操る連載記事一覧
この記事の著者

hiratara(ヒラタラ)

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

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/2622 2008/07/14 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー