2010-09-18 142 views
30

我正在使用AV基礎類從攝像頭捕獲實時視頻流並處理視頻樣本。這很好地工作。但是,一旦完成,我確實遇到了正確釋放AV基礎實例(捕獲會話,預覽圖層,輸入和輸出)的問題。如何正確釋放AVCaptureSession

當我不再需要會話和所有關聯的對象時,我停止捕獲會話並釋放它。這在大部分時間都適用。但是,有時應用程序會崩潰,並在調度隊列創建的第二個線程中引發EXEC_BAD_ACCESS信號(以及處理視頻樣本的位置)。崩潰主要是由於我自己的類實例,它用作樣本緩衝區委託,並在停止捕獲會話後被釋放。

Apple文檔提到了這個問題:停止捕獲會話是一個異步操作。那就是:它不會立即發生。特別是,第二個線程繼續處理視頻樣本並訪問不同的實例,如捕獲會話或輸入和輸出設備。

那麼如何正確地釋放AVCaptureSession和所有相關的實例呢?是否有通知可靠地告訴我AVCaptureSession已完成?

這裏是我的代碼:

聲明:

AVCaptureSession* session; 
AVCaptureVideoPreviewLayer* previewLayer; 
UIView* view; 

情況下的設置:

AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; 
session = [[AVCaptureSession alloc] init]; 

AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice: camera error: &error]; 
[session addInput: input]; 
AVCaptureVideoDataOutput* output = [[[AVCaptureVideoDataOutput alloc] init] autorelease]; 
[session addOutput: output]; 

dispatch_queue_t queue = dispatch_queue_create("augm_reality", NULL); 
[output setSampleBufferDelegate: self queue: queue]; 
dispatch_release(queue); 

previewLayer = [[AVCaptureVideoPreviewLayer layerWithSession: session] retain]; 
previewLayer.frame = view.bounds; 
[view.layer addSublayer: previewLayer]; 

[session startRunning]; 

清理:

[previewLayer removeFromSuperlayer]; 
[previewLayer release]; 
[session stopRunning]; 
[session release]; 

回答

19

這裏是到目前爲止,我已經找到了最好的解決方案。基本的想法是使用調度隊列的終結器。當調度隊列退出時,我們可以確定在處理樣本緩衝區的第二個線程中不會有任何更多的操作。

static void capture_cleanup(void* p) 
{ 
    AugmReality* ar = (AugmReality *)p; // cast to original context instance 
    [ar release]; // releases capture session if dealloc is called 
} 

... 

dispatch_queue_t queue = dispatch_queue_create("augm_reality", NULL); 
dispatch_set_context(queue, self); 
dispatch_set_finalizer_f(queue, capture_cleanup); 
[output setSampleBufferDelegate: self queue: queue]; 
dispatch_release(queue); 
[self retain]; 

... 

不幸的是,我現在必須明確地停止捕獲。否則釋放我的實例將不會釋放它,因爲第二個線程現在也遞增和遞減計數器。

另一個問題是我的課程現在從兩個不同的線程發佈。這是可靠的還是它是導致崩潰的下一個問題?

+0

什麼是capture_cleanup函數中的AugmReality?我沒有得到那件事。 – NiravPatel 2014-08-14 10:01:52

+0

* AugmReality *是我的應用程序實現樣本緩衝區委託的自定義類。所以變量* p *(或* ar *)指的是我想要釋放的實例,但是不能直到捕獲會話完全停止。 – Codo 2014-08-14 14:35:48

1

AVCaptureSession分配後,您可以使用:

NSNotificationCenter *notify = 
[NSNotificationCenter defaultCenter]; 
[notify addObserver: self 
      selector: @selector(onVideoError:) 
      name: AVCaptureSessionRuntimeErrorNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStart:) 
      name: AVCaptureSessionDidStartRunningNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStop:) 
      name: AVCaptureSessionDidStopRunningNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStop:) 
      name: AVCaptureSessionWasInterruptedNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStart:) 
      name: AVCaptureSessionInterruptionEndedNotification 
      object: session]; 

這些回撥後session.stopRunning,session.startRunning等相關的方法

在那裏,你也應該實施一些無證清理塊:

AVCaptureInput* input = [session.inputs objectAtIndex:0]; 
[session removeInput:input]; 
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]; 
[session removeOutput:output]; 

我發現雖然令人困惑是在調用seesion.stopRunning時,onVideoStop:被同步調用!儘管蘋果公司對此案進行了異步假設。

它的工作,但請讓我知道,如果你看到任何詭計。我寧願異步使用它。

謝謝

+1

我已經嘗試使用通知,並發現與您一樣:通知在_session.stopRunning_返回之前立即發送,而第二個線程仍在運行。所以該應用程序仍然不時崩潰。我會嘗試建議的清理代碼,但我會在_session.stopRunning_之後放置它。或者這真的可以有所作爲? – Codo 2010-09-24 10:32:48

+0

不幸的是,您的解決方案無法正常工作。它不時崩潰,因爲第二個線程不會立即退出並訪問已經發布的實例。 – Codo 2010-09-26 17:24:35

1

解決! 也許這是初始化會話的行爲順序。這一次對我的作品:

NSError *error = nil; 

if(session) 
    [session release]; 

// Create the session 
session = [[AVCaptureSession alloc] init]; 


// Configure the session to produce lower resolution video frames, if your 
// processing algorithm can cope. We'll specify medium quality for the 
// chosen device. 
session.sessionPreset = AVCaptureSessionPresetMedium; 

// Find a suitable AVCaptureDevice 
AVCaptureDevice *device = [AVCaptureDevice 
          defaultDeviceWithMediaType:AVMediaTypeVideo]; 

// Create a device input with the device and add it to the session. 
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device 
                    error:&error]; 
if (!input) { 
    // Handling the error appropriately. 
} 
[session addInput:input]; 

// Create a VideoDataOutput and add it to the session 
AVCaptureVideoDataOutput *output = [[[AVCaptureVideoDataOutput alloc] init] autorelease]; 
[session addOutput:output]; 


// Configure your output. 
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL); 
[output setSampleBufferDelegate:self queue:queue]; 
dispatch_release(queue); 

// Specify the pixel format 
output.videoSettings = 
[NSDictionary dictionaryWithObject: 
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] 
          forKey:(id)kCVPixelBufferPixelFormatTypeKey]; 

// If you wish to cap the frame rate to a known value, such as 15 fps, set 
// minFrameDuration. 
output.minFrameDuration = CMTimeMake(1, 15); 

previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session]; 
[delegate layerArrived:previewLayer]; 

NSNotificationCenter *notify = 
[NSNotificationCenter defaultCenter]; 
[notify addObserver: self 
      selector: @selector(onVideoError:) 
      name: AVCaptureSessionRuntimeErrorNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStart:) 
      name: AVCaptureSessionDidStartRunningNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStop:) 
      name: AVCaptureSessionDidStopRunningNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStop:) 
      name: AVCaptureSessionWasInterruptedNotification 
      object: session]; 
[notify addObserver: self 
      selector: @selector(onVideoStart:) 
      name: AVCaptureSessionInterruptionEndedNotification 
      object: session]; 

// Start the session running to start the flow of data 
[session startRunning]; 

順便說一下這個序列似乎解決同步問題的通知:)

+3

對不起,但這沒有任何區別。它仍然崩潰。那麼應該如何解決通知問題?現在通知是否延遲到第二個線程結束?與此同時,我找到了適合我的解決方案(請參閱我自己的答案)。 – Codo 2010-10-04 18:38:10

4

我在蘋果開發者論壇上發佈了一個非常類似的問題,並從Apple員工那裏得到了答案。他說,這是一個已知的問題:

這與AVCaptureSession/VideoDataOutput的問題 的iOS 4.0-4.1已定,並會出現在以後的更新。對於 而言,您可以等待 停止AVCaptureSession之後的一小段時間來解決此問題。在處理 會話和數據輸出之前半秒鐘。

他/她提出了以下代碼:

dispatch_after(
    dispatch_time(0, 500000000), 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), // or main queue, or your own 
    ^{ 
     // Do your work here. 
     [session release]; 
     // etc. 
    } 
); 

我還是喜歡與調度隊列終結更好,因爲當第二個線程可能已經完成了這個代碼只是猜測的方法。

2

使用隊列終結器,您可以爲每個隊列使用dispatch_semaphore,然後在完成後繼續執行清理例程。

#define GCD_TIME(delayInSeconds) dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC) 

static void vQueueCleanup(void* context) { 
    VideoRecordingViewController *vc = (VideoRecordingViewController*)context; 
    if (vc.vSema) dispatch_semaphore_signal(vc.vSema); 
} 

static void aQueueCleanup(void* context) { 
    VideoRecordingViewController *vc = (VideoRecordingViewController*)context; 
    if (vc.aSema) dispatch_semaphore_signal(vc.aSema); 
} 

//In your cleanup method: 
vSema = dispatch_semaphore_create(0); 
aSema = dispatch_semaphore_create(0); 
self.avSession = nil; 
if (vSema) dispatch_semaphore_wait(vSema, GCD_TIME(0.5)); 
if (aSema) dispatch_semaphore_wait(aSema, GCD_TIME(0.5)); 
[self.navigationController popViewControllerAnimated:YES]; 

請記住,你必須設置你的AVCaptureVideoDataOutput/AVCaptureAudioDataOutput對象樣品緩衝代表到零或他們將永遠不會釋放它們相關的隊列,因此從來沒有叫他們的終結,當你釋放你的AVCaptureSession。

[avs removeOutput:vOut]; 
[vOut setSampleBufferDelegate:nil queue:NULL]; 
2
-(void)deallocSession 
{ 
[captureVideoPreviewLayer removeFromSuperlayer]; 
for(AVCaptureInput *input1 in session.inputs) { 
    [session removeInput:input1]; 
} 

for(AVCaptureOutput *output1 in session.outputs) { 
    [session removeOutput:output1]; 
} 
[session stopRunning]; 
session=nil; 
outputSettings=nil; 
device=nil; 
input=nil; 
captureVideoPreviewLayer=nil; 
stillImageOutput=nil; 
self.vImagePreview=nil; 

} 

我大跌眼鏡,推動其他任何視圖之前調用該函數。它解決了我的低內存警告問題。

+0

我有相機凍結問題,在接到電話後,我如何重新給我的相機預覽 – 2015-08-25 06:02:07

2

根據當前的apple文檔(1[AVCaptureSession stopRunning]是一個同步操作,它會阻塞,直到接收器完全停止運行。所以所有這些問題都不應該再發生了。

+1

他們確實發生在我身上iOS 10,Swift 3,Xcode 9 – 2018-02-13 04:13:15