Impact lock Mode with a key value observer causes a failure

I deal with motion detection on an area of ​​the screen. Before starting the detection, I want to set the focus and exposure and lock them so that they do not cause false movement. So I send AVCaptureFocusModeAutoFocus and AVCaptureExposureModeAutoExpose to the device and add KeyvalueObserver. When the observer says that he has finished focusing and changing the exposure, he blocks them (and triggers motion detection). Everything works fine with focus, but locking the exposure causes the application to crash for a few seconds, "despite the fact that in both cases the code is identical.

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

If I comment on the line "device.exposureMode = AVCaptureExposureModeLocked", everything works fine (except that the focus is not locked). If I move the line to the focus observer, everything works fine (except that the exposure is sometimes locked before it is set correctly). If I lock the exposure in any other way, for example. through the timer it works.

The crash log doesn't help me (hope someone can interpret it)

 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 
+6
source share
1 answer

You will not find this in any documentation (that is, I have no “evidence”), but I can tell you from a painful personal experience, consisting of many days (if not weeks) of debugging, that this kind of failure is caused by adding / removing observers for a property inside the KVO notification handler for this property. (The presence of NSKeyValuePopPendingNotificationPerThread in the stack trace is a “smoking gun” in my experience.) I have also empirically observed that the order in which observers of this property are notified is non-deterministic, so even if adding or removing observers inside notification handlers works for a while, it may arbitrarily fail under different circumstances. (I assume there is an unordered data structure in the guts of the KVO that can be listed in different orders, possibly based on the numerical value of the pointer or something similar.) In the past, I worked around this by posting NSNotification just before / after installation properties to give observers the ability to add / remove themselves. This is a clumsy scheme, but it is better than a failure (and allows me to continue to use other things that rely on KVO, like bindings).

In addition, as an aside, I noticed in the code you posted that you do not use contexts to determine your observations, and you do not call super observeValueForKeyPath:... in your implementation. Both of these things can lead to subtle, hard-to-diagnose errors. A more bulletproof pattern for KVO is as follows:

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

EDIT: I wanted to see if I could help in this particular situation. From the code and your comments, I understand that you are looking for these observations in order to effectively be a single shot. I see two ways to do this:

A simpler and bulletproof approach would be to ensure that this object always addObserver:... capture device (ie addObserver:... when initialized, removeObserver:... when you release it), but then the “gate” of the behavior, using a pair of ivars called waitingForFocus and waitingForExposure . In -focusAtPoint , where you currently addObserver:... instead, set ivars to YES . Then in observeValueForKeyPath:... perform the action only if these ivars are YES , and then instead of removeObserver:... just set ivars to NO . This should have the desired effect, without requiring you to add and remove an observation each time.

Another approach that I was thinking about is to call removeObserver:... "later" with a GCD. Therefore, you would removeObserver:... as follows:

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

This will cause the call to be made elsewhere in the run loop after the notification process is completed. This is slightly smaller than bulletproof because it does not guarantee that a notification will not be issued a second time before a deferred call. In this regard, the first approach is more strictly "correct" in achieving the desired one-time behavior.

EDIT 2: I just couldn't let that happen. :) I understood why you are falling apart. I noticed that setting exposureMode , while in the KVO handler for adjustingExposure ends up causing another notification for adjustingExposure , and so the stack explodes until your process is killed. I managed to get it to work by wrapping the observeValueForKeyPath:... , which handles the changes in adjustingExposure to dispatch_async(dispatch_get_main_queue(), ^{...}); (including a possible call to removeObserver:... ). After that, he worked for me and definitely blocked the exposure and focus. However, as I mentioned above, it would probably be better to handle it with ivars to prevent recursion, rather than with an arbitrary delay dispatch_async() .

Hope this helps.

+16
source

Source: https://habr.com/ru/post/947250/


All Articles