【问题标题】:How to compensate frame rate underrun while muxing video to mp4 container with libav如何在使用 libav 将视频混合到 mp4 容器时补偿帧速率不足
【发布时间】:2021-11-05 14:14:09
【问题描述】:

我有一个实时生成视频帧的过程。我正在将生成的视频帧流混合到一个视频文件中(mp4 容器上的 x264 编解码器)。

我正在使用 ffmpeg-libav,并且基于 muxing.c 示例。该示例的问题在于,这不是真实世界的场景,因为在给定的流持续时间的 while 循环上生成帧,从不丢失帧。

在我的程序中,帧应该以 FPS 生成,但是,取决于硬件容量,它生成的帧可能低于 FPS。当我初始化视频流上下文时,我声明帧速率为 FPS:

AVRational r = { 1, FPS };
ost->st->time_base = r;

这指定视频将具有 FPS 帧速率,但如果生成的帧较少,则播放速度会更快,因为它仍会重现视频,就像每秒具有所有声明的帧一样。

在谷歌上搜索了很多关于该主题的内容后,我了解到解决此问题的关键是操纵 pts 和 dts,但我仍然没有找到可行的解决方案。

在 muxing.c 示例中编写视频帧时有两个关键函数,我在我的程序中使用的例程:

AVFrame* get_video_frame(int timestamp, OutputStream *ost, const QImage &image)
{
    /* when we pass a frame to the encoder, it may keep a reference to it
     * internally; make sure we do not overwrite it here */
    if (av_frame_make_writable(ost->frame) < 0)
        exit(1);

    av_image_fill_arrays(ost->tmp_frame->data, ost->tmp_frame->linesize, image.bits(), AV_PIX_FMT_RGBA, ost->frame->width, ost->frame->height, 8);
    libyuv::ABGRToI420(ost->tmp_frame->data[0], ost->tmp_frame->linesize[0], ost->frame->data[0], ost->frame->linesize[0], ost->frame->data[1], ost->frame->linesize[1], ost->frame->data[2], ost->frame->linesize[2], ost->tmp_frame->width, -ost->tmp_frame->height);

    #if 1 // this is my attempt to rescale pts, but crashes with pts<dts
    ost->frame->pts = av_rescale_q(timestamp, AVRational{1, 1000}, ost->st->time_base);
    #else
    ost->frame->pts = ost->next_pts++;
    #endif

    return ost->frame;
}

在原始代码中,pts 只是每个帧的递增整数。我正在尝试做的是在录制开始后以毫秒为单位传递一个时间戳,以便我可以重新调整 pts。当我重新调整 pts 时,程序崩溃并抱怨 pts 低于 dts。

从我一直在阅读的内容来看,pts/dts 操作应该在数据包级别完成,所以我也尝试在 write_frame 例程上进行操作,但没有成功。

int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame)
{
    int ret;

    // send the frame to the encoder
    ret = avcodec_send_frame(c, frame);

    if (ret<0)
    {
        fprintf(stderr, "Error sending a frame to the encoder\n");
        exit(1);
    }

    while (ret >= 0)
    {
        AVPacket pkt = { 0 };

        ret = avcodec_receive_packet(c, &pkt);

        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            break;
        }
        else if (ret<0)
        {
            //fprintf(stderr, "Error encoding a frame: %s\n", av_err2str(ret));
            exit(1);
        }

        /* rescale output packet timestamp values from codec to stream timebase */
        av_packet_rescale_ts(&pkt, c->time_base, st->time_base);
        pkt.stream_index = st->index;

        /* Write the compressed frame to the media file. */
        //log_packet(fmt_ctx, &pkt);
        ret = av_interleaved_write_frame(fmt_ctx, &pkt);
        av_packet_unref(&pkt);

        if (ret < 0)
        {
            //fprintf(stderr, "Error while writing output packet: %s\n", av_err2str(ret));
            exit(1);
        }
    }

    return ret == AVERROR_EOF ? 1 : 0;
}

我应该如何操作 dts 和 pts 以便我可以在某个帧上实现视频,而该视频没有流初始化中指定的所有帧?我应该在哪里进行这种操作?在 get_video_frame 上?在 write_frame 上?两者都有?

我是否朝着正确的方向前进?我错过了什么?

【问题讨论】:

    标签: video ffmpeg libav libavcodec libavformat


    【解决方案1】:

    看起来您所做的大部分事情都是正确的,但您的 time_base 太小,无法达到您的目的。

    您是在告诉复用器,您的帧是以 1/FPS 的增量生成的,例如 1/25,并且在任何情况下都不会小于该值。 如果有时您可以在帧之间设置更短或更长的时间(可变帧率),请增加您的 time_base。

    我不知道为什么,但很多视频软件(包括 FFmpeg 客户端)似乎选择 1/12800 作为 MP4 的 time_base。这也是我在 VFR(通过 UDP 接收视频)的应用程序中使用的,它运行良好。

    在设置帧的 pts 值时不要忘记使用带有 av_rescale() 的代码版本,并且在初始化 AVCodecContext 之后,您也必须在此基础上设置 time_base。永远不要对缩放 AVRational 进行硬编码,始终从 AVCodecContext 重新读取它,因为 libav 内部可以限制该值。

    至于“为什么”必须设置这些字段,我建议阅读 avcodec.h 和 avformat.h 的头文件 Doxygen。最重要的是,它们描述了可以设置哪些字段,以及必须设置哪些字段才能工作。这对于了解图书馆对您作为用户的期望非常有用。

    【讨论】:

    • 我设法找到了解决方案,但当时我问了这个问题,我还没有混合音频。然后我开始遇到音频/视频同步问题。现在信息已经在我的脑海中得到解决,我可以更好地理解你的答案。解决方案是增加视频流的时基。为了简单起见,我选择了与音频流相同的时基,因为在我的情况下,音频制作比视频更可靠,我确切知道当新视频帧到达时已经写入了多少样本,有一个正确的分数。
    猜你喜欢
    • 2021-12-31
    • 2023-03-31
    • 2020-11-09
    • 1970-01-01
    • 2018-10-13
    • 2016-05-07
    • 2014-05-30
    • 1970-01-01
    • 2022-01-17
    相关资源
    最近更新 更多