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

问题描述 投票:1回答:2

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

  • 流包含B帧。

  • 流只有RTP / RTCP给出的PTS。

这是我做的代码

//  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);

我收到了很多这样的错误消息:“应用程序向复用器提供了无效的,非单调递增的dts ...”。

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

因此,在发送到容器之前,如何设置正确的DTS和PTS?

更新:我尝试了一些更改,尽管尚未成功,但发现帧速率下降的原因是由于复用器丢弃了具有错误DTS的帧。此外,如果我将PTS和DTS的开始值设置得太大,则VLC之类的播放器必须延迟一些时间才能显示视频。

ffmpeg mp4 rtsp rtp muxer
2个回答
1
投票

我做过几次实验,并与您分享一些东西。

  1. 不管是否有B帧,mp4混合器要求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文件。


0
投票

[[流仅具有RTP / RTCP给定的PTS。”是不正常的。这里出了点问题。如果没有dts,则意味着您仅应使用pts。如果确实有B帧,那么您的dts值将不同于pts。

尝试使用您的代码dts = pts,看看会发生什么。
© www.soinside.com 2019 - 2024. All rights reserved.