Una alternativa más rápida a glReadPixels en iPhone OpenGL ES 2.0
ios opengl-es (2)
A partir de iOS 5.0, ahora hay una forma más rápida de obtener 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 la carga rápida de marcos de cámara a OpenGL ES, sino que se puede usar en reversa para obtener un acceso rápido a los píxeles brutos dentro de una textura OpenGL ES.
Puede aprovechar esto para tomar los píxeles de un renderizado de OpenGL ES utilizando un objeto framebuffer (FBO) con una textura adjunta, con esa textura provista desde la memoria caché de texturas. Una vez que renderice su escena en ese FBO, los píxeles BGRA para esa escena estarán contenidos en su CVPixelBufferRef, por lo que no será necesario glReadPixels()
usando glReadPixels()
.
Esto es mucho, mucho más rápido que usar glReadPixels()
en mis puntos de referencia. Descubrí que en mi iPhone 4, glReadPixels()
era el cuello de botella en la lectura de marcos de video de 720p para la codificación en el disco. Limitó la codificación de producirse a cualquier valor superior a 8-9 FPS. Reemplazar esto con las lecturas rápidas de caché de texturas me permite codificar videos de 720p a 20 FPS ahora, y el cuello de botella se ha movido de la lectura de píxeles al procesamiento de OpenGL ES y a la película real que codifica partes de la tubería. En un iPhone 4S, esto le permite escribir videos 1080p a 30 FPS completos.
Mi implementación se puede encontrar dentro de la clase GPUImageMovieWriter dentro de mi marco GPUImage código abierto, pero fue inspirada por 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).
Comienzo configurando mi AVAssetWriter, agregando una entrada y configurando una entrada de buffer de píxeles. El siguiente código se usa para configurar la entrada del buffer 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 al que voy a renderizar mis cuadros de video, utilizando 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 cuadro, bloqueo la dirección base del buffer de píxeles:
CVPixelBufferLockBaseAddress(pixel_buffer, 0);
y luego simplemente alimentarlo en mi escritor de activos para ser codificado:
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 estoy leyendo nada manualmente. Además, las texturas están originalmente en formato BGRA, que es lo que AVAssetWriters está optimizado para usar al codificar video, por lo que no hay necesidad de hacer ningún swizzling de color aquí. Los píxeles de BGRA sin procesar se introducen en el codificador para hacer la película.
Aparte del uso de esto en un AVAssetWriter, tengo un código en esta respuesta que he usado para la extracción de píxeles en bruto. 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 conjunto de búferes 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 video.
¿Hay alguna forma más rápida de acceder al buffer de cuadros que utilizando glReadPixels? Necesitaría acceso de solo lectura a un pequeño área de representación rectangular en el búfer de cuadros para procesar aún más los datos en la CPU. El rendimiento es importante porque tengo que realizar esta operación repetidamente. He buscado en la web y he encontrado algunos métodos, como usar Pixel Buffer Object y glMapBuffer, pero parece que OpenGL ES 2.0 no los admite.
Con respecto a lo que atisman mencionó sobre la pantalla negra, también tuve ese problema. Asegúrate de que todo esté bien con tu textura y otras configuraciones. Intentaba capturar la capa OpenGL de AIR, lo que hice al final, el problema fue que cuando no establecí "depthAndStencil" en true en el manifiesto de aplicaciones, mi textura FBO era de la mitad de altura (la pantalla estaba dividida por la mitad y reflejado, supongo que debido a la envoltura de textura param cosas). Y mi video era negro
Eso fue bastante frustrante, ya que según lo que Brad está publicando debería haber funcionado una vez que tuve algunos datos en textura. Desafortunadamente, ese no es el caso, todo tiene que ser "correcto" para que funcione: los datos en textura no son una garantía para ver datos iguales en el video. Una vez que agregué depthAndStencil, mi textura se ajustó a la altura máxima y comencé a obtener la grabación de video directamente desde la capa OpenGL de AIR, sin glReadPixels ni nada :)
Así que sí, lo que Brad describe realmente SI FUNCIONA sin la necesidad de recrear los búferes en cada fotograma, solo necesitas asegurarte de que tu configuración sea correcta. Si se está volviendo negro, intente jugar con los tamaños de video / textura, tal vez o algunos otros ajustes (¿configuración de su FBO?).