Using this tutorial here: http://www.musicalgeometry.com/?p=1297 I have created a custom overlay and image capture with AVCaptureSession
.
I am attempting to allow the user to switch between the front and back camera. Here is my code in CaptureSessionManager
to switch cameras:
- (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");
}
}
}
}
Now in my custom overlay controller I initialize everything like so in 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];
The switch camera button is connected to a method called switchCamera
. I have tried this:
- (void)switchCameraView:(id)sender {
[[self captureManager] addVideoInputFrontCamera:YES]; // set to YES for Front Camera, No for Back camera
}
When calling this, I get the error NSLog
from the CaptureSessionManager
and I cannot figure out why. In viewDidLoad
, if I set the fontCamera
to YES
, it shows the front camera but cannot switch to back, and vice versa.
Any ideas on how to get it to switch properly?
You first need to remove the existing AVCameraInput from the AVCaptureSession and then add a new AVCameraInput to the AVCaptureSession. The following works for me (under 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 (Combined with François-Julien Alcaraz answer):
@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
}
Swift version to @NES_4Life's answer:
@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
}
Based on previous answers I made my own version with some validations and one specific change, the current camera input might not be the first object of the capture session's inputs, so I changed this:
//Remove existing input
AVCaptureInput* currentCameraInput = [self.captureSession.inputs objectAtIndex:0];
[self.captureSession removeInput:currentCameraInput];
To this (removing all video type inputs):
for (AVCaptureDeviceInput *input in self.captureSession.inputs) {
if ([input.device hasMediaType:AVMediaTypeVideo]) {
[self.captureSession removeInput:input];
break;
}
}
Here's the entire code:
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
}
}
Swift 4
You can check full implementation in this gist
Swift 3 version of cameraWithPosition without deprecated warning :
// 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
}
If you want, you can also get the new devicesTypes from iPhone 7+ (dual camera) by changing the deviceTypes array.
Here's a good read : https://forums.developer.apple.com/thread/63347
Here is an updated version of chengsam's code that includes the fix for 'Multiple audio/video AVCaptureInputs are not currently supported'.
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
}
来源:https://stackoverflow.com/questions/20864372/switch-cameras-with-avcapturesession