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];
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?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ć.
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ń:)
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];
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.
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ć.
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
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