更快速地替代iPhone OpenGL ES 2.0中的glReadPixels

ati*_*man 66 iphone opengl-es ios opengl-es-2.0

有没有比使用glReadPixels更快的方式来访问帧缓冲区?我需要对帧缓冲区中的小矩形渲染区域进行只读访问,以便在CPU中进一步处理数据.性能很重要因为我必须重复执行此操作.我在网上搜索并发现了一些方法,比如使用像素缓冲区对象和glMapBuffer,但似乎OpenGL ES 2.0不支持它们.

Bra*_*son 132

从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的范围内.与快速纹理缓存更换这种读取允许我720p视频在20 FPS编码,现在,瓶颈已经从像素读取到的OpenGL ES处理和管道的实际电影编码部分移动.在iPhone 4S上,您可以以30 FPS的速度写入1080p视频.

我的实现可以在我的开源GPUImage框架中的GPUImageMovieWriter类中找到,但它的灵感来自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];
Run Code Online (Sandbox Code Playgroud)

有了这个,我使用以下代码配置我将要渲染视频帧的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);
}
Run Code Online (Sandbox Code Playgroud)

这将从与我的资产编写器输入关联的池中提取像素缓冲区,创建纹理并将其与其关联,并将该纹理用作我的FBO的目标.

一旦我渲染了一个帧,我就锁定了像素缓冲区的基地址:

CVPixelBufferLockBaseAddress(pixel_buffer, 0);
Run Code Online (Sandbox Code Playgroud)

然后简单地将其提供给我的资产编写器进行编码:

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里我没有手动阅读任何内容.此外,纹理本身采用BGRA格式,这是AVAssetWriters在编码视频时优化使用的,因此不需要在这里进行任何颜色调整.原始BGRA像素仅被馈入编码器以制作电影.

除了在AVAssetWriter中使用它之外,我在这个答案中有一些代码用于原始像素提取.与使用相比,它在实践中也经历了显着的加速glReadPixels(),尽管比我在AVAssetWriter中使用的像素缓冲池要少.

遗憾的是,这一切都没有记录在任何地方,因为它为视频捕获性能提供了巨大的推动力.