如何正确释放AVCaptureSession

我正在使用AV基础课程从摄像头捕捉实时videostream并处理video样本。 这很好。 但是,一旦完成,我确实遇到了正确释放AV基础实例(捕获会话,预览图层,input和输出)的问题。

当我不再需要会话和所有关联的对象时,我停止捕获会话并释放它。 这在大多数情况下是有效的。 但是,有时应用程序会崩溃,在调度队列创build的第二个线程(以及处理video样本的位置)中引发EXEC_BAD_ACCESS信号。 崩溃主要是由于我自己的类实例,它作为样本缓冲区委托,并在停止捕获会话后被释放。

Apple文档提到了这个问题:停止捕获会话是一个asynchronous操作。 那就是:它不会立即发生。 具体来说,第二个线程继续处理video样本并访问像捕获会话或input和输出设备的不同实例。

那么如何正确地释放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]; 

这是迄今为止我find的最好的解决scheme。 基本的想法是使用调度队列的终结器。 当调度队列退出时,我们可以确定在处理样本缓冲区的第二个线程中不会有任何更多的动作。

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

不幸的是,我现在必须明确地停止捕获。 否则释放我的实例不会释放它,因为第二个线程现在也增加和减less计数器。

另一个问题是我的课程现在从两个不同的线程发布。 这是可靠的还是它是导致崩溃的下一个问题?

我在苹果开发者论坛上发布了一个非常类似的问题,并得到了苹果员工的回答。 他说这是一个已知的问题:

这是iOS 4.0-4.1中的AVCaptureSession / VideoDataOutput问题,这个问题已经解决,并且会在以后的更新中出现。 目前,您可以通过在停止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; } 

我在popup并推送任何其他视图之前调用了这个函数。 它解决了我的低内存警告问题。

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

在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等callback相关的方法。

在那里,你也应该实现一些无证的清理块:

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

我发现令人困惑的是,在调用seesion.stopRunning时,onVideoStop:被同步调用! 尽pipe苹果公司对此案的asynchronous假设。

它的工作,但请让我知道,如果你看到任何诡计。 我宁愿asynchronous使用它。

谢谢

解决了! 也许这是初始化会话的顺序。 这个为我工作:

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

顺便说一句,这个序列似乎解决了同步通知的问题:)