我想将从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之类的播放器必须延迟一些时间才能显示视频。
我做过几次实验,并与您分享一些东西。
不管是否有B帧,mp4混合器要求DTS必须(至少):
如果流中没有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文件。
[[流仅具有RTP / RTCP给定的PTS。”是不正常的。这里出了点问题。如果没有dts,则意味着您仅应使用pts。如果确实有B帧,那么您的dts值将不同于pts。
尝试使用您的代码dts = pts
,看看会发生什么。