21

如何在 FFmpeg C API 中编码之前计算帧的正确 PTS 值?

对于编码,我使用函数avcodec_encode_video2,然后用av_interleaved_write_frame.

我找到了一些公式,但没有一个有效。

doxygen 示例中,他们正在使用

frame->pts = 0;
for (;;) {
    // encode & write frame
    // ...
    frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base);
}

这个博客说公式必须是这样的:

(1 / FPS) * 采样率 * 帧数

有人只使用帧号来设置pts:

frame->pts = videoCodecCtx->frame_number;

或者另一种方式:

int64_t now = av_gettime();
frame->pts = av_rescale_q(now, (AVRational){1, 1000000}, videoCodecCtx->time_base);

最后一个:

// 40 * 90 means 40 ms and 90 because of the 90kHz by the standard for PTS-values. 
frame->pts = encodedFrames * 40 * 90;

哪一个是正确的?我认为这个问题的答案不仅对我有帮助。

4

3 回答 3

10

在尝试代码之前最好更抽象地考虑 PTS。

您正在做的是将 3 个“时间集”啮合在一起。第一个是我们习惯的时间,基于每秒 1000 毫秒、每分钟 60 秒等等。第二个是您正在使用的特定编解码器的编解码器时间。每个编解码器都有其想要表示时间的特定方式,通常采用 1/数字格式,这意味着每一秒都有“数字”数量的滴答声。第三种格式的工作方式与第二种类似,只是它是您使用的容器的时间基准。

有些人更喜欢从实际时间开始,有些人喜欢从帧数开始,两者都不是“错误的”。

从帧数开始,您需要首先根据您的帧速率对其进行转换。请注意我所说的所有转换都使用 av_rescale_q(...)。此转换的目的是将计数器转换为时间,因此您可以根据帧速率(通常是视频流时基)重新缩放。然后,您必须在编码之前将其转换为视频编解码器的 time_base。

同样,对于实时,您的第一次转换需要从 current_time - start_time 缩放到您的视频编解码器时间。

仅使用帧计数器的任何人都可能使用 time_base 等于其帧速率的编解码器。大多数编解码器都不是这样工作的,而且它们的 hack 是不可移植的。例子:

frame->pts = videoCodecCtx->frame_number;  // BAD

此外,任何在 av_rescale_q 中使用硬编码数字的人都在利用这样一个事实,即他们知道自己的 time_base 是什么,应该避免这种情况。该代码不能移植到其他视频格式。而是使用 video_st->time_base、video_st->codec->time_base 和 output_ctx->time_base 来解决问题。

我希望从更高的层面理解它可以帮助您了解哪些是“正确的”,哪些是“不好的做法”。没有单一的答案,但也许现在您可以决定哪种方法最适合您。

于 2014-04-14T19:29:57.533 回答
3

还有设置它的选项,frame->pts = av_frame_get_best_effort_timestamp(frame)但我不确定这是否是正确的方法。

于 2014-04-11T12:03:09.180 回答
3

时间不是以秒或毫秒或任何标准单位来衡量的。相反,它是由 avCodecContext 的时基测量的。

所以如果你把 codecContext->time_base 设置为 1/1,这意味着使用秒进行测量。

cctx->time_base = (AVRational){1, 1};

假设您想以 30 的稳定 fps 进行编码。那么,一帧编码的时间是framenumber * (1.0/fps)

但再一次,PTS 也不是以秒或任何标准单位来衡量的。它由 avStream 的 time_base 测量。

在问题中,作者提到了 90k 作为 pts 的标准分辨率。但你会发现这并不总是正确的。准确的分辨率保存在 avstream 中。您可以通过以下方式阅读:

    if ((err = avformat_write_header(ofctx, NULL)) < 0) {
        std::cout << "Failed to write header" << err << std::endl;
        return -1;
    }

    av_dump_format(ofctx, 0, "test.webm", 1);
    std::cout << stream->time_base.den  << " " << stream->time_base.num << std::endl;

stream->time_stamp 的值仅在调用后填充avformat_write_header

因此,计算PTS的正确公式为:

//The following assumes that codecContext->time_base = (AVRational){1, 1};
videoFrame->pts = frameduration * (frameCounter++) * stream->time_base.den / (stream->time_base.num * fps);

所以公式中真的有3个成分,

  1. 帧率
  2. codecContext->time_base
  3. 流->time_base

所以pts = fps*codecContext->time_base/stream->time_base

我在这里详细介绍了我的发现

于 2020-01-07T01:20:16.770 回答