見出し画像

AVFoundationを使用したRAW撮影機能の実装方法

AVFoundationを使用してRAW撮影機能を実装する方法についてまとめる。


RAW+JPEG (RAW+L)データの撮影

撮影設定

RAWデータの撮影設定は、AVCapturePhotoSettings を使用して行う。

captureSession.sessionPreset = .photo
var settingsForMonitoring = AVCapturePhotoSettings()
guard let availableRawFormat = photoOutput.availableRawPhotoPixelFormatTypes.first else { return settingsForMonitoring }

guard let processedPhotoCodecType = photoOutput.availablePhotoCodecTypes.first else { return settingsForMonitoring }
settingsForMonitoring = AVCapturePhotoSettings(rawPixelFormatType: availableRawFormat,
                                        processedFormat: [AVVideoCodecKey : processedPhotoCodecType])

guard let thumbnailPhotoCodecType = settingsForMonitoring.availableRawEmbeddedThumbnailPhotoCodecTypes.first else { return updateSettings }

let dimensions: CMVideoDimensions
if #available(iOS 16, *) {
    dimensions = photoOutput.maxPhotoDimensions
} else {
    dimensions = videoDevice.activeFormat.highResolutionStillImageDimensions
}

settingsForMonitoring.rawEmbeddedThumbnailPhotoFormat = [
    AVVideoCodecKey: thumbnailPhotoCodecType,
    AVVideoWidthKey: dimensions.width,
    AVVideoHeightKey: dimensions.height]

if let previewPixelFormat = settingsForMonitoring.availablePreviewPhotoPixelFormatTypes.first {
    settingsForMonitoring.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelFormat]
}

最初に、RAWデータの撮影を行うためのAVCapturePhotoSettings インスタンスを生成する。
キャプチャセッションのsessionPresetには、.photoを代入しておく必要がある。
AVCapturePhotoOutput のavailableRawPhotoPixelFormatTypes で、サポートしているRAWピクセルフォーマットを取得し、availablePhotoCodecTypes で、サポートしている圧縮コーデックを取得する。
AVCapturePhotoSettings のイニシャライザinit(rawPixelFormatType:processedFormat:)で、取得したRAWピクセルフォーマットと圧縮コーデックタイプを引数に指定して、AVCapturePhotoSettings インスタンスを生成する。

次に、RAWデータに埋め込むサムネイルデータの設定を行う。AVCapturePhotoSettings のavailableRawEmbeddedThumbnailPhotoCodecTypes で、サムネイルデータがサポートしている圧縮コーデックを取得する。
AVCapturePhotoOutput のmaxPhotoDimensions または、AVCaptureDevice.Format のhighResolutionStillImageDimensions で、サポートしている最大解像度を取得する。
AVCapturePhotoSettings のrawEmbeddedThumbnailPhotoFormat に、取得した圧縮コーデックタイプと解像度を辞書型で指定することで、サムネイルデータの設定を行う。

最後に、プレビュー画像の設定を行う。
この設定は必ずしも必要ではないが、撮影直後に撮影したJPEGデータのプレビュー画像を画面に表示したい時に使用される。RAW+JPEG (RAW+L)データを写真アルバムに保存する前に、プレビュー画像を画面に表示することで、保存処理を素早く完了させたかのような錯覚をユーザーに持たせることができる。
AVCapturePhotoSettings のavailablePreviewPhotoPixelFormatTypes で、プレビュー画像が対応しているピクセルフォーマットを取得する。previewPhotoFormat に、kCVPixelBufferPixelFormatTypeKey をキーとして、ピクセルフォーマットの値を代入する。

撮影開始

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

AVCapturePhotoOutput のcapturePhoto(with:delegate:) で、撮影を開始する。with 引数には、前項で生成したAVCapturePhotoSettings インスタンスを指定する。

RAW+JPEG (RAW+L)データの出力

public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard error == nil else {
        print("Error broken photo data: \(error!)")
        return
    }
    
    guard let photoData = photo.fileDataRepresentation() else {
        print("No photo data to write.")
        return
    }
    
    if photo.isRawPhoto {
        rawImageFileURL = makeUniqueTempFileURL(extension: "dng")
        do {
            try photo.fileDataRepresentation()!.write(to: rawImageFileURL!)
        } catch {
            fatalError("couldn't write DNG file to URL")
        }
    } else {
        compressedData = photoData
    }
}

capturePhoto(with:delegate:) を実行すると、RAW+JPEG (RAW+L)のデータ出力処理が開始する。RAW・JPEGそれぞれのデータが出力される度に、photoOutput(_:didFinishProcessingPhoto:error:) がコールされるので、photo 引数のisRawPhoto を使用して、出力されたデータがRAWデータか否かを判定する。
一連の撮影処理完了時にコールされるデリゲートメソッドで、各データの保存処理を行うため、RAWデータはtemporaryDirectory に保存しておき、JPEGデータは変数で保持しておく。
また、AVCapturePhoto のpreviewCGImageRepresentation() で、CGImage?型のプレビュー画像を取得することができる。
RAWデータを写真アルバムに保存するために要する時間は大きい。保存処理後に撮影したJPEGデータを画面に表示してしまうと、ユーザーは保存処理が遅く感じてしまう。そこで、このデリゲートメソッド内で取得したプレビュー画像を画面に表示してしまえば、保存処理が遅く感じることを防ぐことができる。

RAW+JPEG (RAW+L) データの保存

func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings,
                 error: Error?) {
    guard error == nil else {
        print("Error capture photo: \(error!)")
        return
    }
    
    guard let compressedData = self.compressedData else {
        print("The expected photo data isn't available.")
        return
    }
    
    PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
        guard status == .authorized else { return }
        PHPhotoLibrary.shared().performChanges {
          let creationRequest = PHAssetCreationRequest.forAsset()
          creationRequest.addResource(with: .photo, data: compressedData, options: nil)
          let options = PHAssetResourceCreationOptions()
          options.shouldMoveFile = true
          creationRequest.addResource(with: .alternatePhoto, fileURL: self.rawImageFileURL!, options: options)
        } completionHandler: { success, error in
            if let _ = error {
                print("Error save photo: \(error!)")
            }
        }
    }
}

performChanges(_:completionHandler:) のcompletionHandler 引数のクロージャ内で写真保存処理を行う。

最初に、JPEGデータの保存処理を行う。
addResource(with:data:options:) のwith 引数には.photo 、data 引数にはJPEGデータのData インスタンスを指定することで、JPEGデータの保存を行うことができる。

次に、RAWデータを写真アルバムに保存する。
addResource(with:fileURL:options:) のwith 引数には.alternatePhoto 、fileURL 引数にはRAWデータを配置しているURL インスタンスを指定することで、RAWデータの保存を行うことができる。
また、options 引数には、shouldMoveFile をtrue にしたPHAssetResourceCreationOptions インスタンスを指定すること。
shouldMoveFile をtrue にしたPHAssetResourceCreationOptions インスタンスを指定することで、RAWデータ保存処理完了後に、保存処理時に使用したURLに配置されているRAWデータのData インスタンスが自動で削除される。


ここから先は

2,482字 / 1画像

¥ 200

最後まで記事をお読みいただきありがとうございます! 記事が参考になればフォロー・♥いただけると凄く励みになります🥳