[iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた 

1 AVFundation

AVFoundationは、音声・動画などのメディアの再生や作成、編集を行うことの出来る巨大なフレームワークです。

入力側をデバイスと考えた場合、AVFoundationは、大まかに、次の3つの構成になっているといえます。

  • デバイス(AVCaptureDevice)
  • セッション(AVCaptureSession)
  • 出力(AVCaptureOutput)

008

AVFoundationを使用するには、まずは、中心となるAVCaptureSessionをインスタンス化します。

そして、このAVCaptureSessionに対して、使用したいデバイスを入力として接続し、利用目的に合致したAVCaptureOutputを出力に接続することで目的を達成します。

例えば、「ビデオを録画してmp3ファイルに保存したい」というような場合は、入力デバイスとして「背面カメラ」及び「マイク」を設定し、出力として「動画ファイル」(AVCaptureMovieFileOutput)を設定すると言った感じです。

下記の図は、その構成を表現したものですが、この時、AVCaptureSessioninputs:[Any]!プロパティには、2つのAVCaptureDeviceオブジェクトが、そして、outputs:[Any]!には、1つのAVCaptureOutputオブジェクトが列挙されることになります。

また、AVCaptureOutputの、connections:[Any]!プロパティでは、それぞれのデバイスとの間のコネクションとして、2つのAVCaptureConnectionが列挙されます。

002

そして、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)

上記のコードで設定されたAVCaptureSessioninputs:[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>

また、AVCaptureOutputconnections:[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(メタデータ)

各種の出力を使用した例は、別記事とさせて頂きましたので、ぜひ、御覧ください。


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

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

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

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

009
[iOS] AVFoundation(AVCaptureVideoDataOutput/AVCaptureAudioDataOutput)でVine風の継ぎ足し撮影アプリを作ってみた

003
[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