双击或使用按钮从后置摄像头切换到前置摄像头(Xcode 8,Swift 3)

12

最近我一直在尝试在Swift 3中实现切换摄像头视图的功能,但是一直没有成功。

目前,我的默认视图是从后置摄像头拍摄照片,可以重拍。但是,有人能否帮助我并展示如何双击屏幕以切换摄像头或者使用指定的按钮来切换?谢谢!

import UIKit
import AVFoundation
import FirebaseDatabase

class CameraView: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

override var prefersStatusBarHidden: Bool {
    return true
}

var captureSession : AVCaptureSession!
var stillImageOutput : AVCaptureStillImageOutput!
var previewLayer : AVCaptureVideoPreviewLayer!

@IBOutlet var cameraView: UIView!
override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

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

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    previewLayer?.frame = cameraView.bounds
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    captureSession = AVCaptureSession()
    captureSession?.sessionPreset = AVCaptureSessionPreset1920x1080

    var backCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    var error : NSError?

    do {
        var input = try! AVCaptureDeviceInput(device: backCamera)
        if (error == nil && captureSession?.canAddInput(input) != nil) {

            captureSession?.addInput(input)

            stillImageOutput = AVCaptureStillImageOutput()
            stillImageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]

            if (captureSession?.canAddOutput(stillImageOutput) != nil) {

                captureSession?.addOutput(stillImageOutput)

                previewLayer = AVCaptureVideoPreviewLayer (session: captureSession)
                previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
                previewLayer.connection.videoOrientation = AVCaptureVideoOrientation.portrait
                cameraView.layer.addSublayer(previewLayer)
                captureSession?.startRunning() }
        }

    } catch {

    }
}

@IBOutlet var tempImageView: UIImageView!
@IBAction func didPressTakePhoto(_ sender: UIButton) {

    if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {

        videoConnection.videoOrientation = AVCaptureVideoOrientation.portrait
        stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {

            (sampleBuffer, error) in
            if sampleBuffer != nil {

                var imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
                var dataProvider = CGDataProvider.init(data: imageData as! CFData)
                var cgImageRef = CGImage.init(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent)

                var image = UIImage (cgImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.right)

                self.tempImageView.image = image
                self.tempImageView.isHidden = false

            }

        })

    }
}

var didTakePhoto = Bool()

@IBAction func didPressTakeAnother(_ sender: UIButton) {

    if didTakePhoto == true {

        tempImageView.isHidden = true
        didTakePhoto = false

    } else {

        captureSession?.startRunning()
        didTakePhoto = true

    }
}
}
3个回答

18

我这里没有发现任何问题 - 这是可行的解决方案:

import Foundation
import UIKit
import AVFoundation

class MainViewController: UIViewController {

    var tempImage: UIImageView?

    var captureSession: AVCaptureSession?
    var stillImageOutput: AVCaptureStillImageOutput?
    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
    var currentCaptureDevice: AVCaptureDevice?

    var usingFrontCamera = false


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

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        videoPreviewLayer!.frame = self.cameraPreviewSurface.bounds
    }

    @IBAction func switchButtonAction(_ sender: Any) {
        usingFrontCamera = !usingFrontCamera
        loadCamera()
    }


    func getFrontCamera() -> AVCaptureDevice?{
        let videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)


        for device in videoDevices!{
            let device = device as! AVCaptureDevice
            if device.position == AVCaptureDevicePosition.front {
                return device
            }
        }
        return nil
    }

    func getBackCamera() -> AVCaptureDevice{
        return AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    }



    func loadCamera() {
        if(captureSession == nil){
            captureSession = AVCaptureSession()
            captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
        }
        var error: NSError?
        var input: AVCaptureDeviceInput!

        currentCaptureDevice = (usingFrontCamera ? getFrontCamera() : getBackCamera())

        do {
            input = try AVCaptureDeviceInput(device: currentCaptureDevice)
        } catch let error1 as NSError {
            error = error1
            input = nil
            print(error!.localizedDescription)
        }

        for i : AVCaptureDeviceInput in (self.captureSession?.inputs as! [AVCaptureDeviceInput]){
            self.captureSession?.removeInput(i)
        }
        if error == nil && captureSession!.canAddInput(input) {
            captureSession!.addInput(input)
            stillImageOutput = AVCaptureStillImageOutput()
            stillImageOutput?.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
            if captureSession!.canAddOutput(stillImageOutput) {
                captureSession!.addOutput(stillImageOutput)
                videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
                videoPreviewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
                videoPreviewLayer!.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
                //self.cameraPreviewSurface.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
                self.cameraPreviewSurface.layer.addSublayer(videoPreviewLayer!)
                DispatchQueue.main.async {
                    self.captureSession!.startRunning()
                }


            }
        }



    }
}

一些注释:

cameraPreviewSurface - 这是您的UIView,相机将在其中显示

不要重新分配会话,不要仅添加输入,而是在添加新输入之前删除现有输入

p.s. 代码使用Swift 3.0.1 / Xcode 8.1完成

干杯 )


1
谢谢您提供的示例。然而,这种方法破坏了我从视频流中捕获静态帧的方式。请问如何从这段代码中获取一帧图像?如果需要的话,我可以发布一个新问题。 - MattEm
使用以下代码:if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {它总是返回false..为什么? - MattEm
这个方法失败了,错误提示为if captureSession!.canAddOutput(stillImageOutput)找到了nil值,原因是什么? - MattEm
在长时间的搜索之后,我从这里得到了更好的解决方案...谢谢@Stepan Maksymov。 - Hari Narayanan
这里他忘记添加视频数据输出的代理了let dataOutput = AVCaptureVideoDataOutput() dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString):NSNumber(value:kCVPixelFormatType_32BGRA)] dataOutput.alwaysDiscardsLateVideoFrames = true if captureSession.canAddOutput(dataOutput) { captureSession.addOutput(dataOutput) }let queue = DispatchQueue(label: "VideoDataOutputQueue") dataOutput.setSampleBufferDelegate(self, queue: queue) - Hari Narayanan

4

Xcode版本: 10.1版本 (10B61)
Swift版本: Swift 4.2
更换AVCaptureSession捕获源

来自Stepan Maksymov的解决方案
我们可以简化操作,通过替换captureSession.inputs

首先创建一个IBAction Outlet并将ViewController连接到更改相机视图

@IBAction private func changeCamera(_ cameraButton: UIButton) {
    usingFrontCamera = !usingFrontCamera
    do{
        captureSession.removeInput(captureSession.inputs.first!)

        if(usingFrontCamera){
            captureDevice = getFrontCamera()
        }else{
            captureDevice = getBackCamera()
        }
        let captureDeviceInput1 = try AVCaptureDeviceInput(device: captureDevice)
        captureSession.addInput(captureDeviceInput1)
    }catch{
        print(error.localizedDescription)
    }
}

第二步:复制简化版的AVCaptureDevice设置 || 参考Stepan Maksymov
func getFrontCamera() -> AVCaptureDevice?{
    return AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .front).devices.first
    return nil
}

func getBackCamera() -> AVCaptureDevice?{
    return AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first
    return nil
}

您可以将 @IBAction 替换为函数,如下所示。
func changeCamera(){
    usingFrontCamera = !usingFrontCamera
    do{
        captureSession.removeInput(captureSession.inputs.first!)

        if(usingFrontCamera){
            captureDevice = getFrontCamera()
        }else{
            captureDevice = getBackCamera()
        }
        let captureDeviceInput1 = try AVCaptureDeviceInput(device: captureDevice)
        captureSession.addInput(captureDeviceInput1)
    }catch{
        print(error.localizedDescription)
    }
}

2

除了Stepan Maksymov发布的文章之外,我建议添加这个函数。

func stopCaptureSession () {
    self.captureSession.stopRunning()

    if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
        for input in inputs {
            self.captureSession.removeInput(input)
        }
    }

}

并且调用它而不是他的发布行:

for i : AVCaptureDeviceInput in (self.captureSession?.inputs as! [AVCaptureDeviceInput]){
    self.captureSession?.removeInput(i)
}

这样摄像头就会更快地切换。


如果让输入等于captureSession.inputs as? [AVCaptureDeviceInput] { for input in inputs { self.captureSession.removeInput(input) } }这段代码非常重要。感谢您提出这个好点子。 - Mehdi Gilanpour

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