55

So, I followed Apple's instructions to capture video session using AVCaptureSession: http://developer.apple.com/iphone/library/qa/qa2010/qa1702.html. One problem I'm facing is that even though the orientation of the camera / iPhone device is vertical (and the AVCaptureVideoPreviewLayer shows a vertical camera stream), the output image seems to be in the landscape mode. I checked the width and height of imageBuffer inside imageFromSampleBuffer: of the sample code, and I got 640px and 480px respectively. Does anyone know why this's the case?

Thanks!

Andriy
  • 2,767
  • 2
  • 21
  • 29
Peter
  • 663
  • 1
  • 6
  • 7

11 Answers11

44

Take a look at the header AVCaptureSession.h. There is a definition for an enum called AVCaptureVideoOrientation that defines various video orientations. On the AVCaptureConnection object there is a property called videoOrientation that is a AVCaptureVideoOrientation. You should be able to set this to change the orientation of the video. You probably want AVCaptureVideoOrientationLandscapeRight or AVCaptureVideoOrientationLandscapeLeft.

You can find the AVCaptureConnections for the session by looking at the outputs for the session. The outputs have a connections property that is an array of connections for that output.

Mark Coleman
  • 40,542
  • 9
  • 81
  • 101
Jon Steinmetz
  • 4,104
  • 1
  • 23
  • 21
  • 8
    `Property 'videoOrientation' not found on object of type 'AVCaptureSession *'` – Jonas Sourlier Oct 03 '12 at 16:35
  • 12
    @cheesus videoOrientation is on the AVCaptureConnection, not the AVCaptureSession – GabCas Aug 28 '13 at 15:34
  • 1
    But where should i set this property in `AVCaptureConnection`? Inside the delegate method? something like: `- (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { [connection setVideoOrientation:AVCaptureVideoOrientationPortrait];` – rptwsthi May 08 '15 at 20:40
  • 12
    @rptwsthi you can set just after you add output into session `captureSession.addOutput(videoOutput) let connection = videoOutput.connectionWithMediaType(AVFoundation.AVMediaTypeVideo) connection.videoOrientation = .Portrait captureSession.startRunning()` – gonglong Mar 16 '16 at 06:01
  • I'd suggest doing the same after you change the camera lens, before committing the changes. – Joel Jul 03 '23 at 13:52
24

Y'all are making this difficult.

In the DidOutputSampleBuffer, simply change the orientation before you grab the image. It's mono, but you have

    public class OutputRecorder : AVCaptureVideoDataOutputSampleBufferDelegate {    
        public override void DidOutputSampleBuffer (AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
        {
            try {
                connection.videoOrientation = AVCaptureVideoOrientation.LandscapeLeft;

in objC it's this method

- ( void ) captureOutput: ( AVCaptureOutput * ) captureOutput
   didOutputSampleBuffer: ( CMSampleBufferRef ) sampleBuffer
      fromConnection: ( AVCaptureConnection * ) connection
drewish
  • 9,042
  • 9
  • 38
  • 51
Nick Turner
  • 927
  • 1
  • 11
  • 21
  • 1
    Tried this and I get `'AVCaptureConnection' does not have a member named 'VideoOrientation'` – drewish Dec 10 '14 at 07:52
  • 1
    It was a capitalization issue. Should have been `videoOrientation` so edited the answer. – drewish Dec 10 '14 at 07:55
  • just one thing I don't understand. When I use the camera with my ipad landscape I have to invert the videoOrientation. For example: if my ipad is `UIDeviceOrientationLeft` I have to do `connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight` and the same for the other landscape, or the image will be upside down. Why the video orientation has to be inverted in terms of device orientation is one more confuse thing thanks to Apple. – Duck Feb 04 '15 at 17:07
  • Works! Thank you SIR! – Roi Mulia May 31 '16 at 10:12
  • Works. Just remember to set the orientation asynchronously on the main queue, because this method is called on a special serial queue. – Andrzej Filipowicz Aug 12 '16 at 09:31
  • @SpaceDog If you look at the definitions of AVCaptureVideoOrientation and UIDeviceOrientation you'll see that the definitions of the landscape orientations are reversed in these enums. Why it's like this is beyond me, but it is documented. I agree it's confusing and assume there's some legacy reason they differ. – Sami Samhuri Oct 25 '16 at 17:38
  • @SpaceDog It is because they are planes facing in opposite directions out of opposite sides of the phone. The left of the rear camera sensor is described as if you were looking at it from its front, i.e. the lens, but if your point of reference is the screen on the front of the device then it is reversed and this is on the right. For the front camera these are more intuitive because the sensor is in the same plane as the screen. It is confusing, but probably best to keep naming consistent in case phones don't always look the way they do today. – alex bird Aug 05 '19 at 15:05
23

I made a simple one-line modification to the imageFromSampleBuffer to correct the orientation problem (see my comment in the code under "I modified ..."). Hope it helps someone because I spent too much time on this.

// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer  {
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0); 

    // Get the number of bytes per row for the pixel buffer
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 

    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 

    // Create a device-dependent RGB color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    // Create a bitmap graphics context with the sample buffer data
    CGContextRef context1 = CGBitmapContextCreate(baseAddress, width, height, 8, 
                                                 bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

    // Create a Quartz image from the pixel data in the bitmap graphics context
    CGImageRef quartzImage = CGBitmapContextCreateImage(context1); 
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);

    // Free up the context and color space
    CGContextRelease(context1); 
    CGColorSpaceRelease(colorSpace);

    // Create an image object from the Quartz image
    //I modified this line: [UIImage imageWithCGImage:quartzImage]; to the following to correct the orientation:
    UIImage *image =  [UIImage imageWithCGImage:quartzImage scale:1.0 orientation:UIImageOrientationRight]; 

    // Release the Quartz image
    CGImageRelease(quartzImage);

    return (image);
}
Willy
  • 9,681
  • 5
  • 26
  • 25
RawMean
  • 8,374
  • 6
  • 55
  • 82
16

Here is a right sequence:

AVCaptureVideoDataOutput *videoCaptureOutput = [[AVCaptureVideoDataOutput alloc] init];

if([self.captureSession canAddOutput:self.videoCaptureOutput]){
    [self.captureSession addOutput:self.videoCaptureOutput];
}else{
    NSLog(@"cantAddOutput");
}

// set portrait orientation
AVCaptureConnection *conn = [self.videoCaptureOutput connectionWithMediaType:AVMediaTypeVideo];
[conn setVideoOrientation:AVCaptureVideoOrientationPortrait];
Rubycon
  • 18,156
  • 10
  • 49
  • 70
  • 2
    This worked for me. Like this code shows, it's important to add the output to the capture session before changing these properties. AVCaptureVideoOrientationLandscapeRight with videoMirrored = true gave me the same effect that you get from using the standard camera app with front facing camera. – spfursich Feb 10 '15 at 20:06
  • 1
    Doesn't this just change the orientation of the local `conn` object, rather than the one belonging to `self.videoCaptureOutput`? – mylogon Jul 03 '17 at 13:56
  • @spfursich what you metioned is very very inconspicuous but important. – KnowNothing Dec 12 '18 at 13:32
13

For instance:

AVCaptureConnection *captureConnection = <a capture connection>;
if ([captureConnection isVideoOrientationSupported]) {
    captureConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
}

The default appears to be AVCaptureVideoOrientationLandscapeRight.

See also QA1744: Setting the orientation of video with AV Foundation.

freya
  • 459
  • 7
  • 14
  • this works for me, i added it to myAVCaptureConnection handler. It rotated the image as expected but the video is mirrored. I see that the isMirrored is deprecated so unclear how to fix the mirroring stuff b/c the new mirroring methods has no documentation on usage. – Jim True Apr 26 '13 at 12:26
  • @Jim: Since `mirrored` in `AVCaptureVideoPreviewLayer` has been deprecated, you should use `AVCaptureConnection`’s `videoMirrored` property instead. It is documented, at least in the header files, where `mirrored` and its friends also explicitly refers you to `videoMirrored` and the matching capture connection methods. – freya Apr 26 '13 at 14:28
  • it looks like that feature isn't available in iOS sadly – Jim True Apr 26 '13 at 21:22
  • 1
    @Jim: No way, dude! It’s right here in the online documentation as well: http://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVCaptureConnection_Class ʘ‿ʘ What made you think it’s not available on iOS? – freya May 01 '13 at 19:27
7

For those people that need to work with CIImage and orientation from buffer is wrong I used this correction.

As easy as that. BTW the numbers 3,1,6,8 are from here https://developer.apple.com/reference/imageio/kcgimagepropertyorientation

And don't ask me why 3,1,6,8 is the right combination. I used brute-force method to find it. If you know why let the explanation in a comment please...

- (void)captureOutput:(AVCaptureOutput *)captureOutput
    didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
           fromConnection:(AVCaptureConnection *)connection
{

    // common way to get CIImage

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate);

    CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer
                                                      options:(__bridge NSDictionary *)attachments];

    if (attachments) {
       CFRelease(attachments);
    }

    // fixing the orientation of the CIImage

    UIInterfaceOrientation curOrientation = [[UIApplication sharedApplication] statusBarOrientation];

    if (curOrientation == UIInterfaceOrientationLandscapeLeft){
        ciImage = [ciImage imageByApplyingOrientation:3];
    } else if (curOrientation == UIInterfaceOrientationLandscapeRight){
        ciImage = [ciImage imageByApplyingOrientation:1];
    } else if (curOrientation == UIInterfaceOrientationPortrait){
        ciImage = [ciImage imageByApplyingOrientation:6];
    } else if (curOrientation == UIInterfaceOrientationPortraitUpsideDown){
        ciImage = [ciImage imageByApplyingOrientation:8];
    }



    // ....

}
Marek Manduch
  • 2,303
  • 1
  • 19
  • 22
  • 1
    The constants you are looking for are defined in kCGImagePropertyorientation... these come from the TIFF/EXIF spec: https://www.awaresystems.be/imaging/tiff/tifftags/orientation.html – ibisum Oct 03 '17 at 07:47
6

If the AVCaptureVideoPreviewLayer orientation is correct, you can simply set the orientation before you capture the image.

AVCaptureStillImageOutput *stillImageOutput;
AVCaptureVideoPreviewLayer *previewLayer;
NSData *capturedImageData;

AVCaptureConnection *videoConnection = [stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) {
    [videoConnection setVideoOrientation:previewLayer.connection.videoOrientation];
}
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
    CFDictionaryRef exifAttachments =
            CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
    if (exifAttachments) {
        // Do something with the attachments.
    }
    // TODO need to manually add GPS data to the image captured
    capturedImageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
    UIImage *image = [UIImage imageWithData:capturedImageData];
}];

Also, it's important to note that UIImageOrientation and AVCaptureVideoOrientation are different. UIImageOrientationUp refers to landscape mode with the volume controls down toward the ground (not up if you think about using the volume controls as a shutter button).

Thus, portrait orientation with the power button pointing to the sky (AVCaptureVideoOrientationPortrait) is actually UIImageOrientationLeft.

mikeho
  • 6,790
  • 3
  • 34
  • 46
3

First of all, in the configuration of your video output, put these lines:

guard let connection = videoOutput.connection(withMediaType: 
AVFoundation.AVMediaTypeVideo) else { return }
guard connection.isVideoOrientationSupported else { return }
guard connection.isVideoMirroringSupported else { return }
connection.videoOrientation = .portrait
connection.isVideoMirrored = position == .front

Then, configure your Target to support just Portait, by unchecking Landscape modes in General configuration.

(Source)

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Laura Corssac
  • 1,217
  • 1
  • 13
  • 23
2

Orientation issue is with the front camera, so check device type and generate new image, it will definitely solve the orientation issue:

-(void)capture:(void(^)(UIImage *))handler{

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.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {

    if (imageSampleBuffer != NULL) {
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        **UIImage *capturedImage = [UIImage imageWithData:imageData];
        if (self.captureDevice == [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo][1]) {
            capturedImage = [[UIImage alloc] initWithCGImage:capturedImage.CGImage scale:1.0f orientation:UIImageOrientationLeftMirrored];
        }**

        handler(capturedImage);
    }
}];
}
YvesLeBorg
  • 9,070
  • 8
  • 35
  • 48
Sandip
  • 21
  • 3
1
// #1
AVCaptureVideoOrientation newOrientation = AVCaptureVideoOrientationLandscapeRight;
if (@available(iOS 13.0, *)) {
    // #2
    for (AVCaptureConnection *connection in [captureSession connections]) {
        if ([connection isVideoOrientationSupported]) {
            connection.videoOrientation = newOrientation;
            break;
        }
    } // #3
} else if ([previewLayer.connection isVideoOrientationSupported]) {
    previewLayer.connection.videoOrientation = newOrientation;
}

Once that you can correctly use your AVCaptureSession, you can set a video orientation. Here a detailed description of the code above. Remember, this code has to be executed after the [captureSession startRunning] execution:

  1. Choose the orientation that you prefer
  2. For ios version >= 13.0 you have to retrieve the active connection from the captureSession. Remember: only video connection supports videoOrientation
  3. For ios version < 13.0 you can use the connection from the previewLayer

If your viewController doesn't have a fixed orientation you can set a new videoOrientation to your connection once the device orientation changes.

Danilo Raspa
  • 795
  • 5
  • 5
-1

You can try this:

private func startLiveVideo() {

    let captureSession = AVCaptureSession()
    captureSession.sessionPreset = .photo
    let captureDevice = AVCaptureDevice.default(for: .video)

    let input = try! AVCaptureDeviceInput(device: captureDevice!)
    let output = AVCaptureVideoDataOutput()
    captureSession.addInput(input)
    captureSession.addOutput(output)

    output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
    output.connection(with: .video)?.videoOrientation = .portrait

    let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    previewLayer.frame = view.bounds
    view.layer.addSublayer(previewLayer)

    captureSession.startRunning()
}
zyc
  • 344
  • 3
  • 7