应用程序在切换到前置摄像头时崩溃

App crashes on switching to front camera

我正在创建一个类似于相机应用程序的 snapchat,以便在屏幕中双击时切换相机。代码编译没有错误,但是当我双击屏幕进入前置摄像头时出现错误。这是错误:

2018-03-30 18:44:46.717257+0530 Camera[369:36651] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[AVCaptureSession addOutput:] Cannot add output to capture session -> because more than one output of the same type is unsupported' *** First throw call stack: (0x18564ed8c 0x1848085ec 0x18b13ca74 0x104b3c140 0x104b3b75c 0x104b3b794 0x18f3fe750 0x18f96b2a4 0x18f560e6c 0x18f3fd7a8 0x18f95cac4 0x18f3f7540 0x18f3f7078 0x18f3f68dc 0x18f3f5238 0x18fbd6c0c 0x18fbd91b8 0x18fbd2258 0x1855f7404 0x1855f6c2c 0x1855f479c 0x185514da8 0x1874f7020 0x18f4f578c 0x104b40ca8 0x184fa5fc0) libc++abi.dylib: terminating with uncaught exception of type NSException

这是我的 ViewController 文件:

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {

@IBOutlet weak var previewView: UIView!
override var prefersStatusBarHidden: Bool { return true }
var session = AVCaptureSession()
var device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
var frontCameraMode = false
var photoOutput : AVCapturePhotoOutput?

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    loadCamera()
    let tap = UITapGestureRecognizer(target: self, action: #selector(swapCamera))
    tap.numberOfTapsRequired = 2
    previewView.addGestureRecognizer(tap)
}

@objc func swapCamera() {
    if(frontCameraMode == true){
        frontCameraMode = false
    }
    else if(frontCameraMode == false){
        frontCameraMode = true
    }
    loadCamera()
}

func loadCamera() {

    if(frontCameraMode == false){
        var device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
    }
    else if (frontCameraMode == true){
        var device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
    }
    do{
        var deviceInput = try AVCaptureDeviceInput(device: device!)
        print(deviceInput)
        session.sessionPreset = AVCaptureSession.Preset.high
        /*if let inputs = session.inputs as? [AVCaptureDeviceInput] {
            for input in inputs {
                session.removeInput(input)
            }
        }*/

        if session.inputs.isEmpty {
            session.addInput(deviceInput)
        }

        var previewLayer = AVCaptureVideoPreviewLayer(session: session)

        previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        previewLayer.frame = view.layer.bounds

        previewView.layer.addSublayer(previewLayer)

        session.startRunning()

        photoOutput = AVCapturePhotoOutput()
        session.addOutput(photoOutput!)
    }
    catch{
        print(error)
    }
}

@IBAction func onShootButtonTap(_ sender: Any) {
    // Make sure capturePhotoOutput is valid
    guard let photoOutput = self.photoOutput else { return }
    // Get an instance of AVCapturePhotoSettings class

    let photoSettings = AVCapturePhotoSettings()
    // Set photo settings for our need

    photoSettings.isAutoStillImageStabilizationEnabled = true

    //photoSettings.isHighResolutionPhotoEnabled = true
    photoSettings.flashMode = .auto
    // Call capturePhoto method by passing our photo settings and a
    // delegate implementing AVCapturePhotoCaptureDelegate

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

}


@available(iOS 11.0, *)
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    let imageData = photo.fileDataRepresentation()

    let capturedImage = UIImage.init(data: imageData! , scale: 1.0)
    if let image = capturedImage {
        // Save our captured image to photos album
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}

确保在请求相机访问权限时为 Info.plist 文件中的 Privacy - Camera Usage Description 键设置值。

您需要在添加输入和输出之前检查会话的 canAddInput 和 canAddOutput

session.beginConfiguration()
session.sessionPreset = AVCaptureSession.Preset.photo
let cameraPosition: AVCaptureDevice.Position = self.configuration.usesFrontCamera ? .front : .back
let aDevice = deviceForPosition(cameraPosition)
if let d = aDevice {
   videoInput = try? AVCaptureDeviceInput(device: d)
}
if let videoInput = videoInput {
   if session.canAddInput(videoInput) {
      session.addInput(videoInput)
   }
   if session.canAddOutput(imageOutput) {
      session.addOutput(imageOutput)
   }
}
session.commitConfiguration()

//A helper function for device position
func deviceForPosition(_ p: AVCaptureDevice.Position) -> AVCaptureDevice? {
    for device in AVCaptureDevice.devices(for: AVMediaType.video) where device.position == p {
        return device
    }
    return nil
}

我建议您不要重新发明轮子,而是使用此资源 - https://github.com/Yummypets/YPImagePicker

每次按下交换按钮时,都会使用已使用的相机会话创建新的相机预览层,这会导致崩溃。修复:

为当前设备输入声明新变量

var deviceInput: AVCaptureDeviceInput?

更改您的 swapCamera 函数以更改设备输入 back/front 并将输入保存在上面声明的变量中,以便在第二次交换中删除。

@objc func swapCamera() {
    frontCameraMode = !frontCameraMode
    let newDeviceInput = try! AVCaptureDeviceInput(device: device!)

    session.beginConfiguration()
    if(frontCameraMode == false){
        device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
        session.removeInput(deviceInput!)
        session.addInput(newDeviceInput)
    }
    else if (frontCameraMode == true){
        device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
        session.removeInput(deviceInput!)
        session.addInput(newDeviceInput)
    }
    session.commitConfiguration();

    deviceInput = newDeviceInput
}

移除 loadCamera 函数中的局部 let 变量

改变

let deviceInput = try AVCaptureDeviceInput(device: device!)

deviceInput = try AVCaptureDeviceInput(device: device!)