使用libav(ffmpeg)使用时间戳/时基进行帧搜索/读取有什么问题?

mtr*_*ree 10 video ffmpeg libav

因此,我想使用libav 在特定时间从视频抓取一个帧作为缩略图使用.

我正在使用的是以下代码.它编译和工作正常(关于检索图片),但我很难找到它来检索正确的图片.

我根本无法理解libav明显使用每个视频的多个时基背后的明确逻辑.具体说明哪些函数期望/返回哪种类型的时基.

不幸的是,文档基本上没有任何帮助.那么救援?

#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);
Run Code Online (Sandbox Code Playgroud)

使用测试影片文件运行此命令,我得到以下输出:

    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)
Run Code Online (Sandbox Code Playgroud)

时代不匹配.这里发生了什么?我究竟做错了什么?


在搜索线索时,我偶然发现了libav-users邮件列表中的这个声明 ...

[...] 数据包PTS/DTS格式上下文的time_base为单位,
其中AVFrame-> pts值以编解码器上下文的time_base为单位.

换句话说,容器可以具有(并且通常具有)与编解码器不同的time_base.大多数libav播放器都不打扰使用编解码器的time_base或pts,因为并非所有编解码器都有一个,但大多数容器都有.(这就是dranger教程说忽略AVFrame-> pts的原因)

鉴于我在官方文件中找不到任何这样的提及,这让我更加困惑.

无论如何,我换了......

double received_time = received_timestamp * av_q2d(stream_time_base);
Run Code Online (Sandbox Code Playgroud)

...与...

double received_time = received_timestamp * av_q2d(codec_time_base);
Run Code Online (Sandbox Code Playgroud)

......输出改为......

...

requested: 10.00 (sec)  -> 300000 (pts)
received:  33.40 (sec)  -> 2002 (pts)
Run Code Online (Sandbox Code Playgroud)

仍然没有比赛.怎么了?

小智 18

它主要是这样的:

  • 流时基是你真正感兴趣的.它是数据包时间戳所在的,也是pkt_pts输出帧的(因为它只是从相应的数据包中复制而来).

  • 编解码器时基(如果设置的话)只是可能在编解码器级头中写入的帧速率的倒数.在没有容器计时信息的情况下(例如,当您正在阅读原始视频时),它可能很有用,但可以安全地忽略.

  • AVFrame.pkt_pts是解码到此帧的数据包的时间戳.如前所述,它只是数据包的直接副本,因此它位于流时基中.这是您要使用的字段(如果容器有时间戳).

  • AVFrame.pts在解码时没有设置任何有用的东西,忽略它(它可能pkt_pts在将来取代,使整个混乱变得更加混乱,但是现在它就像这样,主要是出于历史原因).

  • 格式上下文的持续时间是AV_TIME_BASE(即微秒).它不能存在于任何流时基中,因为您可以拥有三个无数的流,每个流都有自己的时基.

  • 你在寻求之后获得不同时间戳的问题只是寻求不准确.在大多数情况下,您只能寻找最近的关键帧,因此通常需要几秒钟.解码和丢弃不需要的帧必须手动完成.

  • 我的问题有点不清楚.当然,我没想到完全相同的时间戳.但它们至少在某种程度上应该与所要求的时间戳相似,不是吗?与此同时,我设法通过使用`avformat_seek_file`而不是`av_seek_frame`来修复它.感谢您对libav中时间戳的精彩解释.有一个upvote! (2认同)

mtr*_*ree 10

我换了

av_seek_frame(format_context, stream_index, request_timestamp, 0);
Run Code Online (Sandbox Code Playgroud)

avformat_seek_file(format_context, stream_index, INT64_MIN, request_timestamp, INT64_MAX, 0);
Run Code Online (Sandbox Code Playgroud)

突然间我得到了合理的输出.大.
而且在几乎完整的文档黑暗中只花了一天时间.:/