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

編集: この特定の状況でさらに支援できるかどうかを確認するためにフォローアップしたかった. コードとあなたのコメントから、これらの観察が効果的にワンショットになるように探していることがわかります。これを行うには2つの方法があります。

より簡単で防弾のアプローチは、このオブジェクトが常にキャプチャ デバイスを監視するaddObserver:...ことです (つまり、初期化時、解放時) が、 ととremoveObserver:...呼ばれる ivar のカップルを使用して動作を「ゲート」します。現在代わりに ivar を に設定している場所で。次に、単にivar を に設定する代わりに、それらの ivar が である場合にのみアクションを実行します。これにより、毎回観測を追加および削除する必要なく、望ましい効果が得られるはずです。waitingForFocuswaitingForExposure-focusAtPointaddObserver:...YESobserveValueForKeyPath:...YESremoveObserver:...NO

私が考えたもう1つのアプローチは、removeObserver:...GCDを使用して「後で」呼び出すことです。したがって、次のremoveObserver:...ように変更します。

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

これにより、通知プロセスが終了した後、実行ループの他の場所でその呼び出しが行われます。遅延削除呼び出しが発生する前に、通知が 2 回発生しないことを保証するものがないため、これは防弾性がやや劣ります。その点で、最初のアプローチは、目的のワンショット動作を達成する上でより厳密に「正しい」です。

編集2:手放すことができませんでした。:) クラッシュする理由がわかりました。exposureModeの KVO ハンドラーで設定するとadjustingExposure、 の別の通知が発生するadjustingExposureため、プロセスが強制終了されるまでスタックが爆発することがわかりました。observeValueForKeyPath:...変更を処理する部分を(最終的な呼び出しを含む)にラップすることで、機能させることができましたadjustingExposure。この後、それは私にとってはうまくいき、間違いなく露出とフォーカスをロックしていました. とはいえ、上で述べたように、これは再帰を防ぐために ivar で処理する方が適切であり、恣意的な遅延ではありません。dispatch_async(dispatch_get_main_queue(), ^{...});removeObserver:...dispatch_async()

それが役立つことを願っています。

于 2013-06-17T13:15:04.320 に答える