FFmpeg APIの使い方(2): シークやAVFrameなど

前回は、ただデコードしただけでしたが、今回はもう少しいろいろやってみます。

シーク

ファイルの途中から再生したり、スキップしたりする場合、av_seek_frameでシークします。

シークは少しコツがあります。av_seek_frameは以下のように定義されています。

timestampはフレーム表示時刻のタイムスタンプ(PTS, Presentation Time Stamp)で、stream_indexで指定したストリームの時刻で指定します。ドキュメントには、「timestampで指定されたキーフレームにシークする」と説明があります。av_seek_frameは、flagsに何も指定しないと、timestampで指定した時刻より後の最初のキーフレームにseekするようです。例えば、キーフレームのタイムスタンプが1200と1800だった場合、1500を指定してシークすると、1800にシークされます。これだとタイムスタンプ1500のフレームを取得できません。flagsにAVSEEK_FLAG_BACKWARDを指定すると、指定したタイムスタンプより前のキーフレームにシークしてくれます。先程の例の場合は1200にシークしてくれるので、そこからデコードしていけば1500のフレームも取得できます。

ただし、キーフレームへのシークがサポートされていないフォーマットもあります。また、私が試した限りでは、MPEG2-TSでは、タイムスタンプ指定のシークもうまくできないようでした。そのような場合は、AVSEEK_FLAG_BYTEを指定して、ファイルのバイト数で目的の位置を指定すれば、シークできます。

AVSEEK_FLAG_BYTEを指定した場合は、3番目の引数はタイムスタンプではなく、ファイルの先頭からのバイト数になります。

ただし、これだと目的のフレームをデコードするには、ファイルの何バイト目からデコードすればいいかが分かっていないとシークできません。解決方法としては、

  • バイナリサーチで目的のフレームを探索する
  • あらかじめファイルを全部読んで、キーフレームがどこにあったかなどのインデックスを作成しておく

のいずれかになると思います。L-SMASH WorksやAvisynthのFFmpegSourceは、後者の方法を取っています。

av_seek_frameでファイルをシークしたら、パケットをデコーダに入れる前に、デコーダをリセットする必要があります。av_seek_frameはAVFormatContextに対して呼び出すので、AVFormatContextと何の関係もないAVCodecContextは、シークされたことを知らないからです。そのままシーク後のパケットを渡すと、壊れたフレームが返ってきます。

これで、リセットできます。ただ、AVCodecContextを一旦削除して作り直したほうが安全のようです。

また、シークした後は、必ずキーフレームからしかデコードできませんが、ファイルによっては、なぜか、キーフレームでないフレームが最初にデコードされて、avcodec_receive_frameで返ってくることがあります。このフレームは壊れているので、キーフレーム前のフレームは捨てるようにした方が安全です。(バグなのか仕様なのか不明)

QSVデコード

デフォルトでは、ソフトウェアデコーダが使われるところを、HWデコーダを使ってみましょう。Quick Sync Video (QSV) は、Sandy Bridge以降のIntel CPUに搭載されているHWデコーダ・エンコーダです。これを使ってみましょう。前回のデコードサンプルで、AVCodecを探しているところ

これを、以下のように書き換えるとH264ではQSVを使ってくれるようになります。

ただし、QSVでデコードすると、通常のソフトウェアデコーダでは、avcodec_receive_frameでYV12フォーマットのフレームが返ってくるところが、QSVデコーダを使うとNV12で返ってくるので、そこは注意が必要です。

他のHWデコーダもavcodec_find_decoder_by_nameの引数の名前を変えるだけで対応できるはずです。

AVFrameのあれこれ

参照を増やす

ここまでのコードのデコードループにおいて、デコードされたフレームframeは、on_frame_decodedでしかアクセスできませんでした。

on_frame_decodedを抜けるとすぐに次のavcodec_receive_frameでframeが解放されてしまいます。フレームをバッファリングしたり、別スレッドに渡したりしたい場合は、すぐに解放されては困ります。フレームデータの生存管理は参照カウンタで行っているので、そういう場合はav_frame_refで新たに参照を作ってやります。例えば、フレームを溜め込む場合は、以下のようにすればできます。

AVFrameの解放は、データへの参照を持っている場合でも、持っていない場合でも、av_frame_freeを呼び出せばOKです。データへの参照を持っていた場合でも、よしなに処理してくれます。

画像データへのアクセス

frame->data, frame->linesizeでフレームの画像データにアクセスできます。ただし、画像のフォーマットが分からないと、dataに何が入っているのかが分かりません。frame->formatにフォーマット識別の番号がありますが、これだけでは、様々なフォーマットに対応しようとしたときに大変なので、フォーマット情報を取得します。

AVPixFmtDescriptorにプレーン数や、chromaの縦横サイズ、ビット深度などの情報が入っています。

同じプロパティの空フレーム作成

画像データをいじってフレームを追加したり、変更したりしたい場合、同じフォーマット、サイズのフレームを作りたいことがあると思います。以下のようなコードで同じフォーマット、サイズ、プロパティを持つ空フレームを作成できます。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です