3
votes

I want to toggle camera using AVFoundation. Here is my code I have subclass of NSObject as

@interface CaptureSessionManager : NSObject

@property (retain) AVCaptureVideoPreviewLayer *previewLayer;
@property (retain) AVCaptureSession *captureSession;
@property (retain) AVCaptureStillImageOutput *stillImageOutput;
@property (nonatomic, retain) UIImage *stillImage;

In @implementation CaptureSessionManager

    - (id)init {
    if ((self = [super init])) {
        [self setCaptureSession:[[AVCaptureSession alloc] init]];
    }
    return self;
}

- (void)addVideoPreviewLayer {
    [self setPreviewLayer:[[[AVCaptureVideoPreviewLayer alloc] initWithSession:[self captureSession]] autorelease]];
    [[self previewLayer] setVideoGravity:AVLayerVideoGravityResizeAspectFill];

}

- (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");
            }
        }
    }
}

- (void)addStillImageOutput 
{
  [self setStillImageOutput:[[[AVCaptureStillImageOutput alloc] init] autorelease]];
  NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
  [[self stillImageOutput] setOutputSettings:outputSettings];

  AVCaptureConnection *videoConnection = nil;
  for (AVCaptureConnection *connection in [[self stillImageOutput] connections]) {
    for (AVCaptureInputPort *port in [connection inputPorts]) {
      if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
        videoConnection = connection;
        break;
      }
    }
    if (videoConnection) { 
      break; 
    }
  }

  [[self captureSession] addOutput:[self stillImageOutput]];
}

- (void)captureStillImage
{  
    AVCaptureConnection *videoConnection = nil;
    for (AVCaptureConnection *connection in [[self stillImageOutput] connections]) {
        for (AVCaptureInputPort *port in [connection inputPorts]) {
            if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
                videoConnection = connection;
                break;
            }
        }
        if (videoConnection) { 
      break; 
    }
    }

    NSLog(@"about to request a capture from: %@", [self stillImageOutput]);
    [[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection 
                                                       completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) { 
                                                         CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
                                                         if (exifAttachments) {
                                                           NSLog(@"attachements: %@", exifAttachments);
                                                         } else { 
                                                           NSLog(@"no attachments");
                                                         }
                                                         NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];    
                                                         UIImage *image = [[UIImage alloc] initWithData:imageData];
                                                         [self setStillImage:image];
                                                         [image release];
                                                         [[NSNotificationCenter defaultCenter] postNotificationName:kImageCapturedSuccessfully object:nil];
                                                       }];
}

- (void)dealloc {

    [[self captureSession] stopRunning];

    [previewLayer release], previewLayer = nil;
    [captureSession release], captureSession = nil;
  [stillImageOutput release], stillImageOutput = nil;
  [stillImage release], stillImage = nil;

    [super dealloc];
}

-(void)toggleCamera:(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;
            }
        }
    }
    [[self captureSession] beginConfiguration];
    NSError *error = nil;
    AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
    AVCaptureDeviceInput *backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];
    if (front)
    {
        [[self captureSession] removeInput:backFacingCameraDeviceInput];
        if (!error) {
            if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) {
                [[self captureSession] addInput:frontFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add front facing video input");
            }
            [[self captureSession] addInput:frontFacingCameraDeviceInput];
        }
    } else
    {
        [[self captureSession] removeInput:frontFacingCameraDeviceInput];
        if (!error) {
            if ([[self captureSession] canAddInput:backFacingCameraDeviceInput]) {
                [[self captureSession] addInput:backFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add back facing video input");
            }
            [[self captureSession] addInput:backFacingCameraDeviceInput];
        }
    }

    [[self captureSession] commitConfiguration];
}

In my ViewController , I have

@property (retain) CaptureSessionManager *captureManager;
@property (nonatomic,readwrite) BOOL isFrontCameraSelected;

    - (void)viewDidLoad
{
    self.isFrontCameraSelected = NO;

    [self setCaptureManager:[[[CaptureSessionManager alloc] init] autorelease]];

    [[self captureManager] addVideoInputFrontCamera:self.isFrontCameraSelected]; // set to YES for Front Camera, No for Back camera

    [[self captureManager] addStillImageOutput];

    [[self captureManager] addVideoPreviewLayer];
    CGRect layerRect = [[[self innerview] layer] bounds];
    [[[self captureManager] previewLayer] setBounds:layerRect];
    [[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
    [[[self innerview] layer] addSublayer:[[self captureManager] previewLayer]];

    UIImageView *overlayImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"overlaygraphic.png"]];
    [overlayImageView setFrame:CGRectMake(30, 60, 260, 260)];
    [[self innerview] addSubview:overlayImageView];
    [overlayImageView release];

    UIButton *overlayButton3 = [UIButton buttonWithType:UIButtonTypeCustom];
    [overlayButton3 setImage:[UIImage imageNamed:@"camera.png"] forState:UIControlStateNormal];
    [overlayButton3 setFrame:CGRectMake(130, 330, 60, 30)];
    [overlayButton3 addTarget:self action:@selector(innerscanButtonPressed) forControlEvents:UIControlEventTouchUpInside];
    [[self innerview] addSubview:overlayButton3];

    UIButton *cameraSelection = [UIButton buttonWithType:UIButtonTypeCustom];
    [cameraSelection setImage:[UIImage imageNamed:@"camera.png"] forState:UIControlStateNormal];
    [cameraSelection setFrame:CGRectMake(260, 30, 60, 30)];
    [cameraSelection addTarget:self action:@selector(cameraSelectionTapped:) forControlEvents:UIControlEventTouchUpInside];
    [[self innerview] addSubview:cameraSelection];

  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveImageToPhotoAlbum) name:kImageCapturedSuccessfully object:nil];

    [[captureManager captureSession] startRunning];
}

- (void)innerscanButtonPressed {

    [[self scanningLabel] setHidden:NO];
    [[self captureManager] captureStillImage];

}

-(void)cameraSelectionTapped:(id)sender
{
    [[self captureManager] toggleCamera:self.isFrontCameraSelected];
}

When I click on toggleCamera button , it crashes with below error

Couldn't add back facing video input
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** Multiple audio/video AVCaptureInputs are not currently supported.'
*** First throw call stack:
(0x38f2f2a3 0x3725997f 0x39aff977 0x39aff091 0xe943d 0xe7875 0x3a4de0a5 0x3a4de057 0x3a4de035 0x3a4dd8eb 0x3a4ddde1 0x3a4065f1 0x3a3f3801 0x3a3f311b 0x37cad5a3 0x37cad1d3 0x38f04173 0x38f04117 0x38f02f99 0x38e75ebd 0x38e75d49 0x37cac2eb 0x3a4472f9 0xdf1ab 0x36324b20)

I want to add toggle feature so that user can use front / back camera to click photo.

Any kind of help is highly appreciated. Thanks in advance.

3

3 Answers

5
votes

You're trying to add the same device input twice in this code:

if (!error) {
        if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) {
            [[self captureSession] addInput:frontFacingCameraDeviceInput];
        } else {
            NSLog(@"Couldn't add front facing video input");
        }
        [[self captureSession] addInput:frontFacingCameraDeviceInput];
    }

Take out that second call to addInput on both the front and back logic.

3
votes

I edited the code you uploaded and made the toggle work. Here's the code that I had.

CaptureSessionManager.h - I added variable for the Device Inputs

@interface CaptureSessionManager : NSObject {
    AVCaptureDeviceInput *frontFacingCameraDeviceInput;
    AVCaptureDeviceInput *backFacingCameraDeviceInput;

}

@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
@property (retain) AVCaptureStillImageOutput *stillImageOutput;
@property (nonatomic, retain) UIImage *stillImage;


-(void)addVideoPreviewLayer;
-(void)addVideoInput;;
- (void)addVideoInputFrontCamera:(BOOL)front;
- (void)addStillImageOutput;
- (void)captureStillImage;
-(void)toggleCamera:(BOOL)front;

@end

CaptureSessionManager.m - I edited the addVideoInputFrontCamera and toggleCamera functions

- (void)addVideoInputFrontCamera:(BOOL)front {
    [self.captureSession beginConfiguration];
    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;
    frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
    backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];
    if (front) {

        if (!error) {
            if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) {
                [[self captureSession] addInput:frontFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add front facing video input");
            }
        }
    } else {

        if (!error) {
            if ([[self captureSession] canAddInput:backFacingCameraDeviceInput]) {
                [[self captureSession] addInput:backFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add back facing video input");
            }
        }
    }

    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    if (audioDevice) {
        NSError *error;
        AVCaptureDeviceInput *audioIn = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
        if (!error) {
            if ([[self captureSession] canAddInput:audioIn]){
                [[self captureSession] addInput:audioIn];
                NSLog(@"adding audioIn");
            }
            else
                NSLog(@"Couldn't add audio input");
        }
        else
            NSLog(@"Couldn't create audio input");
    }
    else
        NSLog(@"Couldn't create audio capture device");
    [self.captureSession commitConfiguration];
}

-(void)toggleCamera:(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;
            }
        }
    }
    [[self captureSession] beginConfiguration];
    NSError *error = nil;
   // AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
   // AVCaptureDeviceInput *backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];
    if (front)
    {
        [[self captureSession] removeInput:backFacingCameraDeviceInput];
        if (!error) {

          //  frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
            if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) {
                [[self captureSession] addInput:frontFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add front facing video input");
            }

        }
    } else
    {
        [[self captureSession] removeInput:frontFacingCameraDeviceInput];
        if (!error) {
           // backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];

            if ([[self captureSession] canAddInput:backFacingCameraDeviceInput]) {
                [[self captureSession] addInput:backFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add back facing video input");
            }

        }
    }

    [[self captureSession] commitConfiguration];
}

Then on my view controller:

- (IBAction)cameraSelectionClicked:(id)sender {
    [self.captureManager.captureSession stopRunning];

    if (self.isFrontCameraSelected) {
        self.isFrontCameraSelected = NO;
    } else {
        self.isFrontCameraSelected = YES;

    }

    [[self captureManager] toggleCamera:self.isFrontCameraSelected];
    [self.captureManager.captureSession startRunning];
}
0
votes

In swift toggle camera implementation is quite small and error prone. Swift really does some very great modification to check errors. So mine toggle camera code inside CameraController.swift looks like this -:

// toggleCamera basic purpose is to toggle the camera from front to back or vice versa as per user selection

func toggleCamera(){
        println("toggleCamera -----")
        var error:NSError?
        var backCameraDevice:AVCaptureDevice?
        var audio:AVCaptureDevice?
        var frontCameraDevice:AVCaptureDevice?
        var rearCamera: AVCaptureInput?
        var frontCamera: AVCaptureInput?
        for device in availableCameraDevices as! [AVCaptureDevice] {
           if device.position == .Back {
            self.backCameraDevice = device
           }
           else if device.position == .Front {
            self.frontCameraDevice = device
           }
        }

        if let validVideoFrontDevice = self.frontCameraDevice {
                self.frontCamera = AVCaptureDeviceInput.deviceInputWithDevice(validVideoFrontDevice, error: &error) as! AVCaptureDeviceInput
        }

        if let validVideoBackDevice = self.backCameraDevice {
                self.rearCamera = AVCaptureDeviceInput.deviceInputWithDevice(validVideoBackDevice, error: &error) as! AVCaptureDeviceInput
        }

        session.beginConfiguration()
        let inputs = session.inputs as! [AVCaptureInput]
        let newCamera: AVCaptureDevice?
        if(currentCameraDevice!.position == AVCaptureDevicePosition.Back){
            println("Setting new camera with Front")
            newCamera = self.frontCameraDevice
            if self.hasFrontCamera {
                if let validBackDevice = self.rearCamera {
                    if contains(inputs, validBackDevice) {
                        session.removeInput(validBackDevice)
                    }
                }
                if let validFrontDevice = self.frontCamera {
                    if !contains(inputs, validFrontDevice) {
                        session.addInput(validFrontDevice)
                    }
                }
            }
        } else {
            println("Setting new camera with Back")
            newCamera = self.backCameraDevice
            if let validFrontDevice = self.frontCamera {
                if contains(inputs, validFrontDevice) {
                    session.removeInput(validFrontDevice)
                }
            }
            if let validBackDevice = self.rearCamera {
                if !contains(inputs, validBackDevice) {
                    session.addInput(validBackDevice)
                }
            }
        }
        session.commitConfiguration()
        if let validError = error {
            println("Device setup error occured \(validError.localizedDescription)")
        }
        currentCameraDevice! = newCamera!
}

    /// The Bool property to determine if current device has front camera.

public var hasFrontCamera: Bool = {
        let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
        for  device in devices  {
            let captureDevice = device as! AVCaptureDevice
            if (captureDevice.position == .Front) {
                return true
            }
        }
        return false
        }()

In my view controller:

@IBAction func toggleCamera(sender: UIButton) {
    //  toggle the camera side
    println("toggling the camera side")
    self.cameraController.toggleCamera()
}

Hope it helps. Thanks