36

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

Stefan
  • 5,203
  • 8
  • 27
  • 51
Fogmeister
  • 76,236
  • 42
  • 207
  • 306

12 Answers12

46

Well, it's taken me fracking forever but I've done it!

The bit of code I was looking for is

[UIDevice currentDevice].orientation;

This goes in as so

AVCaptureConnection *videoConnection = [CameraVC connectionWithMediaType:AVMediaTypeVideo fromConnections:[imageCaptureOutput connections]];
if ([videoConnection isVideoOrientationSupported])
{
    [videoConnection setVideoOrientation:[UIDevice currentDevice].orientation];
}

And it works perfectly :D

Woop woop!

Bhumit Mehta
  • 16,278
  • 11
  • 50
  • 64
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • 2
    Well, I thought I'd fixed it but alas I was wrong. It fixes the problem of displaying the image on the phone and saves to the library correctly but when uploaded to Facebook or viewed on my computer they are still the wrong way round :( – Fogmeister Sep 08 '10 at 12:57
  • 2
    What was your final solution? I noticed an oddity which your post helped me with. UIDeviceOrientation and AVCaptureVideoOrientation are not identical. LandscapeRight and LandscapeLeft are swapped in the enumeration. I was doing a lengthy (if portrait / set portrait, if landscape left / set landscape left) and my landscape pictures were always upside down! It seems the swap is required to keep the mapping correct, not sure how that works though. – Brian King Jan 08 '11 at 17:40
  • Hmm. For me, the thumbnail orientation in the photo library is rotated, but the image itself is fine. – Old McStopher Aug 09 '11 at 09:42
  • 6
    ...to come back to Stack Overflow when the first response they get is someone having a go at them for asking a question? Stack Overflow is a place for people to find answers. Not for people to get shouted at for using the wrong tag. Posting a link to Google in the comments is sarcastic. You cannot say that it isn't, it is the very definition. Do you think if I'd known to the answer to my question that I'd have asked it? Stop treating people like they're stupid. OK, you've got a stupid amount of rep here but do not abuse it or think it gives you the power to insult other people's questions. – Fogmeister Feb 12 '13 at 07:58
  • @Fogmeister I am having the same issue, can you please tell me how you fixed it? – Jonathan Apr 11 '13 at 18:43
  • the problem when uploading is because UIImage has a flag that tells how is it rotated, probably on exif... refer to this SO question for more info http://stackoverflow.com/questions/5427656/ios-uiimagepickercontroller-result-image-orientation-after-upload – Heavy_Bullets Sep 30 '13 at 04:23
14

Isn't this a little cleaner?

    AVCaptureVideoOrientation newOrientation;
    switch ([[UIDevice currentDevice] orientation]) {
    case UIDeviceOrientationPortrait:
        newOrientation = AVCaptureVideoOrientationPortrait;
        break;
    case UIDeviceOrientationPortraitUpsideDown:
        newOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
        break;
    case UIDeviceOrientationLandscapeLeft:
        newOrientation = AVCaptureVideoOrientationLandscapeRight;
        break;
    case UIDeviceOrientationLandscapeRight:
        newOrientation = AVCaptureVideoOrientationLandscapeLeft;
        break;
    default:
        newOrientation = AVCaptureVideoOrientationPortrait;
    }
    [stillConnection setVideoOrientation: newOrientation];
Bill Cheswick
  • 634
  • 7
  • 12
11

The following is from AVCam, I added too it:

- (void)deviceOrientationDidChange{

    UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];

    AVCaptureVideoOrientation newOrientation;

    if (deviceOrientation == UIDeviceOrientationPortrait){
        NSLog(@"deviceOrientationDidChange - Portrait");
        newOrientation = AVCaptureVideoOrientationPortrait;
    }
    else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown){
        NSLog(@"deviceOrientationDidChange - UpsideDown");
        newOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
    }

    // AVCapture and UIDevice have opposite meanings for landscape left and right (AVCapture orientation is the same as UIInterfaceOrientation)
    else if (deviceOrientation == UIDeviceOrientationLandscapeLeft){
        NSLog(@"deviceOrientationDidChange - LandscapeLeft");
        newOrientation = AVCaptureVideoOrientationLandscapeRight;
    }
    else if (deviceOrientation == UIDeviceOrientationLandscapeRight){
        NSLog(@"deviceOrientationDidChange - LandscapeRight");
        newOrientation = AVCaptureVideoOrientationLandscapeLeft;
    }

    else if (deviceOrientation == UIDeviceOrientationUnknown){
        NSLog(@"deviceOrientationDidChange - Unknown ");
        newOrientation = AVCaptureVideoOrientationPortrait;
    }

    else{
        NSLog(@"deviceOrientationDidChange - Face Up or Down");
        newOrientation = AVCaptureVideoOrientationPortrait;
    }

    [self setOrientation:newOrientation];
}

And be sure to add this to your init method:

NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[notificationCenter addObserver:self
    selector:@selector(deviceOrientationDidChange) 
    name:UIDeviceOrientationDidChangeNotification object:nil];
[self setOrientation:AVCaptureVideoOrientationPortrait];
Andrey Kuznetsov
  • 843
  • 1
  • 18
  • 29
W Dyson
  • 4,604
  • 4
  • 40
  • 68
4

there are two things to notice

a) as Brian King wrote - LandscapeRight and LandscapeLeft are swapped in the enumeration. see AVCamCaptureManager example:

// AVCapture and UIDevice have opposite meanings for landscape left and right (AVCapture orientation is the same as UIInterfaceOrientation)
else if (deviceOrientation == UIDeviceOrientationLandscapeLeft)
    orientation = AVCaptureVideoOrientationLandscapeRight;
else if (deviceOrientation == UIDeviceOrientationLandscapeRight)
    orientation = AVCaptureVideoOrientationLandscapeLeft;

b) There are also UIDeviceOrientationFaceUp and UIDeviceOrientationFaceDown states, that if you try to set as the video orientation, your video will fail recording. Make sure you don't use them when calling [UIDevice currentDevice].orientation !

Max
  • 16,679
  • 4
  • 44
  • 57
iRadium
  • 41
  • 1
  • 2
3

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
        }
    }
}
rockdaswift
  • 9,613
  • 5
  • 40
  • 46
3

If you're using AVCaptureVideoPreviewLayer you can do the following inside your view controller.

(assuming you have an instance of AVCaptureVideoPreviewLayer called "previewLayer")

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
   [self.previewLayer setOrientation:[[UIDevice currentDevice] orientation]];
}
Drew H
  • 1,226
  • 1
  • 12
  • 13
2

I am writing this code in Swift in case may be needful for someone.

Step-1: Generate Orientation notifications (in yourviewDidLoad)

    UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("deviceOrientationDidChange:"), name: UIDeviceOrientationDidChangeNotification, object: nil)

Step-2: Take picture. Here we will interchange orientation of videoConnection. In AVFoundation there is a minor change in orientation especially for landscape orientation. So we will just interchange it. For example we will change from LandscapeRight to LandscapeLeft and viceversa

  func takePicture() {
if let videoConnection = stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo) {

    var newOrientation: AVCaptureVideoOrientation?
    switch (UIDevice.currentDevice().orientation) {
    case .Portrait:
        newOrientation = .Portrait
        break
    case .PortraitUpsideDown:
        newOrientation = .PortraitUpsideDown
        break
    case .LandscapeLeft:
        newOrientation = .LandscapeRight
        break
    case .LandscapeRight:
        newOrientation = .LandscapeLeft
        break
    default :
        newOrientation = .Portrait
        break

    }
    videoConnection.videoOrientation = newOrientation!


  stillImageOutput!.captureStillImageAsynchronouslyFromConnection(videoConnection) {
    (imageDataSampleBuffer, error) -> Void in

    let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {

      dispatch_async(dispatch_get_main_queue()) {

        let image = UIImage(data: imageData!)!
        let portraitImage = image.fixOrientation()


      }
    }


  }
}

  }

NOTE: Please notice the new orientation value for Landscape orientations. Its just the opposite. (This is the culprit ::UHHHH)

Step-3: Fix orientation (UIImage extension)

extension UIImage {

func fixOrientation() -> UIImage {

    if imageOrientation == UIImageOrientation.Up {
        return self
    }

    var transform: CGAffineTransform = CGAffineTransformIdentity

    switch imageOrientation {
    case UIImageOrientation.Down, UIImageOrientation.DownMirrored:
        transform = CGAffineTransformTranslate(transform, size.width, size.height)
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI))
        break
    case UIImageOrientation.Left, UIImageOrientation.LeftMirrored:
        transform = CGAffineTransformTranslate(transform, size.width, 0)
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2))
        break
    case UIImageOrientation.Right, UIImageOrientation.RightMirrored:
        transform = CGAffineTransformTranslate(transform, 0, size.height)
        transform = CGAffineTransformRotate(transform, CGFloat(-M_PI_2))
        break
    case UIImageOrientation.Up, UIImageOrientation.UpMirrored:
        break
    }

    switch imageOrientation {
    case UIImageOrientation.UpMirrored, UIImageOrientation.DownMirrored:
        CGAffineTransformTranslate(transform, size.width, 0)
        CGAffineTransformScale(transform, -1, 1)
        break
    case UIImageOrientation.LeftMirrored, UIImageOrientation.RightMirrored:
        CGAffineTransformTranslate(transform, size.height, 0)
        CGAffineTransformScale(transform, -1, 1)
    case UIImageOrientation.Up, UIImageOrientation.Down, UIImageOrientation.Left, UIImageOrientation.Right:
        break
    }

    let ctx: CGContextRef = CGBitmapContextCreate(nil, Int(size.width), Int(size.height), CGImageGetBitsPerComponent(CGImage), 0, CGImageGetColorSpace(CGImage), CGImageAlphaInfo.PremultipliedLast.rawValue)!

    CGContextConcatCTM(ctx, transform)

    switch imageOrientation {
    case UIImageOrientation.Left, UIImageOrientation.LeftMirrored, UIImageOrientation.Right, UIImageOrientation.RightMirrored:
        CGContextDrawImage(ctx, CGRectMake(0, 0, size.height, size.width), CGImage)
        break
    default:
        CGContextDrawImage(ctx, CGRectMake(0, 0, size.width, size.height), CGImage)
        break
    }

    let cgImage: CGImageRef = CGBitmapContextCreateImage(ctx)!

    return UIImage(CGImage: cgImage)
}


   }
Abdul Yasin
  • 3,480
  • 1
  • 28
  • 42
1

This uses the view controller's orientation method. This works for me, hopefully works for you.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

    AVCaptureConnection *videoConnection = self.prevLayer.connection;
    [videoConnection setVideoOrientation:(AVCaptureVideoOrientation)toInterfaceOrientation];
}
Nate Hat
  • 408
  • 4
  • 11
1

In Swift you should do this:

    videoOutput = AVCaptureVideoDataOutput()
    videoOutput!.setSampleBufferDelegate(self, queue: dispatch_queue_create("sample buffer delegate", DISPATCH_QUEUE_SERIAL))

    if captureSession!.canAddOutput(self.videoOutput) {
        captureSession!.addOutput(self.videoOutput)
    }

    videoOutput!.connectionWithMediaType(AVMediaTypeVideo).videoOrientation = AVCaptureVideoOrientation.PortraitUpsideDown

It works perfectly for me!

vkalit
  • 647
  • 8
  • 19
1

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
}
Pengguna
  • 4,636
  • 1
  • 27
  • 32
0

You can also create an intermediate CIImage, and grab the properties dictionary

NSDictionary *propDict = [aCIImage properties];
NSString *orientString = [propDict objectForKey:kCGImagePropertyOrientation];

And Transform accordingly :)

I love how easy it is to access all this image metadata in iOS5!

johntraver
  • 3,612
  • 18
  • 17
-1

How to use with AVCaptureFileOutput ?

- (void)detectVideoOrientation:(AVCaptureFileOutput *)captureOutput {
    for(int i = 0; i < [[captureOutput connections] count]; i++) {
        AVCaptureConnection *captureConnection = [[captureOutput connections] objectAtIndex:i];
        if([captureConnection isVideoOrientationSupported]) {
            [captureConnection setVideoOrientation:[[UIDevice currentDevice] orientation]];
        }
    }
}
B.Asselin
  • 978
  • 10
  • 19