Alternativa más rápida a glReadPixels en iPhone OpenGL ES 2.0

¿Hay alguna forma más rápida de acceder al búfer de cuadros que usar glReadPixels? Necesitaría acceso de solo lectura a un pequeño área de representación rectangular en el búfer de cuadros para procesar más los datos en la CPU. El performance es importante porque tengo que realizar esta operación repetidamente. He buscado en la web y he encontrado 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 hay una forma más rápida de get datos de OpenGL ES. No es evidente, pero resulta que el soporte de caching de textura agregado en iOS 5.0 no solo funciona para cargar rápidamente los frameworks de la camera en OpenGL ES, sino que también se puede usar al revés para get acceso rápido a los píxeles sin procesar dentro de una textura OpenGL ES.

Puede aprovechar esto para tomar los píxeles de una representación de OpenGL ES utilizando un object framebuffer (FBO) con una textura adjunta, con esa textura suministrada desde el caching de textura. Una vez que reproduzca su escena en ese FBO, los píxeles BGRA para esa escena estarán contenidos en su CVPixelBufferRef, por lo que no será necesario eliminarlos usando glReadPixels() .

Esto es mucho, mucho más rápido que usar glReadPixels() en mis puntos de reference. Descubrí que en mi iPhone 4, glReadPixels() era el cuello de botella en la lectura de frameworks de video de 720p para la encoding en el disco. Limitó la encoding de tener lugar a algo más de 8-9 FPS. Reemplazar esto con las lecturas rápidas de caching de textura me permite codificar el video 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 las partes de la tubería de la película real que codifican. En un iPhone 4S, esto le permite escribir video de 1080p a 30 FPS completos.

Mi implementación se puede encontrar dentro de la class 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 se puso a disposition en WWDC 2011).

Empiezo configurando mi AVAssetWriter, agregando una input y configurando una input de búfer de píxeles. El siguiente código se usa para configurar la input 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 al que haré mis cuadros de video, usando el siguiente código:

 if ([GPUImageOpenGLESContext supportsFastTextureUpload]) { CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[[GPUImageOpenGLESContext shanetworkingImageProcessingOpenGLESContext] 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 de la agrupación asociada con mi input de escritor de activos, crea y asocia una textura con ella y utiliza esa textura como objective para mi FBO.

Una vez que he renderizado un marco, locking la dirección base del búfer de píxeles:

 CVPixelBufferLockBaseAddress(pixel_buffer, 0); 

y luego simplemente aliméntelo 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 count que en ningún momento aquí estoy leyendo algo manualmente. Además, las texturas son de forma nativa en formatting BGRA, que es lo que AVAssetWriters está optimizado para usar cuando codifica video, por lo que no hay necesidad de hacer ningún cambio de color aquí. Los píxeles BGRA sin procesar se introducen en el codificador para crear 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 sin formatting. También experimenta un aumento significativo en la práctica cuando se compara con el uso de glReadPixels() , aunque es less de lo que veo con el grupo de búferes de píxeles que uso con AVAssetWriter.

Es una lástima que nada de esto esté documentado en ninguna parte, ya que proporciona un gran impulso al performance de la captura de video.

Con respecto a lo que Atisman mencionó sobre la pantalla negra, tuve ese problema también. Realmente asegúrate de que todo esté bien con tu textura y otras configuraciones. Estaba intentando capturar la capa OpenGL de AIR, lo que hice al final, el problema fue que cuando no establecí "depthAndStencil" en true por crash en el manifiesto de aplicaciones, mi textura FBO tenía la mitad de altura (la pantalla estaba dividida a la mitad y reflejado, supongo que debido a la textura del envoltorio param). Y mi video fue negro.

Eso fue bastante frustrante, ya que según lo que Brad está publicando, debería haber funcionado una vez que tuviera algunos datos en textura. Desafortunadamente, ese no es el caso, todo tiene que ser "correcto" para que funcione: los datos en textura no son garantía de ver datos iguales en el video. Una vez que agregué depthAndStencil, mi textura se fijó a toda su altura y empecé a grabar video directamente desde la capa OpenGL de AIR, sin glReadPixels ni nada 🙂

Entonces, sí, lo que Brad describe realmente funciona SIN la necesidad de recrear los búferes en todos los frameworks, solo debes asegurarte de que tu configuration sea correcta. Si se está poniendo negro, intente jugar con los tamaños de video / textura tal vez o con alguna otra configuration (configuration de su FBO?).