AVCaptureVideoPreviewLayer 平滑方向旋转

Posted

技术标签:

【中文标题】AVCaptureVideoPreviewLayer 平滑方向旋转【英文标题】:AVCaptureVideoPreviewLayer smooth orientation rotation 【发布时间】:2011-08-10 19:59:04 【问题描述】:

我正在尝试禁用任何可识别的方向旋转到 AVCaptureVideoPreviewLayer,同时仍保持任何子视图的旋转。 AVCaptureVideoPreviewLayer 确实有一个方向属性,并且更改它确实允许图层正确显示任何方向。但是,旋转涉及 AVCaptureVideoPreviewLayer 的一些时髦旋转,而不是像在 Camera 应用中那样保持平滑。

这就是我如何让方向正常工作,减去旋转中的障碍:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

    _captureVideoPreviewLayer.frame = self.view.bounds;
    _captureVideoPreviewLayer.orientation = [[UIDevice currentDevice] orientation];

如何让这个层像相机应用一样工作,同时保持其子视图的旋转?

另外,顺便说一句,我已经看到 AVCaptureVideoPreviewLayer 上的方向属性已被贬值,应该使用 AVCaptureConnection 的 videoOrientation 属性,但我认为我没有 AVCaptureConnection 可以访问这里(我是只需在屏幕上显示相机)。我应该如何设置它以访问 videoOrientation?

【问题讨论】:

【参考方案1】:

在包含预览层的视图上使用仿射变换可创建平滑过渡:

- (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;
    

willAnimateRotationToInterfaceOrientation 函数在旋转动画块中调用,因此此处更改属性将与其余的旋转动画一起动画。此功能已在 ios 6 中逐步淘汰,取而代之的是处理设备旋转的新方法,因此目前可行,但不是最佳解决方案。

【讨论】:

Xcode 告诉我,我不能使用 CGAffineTransformMakeRotation 方法,而对于我的预览层,我需要使用 CATransform3DMakeRotation 方法。你的代码是错的还是我做错了什么? @Will CGAffineTransformMakeRotation 用于 UIViews,CATransform3DMakeRotation 用于 CGLayers willAnimateRotationToInterfaceOrientation:duration: 现在已弃用。通过在[coordinator animateAlongsideTransition:completion: 的动画块内分配变换/帧,您可以使用viewWillTransitionToSize:withTransitionCoordinator: 获得相同的结果。【参考方案2】:

这是一个老问题,但由于它没有公认的答案,而且过去几天我自己一直在处理这个问题,我想我会发布答案。

根据设备方向变化旋转视频的技巧是不旋转AVCaptureVideoPreviewLayerAVCaptureConnection

更改AVCaptureConnection 上的方向(AVCaptureVideoPreviewLayer 的方向已弃用)总是会导致丑陋的动画变化.. 旋转视频后,您仍然需要转换预览层。

执行此操作的正确方法是使用AVAssetWriter 写入视频和音频数据......然后在开始录制时对AVAssetWriterInput 应用转换。

这是来自 Apple 的关于此的简介 --

Receiving rotated CVPixelBuffers from AVCaptureVideoDataOutput

为了请求缓冲区旋转,客户端调用 -setVideoOrientation: on AVCaptureVideoDataOutput 的视频 AVCaptureConnection。注意 物理旋转缓冲区确实会带来性能成本,所以只有 必要时请求轮换。 例如,如果您想要 使用 AVAssetWriter 将旋转视频写入 QuickTime 电影文件, 最好在 AVAssetWriterInput 而不是物理旋转缓冲区 AVCaptureVideoDataOutput。

转换的应用类似于上面发布的代码。

    您注册了设备方向更改。 根据新的设备方向跟踪要应用的变换。 按下记录时,设置 AVAssetWriterInput 并对其应用转换。

希望这对正在寻找答案的人有所帮助。

【讨论】:

在使用“setVideoOrientation”时遇到类似问题,因此感谢 Kal 的评论。既然我知道了这个问题,那么是否有任何例子可以证明这一点?顺便说一句,引用的链接不再有效。试试这个链接:developer.apple.com/library/Mac/releasenotes/AudioVideo/… 找到了一个例子,不出所料,它也是来自 Kal。这是链接:runkalrun.blogspot.com/2013/01/…。谢谢 Kal,很棒的信息!【参考方案3】:

我能够使用以下代码完成此操作:

在需要相机时进行设置:

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

videoPreviewWithFrame 函数。

- (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;

【讨论】:

【参考方案4】:

我希望 AVCaptureVideoPreviewLayer 能够很好地跟随所有旋转。我就是这样做的(overlayView 只是一个恰好具有正确边界的视图)。只有当您将调用 super 准确地放在代码中的位置时,它才会在没有打断的情况下进行动画处理:

- (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];

【讨论】:

缺点:对我来说,这个解决方案还不够。我从这个相机预览中制作的快照在加载到 UIImageView 时,处于相机预览的原始方向。我会为此寻找一个单独的解决方案,也许会提出一个新问题...... 下一个缺点:老板说这不够帅。所以我们像他们一样做,修复纵向的相机预览并旋转控件以监听 UIDeviceOrientation 的变化......【参考方案5】:

您应该使用CATransform3DMakeRotation,它用于层,就像在 cmets 中提到的那样:

例如:

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

在我的例子中,“self.previewLayer”是要旋转的图层。请记住,之后您应该将其剪辑到它的容器视图的边界:

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

。 . .

编辑:如果您要因设备旋转而旋转 (willAnimateRotationToInterfaceOrientation),您应该进行如下转换:

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

这样,图层将像视图一样旋转。

【讨论】:

【参考方案6】:

这就是我的做法:

- (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];
    

【讨论】:

这在 iOS 6 中已被弃用。使用AVCaptureConnection's -setVideoOrientation:【参考方案7】:

我在 swift 中使用:

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
    

【讨论】:

willAnimateRotationToInterfaceOrientation 在 iOS8 中已被弃用 您能提供一个替代解决方案吗? 看看 Kal 的回答。最好的解决方案是不要旋转它。【参考方案8】:

在检查了Apple Example 之后,我想出了如何在 iOS 11 及更高版本中禁用 videoPreviewLayer 的旋转。 (不确定旧版本是否同样适用)

只需在videoPreviewLayerUIView 上设置clipToBounds = false

希望这会有所帮助。

【讨论】:

【参考方案9】:

由于AVCaptureVideoPreviewLayer 是一个CALayer,所以任何更改都会被动画化,http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/AnimatingLayers.html。要停止动画,只需[previewLayer removeAllAnimations]

【讨论】:

以上是关于AVCaptureVideoPreviewLayer 平滑方向旋转的主要内容,如果未能解决你的问题,请参考以下文章