シャッター音の鳴らないカメラアプリを実装する

「カメラ機能をアプリにつけたいけどシャッター音を鳴らしたくない」とか、「カメラ起動時のアニメーションが嫌だ」とか、カメラ機能をもっと自由にカスタマイズしたい場合は、 UIImagePickerController を使うのではなく AVFoundation フレームワークを使う必要があります。
    
静止画撮影はデフォルトの挙動としてシャッター音がなってしまうようになっていてそこは変えられないので、**動画モードで撮影を開始し、必要なフレームを静止画として取り込む**ことで「シャッター音の鳴らないカメラ」を実現します。

以下、プレビュー表示と「シャッター音の鳴らない」撮影ボタンだけからなる、シンプルな無音カメラアプリの実装手順を説明します。

##準備
以下のフレームワークをプロジェクトに追加します。
- AVFoundation.framework
- AssetsLibrary.framework
- CoreMedia.framework
- CoreVideo.framework


##ヘッダでの宣言
撮影を制御するのに使用するフラグと、ビットマップ保存領域用のポインタを定義します。

    BOOL isRequireTakePhoto;
    BOOL isProcessingTakePhoto;
    void *bitmap;


また、撮影画像を保持する UIImage 型のプロパティをイメージバッファを定義します。

    @property (nonatomic, retain) UIImage *imageBuffer;


AVCaptureVideoDataOutputSampleBufferDelegate プロトコルへの準拠を宣言します。

    <AVCaptureVideoDataOutputSampleBufferDelegate>



##カメラ初期化処理の実装
まず、撮影画像を保持するためのバッファを確保します。

    // バッファ作成
    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);

次に、カメラデバイスと入力、セッションを初期化します。
    
    // カメラデバイスの初期化
    AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // 入力の初期化
    NSError *error = nil;
    AVCaptureInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice
                                                                         error:&error];

    // セッション初期化
    AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
    [captureSession addInput:captureInput];
    [captureSession beginConfiguration];
    captureSession.sessionPreset = AVCaptureSessionPreset640x480;
    [captureSession commitConfiguration];


出力を初期化します。今回は動画の撮影なので、AVCaptureVideoDataOutput を使います。（静止画の撮影では出力先として AVCaptureStillImageOutput を使います）

    AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    [captureSession addOutput:videoOutput];


デリゲートを下記のように指定します。こうすることで、AVCaptureVideoDataOutputSampleBufferDelegate プロトコルの captureOutput:didOutputSampleBuffer:fromConnection: が毎フレーム呼ばれるようになります。

    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:メソッドが呼ばれるので、そこで必要に応じて画像の保存処理を行います。

ユーザーがシャッターボタンを押すと isRequireTakePhoto フラグが 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;
                
                // メタデータ取得＆orientation情報追記
                CFDictionaryRef metadataRef = CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary"), &attachmentMode);
                NSMutableDictionary *metadata = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)CFBridgingRelease(metadataRef)];                
    
                // ここではorientaionは一定（6）とする
                [metadata setObject:[NSNumber numberWithInt:6]
                             forKey:(NSString *)kCGImagePropertyOrientation];
    
                // フォトアルバムに保存
                ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
                
                [library writeImageToSavedPhotosAlbum:self.imageBuffer.CGImage
                                             metadata:metadata
                                      completionBlock:^(NSURL *assetURL, NSError *error) {
                                          
                                          NSLog(@"URL:%@", assetURL);
                                          NSLog(@"error:%@", error);
                                          isProcessingTakePhoto = NO;
                                      }];
                
                CVPixelBufferUnlockBaseAddress(pixbuff, 0);
            }
        }
    }


##サンプルコード
今回のサンプルコードは、gumroad よりダウンロードしていただけます。

http://gum.co/pzBMA

（すいません、無料ではなく、85円です）

- iOS 6.0 で動作確認しております。  
- コードのみの販売です。サポート等はご容赦ください。


##参考書籍
上記サンプルは、次の書籍を参考に実装しました。

[iOS4プログラミングブック](http://www.amazon.co.jp/exec/obidos/ASIN/4844329766/22301-22/ref=nosim/)

こちらの第３章『マルチメディア』の章とサンプルコードが非常に参考になります。