AVCaptureVideoPreviewLayer smooth orientation rotation

强颜欢笑 提交于 2019-12-02 20:50:45

Using an affine transform on the view containing the preview layer creates a smooth transition:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    if (cameraPreview) {
        if (toInterfaceOrientation==UIInterfaceOrientationPortrait) {
            cameraPreview.transform = CGAffineTransformMakeRotation(0);
        } else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
            cameraPreview.transform = CGAffineTransformMakeRotation(M_PI/2);
        } else if (toInterfaceOrientation==UIInterfaceOrientationLandscapeRight) {
            cameraPreview.transform = CGAffineTransformMakeRotation(-M_PI/2);
        }
        cameraPreview.frame = self.view.bounds;
    }
}

The willAnimateRotationToInterfaceOrientation function is called within the rotation animation block so changing properties here will animate along with the rest of the rotation animations. This function has been phased out in iOS 6 and replaced with new methods to handle device rotation, so this works for now, but isn't the best solution.

Kal

This is an old question, but since it didn't have an accepted answer and I've been dealing with this issue myself for the past several days, I figured I would post the answer.

The trick to rotating the video based on device orientation changes, is to NOT ROTATE the AVCaptureVideoPreviewLayer or the AVCaptureConnection at all.

Changing the orientation on the AVCaptureConnection ( AVCaptureVideoPreviewLayer's orientation was deprecated ) ALWAYS results in an ugly animated change .. And after the video is rotated, you would still have to transform the preview layer.

The correct way to do this is use an AVAssetWriter to write the Video and Audio data .. and then apply a transform on the AVAssetWriterInput at the time that you start recording.

Here is a blurb from Apple about this --

Receiving rotated CVPixelBuffers from AVCaptureVideoDataOutput

To request buffer rotation, a client calls -setVideoOrientation: on the AVCaptureVideoDataOutput's video AVCaptureConnection. Note that physically rotating buffers does come with a performance cost, so only request rotation if it's necessary. If, for instance, you want rotated video written to a QuickTime movie file using AVAssetWriter, it is preferable to set the -transform property on the AVAssetWriterInput rather than physically rotate the buffers in AVCaptureVideoDataOutput.

The applying of the transform is similar to the code posted above.

  1. You register for device orientation changes.
  2. Keep track of transform to apply based on the new device orientation.
  3. When record is pressed, set up the AVAssetWriterInput and apply transform to it.

Hope this helps whoever is looking for an answer.

I was able to accomplish this with the following code:

Set up when you need the camera:

preview = [[self videoPreviewWithFrame:CGRectMake(0, 0, 320, 480)] retain];
[self.view addSubview:preview];

The videoPreviewWithFrame function.

- (UIView *) videoPreviewWithFrame:(CGRect) frame {
  AVCaptureVideoPreviewLayer *tempPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:[self captureSession]];
  [tempPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
  tempPreviewLayer.frame = frame;

  UIView* tempView = [[UIView alloc] init];
  [tempView.layer addSublayer:tempPreviewLayer];
  tempView.frame = frame;

  [tempPreviewLayer autorelease];
  [tempView autorelease];
  return tempView;
}

I wanted the AVCaptureVideoPreviewLayer to handsomely follow all rotations. This is how I did it (overlayView is just a view that happens to have the correct bounds). It only animates without a hick-up when you place your calls to super exactly where they are in the code:

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

    if ([[[self previewLayer] connection] isVideoOrientationSupported])
    {
        [[[self previewLayer] connection] setVideoOrientation:toInterfaceOrientation];
    }
}

- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    CGRect layerRect = [[self overlayView] bounds];
    [[self previewLayer] setBounds:layerRect];
    [[self previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),
                                                 CGRectGetMidY(layerRect))];

    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration: duration];
}

You should use CATransform3DMakeRotation which is meant for Layers, like it has been mentioned on the comments:

For instance:

float degreesToRotate = 90.0; //Degrees you want to rotate
self.previewLayer.transform = CATransform3DMakeRotation(degreesToRotate / 180.0 * M_PI, 0.0, 0.0, 1.0);

In my case, being "self.previewLayer" the layer to rotate. Have in mind that you should clip it to it's container view's bounds afterwards:

self.previewLayer.frame = self.view.bounds;

. . .

EDIT: If you are going to rotate as a result of the device being rotated (willAnimateRotationToInterfaceOrientation) you should make the transformation like this:

[CATransaction begin];
    [CATransaction setAnimationDuration:duration];
    [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    //**Perform Transformation here**
    [CATransaction commit];

This way, the layer will rotate as the view does.

This is how I do it:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    if (tookFirstPicture)
        return;

    if (toInterfaceOrientation == UIInterfaceOrientationPortrait) {
        [previewLayer setOrientation:AVCaptureVideoOrientationPortrait];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
        [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeLeft];
    }
    else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
        [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeRight];
    }
    else {
        [previewLayer setOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
    }
}

In swift I use:

override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
    if !UIDevice.currentDevice().model.contains("Simulator") {
        if (toInterfaceOrientation == .Portrait) {
            prevLayer?.transform = CATransform3DMakeRotation(0, 0, 0, 1)
        } else if (toInterfaceOrientation == .LandscapeLeft) {
            prevLayer?.transform = CATransform3DMakeRotation(CGFloat(M_PI)/2, 0, 0, 1)
        } else if (toInterfaceOrientation == .LandscapeRight) {
            prevLayer?.transform = CATransform3DMakeRotation(-CGFloat(M_PI)/2, 0, 0, 1)
        }
        prevLayer?.frame = self.scanView.frame
    }
}

After examining Apple Example, I have figured out how to disable the rotation of your videoPreviewLayer in iOS 11 and later. (not sure if the same works for older version)

Simply set the clipToBounds = false on the UIView of your videoPreviewLayer.

Hope this helps.

Since AVCaptureVideoPreviewLayer is a CALayer, then any changes will be animated, http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/AnimatingLayers.html. To stop the animation just [previewLayer removeAllAnimations].

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!