「カメラ機能をアプリにつけたいけどシャッター音を鳴らしたくない」とか、「カメラ起動時のアニメーションが嫌だ」とか、カメラ機能をもっと自由にカスタマイズしたい場合は、UIImagePickerController を使うのではなく AVFoundation フレームワークを使う必要があります。
先日、UIImagePickerController を使わないカメラアプリの実装方法という記事を書いたのですが、そこでは UIImagePickerController を使わず、AVFoundation を用いて静止画を撮影するところまで書きました。
今回は、シャッター音を鳴らさない、いわゆる「静音カメラ」「マナーカメラ」「無音カメラ」と呼ばれている機能を実装します。
静止画撮影はデフォルトの挙動としてシャッター音がなってしまうようになっていてそこは変えられないので、動画モードで撮影を開始し、必要なフレームを静止画として取り込むことで「シャッター音の鳴らないカメラ」を実現します。
参考書籍及びサンプルコード
今回のサンプルコードは、gumroad よりダウンロードしていただけます。
※すいません、無料ではなく、85円です。
(ほんとは50円ぐらいにしたかったけど、gumroadの制限によりこれ以下には設定できませんでした)
【注意点】
- iOS 6.0 で動作確認しております。
- コードのみの販売です。サポート等はご容赦ください。
また、上記サンプルは、次の書籍を参考に実装しました。
インプレスジャパン
売り上げランキング: 32117
こちらの第3章『マルチメディア』の章とサンプルコードが参考になるかと思います。(本記事の数倍参考になります。おすすめです。)
準備
AVFoundation.framework, AssetsLibrary.framework に加えて、以下のフレームワークをプロジェクトに追加します。
- CoreMedia.framework
- CoreVideo.framework
ヘッダでの宣言
変数
撮影を制御するのに使用するフラグと、ビットマップ保存領域用のポインタを定義します。
BOOL isRequireTakePhoto; BOOL isProcessingTakePhoto; void *bitmap;
プロパティ
@property (nonatomic, retain) UIImage *imageBuffer;
プロトコルへの準拠を宣言
<AVCaptureVideoDataOutputSampleBufferDelegate>
実装
イメージバッファ初期化処理
バッファ確保処理をカメラデバイス初期化コード(参考:http://d.hatena.ne.jp/shu223/20121002/1354424588title=前回記事])の前あたりに書いておきます。
size_t width = 640; size_t height = 480; bitmap = malloc(width * height * 4); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(NULL, bitmap, width * height * 4, NULL); CGImageRef cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProviderRef, NULL, 0, kCGRenderingIntentDefault); self.imageBuffer = [UIImage imageWithCGImage:cgImage]; CGColorSpaceRelease(colorSpace); CGDataProviderRelease(dataProviderRef);
出力関連の初期化処理
前回は静止画の撮影だったので出力先として AVCaptureStillImageOutput を用いましたが、今回は動画の撮影なので、AVCaptureVideoDataOutput を使います。
//self.imageOutput = [[AVCaptureStillImageOutput alloc] init];
AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[captureSession addOutput:videoOutput];
デリゲートの設定
下記のようにデリゲートを指定することで、AVCaptureVideoDataOutputSampleBufferDelegate プロトコルの captureOutput:didOutputSampleBuffer:fromConnection: が毎フレーム呼ばれるようになります。
videoOutput.alwaysDiscardsLateVideoFrames = YES; dispatch_queue_t queue = dispatch_queue_create("com.overout223.myQueue", NULL); [videoOutput setSampleBufferDelegate:self queue:queue]; dispatch_release(queue);
シャッターボタンのアクションを実装
ここでは「写真撮るよフラグ」を立てるだけです。
- (IBAction)pressShutter { if (!isProcessingTakePhoto) { isRequireTakePhoto = YES; } }
プロトコルの実装
カメラが動画のフレームを取得する度に(つまり撮影時の毎フレーム)、AVCaptureVideoDataOutputSampleBufferDelegate プロトコルの captureOutput:didOutputSampleBuffer:fromConnection: メソッドが呼ばれるので、そこで必要に応じて画像の保存処理を行います。
ユーザーがシャッターボタンを押すと isProcessingTakePhoto フラグが YES になっているので、そのときだけカメラロールへの保存処理を行うようにします。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { if (isRequireTakePhoto) { isRequireTakePhoto = NO; isProcessingTakePhoto = YES; CVPixelBufferRef pixbuff = CMSampleBufferGetImageBuffer(sampleBuffer); if(CVPixelBufferLockBaseAddress(pixbuff, 0) == kCVReturnSuccess){ memcpy(bitmap, CVPixelBufferGetBaseAddress(pixbuff), 640 * 480 * 4); CMAttachmentMode attachmentMode; CFDictionaryRef metadataDictionary = CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary"), &attachmentMode); // フォトアルバムに保存 ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init]; [library writeImageToSavedPhotosAlbum:self.imageBuffer.CGImage metadata:(NSDictionary *)CFBridgingRelease(metadataDictionary) completionBlock:^(NSURL *assetURL, NSError *error) { NSLog(@"URL:%@", assetURL); NSLog(@"error:%@", error); isProcessingTakePhoto = NO; }]; CVPixelBufferUnlockBaseAddress(pixbuff, 0); } } }
できあがり
プレビュー表示と「シャッター音の鳴らない」撮影ボタンだけからなる、シンプルな無音カメラアプリのできあがりです。
まとめ
AVFoundation フレームワークを用いて、「シャッター音の鳴らないカメラ」を実装する方法についてご紹介しました。
サンプルコードを見ると、一見長くて難しいですが、「画像の保存」などおなじみの処理、各種初期化等の手順がややこしくて覚えにくい処理等をスニペット化してしまえば、30分といわず10分でもできてしまうかもしれません。
AVFoundationはまだまだまだ探求の余地があるので、また勉強しつつ記事にしたいと思います。