web-dev-qa-db-ja.com

iPhone OpenGL ES 2.0のglReadPixelsのより高速な代替

GlReadPixelsを使用するよりも高速にフレームバッファにアクセスする方法はありますか? CPUでデータをさらに処理するには、フレームバッファーの小さな長方形のレンダリング領域への読み取り専用アクセスが必要です。この操作を繰り返し実行する必要があるため、パフォーマンスが重要です。 Webを検索して、Pixel Buffer ObjectとglMapBufferを使用するようなアプローチを見つけましたが、OpenGL ES 2.0はそれらをサポートしていないようです。

66
atisman

IOS 5.0以降、OpenGL ESからデータを取得するより高速な方法があります。すぐにはわかりませんが、iOS 5.0で追加されたテクスチャキャッシュのサポートは、カメラフレームをOpenGL ESに高速アップロードするだけでなく、逆に使用して生のピクセルにすばやくアクセスできることがわかりましたOpenGL ESテクスチャ内。

これを利用して、テクスチャがアタッチされたフレームバッファオブジェクト(FBO)を使用してOpenGL ESレンダリングのピクセルを取得し、そのテクスチャはテクスチャキャッシュから提供されます。シーンをそのFBOにレンダリングすると、そのシーンのBGRAピクセルはCVPixelBufferRef内に含まれるため、glReadPixels()を使用してそれらをプルダウンする必要はありません。

これは、私のベンチマークでglReadPixels()を使用するよりもはるかに高速です。 iPhone 4では、glReadPixels()がディスクへのエンコードのために720pビデオフレームを読み取る際のボトルネックであることがわかりました。 8-9 FPSを超える場所でエンコードが行われるのを制限しました。これを高速テクスチャキャッシュ読み取りに置き換えることで、720 Fのビデオを20 FPSでエンコードできるようになり、ボトルネックがピクセル読み取りからOpenGL ES処理およびパイプラインの実際のムービーエンコード部分に移動しました。 iPhone 4Sでは、これにより、フル30 FPSで1080pビデオを書き込むことができます。

私の実装は、オープンソースのGPUImageMovieWriterクラス内で見つけることができます GPUImage フレームワークですが、それは 主題に関するDennis Muhlesteinの記事 とAppleのChromaKeyサンプルアプリケーション(これはWWDC 2011で入手可能)。

AVAssetWriterの構成、入力の追加、ピクセルバッファー入力の構成から始めます。次のコードを使用して、ピクセルバッファ入力を設定します。

_NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                                       [NSNumber numberWithInt:videoSize.width], kCVPixelBufferWidthKey,
                                                       [NSNumber numberWithInt:videoSize.height], kCVPixelBufferHeightKey,
                                                       nil];

assetWriterPixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterVideoInput sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
_

それができたら、次のコードを使用して、ビデオフレームをレンダリングするFBOを構成します。

_if ([GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[[GPUImageOpenGLESContext sharedImageProcessingOpenGLESContext] context], NULL, &coreVideoTextureCache);
    if (err) 
    {
        NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d");
    }

    CVPixelBufferPoolCreatePixelBuffer (NULL, [assetWriterPixelBufferInput pixelBufferPool], &renderTarget);

    CVOpenGLESTextureRef renderTexture;
    CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
                                                  NULL, // texture attributes
                                                  GL_TEXTURE_2D,
                                                  GL_RGBA, // opengl format
                                                  (int)videoSize.width,
                                                  (int)videoSize.height,
                                                  GL_BGRA, // native iOS format
                                                  GL_UNSIGNED_BYTE,
                                                  0,
                                                  &renderTexture);

    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_Edge);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_Edge);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
}
_

これにより、アセットライターの入力に関連付けられたプールからピクセルバッファーが取得され、テクスチャが作成されて関連付けられ、そのテクスチャがFBOのターゲットとして使用されます。

フレームをレンダリングしたら、ピクセルバッファーのベースアドレスをロックします。

_CVPixelBufferLockBaseAddress(pixel_buffer, 0);
_

それをエンコードするためにアセットライターに入力します。

_CMTime currentTime = CMTimeMakeWithSeconds([[NSDate date] timeIntervalSinceDate:startTime],120);

if(![assetWriterPixelBufferInput appendPixelBuffer:pixel_buffer withPresentationTime:currentTime]) 
{
    NSLog(@"Problem appending pixel buffer at time: %lld", currentTime.value);
} 
else 
{
//        NSLog(@"Recorded pixel buffer at time: %lld", currentTime.value);
}
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);

if (![GPUImageOpenGLESContext supportsFastTextureUpload])
{
    CVPixelBufferRelease(pixel_buffer);
}
_

ここでは、手動で何かを読んでいないことに注意してください。また、テクスチャはネイティブでBGRA形式であり、これはAVAssetWritersがビデオをエンコードするときに使用するために最適化されているため、ここで色を切り替える必要はありません。生のBGRAピクセルはエンコーダーに入力され、ムービーが作成されます。

AVAssetWriterでこれを使用することとは別に、 この答え にいくつかのコードがあり、それを生のピクセル抽出に使用しました。また、glReadPixels()を使用する場合と比較した場合、実際にはAVAssetWriterで使用するピクセルバッファープールで見るよりも大幅に高速化されます。

ビデオキャプチャのパフォーマンスが大幅に向上するため、これがどこにも記載されていないのは残念です。

131
Brad Larson

黒い画面についてアティスマンが言ったことに関して、私もその問題を抱えていました。テクスチャやその他の設定ですべてが正常であることを本当に確認してください。最終的にAIRのOpenGLレイヤーをキャプチャしようとしていましたが、問題は、アプリマニフェストで誤って「depthAndStencil」をtrueに設定しなかった場合、FBOテクスチャが半分の高さであったことです(画面が分割されました半分でミラーリングされているので、ラップテクスチャパラメータなどが原因です。そして、私のビデオは黒でした。

ブラッドが投稿している内容に基づいて、テクスチャにデータを入れてしまえばうまくいくはずだったので、それはかなりイライラしていました。残念ながら、そうではありません。すべてが機能するためには「正しい」必要があります。テクスチャ内のデータは、ビデオ内の同じデータを見るための保証ではありません。 depthAndStencilを追加すると、テクスチャがフルハイトに固定され、AIRのOpenGLレイヤーから直接ビデオ録画を開始しました。glReadPixelsなどは何もありません:)

ですから、Bradが実際に説明していることは、すべてのフレームでバッファーを再作成することなく機能します。セットアップが正しいことを確認するだけです。黒味が出ている場合は、ビデオ/テクスチャサイズまたは他の設定(FBOのセットアップ?)で再生してみてください。

0
user2979732