使用键值观察器锁定exposureMode会导致崩溃

时间:2013-06-13 21:22:18

标签: ios key-value-observing avcapturesession avcapturedevice

我正在屏幕区域进行一些运动检测。在开始检测之前,我想设置焦距和曝光并锁定它们,这样它们就不会触发假动作。因此,我将AVCaptureFocusModeAutoFocus和AVCaptureExposureModeAutoExpose发送到设备并添加KeyvalueObserver。当观察者说它已完成聚焦并改变曝光时,它会锁定它们(并开始运动检测)。一切都可以正常工作,但锁定曝光会在几秒钟内崩溃应用程序“,尽管两种情况都有相同的代码。

static void * const MyAdjustingFocusObservationContext = (void*)&MyAdjustingFocusObservationContext;
static void * const MyAdjustingExposureObservationContext = (void*)&MyAdjustingExposureObservationContext;

-(void)focusAtPoint{

   CGPoint point;
   if(fromRight) point.x = 450.0/480.0;
   else point.x = 30.0/480.0;
   point.y = 245.0/320.0;

   AVCaptureDevice *device =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

   if(device != nil) {
       NSError *error;
       if([device lockForConfiguration:&error]){

          if([device isExposureModeSupported:AVCaptureFocusModeContinuousAutoFocus] && [device isFocusPointOfInterestSupported]) {
             [device setFocusPointOfInterest:point];
             [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
             [device addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:MyAdjustingFocusObservationContext];
             NSLog(@"focus now");
          }

          if([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure] && [device isExposurePointOfInterestSupported]) {
             [device setExposurePointOfInterest:point];
             [device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
             [device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];
             NSLog(@"expose now");
          }

          [device unlockForConfiguration];
      }else{
        NSLog(@"Error in Focus Mode");
      }        
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

   AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
  NSError *error;

  if([keyPath isEqualToString:@"adjustingFocus"]){   
    if(![object isAdjustingFocus]){
       [device removeObserver:self forKeyPath:keyPath context:context];
       if([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
          [device lockForConfiguration:&error];
          device.focusMode = AVCaptureFocusModeLocked;
          [device unlockForConfiguration];
          NSLog(@" focus locked");
       }
    }
  }

  if([keyPath isEqualToString:@"adjustingExposure"]){    
    if(![object isAdjustingExposure]){
       [device removeObserver:self forKeyPath:keyPath context:context];
       if([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
          [device lockForConfiguration:&error];
          device.exposureMode=AVCaptureExposureModeLocked; //causes the crash
          [device unlockForConfiguration];
          NSLog(@" exposure locked");
       }
    }
  }

如果我注释掉“device.exposureMode = AVCaptureExposureModeLocked”这一行,一切正常(除了焦点没有锁定)。如果我将线条移动到焦点观察者,一切正常(除了曝光有时会在正确设置之前锁定)。如果我以其他方式锁定曝光,例如通过计时器,它可以工作。

崩溃日志对我没什么帮助(希望有人可以解释)

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000000
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   Foundation                      0x3209d5e2 NSKVOPendingNotificationRelease + 6
1   CoreFoundation                  0x317b21c8 __CFArrayReleaseValues + 352
2   CoreFoundation                  0x317419f8 _CFArrayReplaceValues + 308
3   CoreFoundation                  0x3174391c CFArrayRemoveValueAtIndex + 80
4   Foundation                      0x3209d6b6 NSKeyValuePopPendingNotificationPerThread + 38
5   Foundation                      0x32090328 NSKeyValueDidChange + 356
6   Foundation                      0x3206a6ce -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 90
7   AVFoundation                    0x30989fd0 -[AVCaptureFigVideoDevice handleNotification:payload:] + 1668
8   AVFoundation                    0x30983f60 -[AVCaptureDeviceInput handleNotification:payload:] + 84
9   AVFoundation                    0x3098fc64 avcaptureSessionFigRecorderNotification + 924
10  AVFoundation                    0x309b1c64 AVCMNotificationDispatcherCallback + 188
11  CoreFoundation                  0x317cee22 __CFNotificationCenterAddObserver_block_invoke_0 + 122
12  CoreFoundation                  0x31753034 _CFXNotificationPost + 1424
13  CoreFoundation                  0x3175460c CFNotificationCenterPostNotification + 100
14  CoreMedia                       0x31d3db8e CMNotificationCenterPostNotification + 114
15  Celestial                       0x34465aa4 FigRecorderRemoteCallbacksServer_NotificationIsPending + 628
16  Celestial                       0x34465826 _XNotificationIsPending + 66
17  Celestial                       0x344657dc figrecordercallbacks_server + 96
18  Celestial                       0x34465028 remrec_ClientPortCallBack + 172
19  CoreFoundation                  0x317cc5d8 __CFMachPortPerform + 116
20  CoreFoundation                  0x317d7170    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
21  CoreFoundation                  0x317d7112 __CFRunLoopDoSource1 + 134
22  CoreFoundation                  0x317d5f94 __CFRunLoopRun + 1380
23  CoreFoundation                  0x31748eb8 CFRunLoopRunSpecific + 352
24  CoreFoundation                  0x31748d44 CFRunLoopRunInMode + 100
25  GraphicsServices                0x3530c2e6 GSEventRunModal + 70
26  UIKit                           0x3365e2fc UIApplicationMain + 1116
27  ShootKing                       0x000ed304 main (main.m:16)
28  ShootKing                       0x000ed28c start + 36

1 个答案:

答案 0 :(得分:16)

你不会在任何文档中找到这个(即我没有“证据”),但是我可以告诉你痛苦的个人经历,包括很多天(如果不是几周)的调试,这个通过在该属性的KVO通知处理程序内添加/删除属性的观察者来导致崩溃。 (根据我的经验,堆栈跟踪中NSKeyValuePopPendingNotificationPerThread的存在是“吸烟枪”。)我还经验性地观察到通知给定属性的观察者的顺序是不确定的,所以即使在通知处理程序中添加或删除观察者工作一些的时间,它可以在不同的情况下任意失败。 (我假设在KVO的某个地方有一个无序的数据结构可以在不同的顺序中枚举,可能基于指针的数值或类似的任意东西。)在过去,我已经解决过这可以通过在设置属性之前/之后立即发布NSNotification来为观察者提供添加/删除自己的机会。这是一个笨重的模式,但它比崩溃更好(并允许我继续使用依赖于KVO的其他东西,比如绑定。)

另外,请注意,在您发布的代码中,您没有使用上下文来识别您的观察结果,而您在observeValueForKeyPath:...实施中并没有调用超级。这两件事都可能导致细微的,难以诊断的错误。 KVO更具防弹性的图案如下:

static void * const MyAdjustingFocusObservationContext = (void*)&MyAdjustingFocusObservationContext;
static void * const MyAdjustingExposureObservationContext = (void*)&MyAdjustingExposureObservationContext;

- (void)focusAtPoint
{
    // ... other stuff ...
    [device addObserver:self forKeyPath:@"adjustingFocus" options:NSKeyValueObservingOptionNew context:MyAdjustingFocusObservationContext];
    [device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];
    // ... other stuff ...
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{
    if (context == MyAdjustingFocusObservationContext)
    {
        // Do stuff
    }
    else if (context == MyAdjustingExposureObservationContext)
    {
        // Do other stuff
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

编辑:我想跟进,看看我是否可以在这种具体情况下提供更多帮助。我从代码和你的评论中收集到你正在寻找这些观察结果,实际上是一次性的。我认为有两种方法可以做到这一点:

对象始终观察捕获设备(即addObserver:...,当你解除启动时,removeObserver:...,然后“}时,这个对象将更加直接和防弹。 “使用名为waitingForFocuswaitingForExposure的几个ivars的行为。在-focusAtPoint,您当前addObserver:...将ivars设置为YES。然后在observeValueForKeyPath:...中,只有当这些ivars为YES时,才会采取措施,然后将{iv}设置为removeObserver:...而不是NO。这应该具有所需的效果,而不需要每次添加和删除观察。

我想到的另一种方法是使用GCD稍后调用removeObserver:...。所以你可以像这样更改removeObserver:...

    dispatch_async(dispatch_get_main_queue(), ^{ [device removeObserver:self forKeyPath:keyPath context:context]; });

这将导致在通知过程完成后在运行循环的其他位置进行调用。这稍微不那么防弹,因为没有什么可以保证在发生延迟删除调用之前第二次不会触发通知。在这方面,第一种方法在实现所需的一次性行为方面更加严格“正确”。

编辑2:我不能放手。 :)我弄清楚你为什么会崩溃。我发现在exposureMode的KVO处理程序中设置adjustingExposure会导致adjustingExposure的另一个通知,因此堆栈会一直爆炸,直到您的进程被终止。通过将处理observeValueForKeyPath:...更改的adjustingExposure部分包裹在dispatch_async(dispatch_get_main_queue(), ^{...});中(包括最终的removeObserver:...调用),我能够使其工作。在此之后它对我有用,并且肯定锁定曝光和焦点。也就是说,就像我上面提到的那样,用ivars可以更好地处理这种情况以防止递归而不是任意延迟dispatch_async()

希望有所帮助。