AVCaptureVideoPreviewLayer方向问题 - 需要横屏显示

71

我的应用只支持横屏。我是这样展示AVCaptureVideoPreviewLayer的:

self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
[self.previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
[self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];                    
NSLog(@"previewView: %@", self.previewView);
CALayer *rootLayer = [self.previewView layer];
[rootLayer setMasksToBounds:YES];
[self.previewLayer setFrame:[rootLayer bounds]];
    NSLog(@"previewlayer: %f, %f, %f, %f", self.previewLayer.frame.origin.x, self.previewLayer.frame.origin.y, self.previewLayer.frame.size.width, self.previewLayer.frame.size.height);
[rootLayer addSublayer:self.previewLayer];
[session startRunning];

self.previewView 的框架是 (0,0,568,320),是正确的。self.previewLayer 的框架记录为 (0,0,568,320),在理论上也是正确的。但是,相机显示作为纵向矩形出现在横向屏幕中央,相机预览图像的方向错误了90度。我做错了什么?我需要相机预览图层以全屏幕,横向模式出现,并且图像应该正确方向。

4
重要提示:对于2016年,请跳到下面正确的答案https://dev59.com/vGUp5IYBdhLWcg3wvpcM#36575423。与许多计算机主题一样,API细节会随着时间发生变化。随着自动布局等技术的引入,这里旧的答案(当时非常好)现在已经不正确了。(另外三四年后可能会有一个新的正确答案!) - Fattie
20个回答

3

适用于Swift 4,Xcode 9:

  override func viewWillTransition(to size: CGSize,
                                   with coordinator: UIViewControllerTransitionCoordinator)
  {
    super.viewWillTransition(to: size, with: coordinator)
    guard
    let conn = self.previewLayer?.connection,
      conn.isVideoOrientationSupported
      else { return }
    let deviceOrientation = UIDevice.current.orientation
    switch deviceOrientation {
    case .portrait: conn.videoOrientation = .portrait
    case .landscapeRight: conn.videoOrientation = .landscapeLeft
    case .landscapeLeft: conn.videoOrientation = .landscapeRight
    case .portraitUpsideDown: conn.videoOrientation = .portraitUpsideDown
    default: conn.videoOrientation = .portrait
    }
  }

这里需要注意的一个细节是,UIDeviceOrientation.landscapeRight 对应 AVCaptureVideoOrientation.landscapeLeft

另一种横屏情况也类似地不匹配。这是故意这样设计的,以适应UIKit和AVFoundation之间的不幸不匹配。如果你通过名称匹配来匹配这些情况,它将不起作用,并且你的视频在横屏配置下会颠倒。


关于UIKit和AVFoundation方向不匹配的问题,你是正确的。这让我很困扰。在我的情况下,我还必须改变previewLayer的框架以匹配新的大小,使用以下代码: previewLayer.position = CGPointMake(size.width/2.0, size.height/2.0) previewLayer.frame = CGRectMake(0, 0, size.width, size.height) - Chuck Krutsinger

2

首先,我们需要创建AVCaptureVideoPreviewLayer:

  1. set its videoGravity (as in my case am using a small view to get video output).
  2. set the frame.

    [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [_videoPreviewLayer setFrame:_viewPreview.layer.bounds];
    
  3. set the orientation initially

    if (_videoPreviewLayer.connection.supportsVideoOrientation) {
            _videoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
        }
  4. set the orientation for each case using a simple switch case

    -(AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:

    (UIInterfaceOrientation)orientation {
    
        switch (orientation) {
            case UIInterfaceOrientationPortrait:
                return AVCaptureVideoOrientationPortrait;
            case UIInterfaceOrientationPortraitUpsideDown:
                return AVCaptureVideoOrientationPortraitUpsideDown;
            case UIInterfaceOrientationLandscapeLeft:
                return AVCaptureVideoOrientationLandscapeLeft ;
            case UIInterfaceOrientationLandscapeRight:
                return AVCaptureVideoOrientationLandscapeRight;
            default:
                break;
        }
        NSLog(@"Warning - Didn't recognise interface orientation (%d)",orientation);
        return AVCaptureVideoOrientationPortrait;
    

    }

  5. Since the device supports both landscapeLeft and landscapeRight use the delegate called on rotation:
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    if (_videoPreviewLayer.connection.supportsVideoOrientation) {
        _videoPreviewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:toInterfaceOrientation];
     }
    }
    

2

大家好,感谢各位对此问题的回答和反馈。我在开发一款Swift应用时遇到了这个问题。您可以使用以下代码使相机随设备旋转:

override func shouldAutorotate() -> Bool {
        if your cameraController.previewLayer.connection != nil {
            var currentDevice: UIDevice = UIDevice.currentDevice()
            var orientation: UIDeviceOrientation = currentDevice.orientation

            var previewLayerConnection : AVCaptureConnection = your cameraController.previewLayer.connection

            if (previewLayerConnection.supportsVideoOrientation)
            {
                switch (orientation)
                {
                case .Portrait:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
                    break
                case .LandscapeRight:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.LandscapeLeft
                    break
                case .LandscapeLeft:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.LandscapeRight
                    break
                default:
                    previewLayerConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
                    break
                }
            }

        }
        return true
    }

希望这能对你有所帮助!

为什么左右方向颠倒了呢?我还是无法保存正确方向的预览图像。如果您有时间,请在此处解答:https://dev59.com/Vabja4cB1Zd3GeqPczVY - user2363025
移除那些 break 语句。这是 Swift,switch 语句中没有像 Objective-C 那样的 fallthrough。 - Rob

1
改进版的Maselko答案,效果非常好!
override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()
  if let connection =  previewView.connection  {
    if connection.isVideoOrientationSupported {
      let videoOrientation = AVCaptureVideoOrientation.init(rawValue: UIApplication.shared.statusBarOrientation.rawValue)!
      connection.videoOrientation = videoOrientation
      previewView.frame = self.view.bounds
    }
  }
}

1

Maselko的答案 差不多对我有用,只是如果状态栏方向翻转,则相机输出会倒置显示。我通过在状态栏翻转时重新调用Maselko的逻辑来解决了这个问题。

这是我修改后的Maselko解决方案(在ios12/swift4上测试):

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    setCameraOrientation()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    setCameraOrientation()
}

@objc func setCameraOrientation() {
    if let connection =  self.previewLayer?.connection  {
        let currentDevice: UIDevice = UIDevice.current
        let orientation: UIDeviceOrientation = currentDevice.orientation
        let previewLayerConnection : AVCaptureConnection = connection
        if previewLayerConnection.isVideoOrientationSupported {
            let o: AVCaptureVideoOrientation
            switch (orientation) {
            case .portrait: o = .portrait
            case .landscapeRight: o = .landscapeLeft
            case .landscapeLeft: o = .landscapeRight
            case .portraitUpsideDown: o = .portraitUpsideDown
            default: o = .portrait
            }

            previewLayerConnection.videoOrientation = o
            previewLayer!.frame = self.view.bounds
        }
    }
}

1

我也遇到了同样的问题 这是为了解决我的相机方向

override func shouldAutorotate() -> Bool {
        return false
    }

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
        return UIInterfaceOrientation.LandscapeLeft
    }

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.LandscapeLeft
    }

修复摄像头

let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.avCaptureSession)    
previewLayer.frame = self.view.layer.frame
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill

0

@Maselko的回答是正确的,但有一点需要注意:您应该使用UIApplication.shared.statusBarOrientation而不是UIDevice.current.orientation,因为设备方向是指您的设备在物理上的位置。当您的设备处于横向模式但您的UI不支持该方向(例如我正在制作一个仅支持横向的相机应用程序并在设备处于纵向位置时启动视图)时,它会出现问题。

private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) {

    layer.videoOrientation = orientation

    previewLayer.frame = self.view.bounds

}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let connection =  self.previewLayer?.connection  {

        let currentDevice: UIDevice = UIDevice.current

        let orientation = UIApplication.shared.statusBarOrientation

        let previewLayerConnection : AVCaptureConnection = connection

        if previewLayerConnection.isVideoOrientationSupported {

            switch (orientation) {
            case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)

                break

            case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft)

                break

            case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight)

                break

            case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown)

                break

            default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)

                break
            }
        }
    }
}

0

这是我在Swift 4中使用的解决方案。

它很简短,对我非常有效。

open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    let videoLayer = self.previewLayer
    coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext) in

        guard let connection = videoLayer?.connection, connection.isVideoOrientationSupported, let orientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue) else {
            return
        }

        connection.videoOrientation = orientation
        videoLayer?.frame = self.view.bounds

    }) { (context: UIViewControllerTransitionCoordinatorContext) in
        // handle any completion logic here...
    }
}

0
唯一在iOS 8到11.1版中没有任何问题的方法是这样做,我应该提到,在我的情况下,我只加载了我的应用程序在横向模式下,但它应该适用于所有方向。(顺便说一句,你可以通过图片视图或任何你想要的方式手动叠加相机,这样非常容易)
@interface ViewController (){
    AVCaptureVideoPreviewLayer * previewLayer;
    AVCaptureSession* session;
}
@property (weak, nonatomic) IBOutlet UIView *cameraPreviewView;


-(void)viewDidLoad{
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:nil];
    if (captureInput) {
        session = [[AVCaptureSession alloc] init];
        [session addInput:captureInput];
    }
    previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    [previewLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
    [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];

    CALayer *rootLayer = [self.cameraPreviewView layer];
    [rootLayer setMasksToBounds:YES];
    [previewLayer setFrame:[self.view bounds]];
    [rootLayer addSublayer:previewLayer];
    [session startRunning];

    //Orientation Code is in viewDidLayoutSubviews method
}
-(void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
    switch (orientation) {
        case UIInterfaceOrientationPortrait:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
            break;
        case UIInterfaceOrientationLandscapeLeft:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
            break;
        case UIInterfaceOrientationLandscapeRight:
            [previewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
            break;
        default:break;
    }
}

0

我在设置相机视图控制器时遇到了同样的问题。

让视图随布局旋转,然后在途中修复预览方向并不是最好的方法,原因有两个:首先,框架实际上会旋转,即使我们在途中修复预览方向,这感觉非常不自然。其次,由于相机物理上连接到设备并随之旋转,我们真正想要的是在纵向模式下固定预览的框架变换。显然,预览内容将自然地随着物理相机旋转。

当我尝试使用单独的视图控制器来阻止预览视图的界面旋转时,解决方案并没有起作用。

最终,我采用了苹果在此技术问答中提出的建议,并获得了成功:

“防止视图旋转” https://developer.apple.com/library/archive/qa/qa1890/_index.html

使用此方法,应用于预览的框架旋转动画被取消,而预览保持在原地,而其余界面则旋转以重新对齐方向。


最好能够嵌入在此处有效的代码,而不仅仅是一个链接。有时链接的内容会被更改或删除,这将使其在未来变得无用。 - TylerJames

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接