Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
160
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Organization

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

「カメラ機能をアプリにつけたいけどシャッター音を鳴らしたくない」とか、「カメラ起動時のアニメーションが嫌だ」とか、カメラ機能をもっと自由にカスタマイズしたい場合は、 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章『マルチメディア』の章とサンプルコードが非常に参考になります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
160
Help us understand the problem. What are the problem?