LoginSignup
159

More than 5 years have passed since last update.

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

Last updated at Posted at 2013-01-08

「カメラ機能をアプリにつけたいけどシャッター音を鳴らしたくない」とか、「カメラ起動時のアニメーションが嫌だ」とか、カメラ機能をもっと自由にカスタマイズしたい場合は、 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 よりダウンロードしていただけます。

(すいません、無料ではなく、85円です)

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

参考書籍

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

iOS4プログラミングブック

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
159