在拍摄照片之前切换 AVCaptureSessionPreset

Switching AVCaptureSessionPreset before capturing photo

我正在制作实时相机滤镜应用。

我使用 AVCaptureVideoDataOutput 将 samplebuffer 传递给 MTKView 用于渲染预览和 AVCapturePhotoOutput 用于捕获照片。

而且我的应用程序有一些用于拍摄照片的纵横比选项。 (包括 16:9 和 4:3)

我想做的是以全屏大小进行预览(16:9,但更接近这个大小也可以),尽管用户 select 4:3 选项.

我打算在预览中显示边框线,以便用户可以计算出照片输出大小。

我需要 16:9 预设选项,如 1280*720 用于预览和照片预设选项用于拍摄照片。

我想出了一些点子。

  1. 有两个具有不同预设的 AVCaptureSessions --> 不利于性能

  2. 使用 1280*720 预设拍摄并将照片输出裁剪为 4:3 纵横比 --> 低分辨率照片

  3. 在调用 photoOutput.capturePhoto 方法之前切换预设。 --> 预览瞬间冻结,因为 AVCaptureSession 必须更新

我决定使用 3,但它给了我一个错误。

(如果有更好的方法,请告诉我)

这是我的代码。

@IBAction func takePhoto(_ sender: UIButton) {
    captureSessionQueue.async {
        var photoSettings = AVCapturePhotoSettings()
        photoSettings.isHighResolutionPhotoEnabled = true

        // switch preset from .hd1280*720 to .photo
        self.session.beginConfiguration()
        if self.session.canSetSessionPreset(.photo) {
            self.session.sessionPreset = .photo
        }
        self.session.commitConfiguration()

        self.photoOutput.capturePhoto(with: photoSettings, delegate: self)

        self.session.beginConfiguration()
        self.session.sessionPreset = .hd1280*720
        self.session.commitConfiguration()
    }
}

错误是,

Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-16800), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x280013720 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}}

我认为这是因为我在会话完成对新预设的更新之前调用了 capturePhoto 方法。

当我在 commitConfiguration() 后 1 或 2 秒调用 self.photoOutput.capturePhoto 方法时,它起作用了。

那么,有什么方法可以让我知道 AVCaptureSession 更新的完成,或者有更好的解决方案来处理视频数据输出和照片输出之间的不同宽高比吗?

为视频和照片输出不同宽高比的最佳方式是在 AVCapturePhotoCaptureDelegate 委托中处理照片图像数据。您可以执行以下操作。在 didFinishProcessingPhoto 函数中,您裁剪图像并从中创建 jpeg 数据。您还需要将照片元数据复制到此 jpeg 数据中。然后在 didFinishCaptureFor 函数中,您可以将此数据保存到图片库。

class MyPhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        // crop image
        if let cgImage = photo.cgImageRepresentation()?.takeUnretainedValue() {
            let cropRect : CGRect = calculateAspectRatioCrop(cgImage:cgImage, aspectRatio:16.0/9.0)
            if let cgCroppedImage = cgImage.cropping(to:cropRect) {
                let image = UIImage(cgImage:cgCroppedImage)
                if let photoData = image.jpegData(compressionQuality: 0.9) {
                    // add meta data from original image to cropped image data
                    self.photoData = addImageProperties(imageData: photoData, properties: photo.metadata as NSDictionary)
                }
            }
        }
    }

    func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
        if let error = error {
            print("Error capturing photo: \(error)")
            return
        }

        guard let photoData = photoData else {
            print("No photo data resource")
            return
        }
        savePhotoData()
    }

    func calculateAspectRatioCrop(cgImage : CGImage, aspectRatio: CGFloat) -> CGRect {
        var width = CGFloat(cgImage.width)
        var height = CGFloat(cgImage.height)
        // should be cropped vertically or horizontally?
        if aspectRatio > width/height {
            height = width / aspectRatio
        } else {
            width = height * aspectRatio
        }
        return CGRect(x: (CGFloat(cgImage.width) - width)/2, y: (CGFloat(cgImage.height) - height)/2, width: width, height: height)
    }

    func addImageProperties(imageData: Data, properties: NSDictionary?) -> Data? {
        // create an imagesourceref
        if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
            // this is of type image
            if let uti = CGImageSourceGetType(source) {
                // create a new data object and write the new image into it
                let destinationData = NSMutableData()
                if let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) {
                    // add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
                    CGImageDestinationAddImageFromSource(destination, source, 0, properties)
                    if CGImageDestinationFinalize(destination) == false {
                        return nil
                    }
                    return destinationData as Data
                }
            }
        }
        return nil
    }

    private var photoData : Data? = nil
}

我留给您填写 savePhotoData() 函数。