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

Posted

技术标签:

【中文标题】我使用时间戳/时基使用 libav(ffmpeg)进行帧搜索/读取有啥问题?【英文标题】:What's wrong with my use of timestamps/timebases for frame seeking/reading using libav (ffmpeg)?我使用时间戳/时基使用 libav(ffmpeg)进行帧搜索/读取有什么问题? 【发布时间】:2013-09-21 03:12:18 【问题描述】:

所以我想使用 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);

用一个测试电影文件运行它,我得到这个输出:

    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 邮件列表中偶然发现了这个 this statement……

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

换句话说,容器可以(并且通常会)具有不同的 time_base 比编解码器。大多数 libav 播放器不会费心使用 编解码器的 time_base 或 pts,因为并非所有编解码器都有一个,但大多数 容器做。 (这就是为什么danger教程说忽略AVFrame->pts)

...这让我更加困惑,因为我在官方文档中找不到任何此类提及。

反正我换了……

double received_time = received_timestamp * av_q2d(stream_time_base);

…与…

double received_time = received_timestamp * av_q2d(codec_time_base);

……输出变成了这个……

...

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

还是不匹配。怎么了?

【问题讨论】:

【参考方案1】:

大多是这样的:

您真正感兴趣的是流时基。它是数据包时间戳所在的位置,以及输出帧上的pkt_pts(因为它只是从相应的数据包中复制而来)。

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

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

AVFrame.pts 在解码时从未设置为任何有用的值,忽略它(它可能会在未来取代 pkt_pts,以使整个混乱不那么混乱,但现在它是这样的,出于历史原因大部分)。

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

您看到在搜索后获得不同时间戳的问题仅仅是搜索不准确。在大多数情况下,您只能寻找最近的关键帧,因此通常会延迟几秒钟。解码和丢弃不需要的帧必须手动完成。

【讨论】:

我的问题有点不清楚。当然,我没想到会出现完全相同的时间戳。但它们至少应该在某种程度上类似于请求的时间戳,不是吗?与此同时,我设法通过使用avformat_seek_file 而不是av_seek_frame 来修复它。不过,感谢您对 libav 中时间戳的精彩解释。投赞成票!【参考方案2】:

我换了

av_seek_frame(format_context, stream_index, request_timestamp, 0);

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

突然间我得到了合理的输出。太好了。 在几乎完全的文档黑暗中只花了一天时间。 :/

【讨论】:

3 年后,ffmpeg docs still note avformat_seek_file “正在建设中。因此不要使用它。它可能随时更改,不要指望 ABI 兼容性!”

以上是关于我使用时间戳/时基使用 libav(ffmpeg)进行帧搜索/读取有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg 中的视频时标、时基或时间戳是啥?

使用 android studio 构建共享库(关于 FFMPEG/Libav 快速傅里叶变换)

FFMPEG 流式传输 RTP:未设置时基

使用 ffmpeg libav 和 libx264 从图像创建视频?

在 c++ 中以可变帧速率使用 FFmpeg 库(不是 libav 分支)以编程方式捕获视频

libav / FFMPEG以一种编码方式进行流式传输,另存为另一种方式