【コラム】

実践! iPhoneアプリ開発

41 ビデオカメラアプリの作り方(2) - ビデオ画像の表示

    木下誠  [2011/06/24]

    前回は、ビデオカメラアプリを作るために必要なセッションの説明をした。メディアのキャプチャを行うには、セッションを作成して、インプットとアウトプットを接続する。アウトプットとしては、AVCaptureVideoDataOutputクラスを利用した。今回は、アウトプットを受け取るところから始めよう。

    AVCaptureVideoDataOutputのデリゲート

    AVCaptureVideoDataOutputクラスは、キャプチャしたデータをそのままアプリに渡してくれるものだ。そのデータを受け取るために、デリゲートメソッドが用意されている。AVCaptureVideoDataOutputSampleBufferDelegateプロトコルで定義される、captureOutput:didOutputSampleBuffer:fromConnection:メソッドだ。宣言は次のようになる。

    List 1.

    - (void)captureOutput:(AVCaptureOutput*)captureOutput 
            didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
            fromConnection:(AVCaptureConnection*)connection;
    

    このメソッドには、引き数が3つある。1つめは、アウトプットのインスタンス。2つめは、CMSampleBufferRefという型のオブジェクトで、ここからキャプチャされたデータを取り出す。3つめは、AVCaptureConnectionというクラスのオブジェクトで、このセッションにおけるインプットとアウトプットの接続状態を表すものだ。

    先に、AVCaptureConnectionを調べてみよう。このクラスを使うと、キャプチャされたビデオやオーディオの状態を知る事ができる。今回はビデオのキャプチャなので、このクラスが持っているプロパティのうち、以下のものに興味がある。

    • videoMirrored
    • supportsVideoMirroring
    • videoOrientation
    • supportsVideoOrientation

    videoMirroredとsupportsVideoMirroringは、ビデオが鏡像表示されているかどうかを表すもの。videoOrientationとsupportsVideoOrientationは、ビデオデータの向きを表すものだ。これらの値によって、取得されたデータをどう取り扱えばいいのかが分かる。

    手元にある、iPhone 4のカメラでこれらの値を確認してみた。すると、videoMirroredとsupportsVideoMirroringは、常にNO。videoOrientationは、バックカメラだとAVCaptureVideoOrientationLandscapeLeft、フロントカメラだとAVCaptureVideoOrientationLandscapeRightという結果だった。つまり、ビデオデータは常に横向きで送られてくることになる。これはデバイスを縦に持っていても、データとしては強制的に横向きにされることを表している。画像処理を行うためにUIImageなどのインスタンスを作るときは、注意が必要だ。

    CMSampleBufferからの画像の取得

    続いて、2つめの引き数であるCMSampleBufferRefを調べてみよう。これは、CMで名前が始まっていることから分かるように、Core Mediaフレームワークのオブジェクトになる。Core Mediaは低レベルのメディアデータに直接アクセスするためのフレームワークだ。CMSampleBufferRefは、そのメディアデータのバッファを表すものになる。

    このバッファからは、ビデオデータの場合CVImageBufferオブジェクトを取り出す事ができる。似たような名前が続いて混乱するかもしれないが、今度はCVで名前が始まっているので、Core Videoフレームワークのオブジェクトになる。CVImageBufferからは、ビデオ画像のピクセルデータを取得する事ができる。

    ピクセルデータさえ取得できれば、これをCore GraphicsのCGImageにすることができ、さらにUIImageにすることができる。こうなれば、自由に画面に貼付けて使えるだろう。

    まとめると、次のような流れになる。

     CMSampleBuffer
    →CVImageBuffer
    →CGImage
    →UIImage
    

    このような変換を行おう。

    ビデオカメラの実現

    では実装だ。AVCaptureVideoDataOutputのデリゲートメソッドを、次のように実装する。

    List 2.

    - (void)captureOutput:(AVCaptureOutput*)captureOutput 
            didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
            fromConnection:(AVCaptureConnection*)connection
    {
        // イメージバッファの取得
        CVImageBufferRef    buffer;
        buffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    
        // イメージバッファのロック
        CVPixelBufferLockBaseAddress(buffer, 0);
    
        // イメージバッファ情報の取得
        uint8_t*    base;
        size_t      width, height, bytesPerRow;
        base = CVPixelBufferGetBaseAddress(buffer);
        width = CVPixelBufferGetWidth(buffer);
        height = CVPixelBufferGetHeight(buffer);
        bytesPerRow = CVPixelBufferGetBytesPerRow(buffer);
    
        // ビットマップコンテキストの作成
        CGColorSpaceRef colorSpace;
        CGContextRef    cgContext;
        colorSpace = CGColorSpaceCreateDeviceRGB();
        cgContext = CGBitmapContextCreate(
                base, width, height, 8, bytesPerRow, colorSpace, 
                kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
        CGColorSpaceRelease(colorSpace);
    
        // 画像の作成
        CGImageRef  cgImage;
        UIImage*    image;
        cgImage = CGBitmapContextCreateImage(cgContext);
        image = [UIImage imageWithCGImage:cgImage scale:1.0f 
                orientation:UIImageOrientationRight];
        CGImageRelease(cgImage);
        CGContextRelease(cgContext);
    
        // イメージバッファのアンロック
        CVPixelBufferUnlockBaseAddress(buffer, 0);
    
        // 画像の表示
        _imageView.image = image;
    }
    

    まず、CMSampleBufferからCVImageBufferを取得しよう。これには、CMSampleBufferGetImageBufferという関数を利用する。

    CVImageBufferを取得したら、処理を始める前にこれをロックしないといけない。ロックしないと、カメラから送られてくるデータで次々と書き換えられてしまう事になる。そこで、CVPixelBufferLockBaseAddressという関数を使ってロックする。

    次に、CVImageBufferの各種情報を取得する。取得しているので、データのベースアドレス、横幅ピクセル数、縦幅ピクセル数、1行あたりのバイト数、だ。

    これらの情報がそろえば、Core Graphicsのイメージコンテキストを作成する事ができる。CGBitmapContextCreateを使って、CGContextを作成しよう。

    後は、CGContextからCGImageを取得し、そこからUIImageを作成する。ただし、このとき1つ気をつける事がある。それは、ビデオデータの向きだ。先ほど説明したように、ビデオデータの向きはAVCaptureConnectionのvideoOrientationから取得できるのだが、それに応じた変換を行わなくてはいけない。そのためだろうか、iOS 4.0からUIImageの初期化メソッドが追加されている。imageWithCGImage:scale:orientation:というものだ。引き数に画像データの向きを指定して、それに合わせて変換してくれるものだ。ここでは、iPhone 4のバックカメラを想定して、UIImageOrientationRightを指定している。

    UIImageの取得ができたら、CVImageBufferを忘れずにアンロックしておこう。これで次のデータが送られてくる。

    取得したUIImageを画面上に貼付ければ、ビデオ画像が表示される事になる。実際にアプリを起動して、デバイスをかざして動かしてほしい。きちんとビデオカメラとして機能する事が分かるだろう。

    キャプチャされる画像のサイズは、AVCaptureSessionのsessionPresetというプロパティで決定される。前回のソースコードでは、AVCaptureSessionPresetMediumを指定していた。指定可能なプリセットと、そのときに取得できる画像サイズの表を示そう。

    • AVCaptureSessionPresetPhoto : 852x640
    • AVCaptureSessionPresetHigh : 1280x720
    • AVCaptureSessionPresetMedium : 480x360
    • AVCaptureSessionPresetLow : 192x144
    • AVCaptureSessionPreset640x480 : 640x480
    • AVCaptureSessionPreset1280x720 : 1280x720

    左からHigh、Medium、Low(クリックで拡大)

    画像サイズが大きくなればなるほど画質は良くなるが、それだけレスポンスも落ちてくる。また、プリセットによって画像のアスペクト比も変化する事に注意してほしい。

    アプリの目的に合わせて適切なサイズを選ぶようにしよう。

    次回は、キャプチャした画像にエフェクトを加えてみよう。

    ここまでのソースコード: VideoCamera-1.zip

    関連したタグ

    新着記事

    特設サイトの情報

      求人情報

      人気記事

      一覧

      イチオシ記事

      新着記事

      特別企画

      転職ノウハウ

      あなたの仕事適性診断

      4つの診断で、自分の適性を見つめなおそう!

      Heroes File ~挑戦者たち~

      働くこと・挑戦し続けることへの思いを綴ったインタビュー

      はじめての転職診断

      あなたにピッタリのアドバイスを読むことができます。

      転職Q&A

      転職に必要な情報が収集できます

      スカウト転職する

      企業からアプローチのメッセージが届きます。

      マイナビニュースマガジン