【发布时间】:2019-06-14 17:45:40
【问题描述】:
在通过网络实现流式视频时,我偶尔会遇到不同程度的口吃。我在 Iphone 7 和 Ipad 9.7 上测试了我的应用程序,两者都偶尔会出现口吃,但 Iphone 7 似乎口吃最多。我想这不仅仅是硬件问题,因为我可以毫无问题地通过 youtube 流式传输视频。
我的第一个网络视频流实现只是将 jpeg 图像从我的电脑发送到我的 Iphone 7,这遇到了同样的问题。我通过增加一个数字并将其添加到我发送的每个包裹中来检查是否有丢弃的包裹,有口吃但没有丢弃的包裹。我检查了问题是否是没有要渲染的图片,因为通过延迟渲染并将接收到的图像保存在缓冲区中,包到达迟了,仍然卡顿。
我的猜测是卡顿的发生是因为我的事件调度器并不总是准时触发(它没有这样做),我测试了不同的事件调度器,但我没有设法实现任何足够精确的东西。我想如果我可以将一个 h264 编码的比特流传递给一些内置的 Objective-C 类,那么它将为我按时管理我的图像的解码和渲染。所以这就是我尝试做的。
我首先遵循this 指南,将 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 占用线程的信息,并且它可以按时弄乱绘图帧,但是所有流式传输视频的网站都可以在我的设备上做到这一点而不会卡顿。当然,我应该能够为我的应用程序做同样的事情吗?我也尝试使用发布版本构建我的应用程序,但这并没有帮助。我知道问“如何解决我的视频流口吃”是一个相当广泛的问题,但我希望提及我的尝试,我的实现的外观以及像 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 objective-c video-streaming