从 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's article on the subject 和 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 中使用它之外,我在 this answer 中有一些代码用于原始像素提取。与使用 glReadPixels() 相比,它在实践中也经历了显着的加速,尽管比我使用 AVAssetWriter 使用的像素缓冲池要少。
遗憾的是,这些都没有记录在任何地方,因为它极大地提高了视频捕获性能。