如何将包含 B 帧但没有 DTS 的视频流写入 MP4 容器?
How to write a video stream containing B-frame and no DTS to a MP4 container?
我想将从 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);
我收到了很多这样的错误信息:
"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
看看会发生什么。
我做了几个实验,有一些东西要分享给你。
无论是否有B帧,mp4 muxer要求DTS必须(至少):
- 单调递增。
- 每帧 DTS <= PTS。
- PTS 和 DTS 应该从接近零的值开始(否则 VLC 等播放器必须延迟一段时间才能显示视频)。
如果码流中没有B帧,可以从PTS复制DTS存帧到
没有任何问题的 mp4 文件。
如果流中有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文件。
我想将从 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);
我收到了很多这样的错误信息: "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
看看会发生什么。
我做了几个实验,有一些东西要分享给你。
无论是否有B帧,mp4 muxer要求DTS必须(至少):
- 单调递增。
- 每帧 DTS <= PTS。
- PTS 和 DTS 应该从接近零的值开始(否则 VLC 等播放器必须延迟一段时间才能显示视频)。
如果码流中没有B帧,可以从PTS复制DTS存帧到 没有任何问题的 mp4 文件。
如果流中有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文件。