Cambiar de cámara con avcapturesession

Usando este tutorial aquí: http://www.musicalgeometry.com/?p=1297 He creado una superposición personalizada y una captura de imagen con AVCaptureSession.

Estoy intentando permitir que el usuario cambie entre la cámara frontal y posterior. Aquí está mi código en CaptureSessionManager para cambiar de cámara:

- (void)addVideoInputFrontCamera:(BOOL)front { NSArray *devices = [AVCaptureDevice devices]; AVCaptureDevice *frontCamera; AVCaptureDevice *backCamera; for (AVCaptureDevice *device in devices) { //NSLog(@"Device name: %@", [device localizedName]); if ([device hasMediaType:AVMediaTypeVideo]) { if ([device position] == AVCaptureDevicePositionBack) { //NSLog(@"Device position : back"); backCamera = device; } else { //NSLog(@"Device position : front"); frontCamera = device; } } } NSError *error = nil; if (front) { AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error]; if (!error) { if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) { [[self captureSession] addInput:frontFacingCameraDeviceInput]; } else { NSLog(@"Couldn't add front facing video input"); } } } else { AVCaptureDeviceInput *backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error]; if (!error) { if ([[self captureSession] canAddInput:backFacingCameraDeviceInput]) { [[self captureSession] addInput:backFacingCameraDeviceInput]; } else { NSLog(@"Couldn't add back facing video input"); } } } } 

Ahora, en mi controlador de superposición personalizada, inicializo todo como tal en viewDidLoad:

 [self setCaptureManager:[[CaptureSessionManager alloc] init]]; [[self captureManager] addVideoInputFrontCamera:NO]; // set to YES for Front Camera, No for Back camera [[self captureManager] addStillImageOutput]; [[self captureManager] addVideoPreviewLayer]; CGRect layerRect = [[[self view] layer] bounds]; [[[self captureManager] previewLayer] setBounds:layerRect]; [[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))]; [[[self view] layer] addSublayer:[[self captureManager] previewLayer]]; [[_captureManager captureSession] startRunning]; 

El botón de cambiar la cámara está conectado a un método llamado switchCamera. He intentado esto:

 - (void)switchCameraView:(id)sender { [[self captureManager] addVideoInputFrontCamera:YES]; // set to YES for Front Camera, No for Back camera } 

Cuando llamo esto, obtengo el error NSLog de CaptureSessionManager y no puedo entender por qué. En viewDidLoad, si configuro fontCamera en YES, muestra la cámara frontal pero no puede retroceder, y viceversa.

¿Alguna idea sobre cómo hacer que cambie correctamente?

Primero debe eliminar la AVCameraInput existente de la sesión AVCaptureSession y luego agregar una nueva AVCameraInput a la sesión AVCaptureSession. Lo siguiente funciona para mí (bajo ARC):

 -(IBAction)switchCameraTapped:(id)sender { //Change camera source if(_captureSession) { //Indicate that some changes will be made to the session [_captureSession beginConfiguration]; //Remove existing input AVCaptureInput* currentCameraInput = [_captureSession.inputs objectAtIndex:0]; [_captureSession removeInput:currentCameraInput]; //Get new input AVCaptureDevice *newCamera = nil; if(((AVCaptureDeviceInput*)currentCameraInput).device.position == AVCaptureDevicePositionBack) { newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront]; } else { newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack]; } //Add input to session NSError *err = nil; AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:newCamera error:&err]; if(!newVideoInput || err) { NSLog(@"Error creating capture device input: %@", err.localizedDescription); } else { [_captureSession addInput:newVideoInput]; } //Commit all the configuration changes at once [_captureSession commitConfiguration]; } } // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found - (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position { NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *device in devices) { if ([device position] == position) return device; } return nil; } 

Swift 4

 @IBAction func switchCameraTapped(sender: Any) { //Change camera source if let session = captureSession { //Indicate that some changes will be made to the session session.beginConfiguration() //Remove existing input guard let currentCameraInput: AVCaptureInput = session.inputs.first else { return } session.removeInput(currentCameraInput) //Get new input var newCamera: AVCaptureDevice! = nil if let input = currentCameraInput as? AVCaptureDeviceInput { if (input.device.position == .back) { newCamera = cameraWithPosition(position: .front) } else { newCamera = cameraWithPosition(position: .back) } } //Add input to session var err: NSError? var newVideoInput: AVCaptureDeviceInput! do { newVideoInput = try AVCaptureDeviceInput(device: newCamera) } catch let err1 as NSError { err = err1 newVideoInput = nil } if newVideoInput == nil || err != nil { print("Error creating capture device input: \(err?.localizedDescription)") } else { session.addInput(newVideoInput) } //Commit all the configuration changes at once session.commitConfiguration() } } // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? { let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified) for device in discoverySession.devices { if device.position == position { return device } } return nil } 

Swift 3 Edit (Combinado con la respuesta de François-Julien Alcaraz):

 @IBAction func switchCameraTapped(sender: Any) { //Change camera source if let session = captureSession { //Indicate that some changes will be made to the session session.beginConfiguration() //Remove existing input guard let currentCameraInput: AVCaptureInput = session.inputs.first as? AVCaptureInput else { return } session.removeInput(currentCameraInput) //Get new input var newCamera: AVCaptureDevice! = nil if let input = currentCameraInput as? AVCaptureDeviceInput { if (input.device.position == .back) { newCamera = cameraWithPosition(position: .front) } else { newCamera = cameraWithPosition(position: .back) } } //Add input to session var err: NSError? var newVideoInput: AVCaptureDeviceInput! do { newVideoInput = try AVCaptureDeviceInput(device: newCamera) } catch let err1 as NSError { err = err1 newVideoInput = nil } if newVideoInput == nil || err != nil { print("Error creating capture device input: \(err?.localizedDescription)") } else { session.addInput(newVideoInput) } //Commit all the configuration changes at once session.commitConfiguration() } } // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found func cameraWithPosition(position: AVCaptureDevicePosition) -> AVCaptureDevice? { if let discoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .unspecified) { for device in discoverySession.devices { if device.position == position { return device } } } return nil } 

Versión Swift a la respuesta de @ NES_4Life:

 @IBAction func switchCameraTapped(sender: AnyObject) { //Change camera source if let session = captureSession { //Indicate that some changes will be made to the session session.beginConfiguration() //Remove existing input let currentCameraInput:AVCaptureInput = session.inputs.first as! AVCaptureInput session.removeInput(currentCameraInput) //Get new input var newCamera:AVCaptureDevice! = nil if let input = currentCameraInput as? AVCaptureDeviceInput { if (input.device.position == .Back) { newCamera = cameraWithPosition(.Front) } else { newCamera = cameraWithPosition(.Back) } } //Add input to session var err: NSError? var newVideoInput: AVCaptureDeviceInput! do { newVideoInput = try AVCaptureDeviceInput(device: newCamera) } catch let err1 as NSError { err = err1 newVideoInput = nil } if(newVideoInput == nil || err != nil) { print("Error creating capture device input: \(err!.localizedDescription)") } else { session.addInput(newVideoInput) } //Commit all the configuration changes at once session.commitConfiguration() } } // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found func cameraWithPosition(position: AVCaptureDevicePosition) -> AVCaptureDevice? { let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) for device in devices { let device = device as! AVCaptureDevice if device.position == position { return device } } return nil } 

Basado en respuestas anteriores hice mi propia versión con algunas validaciones y un cambio específico, la entrada actual de la cámara podría no ser el primer objeto de las entradas de la sesión de captura, así que cambié esto:

 //Remove existing input AVCaptureInput* currentCameraInput = [self.captureSession.inputs objectAtIndex:0]; [self.captureSession removeInput:currentCameraInput]; 

Para esto (eliminar todas las entradas de tipo de video):

 for (AVCaptureDeviceInput *input in self.captureSession.inputs) { if ([input.device hasMediaType:AVMediaTypeVideo]) { [self.captureSession removeInput:input]; break; } } 

Aquí está el código completo:

 if (!self.captureSession) return; [self.captureSession beginConfiguration]; AVCaptureDeviceInput *currentCameraInput; // Remove current (video) input for (AVCaptureDeviceInput *input in self.captureSession.inputs) { if ([input.device hasMediaType:AVMediaTypeVideo]) { [self.captureSession removeInput:input]; currentCameraInput = input; break; } } if (!currentCameraInput) return; // Switch device position AVCaptureDevicePosition captureDevicePosition = AVCaptureDevicePositionUnspecified; if (currentCameraInput.device.position == AVCaptureDevicePositionBack) { captureDevicePosition = AVCaptureDevicePositionFront; } else { captureDevicePosition = AVCaptureDevicePositionBack; } // Select new camera AVCaptureDevice *newCamera; NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; for (AVCaptureDevice *captureDevice in devices) { if (captureDevice.position == captureDevicePosition) { newCamera = captureDevice; } } if (!newCamera) return; // Add new camera input NSError *error; AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:newCamera error:&error]; if (!error && [self.captureSession canAddInput:newVideoInput]) { [self.captureSession addInput:newVideoInput]; } [self.captureSession commitConfiguration]; 

Swift 3

 func switchCamera() { session?.beginConfiguration() let currentInput = session?.inputs.first as? AVCaptureDeviceInput session?.removeInput(currentInput) let newCameraDevice = currentInput?.device.position == .back ? getCamera(with: .front) : getCamera(with: .back) let newVideoInput = try? AVCaptureDeviceInput(device: newCameraDevice) session?.addInput(newVideoInput) session?.commitConfiguration() } // MARK: - Private extension CameraService { func getCamera(with position: AVCaptureDevicePosition) -> AVCaptureDevice? { guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else { return nil } return devices.filter { $0.position == position }.first } } 

Versión de Swift 3 de cameraWithPosition sin advertencia obsoleta:

  // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found func cameraWithPosition(_ position: AVCaptureDevicePosition) -> AVCaptureDevice? { if let deviceDescoverySession = AVCaptureDeviceDiscoverySession.init(deviceTypes: [AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.unspecified) { for device in deviceDescoverySession.devices { if device.position == position { return device } } } return nil } 

Si lo desea, también puede obtener los nuevos dispositivosTipos de iPhone 7+ (cámara dual) cambiando la matriz deviceTypes.

Aquí hay una buena lectura: https://forums.developer.apple.com/thread/63347

Aquí hay una versión actualizada del código de chengsam que incluye la solución para ‘Las múltiples entradas de audio / video AVCaptureInputs actualmente no son compatibles’.

 func switchCameraTapped() { //Change camera source //Indicate that some changes will be made to the session session.beginConfiguration() //Remove existing input guard let currentCameraInput: AVCaptureInput = session.inputs.first else { return } //Get new input var newCamera: AVCaptureDevice! = nil if let input = currentCameraInput as? AVCaptureDeviceInput { if (input.device.position == .back) { newCamera = cameraWithPosition(position: .front) } else { newCamera = cameraWithPosition(position: .back) } } //Add input to session var err: NSError? var newVideoInput: AVCaptureDeviceInput! do { newVideoInput = try AVCaptureDeviceInput(device: newCamera) } catch let err1 as NSError { err = err1 newVideoInput = nil } if let inputs = session.inputs as? [AVCaptureDeviceInput] { for input in inputs { session.removeInput(input) } } if newVideoInput == nil || err != nil { print("Error creating capture device input: \(err?.localizedDescription)") } else { session.addInput(newVideoInput) } //Commit all the configuration changes at once session.commitConfiguration() } // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? { let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified) for device in discoverySession.devices { if device.position == position { return device } } return nil } 
    Intereting Posts