Jak prawidłowo zwolnić AVCaptureSession

Używam zajęć Fundacji AV do przechwytywania strumienia wideo na żywo z kamery i przetwarzania próbek wideo. To działa ładnie. Jednak mam problemy z prawidłowym zwolnieniem instancji AV foundation (sesja przechwytywania, warstwa podglądu, wejście i wyjście) po zakończeniu.

Gdy nie potrzebuję już sesji i wszystkich powiązanych obiektów, Zatrzymuję sesję przechwytywania i zwalniam ją. To działa przez większość czasu. Czasami jednak aplikacja ulega awarii z sygnałem EXEC_BAD_ACCESS podniesiony w drugim wątku, który został utworzony przez kolejkę dispatch (i gdzie przetwarzane są próbki wideo). Awaria jest spowodowana głównie przez moją własną instancję 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ą. Oznacza to, że nie dzieje się to natychmiast. W szczególności drugi wątek nadal przetwarza próbki wideo i uzyskuje dostęp do różnych wystąpienia takie jak sesja przechwytywania lub urządzenia wejściowe i wyjściowe.

Więc jak poprawnie zwolnić AVCaptureSession i wszystkie powiązane instancje? Czy istnieje powiadomienie, które niezawodnie informuje mnie, że avcapturesession się skończył?

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

Oczyszczenie:

[previewLayer removeFromSuperlayer];
[previewLayer release];
[session stopRunning];
[session release];
Author: Codo, 2010-09-18

7 answers

Oto najlepsze rozwiązanie, jakie do tej pory znalazłem. Podstawową ideą jest użycie finalizera kolejki wysyłkowej. Po zakończeniu kolejki wysyłkowej możemy być pewni, że w drugim wątku, w którym przetwarzane są bufory próbek, nie będzie już żadnej akcji.

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 przestać przechwytywać. W przeciwnym razie zwolnienie mojej instancji nie uwolni jej, ponieważ drugi wątek teraz zwiększa i zmniejsza licznik.

[1]}kolejnym problemem jest to, że moja klasa jest teraz zwolniony z dwóch różnych wątków. Czy jest to niezawodne, czy jest to kolejny problem powodujący awarie?
 18
Author: Codo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-18 14:17:41

Zamieściłem bardzo podobne pytanie na forum programistów Apple i dostałem odpowiedź od pracownika Apple. Mówi, że to znany problem:

Jest to problem z avcapturesession / VideoDataOutput w iOS 4.0-4.1, który został naprawiony i pojawi się w przyszłej aktualizacji. Na czasu, można obejść go, czekając na krótki okres po zatrzymanie Avcapturessession, np. pół sekundy, przed usunięciem sesja i dane wyjście.

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 podoba mi się podejście z finalizerem kolejki wysyłkowej, ponieważ ten kod tylko zgaduje, kiedy drugi wątek mógł się skończyć.

 4
Author: Codo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-09 08:33:35

Rozwiązane! Być może jest to sekwencja acionów inicjujących sesję. Ten mi działa:

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 chyba rozwiązuje problem synchronicznych powiadomień:)

 2
Author: VLegakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-04 10:24:26

Z finalizatorami kolejek, możesz użyć dispatch_semaphore 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ć Bufor próbek obiektów AVCaptureVideoDataOutput/AVCaptureAudioDataOutput deleguje na nil, w przeciwnym razie obiekty te nigdy nie zwolnią powiązanych kolejek, a tym samym nigdy nie wywołają finalizatorów po zwolnieniu Avcaptureession.

[avs removeOutput:vOut];
[vOut setSampleBufferDelegate:nil queue:NULL];
 2
Author: Mahyar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-20 02:45:31
 -(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;

}

Wywołałem tę funkcję, zanim popchnąłem i popchnąłem dowolny inny widok. To rozwiązało mój problem ostrzeżenia o niskiej pamięci.

 2
Author: souvickcse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-11 06:47:45

Zgodnie z aktualnymi dokumentami apple(1) [AVCaptureSession stopRunning] jest operacją synchroniczną, która blokuje się, aż odbiornik całkowicie przestanie działać. Więc wszystkie te problemy nie powinny się już wydarzyć.

 2
Author: Kiran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-21 07:49:32

Po alokacji Avcapturession 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];

Wywołują one odpowiednie metody podczas sesji.stopRunning, session.startRunning itp.

Tam też powinieneś zaimplementować jakiś nieudokumentowany blok oczyszczający:

AVCaptureInput* input = [session.inputs objectAtIndex:0];
[session removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0];
[session removeOutput:output];  
To, co uznałem za mylące, to to, że po wywołaniu seesion.stopRunning, onVideoStop: nazywa się synchronicznie! pomimo asynchronicznego założenia Apple ' a w tej sprawie.

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

Dzięki

 1
Author: VLegakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-24 10:01:23