我正在使用AV Foundation类从相机捕获实时视频流并处理视频样本。这很好用。但是,一旦完成,我确实无法正确释放AV基础实例(捕获会话,预览图层,输入和输出)。
当我不再需要会话和所有关联对象时,我会停止捕获会话并释放它。这大部分时间都有效。但是,有时应用程序崩溃时会在调度队列(以及处理视频样本的位置)创建的第二个线程中引发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];
答案 0 :(得分:18)
这是迄今为止我发现的最佳解决方案。基本思想是使用调度队列的终结器。当调度队列退出时,我们可以确定在处理样本缓冲区的第二个线程中不会再有任何操作。
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];
...
不幸的是,我现在必须明确停止捕获。否则释放我的实例将不会释放它,因为第二个线程现在递增并递减计数器。
另一个问题是我的班级现在从两个不同的线程中释放。这是可靠的还是导致崩溃的下一个问题?
答案 1 :(得分:4)
我在Apple开发者论坛上发布了一个非常类似的问题,得到了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 :(得分:3)
根据当前的Apple文档(1)[AVCaptureSession stopRunning]
是一个同步操作,它会阻塞,直到接收器完全停止运行。所以这些问题不应该再发生了。
答案 3 :(得分:2)
解决! 也许是初始化会话的一系列操作。这个对我有用:
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];
顺便说一下这个序列似乎解决了同步通知问题:)
答案 4 :(得分: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对象的样本缓冲区委托设置为nil,否则它们将永远不会释放其关联的队列,因此在您释放AVCaptureSession时永远不会调用它们的终结器。
[avs removeOutput:vOut];
[vOut setSampleBufferDelegate:nil queue:NULL];
答案 5 :(得分: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;
}
我在弹出并推送任何其他视图之前调用了此函数。它解决了我的低内存警告问题。
答案 6 :(得分: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:同步调用!尽管Apple对此案进行了异步假设。
它的工作但请告诉我,以防你看到任何技巧。我宁愿异步使用它。
谢谢