[英]Faster alternative to glReadPixels in iPhone OpenGL ES 2.0
有没有比使用glReadPixels更快的方式来访问帧缓冲区? 我需要对帧缓冲区中的小矩形渲染区域进行只读访问,以便在CPU中进一步处理数据。 性能很重要因为我必须重复执行此操作。 我在网上搜索并发现了一些方法,比如使用像素缓冲区对象和glMapBuffer,但似乎OpenGL ES 2.0不支持它们。
从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的范围内。 用快速纹理高速缓存读取代替它允许我现在以20 FPS编码720p视频,并且瓶颈已经从像素读取转移到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];
有了这个,我使用以下代码配置我将要渲染视频帧的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中使用的像素缓冲池要少。
遗憾的是,这一切都没有记录在任何地方,因为它为视频捕获性能提供了巨大的推动力。
关于atisman提到的关于黑屏的问题,我也有这个问题。 确实用你的纹理和其他设置确保一切都很好。 我试图捕获AIR的OpenGL层,我最后做了,问题是当我没有在应用程序清单中将“depthAndStencil”设置为true时,我的FBO纹理高度为一半(屏幕分割在一半和镜像,我想因为包裹纹理param的东西)。 我的视频是黑色的。
这是相当令人沮丧的,因为基于Brad发布的内容,一旦我在纹理中有一些数据,它应该只是工作。 不幸的是,事实并非如此,一切都必须“正确”才能发挥作用 - 纹理数据并不能保证在视频中看到相同的数据。 一旦我添加了depthAndStencil,我的纹理将自己固定到全高,我开始直接从AIR的OpenGL图层录制视频,没有glReadPixels或任何东西:)
所以,是的,布拉德描述的确实能够在不需要在每个帧上重新创建缓冲区的情况下工作,您只需要确保您的设置正确。 如果你变黑了,可以尝试使用视频/纹理大小或其他一些设置(FBO的设置?)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.