我正在使用AVFoundation框架开发适用于iOS的相机应用程序。它有2种拍摄选项,分别是RAW(DNG)或深度模式。除了我首先制作原始图像然后制作深度图像的场景外,它都可以正常工作。预览布局冻结之后(无论如何图片仍会保存到画廊中),当我玩负责切换到深度模式的切换时,XCode的控制台显示出captureSessionIsMissing被抛出。仅当以这种顺序拍摄照片时才会发生这种情况,因此仅在模式之间切换不会产生这种效果。
我还发现,如果将toggleDepthCapture()函数中的cameraController.switchCameraDevice(to:.rearDual)更改为.rearWide,它可以很好地工作,但在这种情况下,我需要使用双摄像头。
编辑:
中的if let depthData = photo.depthData
(depthData为nil)之后,代码停止执行
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?)
在iPhone 8 Plus和iPhone X上进行测试
XCode版本10.0 beta 6
部署目标11.4
ViewController.swift
@IBAction func toggleRawCapture(_ sender: UISwitch) {
//raw capture is only allowed in rear camera mode
if let position = cameraController.currentCameraPosition,
position == .rear,
sender.isOn, cameraController.rawCaptureMode == false {
//if depth mode is on, first disable it
if toggleDepthCaptureSwitch.isOn {
toggleDepthCaptureSwitch.setOn(false, animated: true)
}
do {
try cameraController.switchCameraDevice(to: .rearWide)
}catch(let error) {print(error)}
cameraController.rawCaptureMode = true
cameraController.depthMode = false
}else {
toggleRawCaptureSwitch.setOn(false, animated: true)
cameraController.rawCaptureMode = false
}
}
@IBAction func toggleDepthCapture(_ sender: UISwitch) {
if sender.isOn, cameraController.depthMode == false {
//if raw mode is on, first disable it
if toggleRawCaptureSwitch.isOn {
toggleRawCaptureSwitch.setOn(false, animated: true)
}
//check the position of the camera (rear or front)
if let position = cameraController.currentCameraPosition {
if position == .rear {
do {
// Allow rear depth capturing on iPhone 7 Plus, 8 Plus and X models only
switch UIDevice().modelName {
//
case "iPhone 7 Plus", "iPhone 8 Plus", "iPhone X": try cameraController.switchCameraDevice(to: .rearDual)
default:
//try cameraController.switchCameraDevice(to: .rearWide)
let alert = UIAlertController(title: "Warning!", message: "Operation not available (only on iPhone 7 Plus, 8 Plus and X)", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Got it", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
} catch(let error) {print("Rear error: \(error)")}
} else{
do {
// Allow front depth capturing on iPhone X only
if case UIDevice().modelName = "iPhone X"
{
try cameraController.switchCameraDevice(to: .frontTrueDepth)
} else {
let alert = UIAlertController(title: "Warning!", message: "Operation not available (only on iPhone X)", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Got it", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)}
}catch(let error) {print("Front error: \(error)")}
}
}
cameraController.depthMode = true
cameraController.rawCaptureMode = false
}else {
//check the position of camera (rear or front)
if let position = cameraController.currentCameraPosition {
if position == .rear {
do {
try cameraController.switchCameraDevice(to: .rearWide)
}catch(let error) {print(error)}
} else{
do {
try cameraController.switchCameraDevice(to: .frontWide)
}catch(let error) {print(error)}
}
}
cameraController.depthMode = false
}
}
CameraController.swift
func switchCameraDevice(to cameraDevice: CameraDevice) throws {
guard let currentCameraDevice = currentCameraDevice, let captureSession = self.captureSession, captureSession.isRunning else {
throw CameraControllerError.captureSessionIsMissing
}
captureSession.beginConfiguration()
func switchToRearDualCamera() throws {
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
let rearDualCamera = self.rearDualCamera else { throw CameraControllerError.invalidOperation}
self.rearCameraInput = try AVCaptureDeviceInput(device: rearDualCamera)
captureSession.removeInput(rearCameraInput)
if captureSession.canAddInput(self.rearCameraInput!) {
captureSession.addInput(self.rearCameraInput!)
self.currentCameraDevice = .rearDual
self.photoOutput?.isDepthDataDeliveryEnabled = true
}else { throw CameraControllerError.invalidOperation}
}
func switchToRearWideCamera() throws {
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
let rearWideCamera = self.rearCamera else { throw CameraControllerError.invalidOperation}
self.rearCameraInput = try AVCaptureDeviceInput(device: rearWideCamera)
captureSession.removeInput(rearCameraInput)
if captureSession.canAddInput(self.rearCameraInput!) {
captureSession.addInput(self.rearCameraInput!)
self.currentCameraDevice = .rearWide
self.photoOutput?.isDepthDataDeliveryEnabled = false
}else { throw CameraControllerError.invalidOperation}
}
func switchToFrontTrueDepthCamera() throws {
guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
let trueDepthCamera = self.frontTrueDepthCamera else { throw CameraControllerError.invalidOperation}
self.frontCameraInput = try AVCaptureDeviceInput(device: trueDepthCamera)
captureSession.removeInput(frontCameraInput)
if captureSession.canAddInput(self.frontCameraInput!) {
captureSession.addInput(self.frontCameraInput!)
self.currentCameraDevice = .frontTrueDepth
self.photoOutput?.isDepthDataDeliveryEnabled = true
}else { throw CameraControllerError.invalidOperation}
}
func switchToFrontWideCamera() throws {
guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
let frontWideCamera = self.frontCamera else { throw CameraControllerError.invalidOperation}
self.frontCameraInput = try AVCaptureDeviceInput(device: frontWideCamera)
captureSession.removeInput(frontCameraInput)
if captureSession.canAddInput(self.frontCameraInput!) {
captureSession.addInput(self.frontCameraInput!)
self.currentCameraDevice = .frontWide
self.photoOutput?.isDepthDataDeliveryEnabled = false
} else { throw CameraControllerError.invalidOperation}
}
//todo: complete implementation
func switchToRearTelephotoCamera() throws {
}
switch cameraDevice {
case .rearWide:
try switchToRearWideCamera()
case .rearDual:
try switchToRearDualCamera()
case .frontWide:
try switchToFrontWideCamera()
case .frontTrueDepth:
try switchToFrontTrueDepthCamera()
case .rearTelephoto:
try switchToRearTelephotoCamera()
}
captureSession.commitConfiguration()
}
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
guard let captureSession = captureSession, captureSession.isRunning else {
completion(nil, CameraControllerError.captureSessionIsMissing);
return
}
var photoSettings: AVCapturePhotoSettings
if let availableRawFormat = self.photoOutput?.availableRawPhotoPixelFormatTypes.first, self.rawCaptureMode{
photoSettings = AVCapturePhotoSettings(rawPixelFormatType: availableRawFormat,
processedFormat: [AVVideoCodecKey : AVVideoCodecType.jpeg])
// RAW capture is incompatible with digital image stabilization.
photoSettings.isAutoStillImageStabilizationEnabled = false
}
// else if self.photoOutput?.availablePhotoCodecTypes.contains(AVVideoCodecType.hevc) != nil {
// photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])
// }
else{
photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
}
photoSettings.flashMode = self.flashMode
if let depthEnabled = self.photoOutput?.isDepthDataDeliverySupported, self.depthMode {
photoSettings.isDepthDataDeliveryEnabled = depthEnabled
photoSettings.embedsDepthDataInPhoto = true
photoSettings.isDepthDataFiltered = true
}
self.photoOutput?.capturePhoto(with: photoSettings, delegate: self)
self.photoCaptureCompletionBlock = completion
}
}
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
if let error = error {
self.photoCaptureCompletionBlock?(nil, error)
}else if photo.isRawPhoto{
// Save the RAW (DNG) file data to a URL.
rawImageFileURL = self.makeUniqueTempFileURL(extension: "dng")
do {
try photo.fileDataRepresentation()!.write(to: rawImageFileURL!)
} catch {
fatalError("couldn't write DNG file to URL")
}
}else if let imageData = photo.fileDataRepresentation(){
self.compressedFileData = imageData
if self.depthMode{
if let depthData = photo.depthData{
saveDepthData(depth: depthData)
// Create a depthmap image
let context = CIContext()
let depthDataMap = depthData.converting(toDepthDataType: kCVPixelFormatType_DepthFloat32).depthDataMap
let ciImage = CIImage(cvPixelBuffer: depthDataMap)
let cgImage = context.createCGImage(ciImage, from: ciImage.extent)!
let imageOrientation: UIImageOrientation
switch currentOrientation {
case .portrait: imageOrientation = .right
case .portraitUpsideDown: imageOrientation = .left
case .landscapeLeft: imageOrientation = .down
default: imageOrientation = .up
}
let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: imageOrientation)
self.photoCaptureCompletionBlock?(uiImage, nil)
}
}
}else{
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
}
}
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings,
error: Error?) {
if let error = error {
print("Error capturing photo: \(error)");
}
guard let compressedData = self.compressedFileData else {return}
PHPhotoLibrary.shared().performChanges({
// Add the compressed (JPEG/HEIF) data as the main resource for the Photos asset.
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: compressedData, options: nil)
if self.rawCaptureMode{
// Add the RAW (DNG) file as an altenate resource.
let options = PHAssetResourceCreationOptions()
options.shouldMoveFile = true
creationRequest.addResource(with: .alternatePhoto, fileURL: self.rawImageFileURL!, options: options)
}
}, completionHandler:{(_, error) in
if let error = error {
print("Error occurred while saving photo to photo library: \(error)")
}
})
}
enum CameraControllerError: Swift.Error {
case captureSessionAlreadyRunning
case captureSessionIsMissing
case inputsAreInvalid
case invalidOperation
case noCamerasAvailable
case unknown
}