使用 AVSampleBufferDisplayLayer 通过网络流式传输视频时断断续续,但在线流媒体服务运行完美

Streaming video over network with AVSampleBufferDisplayLayer stutters, but online streaming services work flawlessly

我在实施网络流视频时偶尔会出现不同程度的卡顿。我已经在 Iphone 7 和 Ipad 9.7 上测试了我的应用程序,两者都偶尔出现卡顿,但 Iphone 7 似乎卡顿得最多。我认为这不仅仅是硬件问题,因为我可以毫无问题地通过 youtube 流式传输视频。

我第一次通过网络实现视频流只是将 jpeg 图像从我的电脑发送到我的 Iphone7,这遇到了同样的问题。我通过增加一个数字并将其添加到我发送的每个包裹来检查是否有丢失的包裹,有口吃但没有丢失的包裹。我检查了问题是否是没有要渲染的图片,因为通过延迟渲染并将接收到的图像保存在缓冲区中,包裹迟到了,仍然断断续续。

我的猜测是卡顿的发生是因为我的事件调度程序并不总是按时触发(它没有这样做),我测试了不同的事件调度程序但我没有设法足够精确地实现任何事情。我想如果我可以将 h264 编码的比特流传递给一些内置的 objective-c class 那么它会为我按时管理图像的解码和渲染。这就是我尝试做的事情。

我开始遵循 将 h264 编码视频流式传输到 IOS 的指南。我必须进行一些更改才能使其正常工作,因为我的 h264 比特流每帧包含多个图片最终单元。我将它们中的每一个附加到 CMBlockBuffer,而不是像链接指南那样仅仅创建一个块缓冲区来封装第一张图片最终单元。

我的示例缓冲区附件也看起来像这样,而不是它们在指南中的显示方式

    CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);

    //CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanTrue);
    CFDictionarySetValue(dict, kCMSampleAttachmentKey_IsDependedOnByOthers, kCFBooleanTrue);

    if (naluType == 1) {
        // P-frame
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanTrue);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanTrue);
    } else {
        // I-frame
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_NotSync, kCFBooleanFalse);
        CFDictionarySetValue(dict, kCMSampleAttachmentKey_DependsOnOthers, kCFBooleanFalse);
    }

这些是 Moonlight Streaming Service 使用的附件。我没有立即显示图片,因为我使用 CMSampleBufferSetOutputPresentationTimeStamp 将每一帧的呈现时间戳设置为比前一帧多 1/60 秒。

可是我还是口吃。我读过有关 IOS 占用线程的信息,它可能会按时弄乱绘图帧,但所有流式传输视频的网站都可以在我的设备上执行此操作而不会卡顿。我当然应该能够为我的应用程序做同样的事情吗?我也尝试过使用发布版本构建我的应用程序,但这没有帮助。我知道问 "How do I fix my video stream stuttering" 是一个非常宽泛的问题,但我希望提及我尝试过的内容、我的实现方式以及像 youtube 这样的网站可以流式传输而不会在我的硬件上卡顿的事实应该对某些人来说已经足够了能够指出我正确的方向。我知道我可以尝试像 WebRTC 这样的基于网络的解决方案,但如果可能的话,我想解决我遇到的问题,而不是做一些全新的事情。

更新 1

在我的项目中,我打印了两个图片包到达之间的时间。过去经常发生的是,即使在延迟流播放 时,流也会在数据包延迟到达的同时断断续续 。在线阅读一些东西让我觉得修复我的内存泄漏可能会解决我的问题。不知道有没有关系,还是我之前的测试做错了,又或者是天大的巧合。但是在修复我的内存泄漏之后,我的项目现在会在数据包延迟到达时出现卡顿,但如果我的流延迟,卡顿也会延迟。现在可能只是我PTS设置错误

更新 2

我可以使用 PTS 适当地延迟我的播放,我可以根据我输入样本缓冲区的时间戳让它播放得更快或更慢,所以我不认为我在设置 PTS 时犯了错误。我录制了我的屏幕以显示它的外观。该示例将 600 帧的数据存储在一个容器中,然后一次性对所有数据进行解码,以确保卡顿不是因为数据包迟到造成的。该视频还打印出数据包到达和前一个数据包到达之间的时间,如果它们之间的时间长于 1/60 秒多一点。范例的视频和PTS相关代码如下

视频:https://youtu.be/Ym5rfHwg-eM

// init function
CMTimeBaseCreateWithMasterClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &_controlTimebase);

_displayLayer.controlTimebase = _controlTimebase;

CMTimebaseSetTime(_displayLayer.controlTimebase, CMTimeMake(0, 60));
CMTimebaseSetRate(_displayLayer.controlTimebase, 1.0);

// f = frame. Starts at 630 to delay playback with 630 frames.
f = 630;

......................
// received packet containing frame function
[frames addObject:data]; // data is an NSData of an encoded frame

// store 600 frames worth of data then queue all of them in one go to make sure that the stutter is not because packets arrive late
if ([frames count] == 600)
{
   for (int i = 0; i < 600; i++)
   {
        uint8_t *bytes = (uint8_t*)[frames[i] bytes];
        [self->cameraView.streamRenderer decodeFrame:bytes length:frames[i].length;
   }
}

......................
// decode frame function
CMSampleBufferSetOutputPresentationTimeStamp(sampleBuffer, CMTimeMake(f, 60));
f++;

更新 3

现在我还尝试使用 CADisplayLink 来获取回调,以便我应该在何时绘制图像,然后在其中使用 Metal 框架渲染 CVPixelBuffer。我使用与更新 2 中描述的示例相同的示例,但我仍然遇到相同的问题。我已确保打印出 Metal 渲染我的 CVPixelBuffer 所需的时间,每个 CVPixelBuffer 大约需要 3 毫秒,远低于屏幕的刷新率。

更新 4

我尝试在渲染像素缓冲区上方渲染当前帧的索引。有了这个,我可以检查显示是否滞后或者像素缓冲区是否有问题。在逐帧浏览视频后,我可以看到即使流滞后,渲染的索引也在增加。现在我认为问题出在视频流的解码或编码上。

问题出在编码期间,在我将数据包发送到我的 IOS 设备之前。我尝试在发送流之前先录制流,但那样也会断断续续。所以答案是显示可能之前有滞后,但可能不会。而且使用 CADisplayLink 和金属框架渲染像素缓冲区绝对不会滞后,问题不在 IOS 范围内。