1

我想将从 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);

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

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

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

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

4

2 回答 2

2

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

  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 文件。

于 2020-02-14T05:27:26.680 回答
0

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

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

于 2020-02-13T08:56:43.740 回答