📷

【iOS】AVFoundationを使用して写真撮影機能を実装する

に公開

AVFoundationのCapture APIを使用して、写真撮影機能を実装する方法について書きます。本記事をお読みいただくことで、以下の内容を学ぶことができます。

  • 静止画データを出力するキャプチャセッションの設定方法
  • 静止画データ撮影を開始する方法
  • デリゲートメソッドを介した静止画データの取得方法
  • 静止画データを写真アルバムに追加する方法

本記事の内容は、「詳解 AVFoundation Capture」から一部の説明を抜粋した内容になっています。
https://naoya-maeda.booth.pm/items/6760109
アプリがカメラやマイク、写真アルバムへアクセスするために必要なアクセス権を取得する方法については、本記事では割愛しています。「詳解 AVFoundation Capture」の「第2章 アクセス権」または、以下のドキュメントをご参照ください。
https://developer.apple.com/documentation/avfoundation/requesting-authorization-to-capture-and-save-media

カメラ撮影機能のアーキテクチャ

カメラ撮影機能のアーキテクチャは、「入力」、「出力ワークフロー」、「キャプチャセッション」の三つで構成されます。入力は、カメラやマイクといったメディアデバイスが記録したデータを、キャプチャセッションに渡す役割を担います。出力ワークフローは、キャプチャセッションから受け取ったデータを、静止画データや動画データといったプログラムで扱うことができる形式のデータで出力する役割を担います。キャプチャセッションは、入力と出力ワークフローとの接続を管理する中間ハブとしての役割を担います。
キャプチャセッションに入力と出力ワークフローを追加すると、キャプチャセッション内部で自動的にこれらを接続し、データフローを確立します。データフローを確立すると、メディアデバイスが記録したデータは、入力を経由してキャプチャセッションに渡され、キャプチャセッションは受け取ったデータを出力ワークフローに渡します。出力ワークフローは受け取ったデータを、静止画データや動画データといったプログラムで扱うことができる形式のデータで出力します。

キャプチャセッションの設定

Capture API を使用してキャプチャセッションの設定を行うには、メディアデバイスの入力を表すAVCaptureDeviceInput オブジェクトと、出力ワークフローを表すAVCaptureOutput を継承するオブジェクトを、AVCaptureSession オブジェクトに追加します。キャプチャセッションの設定が完了すると、メディアデバイスが記録したデータは、AVCaptureDeviceInput オブジェクトを介してAVCaptureSession オブジェクトに渡されます。その後、AVCaptureSession オブジェクトは受け取ったデータを、AVCaptureOutput オブジェクトに渡します。そして、AVCaptureOutput オブジェクトにデータが渡されると、特定のデリゲートメソッドを介して、静止画データや動画データといったプログラムで扱うことができる形式のデータとして取得することができます。

入力設定

AVCaptureDeviceInput オブジェクトをAVCaptureSession オブジェクトに追加することで、キャプチャセッションの入力設定を行うことができます。AVCaptureDeviceInput は、メディアデバイスが記録したデータを、AVCaptureSession オブジェクトに渡すことができるクラスです。
https://developer.apple.com/documentation/avfoundation/avcapturedeviceinput

AVCaptureDeviceInput オブジェクトは、init(device:) イニシャライザで取得することができます。
https://developer.apple.com/documentation/avfoundation/avcapturedeviceinput/init(device:)

device 引数には、AVCaptureDevice オブジェクトを指定するため、AVCaptureDevice オブジェクトを取得しておく必要があります。AVCaptureDevice オブジェクトは、AVCaptureDevicedefault(_:for:position:) メソッドで取得することができます。
https://developer.apple.com/documentation/avfoundation/avcapturedevice/default(_:for:position:)

default(_:for:position:) は、各引数の情報に合致したメディアデバイスを表すAVCaptureDevice オブジェクトを返すメソッドです。第一引数には、AVCaptureDevice.DeviceType オブジェクトを指定します。AVCaptureDevice.DeviceType は、フレームワークがサポートするメディアデバイスの種類を表す構造体です。
https://developer.apple.com/documentation/avfoundation/avcapturedevice/devicetype-swift.struct

.builtInWideAngleCamera (広角レンズカメラ) や.builtInUltraWideCamera (超広角レンズカメラ) といったメディアデバイスの種類を表します。for 引数には、AVMediaType オブジェクトを指定します。AVMediaType は、メデイアタイプを表す構造体です。
https://developer.apple.com/documentation/avfoundation/avmediatype

.video (映像) や.audio (音声) といったメデイアタイプを表します。position 引数には、AVCaptureDevice.Position オブジェクトを指定します。AVCaptureDevice.Position は、メディアデバイスの位置を表す要素を持つEnum です。
https://developer.apple.com/documentation/avfoundation/avcapturedevice/position

以下のソースコードでは、iPhone 背面の広角レンズカメラが記録したデータを、AVCaptureSession オブジェクトに渡すことができるAVCaptureDeviceInput オブジェクトを取得しています。

取得したAVCaptureDeviceInput オブジェクトをAVCaptureSession オブジェクトに追加することで、キャプチャセッションの入力設定を行うことができます。AVCaptureDeviceInput オブジェクトをAVCaptureSession オブジェクトに追加する前に、AVCaptureSession オブジェクトのcanAddInput(_:) メソッドを実行します。
https://developer.apple.com/documentation/avfoundation/avcapturesession/canaddinput(_:)

canAddInput(_:) は、第一引数に指定したAVCaptureDeviceInput オブジェクトを、AVCaptureSession オブジェクトに追加することができるかどうかを表すBool 値を返すメソッドです。第一引数には、AVCaptureSession オブジェクトに追加したいAVCaptureInput オブジェクトを指定します。canAddInput(_:) メソッドが返す値がtrue であることを確認した後に、AVCaptureSession オブジェクトのaddInput(_:) メソッドを実行します。addInput(_:) は、第一引数に指定したAVCaptureDeviceInput オブジェクトを、AVCaptureSession オブジェクトに追加するメソッドです。
https://developer.apple.com/documentation/avfoundation/avcapturesession/addinput(_:)

第一引数には、AVCaptureSession オブジェクトに追加したいAVCaptureInput オブジェクトを指定します。

出力設定

AVCapturePhotoOutput は、静止画データを出力するためのワークフローを提供するクラスです。
https://developer.apple.com/documentation/avfoundation/avcapturephotooutput

AVCaptureOutput を継承しており、AVCaptureSession オブジェクトに追加することで、AVCapturePhotoCaptureDelegate プロトコルで定義されているデリゲートメソッドを介して、撮影した静止画データを取得することができます。

AVCapturePhotoOutput オブジェクトをAVCaptureSession オブジェクトに追加する前に、AVCaptureSession オブジェクトのcanAddOutput(_:) メソッドを実行します。
https://developer.apple.com/documentation/avfoundation/avcapturesession/canaddoutput(_:)

canAddOutput(_:) は、第一引数に指定したAVCaptureOutput オブジェクトを、AVCaptureSession オブジェクトに追加することができるかどうかを表すBool 値を返すメソッドです。canAddOutput(_:) メソッドが返す値がtrue であることを確認した後に、AVCaptureSession オブジェクトのaddOutput(_:) メソッドを実行します。addOutput(_:) は、第一引数に指定したAVCaptureOutput オブジェクトを、AVCaptureSession オブジェクトに追加するメソッドです。
https://developer.apple.com/documentation/avfoundation/avcapturesession/addoutput(_:)

第一引数には、AVCaptureSession オブジェクトに追加したいAVCaptureOutput オブジェクトを指定します。

また、AVCaptureSession オブジェクトは、撮影フォーマットを変更することができるAVCaptureSession.Preset 型のsessionPreset プロパティを持っています。
https://developer.apple.com/documentation/avfoundation/avcapturesession/sessionpreset

今回は、静止画データ撮影向けに、最大の解像度で出力することができるAVCaptureSession.Preset.photo オブジェクトを、sessionPreset プロパティに代入します。AVCaptureSession.Preset.photo オブジェクトをsessionPreset プロパティに代入する前に、AVCaptureSession オブジェクトのcanSetSessionPreset(_:) メソッドを実行します。
https://developer.apple.com/documentation/avfoundation/avcapturesession/cansetsessionpreset(_:)

canSetSessionPreset(_:) は、第一引数に指定したAVCaptureSession.Preset オブジェクトを、sessionPreset プロパティに代入できるかどうかを表すBool 値を返すメソッドです。canSetSessionPreset(_:) メソッドが返す値がtrue であることを確認した後に、sessionPreset プロパティにAVCaptureSession.Preset.photo オブジェクトを代入します。

以下のソースコードでは、iPhone 背面の広角レンズカメラが記録した静止画データを出力するキャプチャセッションの設定を行っています。

カメラ映像プレビューの表示設定

AVCaptureVideoPreviewLayer は、カメラ映像を表示するレイヤーとしての役割を担うCALayer を継承するクラスです。
https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer

AVCaptureVideoPreviewLayer オブジェクトは、init(session:) イニシャライザで取得することができます。
https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/init(session:)

session 引数には、入力設定を行うAVCaptureSession オブジェクトを指定します。AVCaptureVideoPreviewLayer オブジェクトは、session 引数に指定したAVCaptureSession オブジェクトが受け取るカメラ映像のビデオフレームデータを表示することができます。

UIView オブジェクトのlayer プロパティが持つaddSublayer(_:) メソッドの第一引数に、AVCaptureVideoPreviewLayer オブジェクト指定することで、addSublayer(_:) メソッドを実行したレイヤー上にカメラ映像プレビューを表示することができます。以下のソースコードでは、SwiftUI View でカメラ映像プレビューを表示するために、UIViewControllerRepresentable プロトコルに準拠した構造体を定義しています。

VideoPreviewLayerView オブジェクトのイニシャライザで、previewCALayer 引数にAVCaptureVideoPreviewLayer オブジェクトを指定し、カメラ映像プレビューを表示することができるUIViewController を定義しています。UIViewControllerRepresentable プロトコルに準拠することで、SwiftUI View の中でも使用することができるようになっています。以下のソースコードでは、SwiftUI View 側でVideoPreviewLayerView を使用して、カメラ映像のプレビューを表示しています。

ここで注意すべきポイントが一つあります。AVCaptureSession オブジェクトに変更を加える時は、変更を加える前に、AVCaptureSession オブジェクトのbeginConfiguration() メソッドを実行し、変更完了後に、AVCaptureSession オブジェクトのcommitConfiguration() メソッドを実行する必要があります。
https://developer.apple.com/documentation/avfoundation/avcapturesession/beginconfiguration()
https://developer.apple.com/documentation/avfoundation/avcapturesession/commitconfiguration()

beginConfiguration() メソッドは、キャプチャセッションの変更開始AVCaptureSession オブジェクトに通知し、commitConfiguration() メソッドは、キャプチャセッションの変更完了をAVCaptureSession オブジェクトに通知します。一つ以上のキャプチャセッションの変更処理を行った後に、commitConfiguration() メソッドを実行することでbeginConfiguration() メソッドを実行した後からcommitConfiguration() メソッドを実行するまでに行われた、キャプチャセッションの変更処理を一括で行うことができます。キャプチャセッションの設定が完了した後に、AVCaptureSession オブジェクトのstartRunning() メソッドを実行します。
https://developer.apple.com/documentation/avfoundation/avcapturesession/startrunning()

startRunning() は、AVCaptureDevice オブジェクトが記録したデータを、AVCaptureSession オブジェクトを介して、AVCaptureOutput オブジェクトに渡すデータフローを開始するメソッドです。

以下のソースコードでは、iPhone 背面の広角レンズカメラが記録した静止画データを出力するキャプチャセッションの設定を行っています。

静止画データ撮影の開始

AVCapturePhotoOutput オブジェクトのcapturePhoto(with:delegate:) メソッドで、静止画データ撮影を開始することができます。
https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/capturephoto(with:delegate:)

with 引数には、AVCapturePhotoSettings オブジェクトを指定します。AVCapturePhotoSettings は撮影時のフラッシュライトや、ファイル形式の変更など、静止画データ撮影オプション設定を行うことができるクラスです。
https://developer.apple.com/documentation/avfoundation/avcapturephotosettings

今回は、静止画データを撮影する機能に絞って解説を進めるため、撮影オプション設定は行ないません。取得したAVCapturePhotoSettings オブジェクトに対して変更を加えずに、with 引数に指定します。

delegate 引数には、AVCapturePhotoCaptureDelegate プロトコルに準拠するオブジェクトを指定します。AVCapturePhotoCaptureDelegate プロトコルで定義されているデリゲートメソッドについては後述しますが、上記のソースコードでは、self を指定しています。AVCapturePhotoCaptureDelegate プロトコルに準拠したオブジェクトに、静止画データ撮影の開始から完了するまでに呼び出されるデリゲートメソッドを定義します。

プロトコルへの準拠

capturePhoto(with:delegate:) メソッドのdelegate 引数に、静止画データ撮影の開始から完了するまでに呼び出されるデリゲートメソッドの委譲先を指定しました。今回は、移譲先にself を指定したので、capturePhoto(with:delegate:) メソッドを実行するクラス自身にデリゲートメソッドを定義します。デリゲートメソッドを定義するために、selfAVCapturePhotoCaptureDelegate プロトコルに準拠させます。

静止画データの取得

photoOutput(_:didFinishProcessingPhoto:error:) は、AVCapturePhotoCaptureDelegate プロトコルで定義されているデリゲートメソッドです。
https://developer.apple.com/documentation/avfoundation/avcapturephotocapturedelegate/photooutput(_:didfinishprocessingphoto:error:)

静止画データが出力される時に呼び出されます。出力された静止画データは、photo 引数に代入されているAVCapturePhoto オブジェクトで取得することができます。AVCapturePhoto は、静止画データやメタデータ情報を格納することができるクラスです。
https://developer.apple.com/documentation/avfoundation/avcapturephoto

静止画データは、AVCapturePhoto オブジェクトのfileDataRepresentation() メソッドで取得することができます。
https://developer.apple.com/documentation/avfoundation/avcapturephoto/filedatarepresentation()

fileDataRepresentation() は、fileDataRepresentation() を実行するAVCapturePhoto オブジェクトから撮影データを抽出するメソッドです。fileDataRepresentation() を実行するAVCapturePhoto オブジェクト自身に格納されている静止画データを、Data オブジェクトで返します。以下のソースコードでは、fileDataRepresentation() メソッドで取得した静止画データを表すData オブジェクトを、compressedData 変数に代入しています。

静止画データを写真アルバムへ追加

photoOutput(_:didFinishCaptureFor:error:) は、AVCapturePhotoCaptureDelegate プロトコルで定義されているデリゲートメソッドです。
https://developer.apple.com/documentation/avfoundation/avcapturephotocapturedelegate/photooutput(_:didfinishcapturefor:error:)

撮影に関連する全ての処理が完了した時に呼び出されます。静止画データを写真アルバムへ追加する処理は、photoOutput(_:didFinishCaptureFor:error:) メソッド内で行います。静止画データや動画データ(以降、アセットデータ) を写真アルバムに追加する処理は、PhotoKit フレームワークのAPI を使用します。PhotoKit は、写真アルバムを操作するためのAPIが定義されているフレームワークです。写真アルバムに変更を加える時は、PHPhotoLibraryperformChanges(_:) メソッドを実行します。
https://developer.apple.com/documentation/photos/phphotolibrary/performchanges(_:completionhandler:)

performChanges(_:) は、第一引数に指定した写真アルバムに変更を加える処理を、PhotoKit にリクエストするメソッドです。

第一引数には、写真アルバムを操作する処理をクロージャで記述します。写真アルバムに対する変更は、PHAssetCreationRequest オブジェクトのメソッドで行います。PHAssetCreationRequest は、写真アルバムに対する変更をPhotoKit にリクエストすることができるメソッドを持つクラス
です。
https://developer.apple.com/documentation/Photos/PHAssetCreationRequest

PHAssetCreationRequest オブジェクトは、forAsset() メソッドで取得することができます。
https://developer.apple.com/documentation/photos/phassetcreationrequest/forasset()

Data オブジェクトのアセットデータを写真アルバムに追加する時は、PHAssetCreationRequest オブジェクトのaddResource(with:data:options:) メソッドを実行します。
https://developer.apple.com/documentation/photos/phassetcreationrequest/addresource(with:data:options:)

addResource(with:data:options:) は、data 引数に指定したData オブジェクトのアセットデータを、写真アルバムに追加することをPhotoKit にリクエストするメソッドです。
with 引数には、写真アルバムに追加するアセットのタイプを、PHAssetResourceType オブジェクトで指定します。PHAssetResourceType は、アセットデータのタイプを表す要素を持つEnum です。
https://developer.apple.com/documentation/photos/phassetresourcetype

今回は、静止画データを写真アルバムに追加するので、上記のソースコードでは、with 引数に.photo を指定しています。data 引数には、写真アルバムに追加するアセットデータを、Data オブジェクトで指定します。以下のソースコードでは、撮影した静止画データが
代入されているcompressedData 変数を指定しています。
options 引数には、PHAssetResourceCreationOptions オブジェクトを指定します。PHAssetResourceCreationOptions は、写真アルバムに追加するアセットデータのファイル名を指定したり、写真アルバムにアセットデータを追加する際に、元のアセットデータの挙動を制御する追加処理オプションを設定することができるクラスです。
https://developer.apple.com/documentation/photos/phassetresourcecreationoptions

Data オブジェクトのアセットデータを写真アルバムに追加するだけならば、追加処理オプションは不要なので、options 引数にはnil を指定します。以下のソースコードでは、静止画データを写真アルバムに追加しています。

次回は、本記事で解説した内容をベースに、動画撮影機能を実装する方法について解説します。
https://zenn.dev/naoya_maeda/articles/6f35ef13a5e895

参考資料

・詳解 AVFoundation Capture
https://naoya-maeda.booth.pm/items/6760109

・Setting Up a Capture Session, Apple Developer Documentation
https://developer.apple.com/documentation/avfoundation/setting-up-a-capture-session

Discussion