1

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,一个静态布尔,并使用一个信号量,但无济于事......有时那个额外的帧仍然潜入。任何人都有想法吗?可能有一些 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!
...

但通常(大约五分之四)我的控制台看起来像:

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...因为它位于我无法控制的框架的超类中。

4

3 回答 3

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
        });
    });
}

如果有人有任何其他建议,但我很想听听。谢谢!

于 2012-09-30T07:33:25.240 回答
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 更改名称,您的代码就会中断。也许在您的情况下,您设计的解决方案是最好的。不过我会考虑的。好问题。

于 2012-10-01T23:57:27.783 回答
0

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

相机.h:

@interface Camera : NSObject

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

@end

相机.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在点击后可能需要一些时间来更改,这听起来可能是不可接受的,但仍应停止任何不在队列中的处理被窃听。

于 2012-10-02T00:47:25.297 回答