4

I created a custom camera, and I am trying to add a feature where users can switch between the front and back camera while recording a video. My approach right now is to stop and start a new video when they switch the camera, but it cuts off a little bit of the video and I'm not sure why. How can i make it like snapchat, so that it gets the full video, and doesn't cut anything off when they switch the camera. Here is my code so far

@objc func switchCameraInput() {
        self.captureSession.beginConfiguration()
        var existingConnection:AVCaptureDeviceInput!

        for connection in self.captureSession.inputs {
            let input = connection as! AVCaptureDeviceInput
            if input.device.hasMediaType(AVMediaType.video) {
                existingConnection = input
            }
        }

        self.captureSession.removeInput(existingConnection)
        turnFlashOff()

        var newCamera:AVCaptureDevice!
        if let oldCamera = existingConnection {
            if oldCamera.device.position == .back {
                newCamera = self.cameraWithPosition(position: .front)
            } else {
                newCamera = self.cameraWithPosition(position: .back)
            }
        }

        var newInput:AVCaptureDeviceInput!

        do {
            newInput = try AVCaptureDeviceInput(device: newCamera)
            self.captureSession.addInput(newInput)

        } catch {
            ProgressHUD.showError(error.localizedDescription)
        }


        self.captureSession.commitConfiguration()

    // This is where i handle switching while recording
    if self.movieFileOutput.isRecording {
        hasSwappedCamera = true
        turnFlashOff()
        //self.movieFileOutput.stopRecording()
        self.movieFileOutput.connection(with: AVMediaType.video)?.videoOrientation = self.videoOrientation()
        self.movieFileOutput.maxRecordedDuration = self.maxRecordedDuration()
        self.movieFileOutput.startRecording(to: URL(fileURLWithPath:self.videoFileLocation()), recordingDelegate: self)
        turnOnFlash()
    }
}
vApp
  • 249
  • 1
  • 6
  • 18
  • Looks like this question could help you...https://stackoverflow.com/questions/44577331/multiple-avcapturevideodataoutput-in-same-avcapturesession – BHendricks Dec 27 '17 at 00:14
  • @BHendricks This is in objective c, any chance you can help me out with swift? – vApp Dec 27 '17 at 05:36

2 Answers2

2

Since the question I think will help answer your question is in Objective-C and you would prefer Swift, I've "translated" all that code below.

Be warned, I did not compile this, and know several things won't compile to start. Enums values like AVMediaTypeVideo are usually just .video in Swift. Also, I'm pretty sure that answer has some incorrect code, mainly surrounding setting the isFrontRecording and isBackRecording booleans back to false. I think those should happen within the completionHandler, but as mentioned I did not compile this so take that with a grain of salt. I included all code from that question (the Objective-C) along with my quick&dirty translation to Swift.

I hope this helps, though :)

Objective-C:

/* Front camera settings */
@property bool isFrontRecording;
@property (strong, nonatomic) AVCaptureDeviceInput *videoInputBack;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutputBack;
@property (strong, nonatomic) AVCaptureSession *sessionBack;

/* Back camera settings */
@property bool isBackRecording;
@property (strong, nonatomic) AVCaptureDeviceInput *videoInputFront;
@property (strong, nonatomic) AVCaptureStillImageOutput *imageOutputFront;
@property (strong, nonatomic) AVCaptureSession *sessionFront;

Swift:

var isFrontRecording: Bool
var videoInputBack: AVCaptureDeviceInput
var imageOutputBack: AVCaptureStillImageOutput
var sessionBack: AVCaptureSession

var isBackRecording: Bool
var videoInputFront: AVCaptureDeviceInput
var imageOutputFront: AVCaptureStillImageOutput
var sessionFront: AVCaptureSession

Objective-C

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupBackAVCapture];

    self.isFrontRecording = NO;
    self.isBackRecording = NO;
}

- (void)setupBackAVCapture
{
    NSError *error = nil;

    self.sessionBack = [[AVCaptureSession alloc] init];
    self.sessionBack.sessionPreset = AVCaptureSessionPresetPhoto;

    AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    self.videoInputBack = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
    [self.sessionBack addInput:self.videoInputBack];

    self.imageOutputBack = [[AVCaptureStillImageOutput alloc] init];
    [self.sessionBack addOutput:self.imageOutputBack];

}

Swift:

override func viewDidLoad() {
    super.viewDidLoad()
    setupBackAVCapture()

    isFrontRecording = false
    isBackRecording = false
}

func setupBackAVCapture() {
    var error: NSError = nil
    sessionBack = AVCaptureSession()
    sessionBack.sessionPreset = AVCaptureSessionPresetPhoto

    let camera: AVCaptureDevice = AVCaptureDevice(defaultDeviceWithMediaType: AVMediaTypeVideo) 
    videoInputBack = AVCaptureDeviceInput(withDevice: camera, error: error)
    sessionBack.addInput(videoInputBack)

    imageOutputBack = AVCaptureStillImageOutput()
    sessionBack.addOutput(imageOutputBack)
}

Objective-C:

- (IBAction)buttonCapture:(id)sender {
    [self takeBackPhoto];
}

- (void)takeBackPhoto
{
    [self.sessionBack startRunning];
    if (!self.isFrontRecording) {

        self.isFrontRecording = YES;

        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        AVCaptureConnection *videoConnection = [self.imageOutputBack connectionWithMediaType:AVMediaTypeVideo];

        if (videoConnection == nil) {
            return;
        }


        [self.imageOutputBack
         captureStillImageAsynchronouslyFromConnection:videoConnection
         completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {

             if (imageDataSampleBuffer == NULL) {
                 return;
             }

             NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

             UIImage *image = [[UIImage alloc] initWithData:imageData];

             UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);

             [self.imageView setImage:image];

             [self.sessionBack stopRunning];

             // Set up front camera setting and capture photo.
             [self setupFrontAVCapture];
             [self takeFrontPhoto];

         }];

        self.isFrontRecording = NO;
    }
}

Swift:

@IBOutlet func buttonCapture(sender: Any) {
    takeBackPhoto()
}

func takeBackPhoto() {
    sessionBack.startRunning()
    if !isFrontRecording {
        isFrontRecording = true

        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
        let videoConnection: AVCaptureConnection = imageOutputBack.connectionWithMediaType(AVMediaTypeVideo)

        guard let videoConnection = videoConnection else {
            return
        }

        imageOutputBack.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {
            imageDataSampleBuffer: CMSSampleBufferRef, error: NSError in

            guard let imageDataSampleBuffer = imageDataSampleBuffer else {
                return
            }

            let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
            let image = UIImage(data: imageData)
            UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)
            self.imageView.setImage(image)
            self.sessionback.stopRunning()

            // Set up front camera setting and capture photo.
            self.setupFronAVCapture()
            self.takeFrontPhoto()
        })

        isFrontRecording = false
    }
}

Objective-C

- (void)setupFrontAVCapture
{
    NSError *error = nil;

    self.sessionFront = [[AVCaptureSession alloc] init];
    self.sessionFront.sessionPreset = AVCaptureSessionPresetPhoto;

    AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    camera = [self cameraWithPosition:AVCaptureDevicePositionFront];

    self.videoInputFront = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
    [self.sessionFront addInput:self.videoInputFront];

    self.imageOutputFront = [[AVCaptureStillImageOutput alloc] init];
    [self.sessionFront addOutput:self.imageOutputFront];
}

- (void)takeFrontPhoto
{
    [self.sessionFront startRunning];
    if (!self.isBackRecording) {

        self.isBackRecording = YES;

        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        AVCaptureConnection *videoConnection = [self.imageOutputFront connectionWithMediaType:AVMediaTypeVideo];

        if (videoConnection == nil) {
            return;
        }


        [self.imageOutputFront
         captureStillImageAsynchronouslyFromConnection:videoConnection
         completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {

             if (imageDataSampleBuffer == NULL) {
                 return;
             }

             NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

             UIImage *image = [[UIImage alloc] initWithData:imageData];

             UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
             [self.imageViewBack setImage:image];

             [self.sessionFront stopRunning];


         }];

        self.isBackRecording = NO;

    }

}

Swift:

func setupFrontAVCapture() {
    let error: NSError = nil
    sessionFront = AVCaptureSession()
    sessionFront.sessionPreset = AVCaptureSessionPresentPhoto

    var camera: AVCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    camera = camera.cameraWithPosition(AVCaptureDevicePositionFront)

    videoInputFront = AVCaptureDeviceInput(withDevice: camera, error: error)
    sessionFront.addInput(videoInputFront)

    imageOutputFront = AVCaptureStillImageOutput()
    sessionFront.addOutput(imageOutputFront)
}

func takeFrontPhoto() {
    sessionFront.startRunning()

    if !isBackRecording {
        isBackRecording = true

        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
        let videoConnection: AVCaptureConnection = imageOutputFront.connectionWithMediaType(AVMediaTypeVideo)

        guard let videoConnection = videoConnection else {
            return
        }

        imageOutputFront.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {
            imageDataSampleBuffer: CMSampleBufferRef, error: NSError in

            guard let imageDataSampleBuffer = imageDataSampleBuffer else {
                return
            }

            let imageData: NSData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
            let image = UIImage(data: imageData)

            UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)
            self.imageViewBack.setImage(image)
            self.sessionFront.stopRunning()
        })

        isBackRecording = false
    }
}

Good luck getting the switching to work for your project!

BHendricks
  • 4,423
  • 6
  • 32
  • 59
0

I found a fitting solution to this exact same problem, instead of switching the inputs with one capture session and one output, you have to make one session per input (camera) and then switch the output between them.

You can find more details here : https://stackoverflow.com/a/54770398/2448590

Macistador
  • 854
  • 12
  • 23