应用效果到iPhone相机预览“视频”

3
我的目标是编写一个自定义相机视图控制器,它可以:
  1. 在所有四个界面方向下使用前置和后置摄像头拍照。
  2. 正确旋转和缩放预览"视频"以及全分辨率照片。
  3. 允许对预览"视频"和全分辨率照片应用(简单的)效果。
实现(基于iOS 4.2 / Xcode 3.2.5):
由于要求(3),我需要降级到AVFoundation。
我从Technical Q&A QA1702开始,并进行了以下更改:
  1. 将sessionPreset更改为AVCaptureSessionPresetPhoto。
  2. 在启动会话之前添加AVCaptureStillImageOutput作为附加输出。
我遇到的问题是处理预览图像(预览"视频"的帧)的性能问题。
首先,我从captureOutput:didOutputSampleBuffer:fromConnection:的样本缓冲区中获取UIImage结果的imageFromSampleBuffer:。然后,我使用CGGraphicsContext将其缩放和旋转到屏幕上。
此时,帧速已经低于会话视频输出中指定的15 FPS,当我添加效果时,它会降到10以下。很快应用程序由于低内存而崩溃。
我在iPhone 4上将帧速降至9 FPS,在iPod Touch(第四代)上将其降至8 FPS,取得了一些成功。
我还添加了一些代码来“刷新”调度队列,但我不确定它实际上有多大帮助。基本上,每8-10帧,设置一个标志,指示captureOutput:didOutputSampleBuffer:fromConnection:立即返回而不处理该帧。在输出调度队列上的同步操作完成后,重置标志。
此时,我甚至不介意低帧速率,但显然我们无法使用低内存崩溃的应用程序。有人有任何想法如何采取措施防止这种情况下的低内存条件(和/或更好的“刷新”调度队列方式)吗?
2个回答

4
为了避免内存问题,在 captureOutput:didOutputSampleBuffer:fromConnection: 中创建自动释放池即可。
这是有道理的,因为 imageFromSampleBuffer: 返回一个自动释放的 UIImage 对象。此外,它可以立即释放由图像处理代码创建的任何自动释放对象。
// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
fromConnection:(AVCaptureConnection *)connection
{ 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Create a UIImage from the sample buffer data
    UIImage *image = [self imageFromSampleBuffer:sampleBuffer];

    < Add your code here that uses the image >

    [pool release];
}

我的测试表明,即使请求的FPS非常高(例如60),图像处理非常缓慢(例如0.5秒以上),在iPhone 4或iPod Touch(第四代)上运行时也不会出现内存警告。

旧解决方案:

正如Brad所指出的那样,苹果建议将图像处理放在后台线程中,以免干扰UI的响应能力。在这种情况下,我没有注意到太多的滞后,但最佳实践是最佳实践,因此请使用上述具有自动释放池的解决方案,而不是在主调度队列/主线程上运行此解决方案。

为了防止内存问题,只需使用主调度队列而不是创建新的调度队列。

这也意味着当您想要更新UI时,无需在captureOutput:didOutputSampleBuffer:fromConnection:中切换到主线程。

setupCaptureSession中,从以下更改:

// Configure your output.
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

致:

// we want our dispatch to be on the main thread
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];

是的,我猜测这可能会导致会话在无法跟上时丢失帧。然而,我认为苹果公司不鼓励这样做,因为它可能会影响界面的响应性(因为你正在主线程上进行处理)。但似乎仍然应该有一种方法可以在非主队列上处理帧而不会过载。 - Brad Larson
目前它的响应速度已经足够快了,但我可能会尝试在captureOutput:didOutputSampleBuffer:fromConnection:之后将实际处理转移到后台,然后回到主线程更新UI。这种方法确实可以解决问题,这也是苹果在WWDC上展示的GLVideoFrame示例所采用的方法(当然,OpenGL处理非常快)。 - gerry3
在后台进行处理会使帧率降至无法接受的水平,即使 UI 稍微更加响应,应用程序看起来也显著不够响应。在主线程上完成所有操作似乎是这种情况下最好的方法。 - gerry3
实际上,只是添加了一个自动释放池似乎解决了内存问题,同时允许处理保持在后台队列/线程中。我已经更新了答案。 - gerry3
@gerry3 - 你当时是否在控制台上看到了关于缺少自动释放池的错误,还是说你需要更频繁地清空池? - Brad Larson
@Brad 后者。不确定为什么调用者不在每帧清空池。 - gerry3

2
一种基本更好的方法是使用OpenGL来处理尽可能多的与图像相关的繁重工作(正如我在您最新的尝试中所看到的)。但即使如此,构建要处理的帧可能仍会出现问题。
虽然在处理帧时遇到内存积累似乎很奇怪(根据我的经验,如果无法快速处理它们,您只需停止获取帧),但Grand Central Dispatch队列可能会因等待I/O而被堵塞。
也许一个调度信号量可以让您控制将新项目添加到处理队列的速度。更多信息,请参阅Mike Ash的“GCD Practicum”文章,他在其中使用调度信号量优化了一个I/O边界的缩略图处理操作。

OpenGL 提供了很好的帧率,但那不是正确的预设(尽管照片预设视频帧仅比示例略高分辨率)。 是的,很奇怪,但很容易重现。你可以获取苹果的示例代码,将其放入新的 iPhone 应用程序中,在 captureOutput:didOutputSampleBuffer:fromConnection: 中添加短延迟,然后观察它在几秒钟内崩溃。 我还没有探索过这个和 OpenGL 示例之间的一个区别,即后者使用了主调度队列(主线程),这可能最终类似于信号量方法。我会研究两者。 - gerry3
结果证明,只需使用主调度队列即可(请参见我的答案)。 - gerry3
到目前为止最好的解决方案是添加自动释放池。我已经更新了我的答案。 - gerry3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接