AVFoundationのキャプチャ機能について
AVFoundationによるキャプチャ機能をまとめたいと思います。
まず、普段あまり使うことがないAVFoundationの簡単な紹介です。このフレームワークはAppleが用意する「音声・動画などの時間ベースのメディアの再生や作成、編集の細かい作業を行うための超強力な低レベルObjective-C API」です。
しかし、AVFoundationはおそらくAppleが用意するフレームワークの中でも1,2を争う巨大なフレームワークです。その機能を全て紹介することは難しいため、今回はその中でも音声・画像・動画のキャプチャ機能について実際のコードを絡めて紹介したいと思います。 AVFoundationを利用することでUIImagePickerControllerよりもさらにカスタマイズ可能なカメラやレコーダーを開発することができます。
なお、この記事ではかなり噛み砕いて説明しているため初心者の方でも問題無いですが、AVFoundationの機能を使って開発を行うためには、Objective-Cの基礎文法、ブロック、Foundation, CoreFoundation, retainCount、サブスレッドやキューの作成などの知識があるほうがよいです。
こちらの資料にも簡単にまとめてあるので、こちらを見てもどうぞ。
AVFoundationにおけるキャプチャの全体像
全体像としては以下のようになっています。
真ん中の赤で示しているAVCaptureSessionというものがキャプチャに関連する入出力を管理するクラスです。 次に緑で示しているAVCaptureDeviceというものが前面カメラや背面カメラ、マイクといったデバイスそのものを表現するクラスです。 AVCaptureDeviceをAVCaptureSessionにそのまま接続することはできません。AVCaptureSessionに接続するためのクラスが紫のAVCaptureDeviceInputというクラスです。その名の通り入力を表現します。 青で示しているAVCaptureOutputというものが出力方法を表現するクラスです。画像、動画、フレームデータ、音声データ、メターデータなど様々な出力クラスが用意されています。 AVCaptureOutputとAVCaptureSessionの間の黒い矢印はAVCaptureConnectionというクラスです。入力データの向きを変更したりできます。
AVCaptureSession
このクラスはキャプチャに関する入力と出力の管理を行います。
self.session = [[AVCaptureSession alloc] init];
で生成することができます。
セッションに対してキャプチャクオリティの設定ができます。
@property(nonatomic, copy) NSString *sessionPreset;
AVCaptureSessionPresetHigh, AVCaptureSessionPresetMedium, AVCaptureSessionPresetLow, AVCaptureSessionPresetPhotoなどを設定することができます。 AVCaptureSessionPresetPhotoは静止画のキャプチャに対してのみ有効でそのデバイスの最大解像度でキャプチャできます。フレームデータや動画のキャプチャに対しては有効になりません。
セッションの設定を変更する際は
- (void)beginConfiguration;
を呼び、変更し終わった後に、
- (void)commitConfiguration;
を呼ぶようにしてください。
後述するInputやOutputを追加し準備ができた段階で
- (void)startRunning
を呼ぶとセッションが動き出し実際にデータが取得できるようになります。忘れないようにしてください。 またこのメソッド非常に遅いため非同期でも問題ないなら非同期で呼ぶことをおすすめします。
AVCaptureDevice
このクラスはカメラやマイクといったデバイスそのものを表現します。
背面カメラを取得するのであれば
self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
となります。
マイクを取得するのであれば
self.microphone = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
となります。
背面カメラか前面カメラの判定は以下のプロパティを使用します。
@property(nonatomic, readonly) AVCaptureDevicePosition position;
任意のポジションのカメラを取得したい場合は
for (AVCaptureDevice *camera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { if (camera.position == AVCaptureDevicePositionFront) { // 前面カメラ } else if (camera.position == AVCaptureDevicePositionBack) { // 背面カメラ } }
といったコードになります。
iPhoneのカメラには様々な設定をすることができます。 露出モード
@property(nonatomic) AVCaptureExposureMode exposureMode;
フォーカスモード
@property(nonatomic) AVCaptureFocusMode focusMode;
ホワイトバランスモード
@property(nonatomic) AVCaptureWhiteBalanceMode whiteBalanceMode;
フォーカス位置
@property(nonatomic) CGPoint focusPointOfInterest;
などなど他にもフラッシュやトーチなど多くの設定ができます。 また、各プロパティに値をセットすることができるか確認するメソッドがあるので、必ず確認してから値をセットするようにしてください。 例えばiPadでフラッシュをONにしようとするとクラッシュします。
AVCaptureDeviceに対して何かしらの設定を行う場合はデバイスをロックしなければなりません。
- (void)lockForConfiguration:(NSError **)error
でロックし、設定が終わり次第
- (void)unlockForConfiguration
でロックを解除します。
AVCaptureDeviceInput
このクラスは指定したデバイスをセッションに入力するときに使います。
self.cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.camera error:NULL];
で生成することができます。
[self.session addInput:self.cameraInput]
とすることでセッションにカメラが入力機器として接続された状態になります。
この状態でセッションのstartRunningメソッドを呼ぶことでカメラからのデータがセッションに入ります。
AVCaptureOutput
このクラスには様々なサブクラスが用意されています。
- AVCaptureStillImageOutput(静止画)
- AVCaptureMovieFileOutput(動画ファイル)
- AVCaptureAudioFileOutput (音声ファイル)
- AVCaptureVideoDataOutput(動画フレームデータ)
- AVCaptureAudioDataOutput(音声データ)
任意の出力方法を選びセッションに追加することで画像や動画、データがキャプチャできます。
AVCaptureStillImageOutput
カメラからの入力から静止画をキャプチャします。
self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
で生成できます。 iOS7以降は手ぶれ補正をオンにすることができます。
@property(nonatomic) BOOL automaticallyEnablesStillImageStabilizationWhenAvailable
あとはセッションに追加するだけです。
[self.session addOutput:self.stillImageOutput];
画像をキャプチャする
- (void)captureStillImageAsynchronouslyFromConnection:(AVCaptureConnection *)connection completionHandler:(void (^)(CMSampleBufferRef imageDataSampleBuffer, NSError *error))handler;
を呼ぶと非同期で静止画がキャプチャされ、結果がimageDataSampleBufferという形で取得できます。 このままではUIKitで使いづらいため、NSDataに変換するメソッドが用意されています。
(NSData *)jpegStillImageNSDataRepresentation:(CMSampleBufferRef)jpegSampleBuffer
です。 NSDataにはExifなどのメタデータも含まれています。
AVCaptureMovieFileOutput
このクラスは簡単に動画をキャプチャすることができます。
self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
で生成できます。 撮影時間は以下のプロパティで取得できます。
@property(nonatomic, readonly) CMTime recordedDuration;
その他様々なプロパティが用意されています。
あとはセッションに追加するだけです。
[self.session addOutput:self.movieFileOutput];
動画のキャプチャを開始を開始するには以下のメソッドを呼びます。
[self.movieFileOutput startRecordingToOutputFileURL:self.movieURL recordingDelegate:self];
停止するには以下のメソッドを呼びます
[self.movieFileOutput stopRecording];
開始停止のメソッドを呼ぶと対応したAVCaptureFileOutputRecordingDelegateメソッドが呼ばれます。
キャプチャを停止した場合、以下のデリゲートメソッドが呼ばれます。
(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
outputFileURLに動画ファイルが保存されています。
AVCaptureAudioFileOutput
AVCaptureMovieFileOutputと同じようなものなので省きます。
AVCaptureVideoDataOutput
このクラスは入力機器から入ってくる映像の各フレームデータをそのまま取得できます。
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
で生成します。
また、映像の設定として以下の辞書をセットします。
self.videoDataOutput.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
実際にフレームを取得し始めるには
self.videoDataQueue = dispatch_queue_create("jp.co.xxx.videoDataQueue", DISPATCH_QUEUE_SERIAL); [self.videoDataOutput setSampleBufferDelegate:self queue:self.videoDataQueue];
と、サブスレッド用のシリアルキューを用意しそこで処理をさせます。フレームデータは1秒に何度も取得するためメインスレッドを止めないような設計になっています。
あとはセッションに追加するだけです。
[self.session addOutput:self.videoDataOutput];
各フレームデータはAVCaptureVideoDataOutputSampleBufferDelegateの
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
でsampleBufferという形で取得できます。sampleBufferからUIImageに変換することもできます。 ただし、このメソッド内で重い処理をしないようにしましょう。1秒間に30回前後呼ばれます。
AVCaptureAudioDataOutput
AVCaptureVideoDataOutputと同じようなものなので省きます。
すべてのoutputについて生成後、セッションに追加するのを忘れないようにしてください。また、セッションを走らせなければ実際にデータは取得できません。そこも忘れないようにしてください。
AVCaptureVideoPreviewLayer
このクラスはカメラからの映像をユーザに見せるプレビュー画面を簡単に作成できます。
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session]; previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; previewLayer.frame = self.view.bounds;
で生成でき、あとは任意のビューに追加するだけです。
以上の知識のみで自由度の高いカメラアプリを開発することができます。細かなTipsはたくさんありますが、今回はここで終わりにしたいと思います。