iPhone AVFoundation camera orientation

Viewed 36372

I've been tearing my hair out trying to get the AVFoundation camera to capture a picture in the correct orientation (i.e. the device orientation) but I can't get it to work.

I have looked at tutorials, I've watched the WWDC presentation and I've downloaded the WWDC sample program but even that doesn't do it.

The code from my app is...

AVCaptureConnection *videoConnection = [CameraVC connectionWithMediaType:AVMediaTypeVideo fromConnections:[imageCaptureOutput connections]];
if ([videoConnection isVideoOrientationSupported])
{
    [videoConnection setVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
}

[imageCaptureOutput captureStillImageAsynchronouslyFromConnection:videoConnection
                                                completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
{
    if (imageDataSampleBuffer != NULL)
    {
        //NSLog(@"%d", screenOrientation);

        //CMSetAttachment(imageDataSampleBuffer, kCGImagePropertyOrientation, [NSString stringWithFormat:@"%d", screenOrientation], 0);

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [[UIImage alloc] initWithData:imageData];

        [self processImage:image];
    }
}];

(processImage uses the same writeImage... method as the WWDC code)

and the code from the WWDC app is...

AVCaptureConnection *videoConnection = [AVCamDemoCaptureManager connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self stillImageOutput] connections]];
        if ([videoConnection isVideoOrientationSupported]) {
            [videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
        }

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection
                                                             completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
                                                                 if (imageDataSampleBuffer != NULL) {
                                                                     NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
                                                                     UIImage *image = [[UIImage alloc] initWithData:imageData];                                                                 
                                                                     ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
                                                                     [library writeImageToSavedPhotosAlbum:[image CGImage]
                                                                                               orientation:(ALAssetOrientation)[image imageOrientation]
                                                                                           completionBlock:^(NSURL *assetURL, NSError *error){
                                                                                               if (error) {
                                                                                                   id delegate = [self delegate];
                                                                                                   if ([delegate respondsToSelector:@selector(captureStillImageFailedWithError:)]) {
                                                                                                       [delegate captureStillImageFailedWithError:error];
                                                                                                   }                                                                                               
                                                                                               }
                                                                                           }];
                                                                     [library release];
                                                                     [image release];
                                                                 } else if (error) {
                                                                     id delegate = [self delegate];
                                                                     if ([delegate respondsToSelector:@selector(captureStillImageFailedWithError:)]) {
                                                                         [delegate captureStillImageFailedWithError:error];
                                                                     }
                                                                 }
                                                             }];

At the beginning of their code they set the AVOrientation to portrait which seems very odd but I'm trying to get it to detect the device's current orientation and use that.

As you can see I have put [UIApplication sharedApplication]statusBarOrientation to try and get this but it still only save the photos in portrait.

Can anyone offer any help or advice on what I need to be doing?

Thanks!

Oliver

12 Answers

Update the orientation in the preview layer after start the capture session and whenever the device is rotated.

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    coordinator.animate(alongsideTransition: { [weak self] context in
        if let connection = self?.previewLayer?.connection, connection.isVideoOrientationSupported {
            if let orientation = AVCaptureVideoOrientation(orientation: UIDevice.current.orientation) {
                connection.videoOrientation = orientation
            }
        }
    }, completion: nil)
    super.viewWillTransition(to: size, with: coordinator)
}

extension AVCaptureVideoOrientation {
    init?(orientation: UIDeviceOrientation) {
        switch orientation {
        case .landscapeRight: self = .landscapeLeft
        case .landscapeLeft: self = .landscapeRight
        case .portrait: self = .portrait
        case .portraitUpsideDown: self = .portraitUpsideDown
        default: return nil
        }
    }
}

Swift 4 & Swift 5.

Here we go:

private var requests = [VNRequest]()
let exifOrientation = exifOrientationFromDeviceOrientation()
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: exifOrientation, options: [:])
do {
  try imageRequestHandler.perform(self.requests)
} catch {
  print(error)
}

public func exifOrientationFromDeviceOrientation() -> CGImagePropertyOrientation {
    let curDeviceOrientation = UIDevice.current.orientation
    let exifOrientation: CGImagePropertyOrientation

    switch curDeviceOrientation {
    case UIDeviceOrientation.portraitUpsideDown:  // Device oriented vertically, home button on the top
        exifOrientation = .upMirrored
    case UIDeviceOrientation.landscapeLeft:       // Device oriented horizontally, home button on the right
        exifOrientation = .left
    case UIDeviceOrientation.landscapeRight:      // Device oriented horizontally, home button on the left
        exifOrientation = .right
    case UIDeviceOrientation.portrait:            // Device oriented vertically, home button on the bottom
        exifOrientation = .up
    default:
        exifOrientation = .up
    }
    return exifOrientation
}
Related