2010-09-18 4 views
30

Я использую классы AV Foundation для захвата живого видеопотока с камеры и обработки образцов видео. Это хорошо работает. Тем не менее, у меня возникают проблемы с тем, чтобы правильно освободить экземпляры баз данных 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]; 

Cleanup:

[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

Что такое AugmReality в функции capture_cleanup? Я не получаю эту вещь. – NiravPatel

+0

* AugmReality * - это настраиваемый класс моего приложения, реализующий делегат буфера выборки. Таким образом, переменная * p * (или * ar *) относится к экземпляру, который мне нравится выпускать, но не может, пока сеанс захвата полностью не остановится. – Codo

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

Что я нашел в заблуждение, хотя заключается в том, что при вызове seeion.stopRunning, onVideoStop: называется синхронно! несмотря на асинхронное предположение Apple по этому делу.

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

Thanks

+1

Я уже пытался использовать уведомления и нашел то же самое, что и вы: уведомление отправляется непосредственно перед возвратом _session.stopRunning_ и пока второй поток все еще работает. Таким образом, приложение все еще рушилось время от времени. Я попробую предлагаемый код очистки, но я просто поставлю его после _session.stopRunning_. Или это действительно может иметь значение? – Codo

+0

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

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

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

+3

Прошу прощения, но это не имеет никакого значения. Он все еще падает. И как разрешить проблему уведомления? Уведомление теперь отложено до завершения второго потока? Между тем, я нашел решение, которое работает для меня (см. Мой собственный ответ). – Codo

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

С помощью финализаторов очереди вы можете использовать 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

У меня проблема с замораживанием камеры, после получения телефонного звонка, как я могу переориентировать предварительный просмотр камеры –

2

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

+1

, они, похоже, происходят со мной iOS 10, Swift 3, Xcode 9 –

Смежные вопросы