7

我正在屏幕区域上进行一些运动检测。在开始检测之前,我想设置焦点和曝光并锁定它们,这样它们就不会触发错误动作。因此,我向设备发送了 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
4

1 回答 1

18

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

另外,顺便说一句,我在您发布的代码中注意到您没有使用上下文来识别您的观察结果,并且您没有在observeValueForKeyPath:...实现中调用 super 。这两件事都可能导致微妙的、难以诊断的错误。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:...,当您初始化时,当您解除分配时),然后使用几个名为andremoveObserver:...的 ivars “门控”该行为。在您当前将 ivars 设置为. 然后只在那些 ivars 是and 然后才采取行动,而不是将 ivars 设置为. 这应该具有预期的效果,而无需您每次都添加和删除观察。waitingForFocuswaitingForExposure-focusAtPointaddObserver:...YESobserveValueForKeyPath:...YESremoveObserver:...NO

我想到的另一种方法是removeObserver:...使用 GCD 调用“稍后”。所以你会改变removeObserver:...这样的:

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

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

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

希望有帮助。

于 2013-06-17T13:15:04.320 回答