iOS:有选择地禁用captureOutput的处理:didOutputSampleBuffer:fromConnection:来自AVFoundation中的主线程

时间:2012-09-30 03:59:20

标签: ios multithreading avfoundation grand-central-dispatch race-condition

我需要在captureOutput:didOutputSampleBuffer:fromConnection:方法中有选择地(并且可靠地)关闭sampleBuffers的处理。如你所知,它是从GCD队列而不是在主线程上调用的......但是我从UIButton(当然在主线程上)接收用户输入并通过设置BOOL标志告诉我的相机对象停止所有处理

然而,有时我看到在处理被认为停止后,裂缝中有1个额外的框架滑过。有什么办法可以绝对肯定按下按钮后什么都不会被处理?现在我正在做一个微不足道的测试:

// in ViewController:
- (IBAction)tappedStop:(id)sender {
    NSLog("stop processing!");
    _camera.capturing = NO;
}

// in my camera obj:
- (void)captureOutput:(AVCaptureOutput *)captureOutput did... {
    if (!capturing) {
        return;
    }
    NSLog(@"processing!");
}

我尝试过使用@synchronized,一个静态BOOL,并使用一个信号量,但无济于事......有时额外的帧仍然潜入。有人有想法吗?可能有一些GCD方法可以做我想要的但是我不知道如何去做。

以下是我的调试控制台中的结果(有时)(缩短以使其更具可读性):

2012-09-29 23:29:01.869 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.910 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.953 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.994 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.047 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.078 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.121 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.166 -[ViewController tappedButton:] [Line 913] stop processing!
2012-09-29 23:29:02.161 __33-_block_invoke_0 [Line 322] processing!
...

但通常(约5次中有4次)我的控制台看起来像:

2012-09-29 23:29:01.869 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.910 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.953 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:01.994 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.047 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.078 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.121 __33-_block_invoke_0 [Line 322] processing!
2012-09-29 23:29:02.166 -[ViewController tappedButton:] [Line 913] stop processing!

我可能还应该提到,我无法访问正在调用captureOutput:didOutput...的原始队列,因为它位于我无法控制的框架的超类中。

3 个答案:

答案 0 :(得分:1)

我不知道为什么我没有更快地想到这一点,但是如果我在对主线程的异步调用中包装我的整个captureOutput:didOutput ...方法,即使它看起来有点不理想,关闭sampleBuffer处理按预期工作。我想这很有道理;我的触摸事件总是来自主线程,因为我没有处理我的缓冲区输出队列中使用哪个线程的句柄,唯一的方法(我可以看到)可靠地检查isRecording是从主要执行此操作线程也是如此。

- (void)captureOutput:(AVCaptureOutput *)captureOutput did... {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (!isRecording) {
            return;
        }

        NSLog(@"processing!");

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
            // do actual processing
        });
    });
}

如果有人有任何其他建议,虽然我很想听到他们。谢谢!

答案 1 :(得分:0)

我会这样做

// in ViewController:
- (IBAction)tappedStop:(id)sender {
    NSLog("stop processing!");
    dispatch_async(capture_dispatch_queue, ^{_camera.capturing = NO;});
}

这将导致将块插入串行调度队列。它将在任何更多的成像处理块被处理之前执行。就像现在你在_camera.capturing变量上有一个竞争条件。这将通过相对于捕获回调串行执行变量集来解决竞争条件。

编辑:

  

我可能还应该提到我无法访问   调用captureOutput:didOutput ...的原始队列,因为它在   我无法控制的框架的超类。

我错过了那一部分。这很难。从技术上讲,您可以通过Objective-C运行时访问它。但是,如果iVar改变名称,您的代码就会破坏。也许在您的情况下,您设计的解决方案是最好的。我会考虑一下。好问题。

答案 2 :(得分:0)

您可以使用相机类本地的串行调度队列作为一种互斥体。基本思想是所有需要同步的工作将被分派到该队列 - 捕获变量的设置(和可能获取),以及回调中完成的工作。这要求我将所有同步工作都移到相机本身。我将日志保留为同步工作的一部分,以便您可以准确地检测代码是否按顺序执行。它似乎运行良好,每秒运行60次,以模拟60 fps的视频捕捉。

Camera.h:

@interface Camera : NSObject

@property (nonatomic,getter = isCapturing) BOOL capturing;

@end

Camera.m

#define CALLBACK_INTERVAL (1.0/60.0)

@implementation Camera {
    dispatch_queue_t _sync_queue;
    BOOL _capturing;
}

- (id)init
{
    if (self = [super init])
    {
        _capturing = YES;
        _sync_queue = dispatch_queue_create("com.mycompany.whatever", NULL);
        [self performSelector:@selector(triggerCallback) withObject:nil afterDelay:CALLBACK_INTERVAL];
    }

    return self;
}


- (void)setCapturing:(BOOL)capturing
{
    dispatch_async(_sync_queue, ^{
        _capturing = capturing;
        if (!_capturing)
            NSLog(@"STOP");
    });
}

- (void)repeatingCallback
{
    dispatch_async(_sync_queue, ^{
        if (!_capturing)
            return;
        NSLog(@"WORKING");
    });
}

- (void)triggerCallback
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self repeatingCallback];
    });
    [self performSelector:@selector(triggerCallback) withObject:nil afterDelay:CALLBACK_INTERVAL];
}

@end

在视图控制器中:

- (IBAction)stopCapturing:(id)sender
{
    self.camera.capturing = NO;
}

希望这有帮助,如果您有任何疑问,请与我联系。需要考虑的一件事是处理工作与回调频率相比需要多长时间。如果需要相当长的时间才能在队列上积累大量工作,那么点击后_capturing可能需要一些时间才能更改,听起来这可能是不可接受的,但仍应停止任何不在在被点击时排队。