Я работаю над приложением камеры для iOS с использованием фреймворка AVFoundation. Имеется 2 варианта захвата: в RAW (DNG) или в режиме глубины. Он работает хорошо, за исключением сценария, когда я сначала делаю необработанное изображение, а затем глубину. После этого макет предварительного просмотра зависает (хотя картинка все равно сохраняется в галерее), и когда я играю с переключателем, который отвечает за переключение в режим глубины, консоль XCode показывает, что captureSessionIsMissing выбрасывается. Это происходит только тогда, когда снимок сделан в этой последовательности, поэтому простое переключение между режимами не дает такого эффекта.
Я также понял, что если параметр cameraController.switchCameraDevice (to: .rearDual) внутри функции toggleDepthCapture () изменяется на .rearWide, он работает нормально, но в этом случае мне нужно работать с двойной камерой.
РЕДАКТИРОВАТЬ: код перестает выполняться после if let depthData = photo.depthData
(глубина данных равна нулю) в
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?)
Тестирование на iPhone 8 Plus и iPhone X
XCode Version 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
}