2010-09-18 8 views
30

Używam klas AV Foundation do przechwytywania strumienia wideo na żywo z kamery i przetwarzania próbek wideo. To działa ładnie. Mam jednak problemy z właściwym wypuszczeniem instancji fundacji AV (sesja przechwytywania, warstwa podglądu, wejście i wyjście) po zakończeniu.Jak poprawnie zwolnić AVCaptureSession

Gdy nie potrzebuję już sesji i wszystkich powiązanych obiektów, zatrzymuję sesję przechwytywania i zwalniam ją. Działa to przez większość czasu. Czasami jednak aplikacja ulega awarii z sygnałem EXEC_BAD_ACCESS podniesionym w drugim wątku, który został utworzony przez kolejkę wysyłkową (i gdzie przetwarzane są próbki wideo). Przyczyną awarii jest głównie moja własna instancja klasy, która służy jako delegat bufora próbek i jest zwalniana po zatrzymaniu sesji przechwytywania.

Dokumentacja Apple wspomina o problemie: Zatrzymanie sesji przechwytywania jest operacją asynchroniczną. To znaczy: nie dzieje się to natychmiast. W szczególności drugi wątek kontynuuje przetwarzanie próbek wideo i dostęp do różnych instancji, takich jak sesja przechwytywania lub urządzenia wejściowe i wyjściowe.

Jak właściwie wypuścić AVCaptureSession i wszystkie powiązane instancje? Czy istnieje powiadomienie, które niezawodnie informuje mnie, że sesja AVCaptureSession została zakończona?

Oto mój kod:

Deklaracje:

AVCaptureSession* session; 
AVCaptureVideoPreviewLayer* previewLayer; 
UIView* view; 

Konfiguracja instancji:

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

Odpowiedz

19

Oto najlepsze rozwiązanie znalazłem do tej pory. Podstawową ideą jest użycie finalizatora kolejki wysyłkowej. Gdy kolejka wysyłania zostanie zamknięta, możemy być pewni, że nie będzie więcej akcji w drugim wątku, w którym przetwarzane są bufory próbek.

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

... 

Niestety, teraz muszę wyraźnie zatrzymać przechwytywanie. W przeciwnym razie zwolnienie mojej instancji nie zwolni jej, ponieważ drugi wątek również zwiększa i zmniejsza licznik.

Kolejnym problemem jest to, że moja klasa została zwolniona z dwóch różnych wątków. Czy to jest niezawodne, czy też jest to kolejny problem powodujący awarie?

+0

Co to jest AugmReality w funkcji capture_cleanup? nie dostaję tego. – NiravPatel

+0

* AugmReality * to niestandardowa klasa mojej aplikacji implementująca delegata bufora próbki. Zatem zmienna * p * (lub * ar *) odnosi się do instancji, którą chcę zwolnić, ale nie może, dopóki sesja przechwytywania nie zostanie całkowicie zatrzymana. – Codo

1

Po alokacji AVCaptureSession możesz użyć:

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

Są oddzwanianie odpowiednich metod upon session.stopRunning, session.startRunning itp

Nie należy również wdrożyć pewne nieudokumentowane blok oczyszczania:

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

Co znalazłem mylące chociaż jest to, że po wywołaniu funkcji seeingion.stopRunning, onVideoStop: nazywa się synchronicznie! pomimo asynchronicznego założenia Apple w tej sprawie.

Działa, ale proszę dać mi znać, jeśli widzisz jakąś sztuczkę. Wolałbym pracować z nim asynchronicznie.

Dzięki

+1

Próbowałem już użyć powiadomień i znalazłem to samo co ty: Powiadomienie jest wysyłane bezpośrednio przed zwrotem _session.stopRunning_ i gdy drugi wątek nadal działa. Tak więc aplikacja wciąż się zawieszała. Spróbuję zaproponowanego kodu oczyszczania, ale wstawię go po _session.stopRunning_. Czy może to naprawdę coś zmienić? – Codo

+0

Niestety, twoje rozwiązanie nie działa. Wciąż od czasu do czasu ulega awarii, ponieważ drugi wątek nie jest natychmiast zamykany i dostęp do już wydanych wystąpień. – Codo

1

rozwiązany! Być może jest to kolejność działań podczas inicjowania sesji. Ten działa dla mnie:

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 ta sekwencja wydaje się rozwiązać problemu :) synchronicznego powiadomień

+3

Przykro mi, ale to nie ma znaczenia. Nadal się zawiesza. A jak powinno się rozwiązać problem z powiadomieniem? Czy powiadomienie jest teraz opóźnione do czasu zakończenia drugiego wątku? W międzyczasie znalazłem rozwiązanie, które działa dla mnie (zobacz moją własną odpowiedź). – Codo

4

Napisałem bardzo podobne pytanie na Apple Developer Forum i otrzymałem odpowiedź od pracownika Apple. Mówi, że to znany problem:

Jest to problem z AVCaptureSession/VideoDataOutput w iOS 4,0-4,1 że został naprawiony i pojawią się w przyszłej aktualizacji. W tej chwili można go obejść, czekając na krótki okres po zatrzymaniu AVCaptureSession, np. pół sekundy, przed pozbyciem się sesji i danych wyjściowych.

On/ona proponuje następujący kod:

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

nadal lubię podejścia z finalizatora kolejki wysyłka lepiej, bo ten kod tylko domysły, gdy druga nitka mogła zakończona.

2

Za pomocą finalizatorów kolejkowania można użyć dispatch_emaphore dla każdej kolejki, a następnie kontynuować procedurę czyszczenia po zakończeniu.

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

Pamiętaj, że musisz ustawić swoją AVCaptureVideoDataOutput/AVCaptureAudioDataOutput obiekty przykładowe delegatów buforowe do zera lub nigdy nie wyda związane z nimi kolejki, a więc nigdy nie nazywają ich finalizatory po zwolnieniu 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; 

} 

zadzwoniłem tę funkcję przed popping i pchania dowolny inny pogląd. Rozwiązał mój problem ostrzegania o małej ilości pamięci.

+0

Mam problem z zamrożeniem aparatu, po otrzymaniu połączenia telefonicznego, jak mogę ponownie zainicjować mój podgląd kamery –

2

Zgodnie z bieżącym apple docs (1) [AVCaptureSession stopRunning] jest operacją synchroniczną, która blokuje do momentu całkowitego zatrzymania odbiornika. Wszystkie te kwestie nie powinny się już więcej wydarzyć.

+1

zdają się dziać na mnie iOS 10, Swift 3, Xcode 9 –