12

そのため、サムネイルとしてlibavを使用して、特定の時間にビデオからフレームを取得したいと考えています。

私が使用しているのは次のコードです。コンパイルして正常に動作します (画像の取得に関してはまったく) が、正しい画像を取得するのに苦労しています。

ビデオごとに複数のタイムベースを libav が使用しているように見える背後にある明確なロジックを除いて、まったく理解できません。具体的には、どの関数がどのタイプのタイムベースを期待/返すかを把握します。

残念ながら、ドキュメントは基本的にまったく役に立ちませんでした。SO救助に?

#define ABORT(x) do {fprintf(stderr, x); exit(1);} while(0)

av_register_all();

AVFormatContext *format_context = ...;
AVCodec *codec = ...;
AVStream *stream = ...;
AVCodecContext *codec_context = ...;
int stream_index = ...;

// open codec_context, etc.

AVRational stream_time_base = stream->time_base;
AVRational codec_time_base = codec_context->time_base;

printf("stream_time_base: %d / %d = %.5f\n", stream_time_base.num, stream_time_base.den, av_q2d(stream_time_base));
printf("codec_time_base: %d / %d = %.5f\n\n", codec_time_base.num, codec_time_base.den, av_q2d(codec_time_base));

AVFrame *frame = avcodec_alloc_frame();

printf("duration: %lld @ %d/sec (%.2f sec)\n", format_context->duration, AV_TIME_BASE, (double)format_context->duration / AV_TIME_BASE);
printf("duration: %lld @ %d/sec (stream time base)\n\n", format_context->duration / AV_TIME_BASE * stream_time_base.den, stream_time_base.den);
printf("duration: %lld @ %d/sec (codec time base)\n", format_context->duration / AV_TIME_BASE * codec_time_base.den, codec_time_base.den);

double request_time = 10.0; // 10 seconds. Video's total duration is ~20sec
int64_t request_timestamp = request_time / av_q2d(stream_time_base);
printf("requested: %.2f (sec)\t-> %2lld (pts)\n", request_time, request_timestamp);

av_seek_frame(format_context, stream_index, request_timestamp, 0);

AVPacket packet;
int frame_finished;
do {
    if (av_read_frame(format_context, &packet) < 0) {
        break;
    } else if (packet.stream_index != stream_index) {
        av_free_packet(&packet);
        continue;
    }
    avcodec_decode_video2(codec_context, frame, &frame_finished, &packet);
} while (!frame_finished);

// do something with frame

int64_t received_timestamp = frame->pkt_pts;
double received_time = received_timestamp * av_q2d(stream_time_base);
printf("received:  %.2f (sec)\t-> %2lld (pts)\n\n", received_time, received_timestamp);

これをテストムービーファイルで実行すると、次の出力が得られます。

    stream_time_base: 1 / 30000 = 0.00003
    codec_time_base: 50 / 2997 = 0.01668

    duration: 20062041 @ 1000000/sec (20.06 sec)
    duration: 600000 @ 30000/sec (stream time base)
    duration: 59940 @ 2997/sec (codec time base)

    requested: 10.00 (sec)  -> 300000 (pts)
    received:  0.07 (sec)   -> 2002 (pts)

時期が合わない。何が起きてる?私は何を間違っていますか?


手がかりを探しているときに、libav-users メーリング リストからこのステートメントを見つけました…</p>

[...]パケットの PTS/DTSは、フォーマット コンテキストの time_base の単位で
あり、AVFrame->pts値はコーデック コンテキストの time_base の単位です。

つまり、コンテナはコーデックとは異なる time_base を持つことができます (通常はそうします)。ほとんどの libav プレイヤーは、コーデックの time_base や pts をわざわざ使用することはありません。すべてのコーデックにあるわけではありませんが、ほとんどのコンテナーにはあるからです。(これが、drager のチュートリアルで AVFrame->pts を無視するように指示されている理由です)

…公式ドキュメントにそのような言及が見つからなかったため、さらに混乱しました。

とにかく、交換しました…</p>

double received_time = received_timestamp * av_q2d(stream_time_base);

…と…</p>

double received_time = received_timestamp * av_q2d(codec_time_base);

…そして出力はこれに変わりました…</p>

...

requested: 10.00 (sec)  -> 300000 (pts)
received:  33.40 (sec)  -> 2002 (pts)

まだ一致しません。どうしたの?

4

2 に答える 2

18

おおむね次のようになります。

  • ストリームのタイムベースは、あなたが本当に興味を持っているものです。これは、パケットのタイムスタンプが含まれているものでありpkt_pts、出力フレームにもあります (対応するパケットからコピーされたばかりなので)。

  • コーデック タイムベースは (設定されている場合)、コーデック レベルのヘッダーに書き込まれる可能性のあるフレームレートの逆数です。コンテナーのタイミング情報がない場合 (生のビデオを読んでいる場合など) に役立ちますが、それ以外の場合は無視しても問題ありません。

  • AVFrame.pkt_pts は、このフレームにデコードされたパケットのタイムスタンプです。既に述べたように、これはパケットからの単なるコピーであるため、ストリーム タイムベースにあります。これは、使用するフィールドです (コンテナーにタイムスタンプがある場合)。

  • AVFrame.pts は、デコード時に有用なものに設定されることはありません。無視してください (pkt_pts全体の混乱を軽減するために、将来的に置き換える可能性がありますが、現在は主に歴史的な理由から、このようになっています)。

  • 形式コンテキストの持続時間はAV_TIME_BASE(つまりマイクロ秒) です。それぞれが独自のタイムベースを持つ 3 つの膨大な数のストリームを持つことができるため、どのストリーム タイムベースにも置くことはできません。

  • シーク後に別のタイムスタンプを取得する際に発生する問題は、単純にシークが正確でないことです。ほとんどの場合、最も近いキーフレームしかシークできないため、数秒ずれることはよくあることです。不要なフレームのデコードと破棄は、手動で行う必要があります。

于 2013-09-17T06:44:12.530 に答える
10

交換しました

av_seek_frame(format_context, stream_index, request_timestamp, 0);

avformat_seek_file(format_context, stream_index, INT64_MIN, request_timestamp, INT64_MAX, 0);

そして突然、妥当な出力が得られます。偉大な。
そして、ほぼ完全なドキュメントの暗闇の中で、たった1日しかかかりませんでした. :/

于 2013-09-17T14:58:44.863 に答える