AVFoundationで頻繁にお世話になるCMSampleBufferRef
型のサンプルバッファを、UIImageに変換したい場合の高速&低負荷&シンプルな方法が見つかったので、メモとして残します。
// サンプルバッファからピクセルバッファを取り出す
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// ピクセルバッファをベースにCoreImageのCIImageオブジェクトを作成
CIImage *ciimage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
// CIImageからUIImageを作成
UIImage *image = [UIImage imageWithCIImage:ciimage];
なんとたったの3ステップ。iPhone 6(iOS 8.1.3)だと、720pの420vなサンプルバッファを僅か1ms程度(※後注参照)でUIImageに変換してくれます。いったんUIImageになってしまえばUIImageJPEGRepresentation()
でJPEG画像にするのも簡単ですね。
もう、Appleのこのサンプルコードが使いたいがためにビデオフレームをBGRAフォーマットにしてパフォーマンスダウンに悩まされたり、シェーダーでやらせようとしたらglReadPixels()
が遅いのでOpenGL ES3.0に手を出したり、GPUImageがやっているようなIOサーフェスを使った方法に手を出したりして返り討ちにあうこともありません。
ちなみに、サンプルバッファの一部だけ取り出したい場合はCIContext
クラスのcreateCGImage:fromRect:
メソッドを使うのがお手軽です。いったんCGImageを作ってから、UIImageを作成します。
// ピクセルバッファからCIImageオブジェクトを作成
CIImage *ciimage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
// レンダリングコンテキストを作成して、レンダリング
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef cgimage = \
[context createCGImage:ciimage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(pixelBuffer),
CVPixelBufferGetHeight(pixelBuffer))];
// UIImageを作成
UIImage *uiimage = [UIImage imageWithCGImage:cgimage];
この方法は、CIFilter
クラスでピクセルバッファにエフェクトをかけたい場合にも使えます。
スケール・回転させたい場合は、imageWithCIImage:scale:orientation:
メソッドを使うとよいでしょう。
注意
ピクセルバッファを使ってCIImage化するときの注意点として、CIImageの画像はただちに作られるのではなく、必要になったタイミングでレンダリングされる遅延評価の仕組みで作られていることは注意してください。
CIImage化したまましばらく保持しておくと、pixelBufferはいつまでも保持されたまま(フレームワークが使えないまま)になってしまいますし、場合によっては保持している内容が書き換わってしまうかもしれません。