如何将包含 B 帧但没有 DTS 的视频流写入 MP4 容器?

How to write a video stream containing B-frame and no DTS to a MP4 container?

我想将从 RTSP 源接收的 h264 视频流保存到 MP4 容器中。 与 SO 上提出的其他问题不同,这里我面临的挑战是:

这是我做的代码

//  ffmpeg
    pkt->data = ..;
    pkt->size = ..;
    pkt->flags = bKeyFrame? AV_PKT_FLAG_KEY : 0;    
    pkt->dts = AV_NOPTS_VALUE;
    pkt->pts = PTS;

    // PTS is based on epoch microseconds so I ignored re-scaling.
    //av_packet_rescale_ts(pkt, { 1, AV_TIME_BASE }, muxTimebase);

    auto ret = av_interleaved_write_frame(m_pAVFormatCtx, pkt);

我收到了很多这样的错误信息: "Application provided invalid, non monotonically increasing dts to muxer ...".

结果:mp4 文件可以通过 VLC 播放,但 FPS 仅为原始 FPS 的一半,并且视频时长不正确(VLC 显示一个奇怪的数字)。

那么在发送到容器之前如何设置正确的DTS和PTS?

更新: 我尝试了一些更改,虽然还没有成功,但我发现帧速率下降的原因是由于复用器丢弃了具有不正确 DTS 的帧。 此外,如果我将 PTS 和 DTS 值设置得太大,某些播放器(如 VLC)必须延迟一些时间才能显示视频。

流只有RTP/RTCP.给出的PTS”是不正常的。这里有问题。
如果没有 dts,则意味着您应该只使用 pts。如果真的有 B 帧,那么您的 dts 值将不同于 pts。

尝试你的代码 dts = pts 看看会发生什么。

我做了几个实验,有一些东西要分享给你。

  1. 无论是否有B帧,mp4 muxer要求DTS必须(至少):

    • 单调递增。
    • 每帧 DTS <= PTS。
    • PTS 和 DTS 应该从接近零的值开始(否则 VLC 等播放器必须延迟一段时间才能显示视频)。
  2. 如果码流中没有B帧,可以从PTS复制DTS存帧到 没有任何问题的 mp4 文件。

  3. 如果流中有B帧,故事就完全不同了。 在这种情况下,帧的 PTS 不会由于 B 帧而单调增加。 因此,仅仅复制 DTS = PTS 肯定是行不通的。我们必须想办法 通过带外发送 DTS 或从 FPS 和 PTS 计算得到 DTS。

对于带外发送,比较复杂,因为它需要同时处理 RTSP 服务器和 RTSP 客户端。这里我只是想展示一下从FPS和PTS推导出DTS的简单方法。

大致步骤是这样的:

检测帧之间的平均持续时间(或 FPS)

  • 从接收RTSP会话的SDP解析FPS。这种方式取决于 RTSP 服务器的支持。有的支持,有的不支持。

  • 另一种方法是根据 帧。您可以缓冲等于一个 GOP 大小的帧数, 获取 GOP 的第一帧和最后一帧的 PTS 差异 除以帧数,您将得到平均持续时间。 例如,假设 FPS 为 30,则计算平均持续时间 应该是大约 33,333 us.

保存到容器

// Initialize container

    pAVStream->time_base = { 1, AV_TIME_BASE }; // PTS/DTS in microseconds.
    pAVFormatCtx->oformat->flags |= AVFMT_SEEK_TO_PTS;
    ret = avformat_write_header(m_pAVFormatCtx, &priv_opts);

    Assume that you have pre-calculated average duration: 
    nAvgDuration = 33'333LL;

    //  Per each frame

    if (waitingForTheFirstKeyFrame) {
        if (!bsKeyFrame) {
            return false;
        }

        waitingForTheFirstKeyFrame = false;
        nPTSOffset = nPTS; // pts will start from 0
        nStartDTS = nPTS - nAvgDuration; // dts will start from -nAvgDuration
    }

    nDTS = nStartDTS;
    nStartDTS += nAvgDuration; // dts is monotonically increasing

    pkt->pts = nPTS - nPTSOffset;
    pkt->dts = nDTS - nPTSOffset;

    //  Since PTS/DTS are in microseconds, no need to rescalling more.
    //  Of course, you can use a different time_base.

    auto ret = av_interleaved_write_frame(m_pAVFormatCtx, pkt);

注意事项:

假设流的原始 PTS(在服务器端)单调递增,帧与帧之间没有间隙,也没有帧丢失,此解决方案效果很好。否则可能会降低DTS的精度甚至无法播放mp4文件。