【问题标题】:Locking exposureMode with key value observer causes crash用键值观察者锁定exposureMode会导致崩溃
【发布时间】:2013-06-13 21:22:17
【问题描述】:

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

【问题讨论】:

    标签: ios key-value-observing avcapturesession avcapturedevice


    【解决方案1】:

    你不会在任何文档中找到这一点(即我没有“证据”),但我可以从痛苦的个人经历告诉你,这包括很多天(如果不是几周)的调试,这某种崩溃是由在该属性的 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];
        }
    }
    

    编辑:我想跟进,看看我是否可以在这种特定情况下提供更多帮助。我从代码和您的 cmets 中收集到,您正在寻找这些观察结果有效地是一次性的。我看到了两种方法:

    更直接和防弹的方法是让该对象始终观察捕获设备(即,当您初始化时为addObserver:...,当您解除分配时为removeObserver:...),然后“门控”该行为使用几个名为 waitingForFocuswaitingForExposure 的 ivars。在您当前addObserver:...-focusAtPoint 中,将ivars 设置为YES。然后在observeValueForKeyPath:... 中,仅当这些 ivars 是 YES 时才采取行动,然后将 removeObserver:... 设置为 NO 而不是 removeObserver:...。这应该会产生预期的效果,而无需您每次都添加和删除观察结果。

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

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

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

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

    希望对您有所帮助。

    【讨论】:

    • 感谢您的回答。你可能是对的,使用上下文更好,即使在这种情况下它没有帮助。我已经更新了我的问题。你是绝对正确的,当我尝试移除观察者时发生了一些奇怪的事情(它开始以惊人的速度发送值直到它崩溃)。我尝试更改添加 KVO:s 的时间和顺序,但没有任何运气。我不确定如何测试您发布 NSNotification 的建议,因为adjustFocus 是只读的(或者我可能误解了您)。
    • 不幸的是,我一开始就一直在运行 KVO,但它也不起作用(我已经再次测试以确定)。问题似乎是 AVCaptureExposureModeLocked 导致 KVO 疯狂运行,与您是否移除观察者无关。而 AVCaptureFocusModeLocked 的行为符合预期。因此,导致问题的原因是 Exposure 和 KVO 的组合。我真的很感谢你的努力,但我可能必须找到另一种方法来实现它。
    • 现在我们正在取得进展:) 这似乎确实有效。谢谢,对你的坚持加分。
    • @ipmcc - 好答案!!!!我正在努力尝试为曝光和焦点添加观察者。我将尝试您的建议,如果我仍然有问题,那么我会添加一个问题。但是感谢您提供的所有信息。很有帮助!! +1
    • @ipmcc - 我还有问题。这对我有用,但不是我想要的顺序。还没有人回答我的问题。如果您收到此消息,您会看一下吗? stackoverflow.com/questions/22029381/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 2021-09-22
    • 1970-01-01
    • 1970-01-01
    • 2021-01-27
    • 2017-04-01
    相关资源
    最近更新 更多