【AVFoundation】リアルタイムキャプチャの基本

#この記事は何か？
iPhoneのカメラからリアルタイムに映像データを得るために使用する`AVFoudation`について調べました。


#AVFoundationフレームワーク
主に、4つのグループに分けて捉えることができます。

- セッション
`AVCaptureSession`クラス

- デバイス
`AVCaptureDevice`クラス

- 入力
`AVCaptureInput`クラス、`AVCaptureDeviceInput`クラス

- 出力
`AVCaptureOutput`クラス、`AVCaptureVideoDataOutput`クラス

`AVCaptureSession`がデータを整理し、セッションの開始または停止の指示を受けます。
セッションには、ひとつ以上の入力を追加します。`AVCaptureDeviceInput`は、カメラやマイクのためのラッパーです。
セッションからデータを受け取るには、出力も追加する必要があります。
入力と出力が追加されると、セッションは互換性のある入力と出力を自動的に接続します。
`AVCaptureSession`では、各セッションに１つのカメラだけが許容されます。なお、iOS 13で導入された`AVCaptureMultiCamSession`では、複数の入出力を扱うことが可能になりました。

##[AVCaptureSessionクラス](https://developer.apple.com/documentation/avfoundation/avcapturesession)
````Swift:定義
class AVCaptureSession : NSObject
````
キャプチャ動作を管理し、入力デバイスからキャプチャ出力へのデータの流れを調整（コーディネート）するオブジェクト。

リアルタイムにキャプチャをするには、`AVCaptureSession`型のインスタンスに、適切な入力と出力を追加します。

````Swift:オーディオを録音するためにキャプチャデバイスを設定するコードの例
// キャプチャセッションを作成して、標準のオーティオデバイスを設定する
let captureSession = AVCaptureSession()
guard let audioDevice = AVCaptureDevice.default(for: .audio) else { return } 

do {
    // オーディオデバイスをラップして、「キャプチャデバイスの入力」にする
    // 可能であれば、その入力をセッションに追加する
    let audioInput = try AVCaptureDeviceInput(device: audioDevice)
    if captureSession.canAddInput(audioInput) {
        captureSession.addInput(audioInput)
    }
} catch {
    // オーディオデバイスを「キャプチャデバイスの入力」にできなかった場合のエラー処理
}
````

入力から出力へのデータの流れを開始するには`startRunning()`メソッドを呼び出します。また、停止するには`stopRunning()`メソッドを呼び出します。

`startRunning()`メソッドは、いくらか時間のかかる恐れがあるブロッキング呼び出しなので、メインキューがブロックされないように**並列キューでセッションのセットアップを実行する**必要があります。こうすることで、 UIの応答性を維持できます。
実装例については[AVCam: Building a Camera App](https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/avcam_building_a_camera_app)を参照してください。

出力の品質レベル、ビットレート、または他の設定をカスタマイズするには、`sessionPreset`プロパティを使用します。ほとんどの一般的なキャプチャ設定はセッションプリセットを通して利用可能ですが、いくつかの特殊なオプション（高フレームレートなど）は、`AVCaptureDevice`インスタンスにキャプチャフォーマットを直接設定する必要があります。

###[addInput(_:)メソッド](https://developer.apple.com/documentation/avfoundation/avcapturesession/1387239-addinput)
````Swift:定義
func addInput(_ input: AVCaptureInput)
````
「取得した入力」をセッションに追加します。

このメソッドを使用してセッションに入力を追加できるのは、`canAddInput(_:)`メソッドが`true`を返した場合のみです。`canAddInput(_:)`メソッドを呼び出した結果が`false`の場合、このメソッドは例外をスローします。

このメソッドは、セッションの実行中に呼び出すことができます。

###addOutput(_:)メソッド
````Swift:定義
func addOutput(_ output: AVCaptureOutput)
````
このメソッドを使用してセッションに出力を追加できるのは、`canAddOutput(_:)`メソッドが`true`を返した場合のみです。`canAddOutput(_:)`メソッドを呼び出した結果が`false`の場合、このメソッドは例外をスローします。

このメソッドは、セッションの実行中に呼び出すことができます。

##[AVCaptureDeviceクラス](https://developer.apple.com/documentation/avfoundation/avcapturedevice)
````Swift:定義
class AVCaptureDevice : NSObject
````
キャプチャセッションに「オーディオやビデオなどの入力」を提供し、ハードウェア固有のキャプチャ機能向けにコントロールを提供するデバイスです。

`AVCaptureDevice`オブジェクトは、物理的なキャプチャデバイスと「そのデバイスに関連付けられたプロパティ」をモデル化したものです。キャプチャデバイスを使用して、基礎となるハードウェアのプロパティを設定します。また、キャプチャデバイスは`AVCaptureSession`オブジェクトに「オーディオやビデオなどの入力データ」を提供します。

`AVCaptureDevice`クラスのメソッドを使用して、利用可能なデバイスを列挙し、それらの機能を呼び出し、デバイスが出入りするときに通知を受けることができます。キャプチャデバイスのプロパティ（フォーカスモード、露出モードなど）を設定しようとする前に、最初に`lockForConfiguration()`メソッドを使用してデバイスのロックを取得する必要があります。また、設定しようとしている新しいモードがそのデバイスで有効であることを確認するために、デバイスの機能を問い合わせる必要があります。その後、 `unlockForConfiguration()`メソッドを使用してプロパティを設定し、ロックを解除します。設定可能なデバイス・プロパティをすべて変更せずに残したい場合は、ロックを保持することができます。しかし、デバイスのロックを不必要に保持すると、デバイスを共有している他のアプリケーションでキャプチャの品質が低下する可能性があり、お勧めできません。

キャプチャ設定のほとんどの一般的な構成は、`AVCaptureSession`オブジェクトとその利用可能なプリセットを介して利用できます。しかし、iOSデバイスでは、いくつかの特殊なオプション（高フレームレートなど）は、`AVCaptureDevice`インスタンスにキャプチャ形式を直接設定する必要があります。

iOSでは、キャプチャデバイスの`activeFormat`プロパティを直接設定すると、セッションのプリセットが`inputPriority`に変更されます。この変更を行うと、セッションのトポロジー（配置した入力、出力のこと）を変更した後に`startRunning()`メソッドか`commitConfiguration()`メソッドを呼び出すと、セッションは自動的にキャプチャフォーマットを設定しなくなります。
macOSでは、変更を行った後もキャプチャセッションは自動的にキャプチャフォーマットを構成することができます。

###[lockForConfiguration()メソッド](https://developer.apple.com/documentation/avfoundation/avcapturedevice/1387810-lockforconfiguration)
デバイスのハードウェア設定に対する排他的アクセスを要求します。

````Swift:宣言
func lockForConfiguration() throws
````

このメソッドは`try`付きで呼び出し、`do`構文の`catch`節でエラーを処理します。デバイスのハードウェア関連の設定を構成しようとする前に、このメソッドを呼び出す必要があります。
デバイスのプロパティを変更したくない場合は、（ロックを解除する代わりに）ロックを維持できます。ただし、不必要にデバイスをロックすると、デバイスを共有している他のアプリで行うキャプチャの品質が低下する恐れがあります。

iOSでは、キャプチャデバイスの`activeFormat`プロパティを直接設定すると、セッションのプリセットが`inputPriority`に変更されます。この変更を行った場合、セッションのトポロジーを変更した後（つまり、キャプチャの入力と出力を追加、削除、再配置した後）に`startRunning()`メソッドか`commitConfiguration()`メソッドを呼び出しても、セッションは自動的にキャプチャフォーマットを設定しなくなります。

##AVCaptureInputクラス
キャプチャセッションに入力データを提供するオブジェクトの抽象的なスーパークラスです。

````Swift:宣言
class AVCaptureInput : NSObject
````

`AVCaptureInput`オブジェクトをセッションに関連付けるには、セッションで`addInput(_:)`メソッドを呼び出します。
`AVCaptureInput`オブジェクトは、生成できるデータストリームごとに1つ以上のポート (`AVCaptureInput.Port`のインスタンス) を持っています。例えば、1つのビデオデータストリームを提示する `AVCaptureDevice`オブジェクトにはポートが1つあります。

##AVCaptureDeviceInputクラス
````Swift:宣言
class AVCaptureDeviceInput : AVCaptureInput
````
デバイスからセッションにメディアを提供するキャプチャ入力です。

`AVCaptureDeviceInput`は、`AVCaptureDevice`オブジェクトからデータをキャプチャするために使用する `AVCaptureInput`の具体的なサブクラスです。

##AVCaptureOutputクラス
````Swift:宣言
class AVCaptureOutput : NSObject
````
キャプチャセッションで記録したメディアを出力するオブジェクトの抽象的なスーパークラスです。

`AVCaptureOutput`は、`AVCaptureSession`オブジェクトの出力先を記述する抽象的な基底クラスです。
`AVCaptureOutput`は、ファイルやビデオプレビューなどのキャプチャ出力先をセッション（`AVCaptureSession`のインスタンス）に接続するための抽象的なインターフェイスを提供します。キャプチャ出力は`AVCaptureInput`から受け取るメディアのストリームごとに`AVCaptureConnection`オブジェクトで表される「いくつかの接続」を持つことができます。最初にキャプチャ出力が作成されたとき、接続はひとつもありません。セッションに出力を追加すると、セッションは「そのセッションの入力」から出力にメディアデータをマップする接続を作成します。

`AVCaptureOutput`インスタンスをセッションに追加するには、セッションの`addOutput(_:)`メソッドを使用します。

##AVCaptureVideoDataOutputクラス
````Swift:宣言
class AVCaptureVideoDataOutput : AVCaptureOutput
````
ビデオを記録して、ビデオフレームを処理するためのアクセスを提供するキャプチャ出力です。

この出力を使用して、キャプチャしたビデオから圧縮または非圧縮のフレームを処理します。
フレームには`captureOutput(_:didOutput:from:)`デリゲートメソッドを使用してアクセスできます。

###alwaysDiscardsLateVideoFramesプロパティ
````Swift:宣言
var alwaysDiscardsLateVideoFrames: Bool { get set }
````
ビデオフレームに遅延があった場合に、フレームを削除するかどうかを示します。

このプロパティが`true`の場合、「既存のフレーム」を扱うディスパッチキューが`captureOutput(_:didOutput:from:)`デリゲートメソッドでブロックしている間に、出力は「キャプチャしたフレーム」を直ちに破棄します。
`false`にすると、出力は「新しいフレーム」を破棄する前に「古いフレーム」を処理する猶予をデリゲートに与えますが、アプリケーションのメモリ使用量は結果として大幅に増加する恐れがあります。
デフォルトは`true`です。

###videoSettingsプロパティ
````Swift:宣言
var videoSettings: [String : Any]! { get set }
````
出力の圧縮設定です。

この辞書には，`videoSettings`で定義された圧縮設定キー，または`CVPixelBuffer`で定義されたピクセルバッファ属性キーの値が含まれます。現時点では、`kCVPixelBufferPixelFormatTypeKey`キーのみがサポートされています。
サポートされているビデオピクセルフォーマット（`kCVPixelBufferPixelFormatTypeKey`）の値を取得するには、`availableVideoPixelFormatTypes`を参照してください。また、サポートされているビデオコーデックフォーマット（`AVVideoCodecKey`）の値を取得するには、`availableVideoCodecTypes`を参照してください。
デバイスのネイティブフォーマットでサンプルを受信するには、このプロパティを空の辞書に設定します。

###setSampleBufferDelegate(_:queue:)
````Swift:宣言
func setSampleBufferDelegate(_ sampleBufferDelegate: AVCaptureVideoDataOutputSampleBufferDelegate?, 
                    queue sampleBufferCallbackQueue: DispatchQueue?)
````

サンプルバッファのデリゲートの設定、コールバックが呼び出されるべきキューを設定します。

キャプチャされたビデオの新しいサンプルバッファは、`captureOutput(_:didOutput:from:)`メソッドを使用してサンプルバッファのデリゲート先に送信されます。すべてのデリゲートメソッドは、指定されたディスパッチキューで呼び出されます。

新しいフレームがキャプチャされたときにキューがブロックされている場合、それらのフレームは、常に`discardsLateVideoFrames`プロパティの値に基づいて自動的にドロップされます。これにより、同じキューで既存のフレームを処理する際に、処理が入ってくるフレームのレートに追いつけない場合に発生するであろうメモリ使用量の増加を管理する必要がなくなります。