AVFoundation: 静止画像やビデオ画像の撮影 †デバイス(カメラやマイクなど)からデータをキャプチャ処理するには、 AVCaputeSession を用いる。
セッション AVCaptureSession †
//[Swift] var session: AVCaptureSession! session = AVCaptureSession() ... session.startRunning() ... session.stopRunning() //[Objective-C] AVCaptureSession *session = [[AVCaptureSession alloc] init]; ... [session startRunning] ... [session stopRunning] セッションの設定 †
//[Swift] var session: AVCaptureSession! ... if session.canSetSesstionPreset(AVCaptureSessionPresetHight) { session.sessionPreset = AVCaptureSessionPresetHigh } else { ... } ... mySession.beginConfiguration() ... mySession.commitConfiguration() //[Objective-C] if ([session canSetSessionPreset: AVCaptureSessionPreset1280x720]) { session.sessionPreset = AVCaptureSessionPreset1280x720; } ... [session beginConfiguration] // 設定変更 [session commitConfiguration] 入力デバイス AVCaptureDevice †
//[Swift] バック・カメラ・デバイスを取り出す var camera: AVCaptureDevice! let devices = AVCaptureDevice.devices() for device in devices { if (device.position == AVCaptureDevicePosition.Back) { camera = device as! AVCaptureDevice } } //[Objective-C] バック・カメラ・デバイスを取り出す NSArray *devices = [AVCaptureDevice devices]; for (AVCaptureDevice* device in devices) { NSLog(@"Device name: %@", [device localizedName]); if ([device hasMediaType: AVMediaTypeVideo]) { if ([device position] == AVCaptureDevicePositionBack) { NSLog(@"Device position: back"); } else { NSLog(@"Device position: front"); } } } //[Objective-C]トーチ・モードを備え 640x480 のビデオ撮影が可能なデバイスを探す。結果は torchDevices 配列に入れる。 NSArray *devices = [AVCaptureDevice deviceWithMediaType: AVMediaTypeVideo]; NSMutableArray *torchDevices = [[NSMutableArray alloc] init]; for (AVCaptureDevice *device in devices) { if ([device hasTorch] && [device supportsAVCaptureSessionPreset: AVCaptureSessionPreset640x480]) { [touchDevices addObject:device]; } } フォーカス・モード †
露出モード †
フラッシュ・モード †
トーチ・モード †フラッシュが低電力で点灯したままになる。
デバイスの設定 †
//[Objective-C] デバイスの設定を変更する if ([device isFocusModeSupported: AVCaptureFocusModeLocked]) { NSError *error = nil; if ([device lockForConfiguration: &error]) { device.focusMode = AVCaptureFocusModeLocked; [device unlockForConfiguration]; } } デバイスの切り替え †一旦デバイスを削除してから、別のデバイスを追加する。
//[Swift] var session: AVCaptureSession! var frontFacingCameraDeviceInput: AVCaptureDeviceInput! var backFacingCameraDeviceInput: AVCaptureDeviceInput! session.beginConfiguration() session.removeInput(frontFacingCameraDeviceInput) session.addInput(backFacingCameraDeviceInput) session.commitConfiguration() //[Objective-C] AVCaptureSession *session = ...; [session beginConfiguration] [session removeInput: frontFacingCameraDeviceInput]; [session addInput: backFacingCameraDeviceInput]; [session commitConfiguration] 入力 AVCaptureDeviceInput †セッションが入力として扱うのは、入力デバイスの入力ポートである。 セッションに addInput() を用いて AVCaptureDeviceInputオブジェクトを追加する。 セッションは AVCaptureConnectionオブジェクトを用いて、AVCaptureInputPortとAVCaptureOutputの対応を定義する。 //[Swift] var session: AVCaptureSession! var input: AVCaptureDeviceInput! ... if session.canAddInput(input) { session.addInput(input) } AVCaptureSession *session = [[AVCaptureSession alloc] init]; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error] ... if ([session canAddInput: input]) { [session addInput: input]; } 出力 AVCaptureOutput †セッションに出力として追加するのは AVCaptureOutput クラスのサブクラスである。
セッションの addOutput() メソッドを用いて出力を追加する。 //[Swift] var session: AVCaptureSession! var output: AVCaptureMovieFileOutput! ... if session.canAddOutput(output) { session.addOutput(output) } //[Objective-C] AVCaptureSession *session = ... AVCaptureMovieFileOutput *output = ... if ([session canAddOutput: output]) { [session addOutput: output] } ビデオをフレーム単位で処理する場合 AVCaptureVideDataOutput †AVCaptureVideDataOutput オブジェクトは、デリゲートを使用してビデオフレームを提供する。
BGRAフォーマットは CoreGraphics でも OpenGL でも正常に動作する。(すなわちこのフォーマットを使えということか...) //[Swift] class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate { ... var session: AVCaptureSession! ... var output: AVCaptureVideoDataOutput! output = AVCaptureVideoDataOutput() output.videoSettings = [kCVPixelBufferPixelFormatTypeKey : Int(kCVPixelFormatType_32BGRA)] output.setSampleBufferDelegate(self,queue:dispatch_get_main_queue()) output.alwaysDiscardsLateVideoFrames = true if (session.canAddOutput(output)) { session.addOutput(myVideoOutput) } //[Objective-C] AVCaptureVideDataOutput *output = [AVCaptureVideDataOutput new]; NSDictionary *dic = @{ (NSString *) kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }; output.videoSettiong = dic; // 出力queueがブロックされていたら捨てる (静止画を撮影中などの場合) [output setAlwaysDiscardsLateVideoFrames: YES]; // 同期dispatch queueを作成する。サンプリング・バッファのデリゲートで使うため。 videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL); [output setSampleBufferDelegate: self queue: videoDataOutputQueue]; ... AVCaptureSession *session = ... if ([session canAddOutput: videoDataOutput]) { [session addOutput: videoDataOutput]; } デリゲートのメソッドは captureOutput:didOutputSampleBuffer である。 ビデオ・フレームの方向を整えたあとで、sampleBuffer の内容を UIImage に変換する。 変換方法は以下の通り。 //[Swift] @IBOutlet weak var imageView: UIImageView! ... func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) { if connection.supportsVideoOrientation { // 画像の向きを設定する connection.videoOrientation = AVCaptureVideoOrientation.Portrait } dispatch_async(dispatch_get_main_queue(), { // メイン・キューの上で実行する let image = self.imageFromSampleBuffer(sampleBuffer) // サンプルバッファをUIImageに変換して self.imageView.image = image; // Image View に表示する。 }) } ビデオ・フレームの向きを指定する †//[Swift] if connection.supportsVideoOrientation { connection.videoOrientation = AVCaptureVideoOrientation.Portrait // .Portrait, .LandscapeRight, .LandscapeLeft or .PortraitUpsideDown } ビデオ・フレームをUIImageへ変換する †ビデオ・フレームは CMSampleBufferRef 型で渡されてくる。 これを UIImage に変換する方法は次の通り。 func imageFromSampleBuffer(sampleBuffer: CMSampleBufferRef) -> UIImage { let imageBuffer: CVImageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)! CVPixelBufferLockBaseAddress(imageBuffer, 0) // Lock Base Address let baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0) // Get Original Image Information let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) let width = CVPixelBufferGetWidth(imageBuffer) let height = CVPixelBufferGetHeight(imageBuffer) let colorSpace = CGColorSpaceCreateDeviceRGB() // RGB ColorSpace let bitmapInfo = (CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) let context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) let imageRef = CGBitmapContextCreateImage(context) // Create Quarts image CVPixelBufferUnlockBaseAddress(imageBuffer, 0) // Unlock Base Address let resultImage: UIImage = UIImage(CGImage: imageRef!) return resultImage } 静止画像をキャプチャする場合 AVCaptureStillImageOutput †AVCaptureStillImageOutput 出力を利用する。 静止画像のフォーマットとコーデックの設定
//[Swift] var session: AVCaptureSession! ... var output: AVCaptureStillImageOutput! output = AVCaptureStillImageOutput() session.addOutput(output) //[Objective-C] AVCaptureStilImageOutput *output = [[AVCaptureStillImageOutput alloc] init]; NSDictionary *dic = @{ AVVideoCodecKey: AVVideoCodecJPEG }; [output setOutputSettings: dic]; 静止画像のキャプチャ 出力のメソッド
jpeg画像をキャプチャする場合は、圧縮フォーマットを指定せずデフォルトに任せた方がよい。 そのjpeg 画像に jpegStillImageNSDataRepresentaion: メソッドを使用すると、 再圧縮せずにNSDataオブジェクトが取得できる。 //[Swift] @IBOutlet weak var imageView; UIImageView! ... var output : AVCaptureStillImageOutput! ... var connection: AVCaptureConnecton! connection = output.connectionWithMediaType(AVMediaTypeVideo) output.captureStillImageAsynchronouslyFromConnection(connection, completionHandler: { (imageDataBuffer,error) -> Void in let jpgData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataBuffer) let image: UIImage! = UIImage(data:jpgData) // このimageを利用すればよい self.imageView.image = image }) //[Objective-C] 入力ポートがビデオをサポートしているコネクションを探す AVCaptureConnection *videoConnection = nil; for (AVCaptureConnection *conn in stillImageOutput.connections) { for (AVCaptureInputPort *port in [conn inputPorts]) { if ([[port mediaType] isEqual: AVMediaTypeVideo]) { videoConnection = conn; break; } } if (videoConnection) break; } ... AVCaptureStillImageOutput *output = ... [output captureStillImageAsynchronouslyFromConnection: videoConnection completionHandler: ^(CMSampleBufferRef buf, NSError *error) { CFDictionaryRef exif = CMGetAttachment(buf,kCGImagePropertyExifDictionary,NULL); if (exif) { // アタッチメントを使う } ... }]; ムービーファイルに保存する場合 AVCaptureMovieFileOutput †出力の解像度とビットレートは sessionPreset によって異なる。 通常のエンコードは、ビデオは H.264、オーディオは AAC である。 //[Objective-C] AVCaptureMovieFileOutput *output = [[AVCaptureMovieFileOutput alloc] init]; output.maxRecordedDuration = ... output.minFreeDiskSpaceLimit = ... 録画の開始 †
ファイルの書き込みの確認 †
よくあるエラーの種類
//[Objective-C] - (void) captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL: (NSURL *)outputFileURL fromConnections: (NSArray *)connections error: (NSError *) error { BOOL success = YES; if ([error code] != noErr) { id value = [[error userInfo] objectForKey: AVErrorRecordingSuccessfullyFinishedKey]; if (value) { success = [value boolValue]; } } // success の値で判断できる ... } ファイルへメタデータを追加する †ファイル出力のメタデータは AVMetadataItem オブジェクトの配列である。 このサブクラスの AVMutableMetadataItem を用いて独自のメタデータが作成できる。 //[Objective-C] AVCaptureMovieFileOutput *output = ... NSArray *oldMeatadata = output.metadata; NSMutableArray *metadata = nil; if (oldMetadata) { metadata = [oldMetadata mutableCopy]; } else { metadata = [[NSMutableArray alloc] init]; } AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init]; item.keySpace = AVMetadataKeySpaceCommon; item.key = AVMetadataCommonKeyLocation; CLLocation *loc = ... item.value = [NSString stringWithFormat:@"%+08.41f%+09.4lf/" loc.coordinate.latitude, loc.coordinate.longiture]; [metadata addObject: item]; output.metadata = metadata; 音声データを処理する場合 AVCaptureAudioDataOutput †略 ユーザに対する録画内容の表示 †カメラで録画している内容をユーザに提示するには、ブレビューレイヤを使用する。 マイクで録音中の音声をユーザに提示するには、オーディオチャンネルを監視する。 ビデオのプレビュー †CALayer のサブクラスのAVCaptureVideoPreviewLayerオブジェクトを使用してプレビューを表示する。 AVCaptureVideoDataOutputを用いて、表示する前にビデオのピクセルにアクセスできる。 //[Swift] 背景全体にプレビューを表示する var session: AVCaptureSession! ... let layer = AVCaptureVideoPreviewLayer(session: session) layer.frame = view.bounds layer.videoGravity = AVLayerVideoGravityResizeAspectFill view.layer.insertSublayer(layer,atIndex:0) //[Objective-C] AVCaptureSession *session = ... CALayer *layer = ... AVCaptureVidePreviewLayer *preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession: session]; [layer addSublayer: preview]; プレビュー・レイヤは他のレンダリング・ツリー内の他の CALayer オブジェクトと同じように動作し、他のレイヤと同じく拡大縮小・回転・平行移動などができる。 ただひとつ異なる点が、画像の回転方向を指定するためにレイヤのorientationプロパティを設定する必要がある場合があることである。 ミラーリングが可能かどうかは supportsVideoMirroring プロパティで判断でき、 にミラーリングするときは videoMirrored プロパティを設定する (automaticallyAdjustVideoMirroringプロパティがYESならば自動的に設定される)。 videoGravityに次の値を設定できる。
オーディオレベルの表示 †オーディオ・チャネルの平均出力レベルやピーク出力レベルを監視するには、AVCaptureAudioChannelオブジェクトを使用する。 値を調べたいタイミングでポーリングする必要がある。 //[Objective-C] AVCaptureAudioDataOutput *output = ... NSArray *connections = output.connections; if ([connections count] > 0) { // AVCaptureAudioDataOutput ではコネクションは1個だけしか持てない AVCaptureConnection *conn = [connections objectAtIndex: 0]; NSArray channels = conn.audioChannels; for (AVCaptureAudioChannel) *chan in channels) { float avg = chan.averagePower; float peak = chan.peakHoldLevel; // 平均とピークの値を表示する } } UIImageオブジェクトとしてのビデオフレームのキャプチャ †ビデオをキャプチャし、取得したフレームをUIImageオブジェクトに変換する方法は以下の通り。
録画の開始と停止 †録画が許可されているか調べる //[Objective-C] NSString *type = AVMediaTypeVideo; [AVCaptureDevice requestAccessForMediaType: type completionHander: ^(BOOL granted) { if (granted) { [self setDeviceAuthorized: YES] } else { dispatch_sync(dispatch_get_main_queue(), ^{ [[[UIAlertView alloc] initWithTitle: @"Caution" message: @"permission denied to use Camera" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil] show] [self setDeviceAuthorized: NO]; }); } }] カメラセッションが設定されていて、ユーザがアクセスを承認している場合は、startRunning で録画を開始する。 startRunningメソッドはブロック型で多少時間がかかるため、 セッションの設定処理を同期キュー(synchronized queue)上で実行し、 メインキューがブロックされないようにする必要がある。 録画の停止には stopRunning メソッドを使う。 再生 †AVPayerのインスタンスは setRate: メソッドで値を設定して再生速度を変更することができる。 AVPlayerItemオブジェクトの audioTimePitchAlgorithm プロパティには、 音声再生方法(Time Pitch Algorithm Settings)を示す定数を設定する。 |