6

Tôi đang thực hiện một số phát hiện chuyển động trên một khu vực của màn hình. Trước khi bắt đầu phát hiện, tôi muốn đặt lấy nét và phơi sáng và khóa chúng để chúng không kích hoạt chuyển động sai. Do đó, tôi gửi một AVCaptureFocusModeAutoFocus và AVCaptureExposureModeAutoExpose vào thiết bị và thêm một KeyvalueObserver. Khi người quan sát nói rằng nó đã hoàn thành việc lấy nét và thay đổi phơi sáng, nó sẽ khóa chúng (và bắt đầu phát hiện chuyển động). Tất cả mọi thứ hoạt động tốt với trọng tâm, nhưng khóa phơi sáng bị treo ứng dụng trong vòng vài giây", mặc dù có mã giống hệt nhau trong cả hai trường hợp.Khóa phơi sángMode với người quan sát giá trị quan trọng gây ra tai nạn

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

Nếu tôi nhận xét ra dòng 'device.exposureMode = AVCaptureExposureModeLocked' tất cả mọi thứ công trình Nếu tôi di chuyển đường tới máy quan sát lấy nét, mọi thứ hoạt động tốt (ngoại trừ việc phơi sáng đôi khi khóa trước khi nó được đặt chính xác). Nếu tôi khóa phơi sáng theo cách khác, ví dụ: thông qua

Nhật ký sự cố không giúp ích nhiều cho tôi (hy vọng ai đó có thể giải thích nó)

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 

Trả lời

16

Bạn sẽ không tìm thấy điều này trong bất kỳ tài liệu nào (tức là Tôi không có "bằng chứng"), nhưng tôi có thể cho bạn biết từ kinh nghiệm cá nhân, đau đớn bao gồm nhiều ngày (nếu không phải tuần) gỡ lỗi, rằng loại vụ tai nạn này là do thêm/loại bỏ các nhà quan sát cho một tài sản bên trong KVO xử lý thông báo cho thuộc tính đó. (Sự hiện diện của NSKeyValuePopPendingNotificationPerThread trong dấu vết ngăn xếp là "khẩu súng hút thuốc" trong kinh nghiệm của tôi.) Tôi cũng đã quan sát thực nghiệm rằng thứ tự quan sát của một thuộc tính đã cho là không xác định, vì vậy ngay cả khi thêm hoặc loại bỏ người quan sát bên trong xử lý thông báo hoạt động một số của thời gian, nó có thể tùy ý thất bại trong các trường hợp khác nhau. (Tôi giả sử có một cấu trúc dữ liệu không có thứ tự trong ruột của KVO ở đâu đó có thể được liệt kê theo các thứ tự khác nhau, có lẽ dựa trên giá trị số của một con trỏ hoặc một cái gì đó tùy ý như thế.) Trong quá khứ, tôi đã làm việc điều này bằng cách đăng một NSNotification ngay lập tức trước/sau khi thiết lập các tài sản để cung cấp cho các nhà quan sát một cơ hội để thêm/loại bỏ mình. Đó là một mô hình vụng về, nhưng nó tốt hơn là đâm (và cho phép tôi tiếp tục sử dụng những thứ khác dựa vào KVO, như ràng buộc.)

Ngoài ra, cũng giống như một sang một bên, tôi nhận thấy trong mã bạn đã đăng rằng bạn không sử dụng ngữ cảnh để xác định các quan sát của bạn và bạn không gọi điện thoại siêu trong triển khai observeValueForKeyPath:... của mình. Cả hai thứ này đều có thể dẫn tới những lỗi nhỏ, khó chẩn đoán. Một mẫu chống đạn cho KVO trông giống như sau:

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: Tôi muốn theo dõi xem tôi có thể trợ giúp nhiều hơn với tình huống cụ thể này không. Tôi thu thập từ mã và từ ý kiến ​​của bạn rằng bạn đang tìm kiếm những quan sát này để có hiệu quả là một bức ảnh. Tôi thấy hai cách để làm điều này:

Phương pháp đơn giản hơn và chống đạn sẽ cho đối tượng này để luôn quan sát các thiết bị chụp (tức là addObserver:... khi bạn init, removeObserver:... khi bạn dealloc) nhưng sau đó "cổng" hành vi sử dụng một vài màu ivars được gọi là waitingForFocuswaitingForExposure. Trong số -focusAtPoint, nơi bạn hiện đang addObserver:..., thay vào đó hãy đặt ivars thành YES. Sau đó, trong observeValueForKeyPath:... chỉ thực hiện hành động nếu những ivars đó là YES và sau đó thay vì removeObserver:..., chỉ cần đặt ivars thành NO. Điều này sẽ có hiệu quả mong muốn mà không yêu cầu bạn thêm và loại bỏ các quan sát mỗi lần.

Cách tiếp cận khác mà tôi đã nghĩ đến là gọi removeObserver:... "sau" bằng GCD. Vì vậy, bạn sẽ thay đổi removeObserver:... như thế này:

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

Điều này sẽ khiến cuộc gọi mà phải được thực hiện ở đâu đó trong vòng lặp chạy, sau quá trình thông báo đã hoàn tất. Điều này là ít đạn hơn vì không có gì đảm bảo rằng thông báo sẽ không được kích hoạt lần thứ hai trước khi cuộc gọi gỡ bỏ bị trì hoãn xảy ra. Về vấn đề đó, cách tiếp cận đầu tiên là "đúng" một cách nghiêm ngặt hơn trong việc đạt được hành vi một-shot mong muốn.

CHỈNH SỬA 2: Tôi không thể bỏ qua. :) Tôi đã tìm ra lý do tại sao bạn đang gặp sự cố. Tôi quan sát rằng thiết lập exposureMode trong khi trong một xử lý KVO cho adjustingExposure kết thúc lên gây ra một thông báo cho adjustingExposure, và do đó ngăn xếp thổi lên cho đến khi quá trình của bạn bị giết. Tôi có thể làm cho nó hoạt động bằng cách gói phần observeValueForKeyPath:... xử lý các thay đổi cho adjustingExposure trong một dispatch_async(dispatch_get_main_queue(), ^{...}); (bao gồm cả cuộc gọi removeObserver:... cuối cùng). Sau khi nó làm việc cho tôi, và chắc chắn khóa phơi sáng và tập trung. Điều đó nói rằng, như tôi đã đề cập ở trên, điều này được cho là sẽ được xử lý tốt hơn với các cây ngà để ngăn chặn sự đệ quy và không bị chậm trễ tùy ý dispatch_async().

Hy vọng điều đó sẽ hữu ích.

+0

Cảm ơn câu trả lời của bạn. Bạn có lẽ đúng khi sử dụng ngữ cảnh tốt hơn, ngay cả khi nó không giúp ích trong trường hợp này. Tôi đã cập nhật câu hỏi của mình. Bạn hoàn toàn đúng trong đó có một cái gì đó kỳ lạ xảy ra khi tôi cố gắng để loại bỏ các quan sát viên (nó bắt đầu gửi giá trị ở một tỷ lệ tuyệt vời cho đến khi nó bị treo). Tôi đã thử thay đổi khi nào và theo thứ tự nào tôi thêm KVO: s, không may mắn. Tôi không chắc chắn làm thế nào để kiểm tra đề xuất của bạn để đăng một NSNotification kể từ khi AdjustFocus là chỉ đọc (hoặc tôi có thể đã hiểu lầm bạn). – Sten

+1

@Sten cập nhật với nhiều suy nghĩ hơn – ipmcc

+0

Thật không may tôi đã chạy KVO liên tục để bắt đầu, và nó không hoạt động (hoặc tôi đã thử nghiệm một lần nữa để chắc chắn). Vấn đề có vẻ là AVCaptureExposureModeLocked khiến KVO chạy tự do, độc lập nếu bạn loại bỏ người quan sát hay không. Trong khi AVCaptureFocusModeLocked hoạt động như mong đợi. Vì vậy, nó là một cái gì đó trong sự kết hợp của phơi sáng và KVO gây ra vấn đề. Tôi thực sự đánh giá cao nỗ lực của bạn, nhưng tôi có lẽ phải tìm cách khác để thực hiện nó. – Sten

Các vấn đề liên quan