iOS 相机 didFinishProcessingPhoto 总是给出随机的错误方向

问题描述 投票:0回答:1

我创建了下面的代码示例,它重现了整个问题。

请注意,我在控制中心设置了 iOS“旋转锁定”。

在运行 iOS 17 的 iPhone 13 上进行测试。

imageViewForOutput
中设置的图像方向总是错误的。

当我以普通肖像拍摄照片时,图像会水平翻转(即右侧出现在左侧,左侧出现在右侧)。方向打印为 6。

当我将设备顺时针旋转 90 度并拍照时,正在设置的图像逆时针旋转 90 度。方向打印为 3。

当我将设备逆时针旋转 90 度拍照时,设置的图像是上下颠倒的。方向打印为 1。

我不知道是什么使方向发生如此随机的变化。我关注了其他堆栈溢出帖子,并确保我在

photoOutput
上设置方向更改。

import UIKit
import SnapKit
import AVFoundation

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {
    
    var clickerButton = UIButton()
    var flipCameraButton = UIButton()
    var frontCameraDeviceInput: AVCaptureDeviceInput?
    var backCameraDeviceInput: AVCaptureDeviceInput?
    let captureSession = AVCaptureSession()
    let photoOutput = AVCapturePhotoOutput()
    let viewForCameraPreview = UIView()
    var previewLayer : AVCaptureVideoPreviewLayer?
    var imageViewForOutput = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .black
        
        view.addSubview(viewForCameraPreview)
        viewForCameraPreview.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        let padding = 15.0
        
        let buttonSize = 70.0
        
        
        clickerButton.addTarget(self, action: #selector(clickPhoto(sender:)), for: .touchUpInside)
        view.addSubview(clickerButton)
        clickerButton.snp.makeConstraints { make in
            make.size.equalTo(buttonSize)
            make.centerX.equalToSuperview()
            make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).inset(padding)
        }
        clickerButton.layer.cornerRadius = buttonSize / 2
        clickerButton.clipsToBounds = true
        clickerButton.layer.borderColor = UIColor.white.cgColor
        clickerButton.layer.borderWidth = 5
        
        flipCameraButton.addTarget(self, action: #selector(flipCamera(sender:)), for: .touchUpInside)
        flipCameraButton.setTitle("F", for: .normal)
        flipCameraButton.titleLabel?.font = UIFont.systemFont(ofSize: 50, weight: .bold)
        flipCameraButton.setTitleColor(.white, for: .normal)
        view.addSubview(flipCameraButton)
        flipCameraButton.snp.makeConstraints { make in
            make.size.equalTo(buttonSize)
            make.right.equalTo(clickerButton.snp.left).inset(-padding)
            make.centerY.equalTo(clickerButton)
        }
        
        imageViewForOutput.layer.borderColor = UIColor.white.cgColor
        imageViewForOutput.layer.borderWidth = 2
        imageViewForOutput.contentMode = .scaleAspectFit
        view.addSubview(imageViewForOutput)
        imageViewForOutput.snp.makeConstraints { make in
            make.size.equalTo(200)
            make.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(padding)
            make.left.equalTo(view.safeAreaLayoutGuide.snp.left).offset(padding)
        }
        
        configureCameras()
        whichCameraStuff()
        startCamera()
    }
    
    @objc func flipCamera(sender: UIButton) {
        sender.isSelected = !sender.isSelected
        whichCameraStuff()
    }

    func configureCameras(){
        if let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
            frontCameraDeviceInput = try? AVCaptureDeviceInput(device: frontCamera)
        }
        if let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
            backCameraDeviceInput = try? AVCaptureDeviceInput(device: backCamera)
        }
        photoOutput.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecType.jpeg])], completionHandler: nil)
        if captureSession.canAddOutput(photoOutput) {
            captureSession.addOutput(photoOutput)
        }
    }
    
    func whichCameraStuff(){
        captureSession.beginConfiguration()
        
        if flipCameraButton.isSelected {
            if let b = backCameraDeviceInput, captureSession.inputs.contains(b) {
                captureSession.removeInput(b)
            }
            if let f = frontCameraDeviceInput, !captureSession.inputs.contains(f) {
                captureSession.addInput(f)
            }
        } else {
            if let f = frontCameraDeviceInput, captureSession.inputs.contains(f) {
                captureSession.removeInput(f)
            }
            if let b = backCameraDeviceInput, !captureSession.inputs.contains(b) {
                captureSession.addInput(b)
            }
        }
        
        captureSession.commitConfiguration()
    }
    
    func startCamera(){
        DispatchQueue.global().async {
            self.captureSession.startRunning()
            self.addCameraPreviewIfNeeded()
        }
    }
    
    func addCameraPreviewIfNeeded(){
        DispatchQueue.main.async {
            if self.previewLayer == nil {
                self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
                if let pl = self.previewLayer {
                    pl.videoGravity = AVLayerVideoGravity.resizeAspectFill
                    pl.connection?.automaticallyAdjustsVideoMirroring = false
                    self.viewForCameraPreview.layer.insertSublayer(pl, at: 0)
                    pl.frame = self.viewForCameraPreview.bounds
                    self.updateVideoOrientation()
                }
            }
            self.previewLayer?.isHidden = false
        }
    }
    
    func updateVideoOrientation() {
        guard let pl = self.previewLayer, let connection = pl.connection else {
            return
        }
        guard connection.isVideoOrientationSupported else {
            return
        }

        let videoOrientation = UIDevice.current.orientation.asCaptureVideoOrientation

        if connection.videoOrientation == videoOrientation {
            print("no change to videoOrientation")
            return
        }
        
        print("videoOrientation: \(videoOrientation)")
        previewLayer?.connection?.videoOrientation = videoOrientation
        photoOutput.connection(with: AVMediaType.video)?.videoOrientation = videoOrientation
        previewLayer?.frame = viewForCameraPreview.bounds
        previewLayer?.removeAllAnimations()
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        coordinator.animate(alongsideTransition: nil, completion: { [weak self] (context) in
            DispatchQueue.main.async(execute: {
                self?.updateVideoOrientation()
            })
        })
    }
    
    @objc func clickPhoto(sender: UIButton) {
        guard captureSession.isRunning else {
            return
        }
        let settings = AVCapturePhotoSettings()
        settings.flashMode = .off
        photoOutput.capturePhoto(with: settings, delegate: self)
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let e = error {
            print("Error: \(e)")
        } else if let cgImage = photo.cgImageRepresentation() {
            let orientation = photo.metadata[kCGImagePropertyOrientation as String] as! NSNumber
            let imageOrientation = UIImage.Orientation(rawValue: orientation.intValue)!
            print("orientation: \(orientation), imageOrientation: \(imageOrientation)")
            let image = UIImage(cgImage: cgImage, scale: 1, orientation: imageOrientation)
            imageViewForOutput.image = image
        }
    }
}

extension UIDeviceOrientation {
    var asCaptureVideoOrientation: AVCaptureVideoOrientation {
        switch self {
        case .landscapeLeft: return .landscapeRight
        case .landscapeRight: return .landscapeLeft
        case .portraitUpsideDown: return .portraitUpsideDown
        default: return .portrait
        }
    }
}
ios swift avfoundation device-orientation
1个回答
0
投票

我找到了解决办法。我在

photo.fileDataRepresentation()
中使用了
didFinishProcessingPhoto
,然后我根本不需要处理方向元数据。

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    if let e = error {
        print("Error: \(e)")
    } else if let photoData = photo.fileDataRepresentation(), let photoImage = UIImage(data: photoData) {
        imageViewForOutput.image = photoImage
    }
}

请注意,当发生方向变化时或至少在拍摄照片之前,必须确保存在以下内容。否则就行不通。对我来说,我之前已经在我的

updateVideoOrientation
函数中使用了它:

photoOutput.connection(with: AVMediaType.video)?.videoOrientation = videoOrientation
© www.soinside.com 2019 - 2024. All rights reserved.