2012-11-22 132 views
12

我正在構建一個iOS應用程序(我的第一個),它可以實時處理視頻靜幀。爲了深入瞭解這一點,我遵循了Apple的example from the AV* documentationAVCaptureDeviceOutput不調用委託方法captureOutput

該過程涉及設置輸入(相機)和輸出。輸出與委託一起工作,在這種情況下委託是控制器本身(它符合並實現所需的方法)。

我遇到的問題是委託方法永遠不會被調用。下面的代碼是控制器的實現,它有幾個NSLog。我可以看到「已啓動」消息,但「所謂的委託方法」從未顯示。

此代碼全部在實現「AVCaptureVideoDataOutputSampleBufferDelegate」協議的控制器中。

- (void)viewDidLoad { 

    [super viewDidLoad]; 

    // Initialize AV session  
     AVCaptureSession *session = [AVCaptureSession new]; 

     if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 
      [session setSessionPreset:AVCaptureSessionPreset640x480]; 
     else 
      [session setSessionPreset:AVCaptureSessionPresetPhoto]; 

    // Initialize back camera input 
     AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 

     NSError *error = nil; 

     AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error]; 

     if([session canAddInput:input]){ 
      [session addInput:input]; 
     } 


    // Initialize image output 
     AVCaptureVideoDataOutput *output = [AVCaptureVideoDataOutput new]; 

     NSDictionary *rgbOutputSettings = [NSDictionary dictionaryWithObject: 
              [NSNumber numberWithInt:kCMPixelFormat_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]; 
     [output setVideoSettings:rgbOutputSettings]; 
     [output setAlwaysDiscardsLateVideoFrames:YES]; // discard if the data output queue is blocked (as we process the still image) 


     //[output addObserver:self forKeyPath:@"capturingStillImage" options:NSKeyValueObservingOptionNew context:@"AVCaptureStillImageIsCapturingStillImageContext"]; 

     videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL); 
     [output setSampleBufferDelegate:self queue:videoDataOutputQueue]; 


     if([session canAddOutput:output]){ 
      [session addOutput:output]; 
     } 

     [[output connectionWithMediaType:AVMediaTypeVideo] setEnabled:YES]; 


    [session startRunning]; 

    NSLog(@"started"); 


} 


- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 

     NSLog(@"delegate method called"); 

     CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer]; 

     self.theImage.image = [UIImage imageWithCGImage: cgImage ]; 

     CGImageRelease(cgImage); 

} 

注:我與iOS 5.0建設爲目標。

編輯:

我發現一個question,雖然要求解決不同的問題,是做什麼我的代碼是應該做的。我已將該問題的代碼逐字複製到空白的xcode應用程序中,並將NSLog添加到捕獲輸出函數,並且它不會被調用。這是一個配置問題?有什麼我失蹤?

+2

如果開始運行會話時發生錯誤(可能是因爲您沒有收到任何幀),那麼它會發佈一個'AVCaptureSessionRuntimeErrorNotification'通知。使用'[[NSNotificationCenter defaultCenter] addObserver:selector:name:object:];'來監聽它,當你的選擇器被調用時,從用戶字典中獲取'AVCaptureSessionErrorKey'來查看錯誤。 – lnafziger

+1

感謝您的輸入@Inafziger。我訂閱了AVCaptureSessionRuntimeErrorNotification,但似乎沒有觸發:| – SuitedSloth

+0

通過什麼方式創建視圖控制器? '開始'獲得輸出嗎? – Tommy

回答

28

您的session是一個局部變量。其範圍限於viewDidLoad。由於這是一個新項目,我認爲可以安全地說你正在使用ARC。在這種情況下,該對象不會泄漏,因此會繼續像鏈接問題那樣生存,而編譯器將確保在退出viewDidLoad之前取消分配該對象。

因此,您的會話沒有運行,因爲它不再存在。

(旁白:在self.theImage.image = ...是不安全的,因爲它會執行主隊列的UIKit的動作;你可能要dispatch_async是到dispatch_get_main_queue()

因此,樣本更正:

@implementation YourViewController 
{ 
    AVCaptureSession *session; 
} 

- (void)viewDidLoad { 

    [super viewDidLoad]; 

    // Initialize AV session  
     session = [AVCaptureSession new]; 

     if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 
      [session setSessionPreset:AVCaptureSessionPreset640x480]; 
     else 
     /* ... etc ... */ 
} 


- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 

     NSLog(@"delegate method called"); 

     CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer]; 

     dispatch_sync(dispatch_get_main_queue(), 
     ^{ 
      self.theImage.image = [UIImage imageWithCGImage: cgImage ]; 
      CGImageRelease(cgImage); 
     }); 
} 

大多數人現在主張在實例變量名稱的開頭使用下劃線,但爲簡單起見,我省略了它。您可以使用Xcode內置的重構工具在確認診斷正確後解決該問題。

我將CGImageRelease移到發送到主隊列的塊中,以確保其生命週期延伸超過其捕獲到UIImage。我無法立即找到任何文檔來確認CoreFoundation對象在塊中捕獲時會自動延長其生命週期。

+0

非常感謝你先生,完全做到了:) - 它didn對我來說,測試會話是一個實例變量。當然,現在我回頭看看SquareCam,它當然也在那裏。 我從這個問題中學到了很多其他的東西,所以這不是一切都是浪費。再次感謝! – SuitedSloth

+2

我嘗試過這個解決方案,但它不適合我。你能否給我任何其他見解? – tuler

+1

@tuler我發現了另一個爲什麼'didOutputSampleBuffer'委託方法可能不會被調用的原因,請查看下面的答案。 http://stackoverflow.com/a/27704982/2979418 –

2

在我的情況下,問題是存在的,因爲我叫

if ([_session canAddOutput:_videoDataOutput]) 
     [_session addOutput:_videoDataOutput]; 

我打電話

[_session startRunning]; 

前我剛開始叫addOutput:startRunning

希望它幫助別人。

+0

@nbanic請停止更新_just_將單詞'cuz'更改爲'因爲',而不對這篇文章進行其他改進。到目前爲止,您已經編輯了幾個答案,可以做其他重大改進。 – doppelgreener

+1

@JonathanHobbs收到的消息,我會停下來。 – nbanic

+9

爲什麼會話啓動後會調用addOutput? – jgvb

16

我發現多了一個理由didOutputSampleBuffer委託方法可能不會調用 - 保存到文件獲取樣品緩衝輸出連接是互斥的。換句話說,如果您的會話已經有AVCaptureMovieFileOutput,然後您添加AVCaptureVideoDataOutput,則只調用AVCaptureFileOutputRecordingDelegate委託方法。

僅供參考,我在AV Foundation框架文檔中找不到任何地方明確描述了此限制,但Apple支持在幾年前證實了這一點,正如在此SO answer中所述。

解決該問題的一種方法是完全刪除AVCaptureMovieFileOutput,並將錄製的幀手動寫入didOutputSampleBuffer委託方法中的文件,並與您的自定義緩衝區數據處理一起。你可能會發現thesetwo SO答案很有用。