这是一个简洁的问题,而不是“告诉我什么代码有效”,而是“我如何在逻辑上处理这种情况”的问题。
简而言之,我有来自 IP 摄像机通过 RTSP 的视频 + 音频。
视频和音频通过单独的线程逐帧解码并记录到单个 mp4 容器中(如下所示)。
问题是视频和音频随着时间的推移变得越来越不同步,这是由于每个视频帧的 TimeSpan 结束时间和开始时间缺乏精确性。
每个视频帧的持续时间应该是 1 / framerate = 0.0333667000333667,但它使用(即使使用 FromTicks() 方法),第一帧的开始时间 = 0.0 和结束时间 0.0333667。
我可以从 29.97 调整视频解码器的帧率值(它是从相机的设置声明的帧率中提取的),导致视频先于音频,或者滞后于音频——这只是让每个视频 mediaBuffer.StartTime 和 mediaBuffer .EndTime 与音频相比太早或太晚。
随着时间的推移,微小的十进制截断最终会使视频和音频不同步——录制的时间越长,两个轨道就越不同步。
我真的不明白为什么会发生这种情况,因为舍入误差在逻辑上应该不重要。
即使我只有 1 秒的精度,我也只会每秒写一个视频帧,它在时间轴中的位置大致应该是 +- 1 秒,这应该使每个渐进帧都相同 + - 1 秒到它应该在的位置,而不是逐渐增加更多的错位。我想象这对于每一帧来说都是这样的:
[<-------- -1 秒 --------> 预期的确切帧时间 <-------- +1 秒 -------->] --- ------------------------------------------------- 记录帧时间--------
我在这里错过了什么吗?
我不是在做“新帧开始时间 = 上一帧结束时间,新帧结束时间 = 新帧开始时间 + 1 / 帧率”——我实际上是在做“新帧开始时间 = 帧索引 - 1 / 帧率,新帧结束时间 = 帧索引 / 帧率”。
也就是说,我正在根据它们应该具有的预期时间(帧时间 = 帧位置/帧速率)计算帧开始和结束时间。
我的代码在做什么是这样的:
预期时间 ---------- 预期时间 ---------- 预期时间 帧时间 帧时间 帧时间
我从数学上理解这个问题,我只是不明白为什么小数截断会证明这样一个问题,或者从逻辑上知道解决它的最佳解决方案是什么。
如果我实现“每 x 帧,使用“(1 / 帧率)+ 一些量”来弥补所有缺失的时间,是否有可能使帧匹配到它们应该在的位置,或者只是导致混乱的视频?
public void AudioDecoderThreadProc()
{
TimeSpan current = TimeSpan.FromSeconds(0.0);
while (IsRunning)
{
RTPFrame nextFrame = jitter.FindCompleteFrame();
if (nextFrame == null)
{
System.Threading.Thread.Sleep(20);
continue;
}
while (nextFrame.PacketCount > 0 && IsRunning)
{
RTPPacket p = nextFrame.GetNextPacket();
if (sub.ti.MediaCapability.Codec == Codec.G711A || sub.ti.MediaCapability.Codec == Codec.G711U)
{
MediaBuffer<byte> mediaBuffer = new MediaBuffer<byte>(p.DataPointer, 0, (int)p.DataSize);
mediaBuffer.StartTime = current;
mediaBuffer.EndTime = current.Add(TimeSpan.FromSeconds((p.DataSize) / (double)audioDecoder.SampleRate));
current = mediaBuffer.EndTime;
if (SaveToFile == true)
{
WriteMp4Data(mediaBuffer);
}
}
}
}
}
public void VideoDecoderThreadProc()
{
byte[] totalFrame = null;
TimeSpan current = TimeSpan.FromSeconds(0.0);
TimeSpan videoFrame = TimeSpan.FromTicks(3336670);
long frameIndex = 1;
while (IsRunning)
{
if (completedFrames.Count > 50)
{
System.Threading.Thread.Sleep(20);
continue;
}
RTPFrame nextFrame = jitter.FindCompleteFrame();
if (nextFrame == null)
{
System.Threading.Thread.Sleep(20);
continue;
}
if (nextFrame.HasSequenceGaps == true)
{
continue;
}
totalFrame = new byte[nextFrame.TotalPayloadSize * 2];
int offset = 0;
while (nextFrame.PacketCount > 0)
{
byte[] fragFrame = nextFrame.GetAssembledFrame();
if (fragFrame != null)
{
fragFrame.CopyTo(totalFrame, offset);
offset += fragFrame.Length;
}
}
MediaBuffer<byte> mediaBuffer = new MediaBuffer<byte>(
totalFrame,
0,
offset,
TimeSpan.FromTicks(Convert.ToInt64((frameIndex - 1) / mp4TrackInfo.Video.Framerate * 10000000)),
TimeSpan.FromTicks(Convert.ToInt64(frameIndex / mp4TrackInfo.Video.Framerate * 10000000)));
if (SaveToFile == true)
{
WriteMp4Data(mediaBuffer);
}
lock (completedFrames)
{
completedFrames.Add(mediaBuffer);
}
frameIndex++;
}
}