Как правильно выпустить AVCaptureSession - PullRequest
30 голосов
/ 18 сентября 2010

Я использую классы 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];

Ответы [ 7 ]

18 голосов
/ 04 октября 2010

Вот лучшее решение, которое я нашел до сих пор. Основная идея заключается в использовании финализатора очереди отправки. Когда очередь отправки выйдет, мы можем быть уверены, что во втором потоке, где обрабатываются примеры буферов, больше не будет никаких действий.

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

...

К сожалению, теперь я должен явно прекратить захват. В противном случае освобождение моего экземпляра не освободит его, потому что второй поток теперь также увеличивает и уменьшает счетчик.

Еще одна проблема заключается в том, что мой класс теперь выпускается из двух разных потоков. Это надежно или это следующая проблема, вызывающая сбои?

4 голосов
/ 09 октября 2010

Я опубликовал очень похожий вопрос на форуме разработчиков Apple и получил ответ от сотрудника Apple.Он говорит, что это известная проблема:

Это проблема с AVCaptureSession / VideoDataOutput в iOS 4.0-4.1, которая была исправлена ​​и появится в будущем обновлении.В настоящее время вы можете обойти это, подождав короткий период после остановки AVCaptureSession, например, полсекунды, прежде чем избавиться от сеанса и вывода данных.

Он / она предлагает следующееcode:

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.
    }
);

Мне все еще больше нравится подход с финализатором очереди отправки, потому что этот код только догадывается, когда мог закончиться второй поток.

3 голосов
/ 31 января 2014

Согласно текущим документам Apple ( 1 ) [AVCaptureSession stopRunning] - это синхронная операция, которая блокируется до тех пор, пока приемник полностью не остановится.Так что все эти проблемы больше не должны происходить.

2 голосов
/ 11 декабря 2013
 -(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;

}

я вызвал эту функцию перед тем, как всплывать и толкать любой другой вид.Это решило мою проблему предупреждения о нехватке памяти.

2 голосов
/ 20 января 2012

С помощью финализаторов очереди вы можете использовать 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];
2 голосов
/ 04 октября 2010

Решено! Возможно, это последовательность действий при инициализации сеанса. Этот работает для меня:

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

Кстати, эта последовательность, похоже, решает проблему синхронных уведомлений :)

1 голос
/ 24 сентября 2010

После выделения 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 по этому делу.

Это работает, но, пожалуйста, дайте мне знать, если увидите какой-нибудь трюк. Я бы предпочел работать с ним асинхронно.

Спасибо

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...