![[iOS] AVFoundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた](https://devio2023-media.developers.io/wp-content/uploads/2015/12/ios.png)
[iOS] AVFoundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 AVFoundation
AVFoundationは、音声・動画などのメディアの再生や作成、編集を行うことの出来る巨大なフレームワークです。
入力側をデバイスと考えた場合、AVFoundationは、大まかに、次の3つの構成になっているといえます。
- デバイス(AVCaptureDevice)
 - セッション(AVCaptureSession)
 - 出力(AVCaptureOutput)
 

AVFoundationを使用するには、まずは、中心となるAVCaptureSessionをインスタンス化します。
そして、このAVCaptureSessionに対して、使用したいデバイスを入力として接続し、利用目的に合致したAVCaptureOutputを出力に接続することで目的を達成します。
例えば、「ビデオを録画してmp3ファイルに保存したい」というような場合は、入力デバイスとして「背面カメラ」及び「マイク」を設定し、出力として「動画ファイル」(AVCaptureMovieFileOutput)を設定すると言った感じです。
下記の図は、その構成を表現したものですが、この時、AVCaptureSessionのinputs:[Any]!プロパティには、2つのAVCaptureDeviceオブジェクトが、そして、outputs:[Any]!には、1つのAVCaptureOutputオブジェクトが列挙されることになります。
また、AVCaptureOutputの、connections:[Any]!プロパティでは、それぞれのデバイスとの間のコネクションとして、2つのAVCaptureConnectionが列挙されます。

そして、inputs:[Any]!、outputs:[Any]!、connections:[Any]!を使用して、それぞれのオブジェクトから、関連するオブジェクトを辿ることができます。
2 AVCaptureDevice
AVCaptureDeviceオブジェクトは、物理的な入力デバイスと、そのデバイスに関連付けられたプロパティを表現しています。
iPhoneの場合、AVFoundationで使用可能な入力デバイスは、前後のカメラとマイクになります。
使用可能なデバイスを列挙するようなコードをiPhoneで実行すると、次のような出力が確認できます。
for device in AVCaptureDevice.devices() as! [AVCaptureDevice]{
    print("localizedName=\(device.localizedName!) modelID=\(device.modelID!)")
}
localizedName=Back Camera modelID=com.apple.avfoundation.avcapturedevice.built-in_video:0 localizedName=Front Camera modelID=com.apple.avfoundation.avcapturedevice.built-in_video:1 localizedName=iPhone マイク modelID=com.apple.avfoundation.avcapturedevice.built-in_audio:0
(1) デバイスの初期化
defaultDevice(withDeviceType:mediaType:position:)にメディアの種類を指定してデバイスの初期化が可能です。
let videoDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio)
なお、上記の場合は、カメラは、デフォルトとして背面のものが使用されますが、もし、前面のカメラが使用したいのであれば、下記のように記述します。
var videoDevice: AVCaptureDevice
for device in AVCaptureDevice.devices() {
    if (device as AnyObject).position == AVCaptureDevicePosition.front {
        videoDevice = device as! AVCaptureDevice
    }
}
(2) デバイスのプロパティ
デバイスによって各種のプロパティが設定可能です。
// 露出モード
var exposureMode: AVCaptureExposureMode { get set }
// フォーカスモード
var focusMode: AVCaptureFocusMode { get set }
// フラッシュ
var flashMode: AVCaptureFlashMode { get set }
// フォーカス位置
var focusPointOfInterest: CGPoint { get set }
// ホワイトバランスモード
var whiteBalanceMode: AVCaptureWhiteBalanceMode { get set }
この他にも、非常に多くのプロパティがありますが、詳しくは、下記をご参照ください。
https://developer.apple.com/reference/avfoundation/avcapturedevice
(3) ロックと有効性確認
デバイスのプロパティを設定する際は、lockForConfiguration()メソッドを使用してデバイスをロック(unlockForConfiguration()で解除)する必要があります。
また、プロパティは、そのデバイスで有効なものしか設定できません。例えば、前面カメラでフラッシュをONに設定しようとしたりすると、たちまちアプリがクラッシュしてしまいます。プロパティ設定の前に、それが有効かどうかを確認しなければなりません。
下記は、カメラのフラッシュをONに設定している例です。
let cameraDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
    try cameraDevice.lockForConfiguration() // ロック開始
    if cameraDevice.isFlashModeSupported(AVCaptureFlashMode.on) { // プロパティの有効性を確認
        cameraDevice.flashMode = AVCaptureFlashMode.on // プロパティの設定
    }
} catch {
    // error
}
cameraDevice.unlockForConfiguration() // ロック解除
3 AVCaptureDeviceInput
AVCaptureDeviceオブジェクトからデータをキャプチャするために使用するAVCaptureInputのサブクラスです。 これを使用して、デバイスをAVCaptureSessionに繋ぎます。
4 AVCaptureSession
AVCaptureSessionは、入力デバイスから出力へのデータの流れを管理するクラスです。
(1) インスタンス化と入出力の接続
下記は、AVCaptureSessionをインスタンス化し、入力側と出力側を初期化している例です。
// セッションのインスタンス化 let captureSession = AVCaptureSession() // デバイスの初期化 let videoDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) // カメラ let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio) // マイク // デバイスの接続 let videoInput = try! AVCaptureDeviceInput.init(device: videoDevice) captureSession.addInput(videoInput) let audioInput = try! AVCaptureDeviceInput.init(device: audioDevice) captureSession.addInput(audioInput); // 出力の初期化 let fileOutput = AVCaptureMovieFileOutput() // 映像ファイル // 出力の接続 captureSession.addOutput(fileOutput)
上記のコードで設定されたAVCaptureSessionのinputs:[Any]及び、outputs:[Any]!は、次のようになります。
for device in captureSession.inputs {
    print(device)
}
// <AVCaptureDeviceInput: 0x136d3bd50 [Back Camera]>
// <AVCaptureDeviceInput: 0x136d4bb40 [iPhone マイク]>
for output in captureSession.outputs {
    print(output)
}
// <AVCaptureMovieFileOutput: 0x136d32ee0>
また、AVCaptureOutputのconnections:[Any]!プロパティは、次のようになります。
for connection in fileOutput.connections as! [AVCaptureConnection] {
    print(connection)
    for port in connection.inputPorts as! [AVCaptureInputPort] {
        if port.mediaType == AVMediaTypeVideo {
            print("Video")
        }
    }
}
// <AVCaptureConnection: 0x127d3bc40 [type:vide][enabled:1][active:1]>
// Video
// <AVCaptureConnection: 0x127d3dd90 [type:soun][enabled:1][active:1]>
(2) startRunning/stopRunning
入力側と出力側の接続が終わった後、startRunning()を呼び出して入力から出力までのデータフローを開始し、stopRunning()を呼び出してフローを停止します。
なお、startRunning()は、処理が重いため、ブロックしないようにする必要があります。
DispatchQueue.global(qos: .userInitiated).async {
    self.captureSession.startRunning()
}
(3) sessionPreset
sessionPresetプロパティを使用して、キャプチャの品質レベル、ビットレートなどのクオリティを設定できます。 (高フレームレートなどの特殊なオプションは、AVCaptureDeviceで設定)
なお、セッションが実行中の場合は、設定変更の前に、beginConfiguration()を呼ぶ必要があります。そして変更した設定は、commitConfiguration()を呼んだ時点で反映されます。
captureSession.beginConfiguration()
if captureSession.canSetSessionPreset(AVCaptureSessionPresetHigh) {
    captureSession.sessionPreset = AVCaptureSessionPresetHigh
}
captureSession.commitConfiguration()
設定できるキャプチャのクオリティの種類は、次のとおりです。
| Symbol | Description | 
|---|---|
| AVCaptureSessionPresetPhoto | 高解像度の写真品質出力 | 
| AVCaptureSessionPresetHigh | 高品質のビデオおよびオーディオ出力 | 
| AVCaptureSessionPresetMedium | WiFi経由での共有出力ビデオおよびオーディオビットレート | 
| AVCaptureSessionPresetLow | 3G経由での共有出力ビデオおよびオーディオビットレート | 
| AVCaptureSessionPreset320x240 | 320x240ピクセルビデオ出力 | 
| AVCaptureSessionPreset352x288 | CIF画質(352x288ピクセル)ビデオ出力 | 
| AVCaptureSessionPreset640x480 | VGA画質(640x480ピクセル)ビデオ出力 | 
| AVCaptureSessionPreset960x540 | クオータHD品質(960x540ピクセル)ビデオ出力 | 
| AVCaptureSessionPreset1280x720 | 720p画質(1280x720ピクセル)のビデオ出力 | 
| AVCaptureSessionPreset1920x1080 | 1080p品質(1920x1080ピクセル)ビデオ出力 | 
| AVCaptureSessionPreset3840x2160 | 2160p(UHDまたは4Kとも呼ばれる)画質(3840x2160ピクセル)ビデオ出力 | 
| AVCaptureSessionPresetiFrame960x540 | AACオーディオで960x540の高品質iFrame H.264ビデオを約30Mbits/secで実現する設定 | 
| AVCaptureSessionPresetiFrame1280x720 | 1280x720の高品質iFrame H.264ビデオをAACオーディオで約40 Mbits/secで実現する設定 | 
| AVCaptureSessionPresetInputPriority | キャプチャセッションがオーディオおよびビデオ出力設定を制御しないことを指定 | 
API Reference Video Input Presets より
(4) Notificatoin
セッションの開始と停止のタイミングは、次のような通知を受け取る事が可能です。 しかし、通常は、AVCaptureOutputのそれぞれのクラスに用意されているdelegateで処理することが多いようです。
static let AVCaptureSessionDidStartRunning: NSNotification.Name static let AVCaptureSessionDidStopRunning: NSNotification.Name static let AVCaptureSessionWasInterrupted: NSNotification.Name static let AVCaptureSessionInterruptionEnded: NSNotification.Name
5 AVCaptureOutput
AVCaptureOutputを基底クラスとする、出力に使用できるクラスには次のようなものが有ります。
- AVCaptureMovieFileOutput(動画ファイル)
 - AVCaptureAudioFileOutput (音声ファイル)
 - AVCaptureVideoDataOutput(動画フレームデータ)
 - AVCaptureAudioDataOutput(音声データ)
 - AVCapturePhotoOutput(静止画) ※AVCaptureStillImageOutputは、iOS10で廃止となっています。
 - AVCaptureMetadataOutput(メタデータ)
 
各種の出力を使用した例は、別記事とさせて頂きましたので、ぜひ、御覧ください。

[iOS] AVFoundation(AVCaptureMovieFileOutput)を使用したビデオ録画を作ってみた

[iOS] AVFoundation(AVCaptureVideoDataOutput)で連写カメラを作ってみた

 [iOS] AVFoundation(AVCaptureMetadataOutput)でQRコードリーダーを作ってみた

 [iOS] AVFoundation(AVCaptureMetadataOutput)でバーコードリーダーを作ってみた
 
 [iOS] AVFoundation(AVCaptureVideoDataOutput/AVCaptureAudioDataOutput)でVine風の継ぎ足し撮影アプリを作ってみた
 
 [iOS] AVFoundation(AVCaptureVideoDataOutput)+OpenCVで劇画調カメラを作ってみた
6 AVCaptureVideoPreviewLayer
カメラを使用する場合、通常、リアルタイムにカメラに写っている映像を見ながら作業する事になると思います。 このような場合、AVCaptureSessionを引数にして、AVCaptureVideoPreviewLayerオブジェクトを初期化し、表示したいビューのlayoutに追加することで、モニターが可能になります。
下記は、その例です。
既に、captureSession:AVCaptureSessionの入力にはカメラが初期化されており、そのプレビューをmoniteorView:UIViewに表示しています。
if let videoLayer = AVCaptureVideoPreviewLayer.init(session: captureSession) {
    videoLayer.frame = monitorView.bounds
    videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
    monitorView.layer.addSublayer(videoLayer)
}
7 info.plistへの許可
iOS10から、カメラやマイクを使用する場合に、info.plistに記述がないとアプリが落ちてしまいます。
privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
info.plistにカメラとマイクの利用についての記述した例は、次のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSCameraUsageDescription</key>
    <string>ビデオ撮影のためにカメラを使用します。</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>ビデオ撮影のためにマイクを使用します。</string>
・・・省略・・・
参考:[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです
8 最後に
今回、デバイスを入力として AVFoundation を使用する場合の、全体像を纏めてみました。 すべての処理は、AVCaptureSessionを中心に作業しますので、この辺をしっかり押さえておけば、かなり見通しが良くなるのではと感じました。
9 参考資料
API Reference AVCaptureSession
API Reference AVCaptureDevice
API Reference AVCaptureDeviceInput
API Reference AVCaptureOutput
API Reference AVCaptureConnection









