在iPhone OpenGL ES 2.0中,glReadPixels的替代速度更快

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

从iOS 5.0开始,现在有更快的方式从OpenGL ES中获取数据。 这不是显而易见的,但事实certificate,在iOS 5.0中添加的纹理caching支持不仅适用于将相机帧快速上传到OpenGL ES,而且可以反向使用,以便快速访问原始像素在OpenGL ES纹理中。

您可以利用这个优势,通过使用具有附加纹理的帧缓冲区对象(FBO)来获取OpenGL ES渲染的像素,并从纹理caching中提供该纹理。 一旦将场景渲染到FBO中,该场景的BGRA像素将被包含在CVPixelBufferRef中,因此不需要使用glReadPixels()将其拉下。

这比在我的基准testing中使用glReadPixels()要快得多。 我发现在我的iPhone 4上, glReadPixels()是读取720pvideo帧编码到磁盘的瓶颈。 它限制了超过8-9 FPS的编码。 用快速纹理caching读取代替,可以让我以20 FPS的速度对720pvideo进行编码,瓶颈已经从像素读取转移到了OpenGL ES处理和实际的电影编码部分。 在iPhone 4S上,这可以让您以全速30 FPS编写1080pvideo。

我的实现可以在我的开源GPUImage框架中的GPUImageMovieWriter类中find,但是它受到Dennis Muhlestein关于这个主题和Apple的ChromaKey样例应用程序(仅在WWDC 2011提供) 的文章的启发。

我首先configuration我的AVAssetWriter,添加一个input,并configuration一个像素缓冲区input。 以下代码用于设置像素缓冲区input:

 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]; 

一旦我有了,我将FBOconfiguration成我的video帧,使用下面的代码:

 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); } 

这会从与我的资产编写器input关联的池中拉出一个像素缓冲区,创build并关联一个纹理,并将该纹理用作我的FBO的目标。

一旦我提交了一个帧,我locking了像素缓冲区的基地址:

 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在编码video时被优化使用的,所以不需要在这里做任何颜色的调整。 原始的BGRA像素只是送入编码器制作电影。

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

遗憾的是,这些都没有logging在任何地方,因为它提供了video捕捉性能的巨大提升。

关于黑客提到的东西,我也有这个问题。 确实一切都很好,你的纹理和其他设置。 我试图捕获AIR的OpenGL层,最后我做了这个问题,当我没有在应用程序清单中将“depthAndStencil”设置为true时,我的FBO纹理的高度是一半(屏幕被分割了在一半和镜像,我猜是因为包装纹理参数的东西)。 而我的video是黑色的。

这真是令人沮丧,因为基于Brad发布的内容,只要我有一些纹理数据,就应该可以工作。 不幸的是,情况并非如此,一切都必须“正确”才能正常工作 – 纹理数据并不能保证在video中看到相同的数据。 一旦我添加了depthAndStencil,我的纹理将自己固定到完整的高度,然后我开始直接从AIR的OpenGL层获取video,没有glReadPixels或任何东西:)

所以,布拉德所描述的真的能做到,而不需要在每一帧都重新创build缓冲区,只需要确保你的设置是正确的。 如果你变得黑暗,尝试使用video/纹理尺寸或者其他设置(你的FBO的设置?)。