Alternativa más rápida a glReadPixels en iPhone OpenGL ES 2.0
¿Existe alguna forma más rápida de acceder al búfer de cuadros que usar glReadPixels? Necesitaría acceso de solo lectura a una pequeña área de representación rectangular en el búfer de fotogramas para procesar más los datos en la CPU. El rendimiento es importante porque tengo que realizar esta operación repetidamente. Busqué en la web y encontré algún enfoque como usar Pixel Buffer Object y glMapBuffer, pero parece que OpenGL ES 2.0 no los admite.
A partir de iOS 5.0, ahora existe una forma más rápida de capturar datos de OpenGL ES. No es evidente, pero resulta que el soporte de caché de texturas agregado en iOS 5.0 no solo funciona para cargar rápidamente fotogramas de la cámara a OpenGL ES, sino que también se puede usar a la inversa para obtener acceso rápido a los píxeles sin procesar. dentro de una textura OpenGL ES.
Puede aprovechar esto para capturar los píxeles para una representación de OpenGL ES utilizando un objeto framebuffer (FBO) con una textura adjunta, y esa textura se ha proporcionado desde el caché de texturas. Una vez que renderice su escena en ese FBO, los píxeles BGRA para esa escena estarán contenidos dentro de su CVPixelBufferRef, por lo que no será necesario bajarlos usando glReadPixels()
.
Esto es mucho, mucho más rápido que usarlo glReadPixels()
en mis puntos de referencia. Descubrí que en mi iPhone 4 glReadPixels()
había un cuello de botella al leer fotogramas de vídeo de 720p para codificarlos en el disco. Limitó la codificación para que no se realizara a más de 8-9 FPS. Reemplazar esto con lecturas rápidas de caché de textura me permite codificar video de 720p a 20 FPS ahora, y el cuello de botella se ha movido de la lectura de píxeles al procesamiento OpenGL ES y a partes reales del proceso de codificación de películas. En un iPhone 4S, esto le permite escribir videos de 1080p a 30 FPS completos.
Mi implementación se puede encontrar dentro de la clase GPUImageMovieWriter dentro de mi marco GPUImage de código abierto , pero se inspiró en el artículo de Dennis Muhlestein sobre el tema y la aplicación de muestra ChromaKey de Apple (que solo estuvo disponible en la WWDC 2011).
Empiezo configurando mi AVAssetWriter, agregando una entrada y configurando una entrada de búfer de píxeles. El siguiente código se utiliza para configurar la entrada del búfer de píxeles:
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];
Una vez que tengo eso, configuro el FBO en el que renderizaré mis cuadros de video, usando el siguiente código:
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);
}
Esto extrae un búfer de píxeles del grupo asociado con la entrada de mi escritor de activos, crea y asocia una textura con él y usa esa textura como objetivo para mi FBO.
Una vez que he renderizado un fotograma, bloqueo la dirección base del búfer de píxeles:
CVPixelBufferLockBaseAddress(pixel_buffer, 0);
y luego simplemente introdúzcalo en mi escritor de activos para codificarlo:
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);
}
Tenga en cuenta que en ningún momento leo nada manualmente. Además, las texturas están nativamente en formato BGRA, que es para lo que los AVAssetWriters están optimizados para usar al codificar video, por lo que no hay necesidad de cambiar ningún color aquí. Los píxeles BGRA sin procesar simplemente se introducen en el codificador para crear la película.
Aparte del uso de esto en AVAssetWriter, tengo algo de código en esta respuesta que he usado para la extracción de píxeles sin procesar. También experimenta una aceleración significativa en la práctica en comparación con el uso de glReadPixels()
, aunque menos de lo que veo con el grupo de búfer de píxeles que uso con AVAssetWriter.
Es una pena que nada de esto esté documentado en ninguna parte, porque proporciona un gran impulso al rendimiento de la captura de vídeo.