【问题标题】:Applying Effect to iPhone Camera Preview "Video"将效果应用于 iPhone 相机预览“视频”
【发布时间】:2011-02-04 01:18:32
【问题描述】:

我的目标是编写一个自定义的相机视图控制器:

  1. 可以使用后置摄像头和前置摄像头(如果有的话)在所有四个界面方向上拍照。
  2. 正确旋转和缩放预览“视频”以及全分辨率照片。
  3. 允许对预览“视频”和全分辨率照片应用(简单)效果。

实现(在 iOS 4.2 / Xcode 3.2.5 上):

由于要求 (3),我需要下拉到 AVFoundation。

我从Technical Q&A QA1702 开始并进行了以下更改:

  1. 将 sessionPreset 更改为 AVCaptureSessionPresetPhoto。
  2. 在开始会话之前添加了 AVCaptureStillImageOutput 作为附加输出。

我遇到的问题是处理预览图像(预览“视频”的一帧)的性能。

首先,我从captureOutput:didOutputSampleBuffer:fromConnection: 获得样本缓冲区上imageFromSampleBuffer: 的UIImage 结果。然后,我使用 CGGraphicsContext 为屏幕缩放和旋转它。

此时,帧速率已经低于会话视频输出中指定的 15 FPS,当我添加效果时,它会降至 10 以下或左右。由于内存不足,应用程序很快就会崩溃.

我在将 iPhone 4 的帧速率降低到 9 FPS 和 iPod Touch(第 4 代)降低到 8 FPS 方面取得了一些成功。

我还添加了一些代码来“刷新”调度队列,但我不确定它实际上有多大帮助。基本上,每 8-10 帧,就会设置一个标志,指示captureOutput:didOutputSampleBuffer:fromConnection: 立即返回而不是处理该帧。在输出调度队列上的同步操作完成后,该标志被重置。

在这一点上,我什至不介意低帧速率,但显然我们不能在交付时出现低内存崩溃。任何人都知道在这种情况下如何采取措施来防止内存不足的情况(和/或“刷新”调度队列的更好方法)?

【问题讨论】:

    标签: iphone grand-central-dispatch avcapturesession


    【解决方案1】:

    为了防止内存问题,只需在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(第 4 代)上运行而不会出现内存警告。

    旧解决方案:

    正如 Brad 所指出的,Apple 建议在后台线程上处理图像,以免干扰 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()];
    

    【讨论】:

    • 是的,我猜这会导致会话在无法跟上时丢帧。但是,我认为 Apple 不鼓励这样做,因为它可能会对界面的响应能力产生影响(因为您在主线程上进行的处理)。似乎仍然应该有一种方法来处理非主队列上的帧而不会超载。
    • 嗯,到目前为止它实际上已经足够响应了,但是我可能会尝试在captureOutput:didOutputSampleBuffer:fromConnection: 之后将实际处理启动到后台,然后回到主线程来更新 UI。这种方法确实解决了这个问题,这就是来自 WWDC 的 Apple 的 GLVideoFrame 示例所做的(当然,OpenGL 处理速度非常快)。
    • 在后台进行处理会将帧速率降低到无法接受的水平,并使应用程序的响应速度明显降低,即使 UI 的响应速度稍快一些。在这种情况下,在主线程上做所有事情似乎是最好的方法。
    • 实际上,仅添加自动释放似乎已经解决了内存问题,同时允许处理保留在后台队列/线程上。我已经更新了答案。
    • @gerry3 - 您是在控制台上看到有关缺少自动释放池的错误,还是您需要更频繁地排空池?
    【解决方案2】:

    从根本上更好的方法是使用 OpenGL 为您处理尽可能多的与图像相关的繁重工作(正如我看到您在 your latest attempt 中尝试的那样)。但是,即使这样,您也可能在构建要处理的帧时遇到问题。

    虽然您在处理帧时遇到内存积累似乎很奇怪(根据我的经验,如果处理速度不够快,您就会停止获取它们),如果它们正在等待 I/O。

    也许调度信号量可以让您限制将新项目添加到处理队列中。有关这方面的更多信息,我强烈推荐 Mike Ash 的“GCD Practicum”文章,其中他着眼于使用调度信号量优化 I/O 绑定的缩略图处理操作。

    【讨论】:

    • OpenGL 提供了很好的帧速率,但这不是正确的预设(尽管照片预设视频帧的分辨率仅比示例略高)。是的,很奇怪,但很容易重现。您可以获取 Apple 的示例代码,将其放入一个新的 iPhone 应用程序中,在 captureOutput:didOutputSampleBuffer:fromConnection: 中添加一个短暂的延迟,然后在几秒钟内看到它崩溃。这与我尚未探索的 OpenGL 示例之间的一个区别是后者使用了可能最终类似于信号量方法的主调度队列(主线程)。我会调查这两个。
    • 事实证明就像使用主调度队列一样简单(见我的回答)。
    • 目前最好的解决方案是添加一个自动释放池。我已经更新了我的答案。
    猜你喜欢
    • 1970-01-01
    • 2013-05-23
    • 2014-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 2014-05-21
    相关资源
    最近更新 更多