我正在开发一种快速图像扫描应用程序.
在-captureOutput:didOutputSampleBuffer:fromConnection:
方法中,我拿起CVPixelBuffer并将它们添加到NSOperation
子类中.该NSOperation
取缓冲,并将其转化为保存在文件系统中的图像.返回
的-isConcurrent
方法是NSOperation
YES.创建操作后,添加到NSOperationQueue
.
除了影响扫描帧速率的一个问题外,一切运行正常.
使用时间分析器我发现一些NSOperation运行在我创建的AVCaputureVideoOutput委托的同一个线程上:
dispatch_queue_t queue = dispatch_queue_create("it.CloudInTouchLabs.avsession", DISPATCH_QUEUE_SERIAL); [dataOutput setSampleBufferDelegate:(id)self queue:queue];
当一个操作在AV会话队列的同一个线程上运行时,它会影响帧速率,它只发生在它们中的一些,可能GCD正在决定在活动线程上调度.
我发现解决该问题的唯一方法是创建一个单独的线程并将其传递给单个操作,强制它们在其上运行.
还有另一种方法,强制在不同的线程上运行操作吗?
[编辑]
按照@impcc的建议我做了一些测试.
即使有些不一致,结果也很有趣.
测试平台是一个通过MBP与调试模式连接的iPhone 5,具有16GB的RAM四核i7.会话使用RosyWriter apple示例代码中的算法测试了60fps的输出.
AVSession队列同步针对高优先级队列
26 fps,有时队列的线程与其中一个操作共享.委托方法内部的时间平均为0.02秒
14 fps,只为操作创建一个线程,如果在该线程上没有调用main方法,则将强制启动该特定线程.这个线程创建一次并使用虚拟端口保持活动状态..代理内部的时间为0.008.
AVSession队列并发目标为高优先级队列
13.5 fps,有时队列的线程与其中一个操作共享.委托方法内部所花费的时间平均为0.02秒,与具有同步队列的对应物相等.
14 fps,只为操作创建一个线程,如果在该线程上没有调用main方法,则将强制启动该特定线程.这个线程创建一次并使用虚拟端口保持活动状态.代表内部的时间是0.008.
结论
并发或串行队列似乎没有太大的区别,但是对我而言并发是不正确的,因为我需要采取时间戳来保留单个图片的序列.让我感到惊讶的事实是使用ad-hoc线程的帧丢弃,即使委托方法内部所花费的时间相当少,帧速率也会下降大约10fps.只是信任GCD,帧速率更好,但委托方法需要2次以上才能完成,这可能是因为有时AV队列也被nsoperations使用.
我无法理解为什么委托内部的时间似乎与fps无关.不应该越快越好?
通过进一步的研究,似乎在添加并可能在队列中执行操作的过程中窃取时间si.
我想你可能会误解其含义isConcurrent
.这完全可以理解,因为它的命名非常糟糕.当你YES
从-isConcurrent
它的真正含义返回时,"我将处理与此操作相关的任何并发需求,否则将异步运行." 在这种情况下,NSOperationQueue
可以-start
在添加操作的线程上同步调用您的操作.该NSOperationQueue
期待的是,因为你已经宣布将要管理自己的并发性,那你的-start
方法简单地揭开序幕异步进程并立即返回.我怀疑这是你问题的根源.
如果您通过覆盖来实现你的操作-main
,那么你几乎肯定要被返回NO
的isConcurrent
.为了使问题更复杂,与之相关联的行为isConcurrent
已改变多年来(但都被覆盖在官方文档).一个梦幻般的如何正确实施returns-解释YES
-from- isConcurrent
NSOperation
可以发现在这里.
我在这里阅读你的问题的方式,听起来你的NSOperation
子类实际上并不需要"管理它自己的并发",而是你只是想让它以异步方式执行,"在后台线程上",可能是"同时"与其他业务.
在确保AVCaptureVideoDataOutputSampleBufferDelegate
回调的最佳性能方面,我建议让你传入的队列-setSampleBufferDelegate:queue:
(本身)并发,并将目标定位为高优先级的全局并发队列,如下所示:
dispatch_queue_t queue = dispatch_queue_create("it.CloudInTouchLabs.avsession", DISPATCH_QUEUE_CONCURRENT); dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); [dataOutput setSampleBufferDelegate:(id)self queue:queue];
然后你应该让委托回调方法尽可能轻量级 - 只需打包你需要的信息NSOperation
并将其添加到NSOperationQueue
.
这应该确保回调始终优先于NSOperations
.(这是我的理解,NSOperationQueue
目标是主队列(NSOperationQueue
与主线程和运行循环相关联)或默认优先级后台队列.)这应该允许您的回调完全跟上帧速率.
另一个重要的事情就是拿走(另一位评论者得到的)是,如果你使用GCD进行所有的并发操作,那么只有一个特殊的线程 - 主线程.除此之外,线程只是GCD可以(并且将)彼此互换使用的通用资源.事实上,线程ID为X的线程在一个点上用于为您的委托回调提供服务,而在另一个点用于进行处理本身并不表示存在问题.
希望这可以帮助!