Блокировка exposureMode с наблюдателем ключевых значений вызывает сбой
Я делаю некоторое обнаружение движения в области экрана. Перед началом обнаружения я хочу установить фокус и экспозицию и заблокировать их, чтобы они не запускали ложное движение. Поэтому я отправляю 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");
}
}
}
Если я прокомментирую строку " устройство.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 ответов
вы не найдете этого ни в одной документации (т. е. у меня нет "доказательства"), но я могу сказать вам из болезненного, личного опыта, состоящего из многих дней (если не недель) отладки, что этот вид сбоя вызван добавлением/удалением наблюдателей для свойства внутри обработчика уведомлений KVO для этого свойства. (Присутствие NSKeyValuePopPendingNotificationPerThread
в трассировке стека есть "дымящийся пистолет" в моем опыте.) Я также эмпирически заметил, что порядок, в котором наблюдатели данного свойства уведомление недетерминировано, поэтому даже если добавление или удаление наблюдателей внутри обработчиков уведомлений работает некоторые времени, он может не произвольно в разных обстоятельствах. (Я предполагаю, что где-то в кишках кво есть неупорядоченная структура данных, которая может быть перечислена в разных порядках, возможно, на основе числового значения указателя или чего-то произвольного.) В прошлом я работал над этим, немедленно разместив NSNotification до / после установки свойства, чтобы дать наблюдателям возможность добавлять / удалять себя. Это неуклюжий шаблон, но это лучше, чем сбой (и позволяет мне продолжать использовать другие вещи, которые полагаются на KVO, такие как привязки.)
кроме того, просто в стороне, я замечаю в коде, который вы опубликовали, что вы не используете контексты для идентификации своих наблюдений, и вы не вызываете super в своем observeValueForKeyPath:...
реализация. Обе эти вещи могут привести к тонким, труднодиагностируемым ошибкам. Более пуленепробиваемый шаблон для кво выглядит так:
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: я хотел продолжить, чтобы узнать, могу ли я больше помочь в этой конкретной ситуации. Из кода и ваших комментариев я заключаю, что вы ищете, чтобы эти наблюдения были фактически одноразовыми. Я вижу два способа сделать это:
более простой и пуленепробиваемый подход был бы для этого объекта всегда наблюдайте за устройством захвата (т. е. addObserver:...
когда вы init,removeObserver:...
когда вы dealloc), но затем "ворота" поведение с помощью пары Ивар называется waitingForFocus
и waitingForExposure
. В -focusAtPoint
где вы в настоящее время addObserver:...
вместо этого установите ivars в YES
. Потом в observeValueForKeyPath:...
только принять меры, если эти ивары YES
и removeObserver:...
просто установите ivars в NO
. Это должно иметь желаемый эффект без необходимости добавлять и удалять замечания каждый раз.
другой подход, о котором я думал, это позвонить removeObserver:...
"далее" с помощью GCD. Таким образом, вы измените removeObserver:...
такой:
dispatch_async(dispatch_get_main_queue(), ^{ [device removeObserver:self forKeyPath:keyPath context:context]; });
это приведет к тому, что вызов будет сделан в другом месте цикла выполнения, после завершения процесса уведомления. Это немного менее пуленепробиваемо, потому что нет ничего, что гарантирует, что уведомление не будет запущено во второй раз, прежде чем произойдет отложенный вызов удаления. В этом отношении первый подход является более строго "правильным" в достижении желаемого одноразового поведения.
EDIT 2: я просто не мог отпустить. :) Я понял, почему ты разбиваешься. Я наблюдаю за этой обстановкой exposureMode
в то время как в обработчике KVO для adjustingExposure
заканчивается тем, что вызывает другое уведомление для adjustingExposure
, и поэтому стек взрывается, пока ваш процесс не будет убит. Я смог заставить его работать, обернув часть observeValueForKeyPath:...
, который обрабатывает изменения adjustingExposure
на dispatch_async(dispatch_get_main_queue(), ^{...});
(включая возможное removeObserver:...
звонок). После этого он работал для меня, и определенно фиксировать экспозицию и фокус. Тем не менее, как я уже упоминал примерно выше, это, возможно, было бы лучше обработано с помощью ivars, чтобы предотвратить рекурсию, а не произвольно задержанную dispatch_async()
.
надеюсь, это поможет.