如何正确释放AVCaptureSession

问题描述:

我正在使用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]; 

这里是到目前为止,我已经找到了最好的解决方案。基本的想法是使用调度队列的终结器。当调度队列退出时,我们可以确定在处理样本缓冲区的第二个线程中不会有任何更多的操作。

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

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

解决! 也许这是初始化会话的行为顺序。这一次对我的作品:

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

我在苹果开发者论坛上发布了一个非常类似的问题,并从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. 
    } 
); 

我还是喜欢与调度队列终结更好,因为当第二个线程可能已经完成了这个代码只是猜测的方法。

使用队列终结器,您可以为每个队列使用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]; 

-(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

根据当前的apple文档(1[AVCaptureSession stopRunning]是一个同步操作,它会阻塞,直到接收器完全停止运行。所以所有这些问题都不应该再发生了。

+1

他们确实发生在我身上iOS 10,Swift 3,Xcode 9 – 2018-02-13 04:13:15