美文网首页
使用 AVFoundation 实现自定义相机拍照

使用 AVFoundation 实现自定义相机拍照

作者: l蓝色梦幻 | 来源:发表于2018-06-29 11:38 被阅读67次

    使用摄像头拍照

    使用 AVFoundation 拍照需要至少使用以下类:

        private(set) var session = AVCaptureSession()
        private(set) var device: AVCaptureDevice?
        private(set) var input: AVCaptureDeviceInput?
        private(set) var imageOutput: AVCapturePhotoOutput?
        private(set) var previewLayer: AVCaptureVideoPreviewLayer?
    

    其中, AVCaptureSession 用于视频捕获,AVCaptureDevice用于管理设备,AVCaptureDeviceInput是当前工作的摄像头(不限于摄像头),AVCapturePhotoOutput是用来输出图像,AVCaptureVideoPreviewLayer用于显示当前捕捉到的视频。

    开始捕捉视频图像

        // 照相功能初始化
        private func setupCaptureSession() {
            // 判断是否初始化完成
            var successful = true
            defer {
                if !successful {
                    // log error msg...
                    print("error setting capture session")
                }
            }
            
            // 判断至少有一个摄像头,并对涉嫌头初始化
            guard let d = AVCaptureDevice.default(for: .video), let device = tunedCaptureDevice(device: d) else {
                successful = false
                return
            }
            
            // 配置 session 
            session.beginConfiguration()
            defer {
                session.commitConfiguration()
            }
            
            session.sessionPreset = .photo
            
            do {
                input = try AVCaptureDeviceInput(device: device)
                if session.canAddInput(input!) {
                    session.addInput(input!)
                } else {
                    successful = false
                    return
                }
            } catch {
                print(error.localizedDescription)
                successful = false
                return
            }
            
            imageOutput = AVCapturePhotoOutput()
            let settings = AVCapturePhotoSettings.init(format: [AVVideoCodecKey: AVVideoCodecJPEG])
            imageOutput?.setPreparedPhotoSettingsArray([settings]) { (a, e) in
                print(e?.localizedDescription ?? "")
            }
            
            if session.canAddOutput(imageOutput!) {
                session.addOutput(imageOutput!)
            } else {
                successful = false
                return
            }
            
            // 图像显示 layer 配置
            previewLayer = AVCaptureVideoPreviewLayer(session: session)
            previewLayer!.videoGravity = .resizeAspectFill
            previewLayer!.frame = bounds
            self.layer.insertSublayer(previewLayer!, at: 0)
        }
        
        // 摄像头参数配置
        private func tunedCaptureDevice(device: AVCaptureDevice) -> AVCaptureDevice? {
            do {
                try device.lockForConfiguration()
                if device.isFocusModeSupported(.continuousAutoFocus) {
                    device.focusMode = .continuousAutoFocus
                }
                if device.isExposureModeSupported(.continuousAutoExposure) {
                    device.exposureMode = .continuousAutoExposure
                }
                if device.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) {
                    device.whiteBalanceMode = .continuousAutoWhiteBalance
                }
                device.unlockForConfiguration()
                return device
            } catch {
                print(error.localizedDescription)
                return nil
            }
        }
    

    关于捕捉到的图像与设备之前的横竖屏切换的设置

    捕捉到的图像并不是根据设备自动切换横竖屏的,因此需要在代码里面手动的设置捕捉到的图像的状态。

        // 根据当前设备的状态计算捕捉到的图像的状态
        var videoOrientationFromCurrentDeviceOrientation: AVCaptureVideoOrientation? {
            get {
                switch UIDevice.current.orientation {
                case .landscapeLeft:
                    return .landscapeRight
                case .landscapeRight:
                    return .landscapeLeft
    //            case .portraitUpsideDown:
    //                return .portraitUpsideDown
                case .portrait:
                    return .portrait
                default:
                    return nil
                }
            }
        }
        
        // 切换图像横竖屏状态
        func updateOrientation() {
            guard let v = videoOrientationFromCurrentDeviceOrientation else {
                return
            }
            previewLayer?.connection?.videoOrientation = v
            imageOutput?.connection(with: .video)?.videoOrientation = v
        }
    

    前后摄像头切换

    获取摄像头

        // 当前设备支持的摄像头
        var cameras: [AVCaptureDevice] {
            get {
                let s = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: .video, position: .unspecified)
                return s.devices
            }
        }
    
        // 根据摄像头位置获取前后摄像头
        private func camera(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
            let s = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: .video, position: position)
            
            for d in s.devices {
                if d.position == position {
                    return tunedCaptureDevice(device: d)
                }
            }
            return nil
        }
    

    切换摄像头

        func changeCamera() {
            if cameras.count > 1 {
                let animation = CATransition()
                animation.duration = 0.5
                animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
                animation.type = "oglFlip"
                
                var newCamera: AVCaptureDevice?
                
                let position = input?.device.position
                if position == .back {
                    newCamera = camera(position: .front)
                    animation.subtype = kCATransitionFromLeft
                } else {
                    newCamera = camera(position: .back)
                    animation.subtype = kCATransitionFromRight
                }
                
                guard let ca = newCamera, let newInput = try? AVCaptureDeviceInput(device: ca) else {
                    return
                }
                
                OnMainThreadAsync {
                    self.previewLayer?.add(animation, forKey: nil)
                    self.session.beginConfiguration()
                    defer {
                        self.session.commitConfiguration()
                    }
                    if self.input != nil {
                        self.session.removeInput(self.input!)
                    }
                        
                    if self.session.canAddInput(newInput) {
                        self.session.addInput(newInput)
                        self.input = newInput
                    } else {
                        if self.input != nil {
                            self.session.addInput(self.input!)
                        }
                        return
                    }
                }
            }
        }
    

    拍照

    func captureImage() {
            OnMainThreadAsync {
                if let output = self.session.outputs.first as? AVCapturePhotoOutput {
                    output.isHighResolutionCaptureEnabled = true
                    let settings = AVCapturePhotoSettings()
                    settings.isHighResolutionPhotoEnabled = true
    //                settings.flashMode = AVCaptureDevice.FlashMode.auto
                    let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first!
                    let previewFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelType,
                                         kCVPixelBufferWidthKey as String: 160,
                                         kCVPixelBufferHeightKey as String: 160,
                                         ]
                    settings.previewPhotoFormat = previewFormat
                    if output.supportedFlashModes.contains(AVCaptureDevice.FlashMode.auto) {
                        settings.flashMode = AVCaptureDevice.FlashMode.auto
                    } else if output.supportedFlashModes.contains(AVCaptureDevice.FlashMode.on) {
                        settings.flashMode = AVCaptureDevice.FlashMode.on
                    } else if output.supportedFlashModes.contains(AVCaptureDevice.FlashMode.off) {
                        settings.flashMode = AVCaptureDevice.FlashMode.off
                    }
                    
                    output.capturePhoto(with: settings, delegate: self)
                }
            }
        }
        
        // AVCapturePhotoCaptureDelegate
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
            
            guard let didFinishProcessingPhoto = photoSampleBuffer, let previewPhoto = previewPhotoSampleBuffer else {
                self.delegate?.ErrorOnTakePhoto(view: self, error: error)
               return
            }
            
            guard let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: didFinishProcessingPhoto, previewPhotoSampleBuffer: previewPhoto) else {
                self.delegate?.ErrorOnTakePhoto(view: self, error: error)
                return
            }
            guard let image = UIImage(data: data) else {
                self.delegate?.ErrorOnTakePhoto(view: self, error: error)
                return
            }
            self.delegate?.ImageDidSelected(view: self, image: image)
            self.session.stopRunning()
        }
    

    遇到的问题。

    1. 拍照的时候概率性的出现 Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x17025db20 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16800), NSLocalizedDescription=The operation could not be completed} 这个错误。这个错误引起的原因是 output.capturePhoto 之后立即执行 session.stopRunning() 了,造成同一个 frametime 有n帧图片,这样就会报错。

      解决方法:
      在代理中停止 session。

    2. 横竖屏设置的时候图像横屏的时候总是反的。原因在于 AVCaptureVideoOrientation 的 landscapeRight 对应的是 UIDeviceOrientation 的 landscapeLeft。Right 对应的是 Left。

    相关文章

      网友评论

          本文标题:使用 AVFoundation 实现自定义相机拍照

          本文链接:https://www.haomeiwen.com/subject/nptayftx.html