2010-09-18 28 views
30

Estoy utilizando las clases AV Foundation para capturar la transmisión de video en vivo de la cámara y procesar las muestras de video. Esto funciona bien Sin embargo, tengo problemas para liberar correctamente las instancias de la base AV (sesión de captura, capa de vista previa, entrada y salida) una vez que he terminado.Cómo liberar correctamente una sesión AVCaptureSession

Cuando ya no necesito la sesión y todos los objetos asociados, detengo la sesión de captura y la lanzo. Esto funciona la mayor parte del tiempo. Sin embargo, a veces la aplicación se bloquea con una señal EXEC_BAD_ACCESS planteada en el segundo subproceso creado por la cola de distribución (y donde se procesan las muestras de video). El bloqueo se debe principalmente a mi propia instancia de clase, que sirve como delegado del buffer de muestra y se libera después de detener la sesión de captura.

La documentación de Apple menciona el problema: detener la sesión de captura es una operación asincrónica. Es decir: no ocurre de inmediato. En particular, el segundo hilo continúa procesando muestras de video y accediendo a diferentes instancias como la sesión de captura o los dispositivos de entrada y salida.

¿Cómo puedo liberar correctamente la sesión de AVCaptureSession y todas las instancias relacionadas? ¿Hay alguna notificación que me indique confiablemente que AVCaptureSession ha terminado?

Aquí está mi código:

Declaraciones:

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

de configuración de instancias:

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

Limpieza:

[previewLayer removeFromSuperlayer]; 
[previewLayer release]; 
[session stopRunning]; 
[session release]; 

Respuesta

19

Aquí es la mejor solución que he encontrado hasta ahora. La idea básica es usar el finalizador de la cola de envío. Cuando se cierra la cola de distribución, podemos estar seguros de que no habrá más acción en el segundo subproceso donde se procesan los almacenamientos intermedios de muestra.

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

... 

Desafortunadamente, ahora tengo que dejar de capturar explícitamente. De lo contrario, liberar mi instancia no lo liberará porque el segundo hilo ahora también incrementa y disminuye el contador.

Otro problema es que mi clase ahora está liberada de dos hilos diferentes. ¿Es esto confiable o es el siguiente problema que causa fallas?

+0

¿Qué es AugmReality en la función capture_cleanup? No estoy recibiendo eso. – NiravPatel

+0

* AugmReality * es una clase personalizada de mi aplicación que implementa el delegado de buffer de muestra. Entonces la variable * p * (o * ar *) se refiere a la instancia que me gusta liberar pero no puede hasta que la sesión de captura se haya detenido por completo. – Codo

1

Después de la asignación AVCaptureSession se puede utilizar:

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

Estos están llamando de vuelta los métodos pertinentes sobre session.stopRunning, etc. session.startRunning

Allí también se debe aplicar algún bloque de limpieza indocumentado:

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

Lo que me pareció confuso, aunque es que al llamar a seesion.stopRunning, onVideoStop: se llama sincrónicamente! a pesar de la asunción asincrónica de Apple en el caso.

Su funcionamiento, pero hágamelo saber en caso de que vea algún truco. Preferiría trabajar con él de forma asíncrona.

Gracias

+1

Ya he intentado utilizar las notificaciones y he encontrado lo mismo que usted: la notificación se envía inmediatamente antes de que _session.stopRunning_ returns y mientras el segundo hilo aún se está ejecutando. Por lo tanto, la aplicación aún se bloqueaba de vez en cuando. Probaré el código de limpieza propuesto, pero lo pondré después de _session.stopRunning_. ¿O puede esto realmente hacer alguna diferencia? – Codo

+0

Desafortunadamente, su solución no está funcionando. Todavía se cuelga de vez en cuando porque el segundo subproceso no se cierra de inmediato y accede a instancias ya publicadas. – Codo

1

Resuelto! Quizás sea la secuencia de acciones al inicializar la sesión. Esta funciona para mí:

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

Por cierto esta secuencia parece resolver el problema de las notificaciones sincrónica :)

+3

Lo siento pero esto no hace ninguna diferencia. Todavía se cuelga. ¿Y cómo se supone que resolverá el problema de notificación? ¿La notificación ahora se retrasa hasta que el segundo hilo ha terminado? Mientras tanto, he encontrado una solución que funciona para mí (ver mi propia respuesta). – Codo

4

He publicado una pregunta muy similar en el Foro de desarrolladores de Apple y recibí una respuesta de un empleado de Apple. Dice que es un problema conocido:

Este es un problema con el AVCaptureSession/VideoDataOutput en iOS 4,0-4,1 que se ha fijado y aparecerá en una futura actualización. Para , puede solucionarlo esperando un breve período después de detener la sesión de AVCaptureSession, p. medio segundo, antes de deshacerse de la sesión y la salida de datos.

Él/ella propone el siguiente código:

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

todavía me gusta el enfoque con el finalizador cola de distribución mejor, porque este código sólo conjeturas cuando el segundo hilo podría haber terminado.

2

Con los finalizadores de cola, puede usar un dispatch_semaphore para cada cola y luego continuar con su rutina de limpieza una vez hecho.

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

Recuerde que usted tiene que fijar objetos a su AVCaptureVideoDataOutput/AVCaptureAudioDataOutput delegados tampón de muestra a cero o que nunca se dará a conocer sus colas asociadas, y por lo tanto nunca llamar a sus finalizadores al levantar el 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; 

} 

Llamé a esta función antes de hacer estallar y empujando a cualquier otro punto de vista. Solucionó mi problema de advertencia de memoria baja.

+0

tengo un problema de congelación de la cámara, al recibir una llamada telefónica, ¿cómo puedo reiniciar mi cámara? Vista previa –

2

Según los documentos de apple actuales (1) [AVCaptureSession stopRunning] es una operación síncrona que bloquea hasta que el receptor haya dejado de funcionar por completo. Entonces todos estos problemas no deberían pasar más.

+1

parece que me están sucediendo iOS 10, Swift 3, Xcode 9 –

Cuestiones relacionadas