web-dev-qa-db-fra.com

Alternative plus rapide à glReadPixels dans iPhone OpenGL ES 2.0

Existe-t-il un moyen plus rapide d'accéder au tampon de trame que d'utiliser glReadPixels? J'aurais besoin d'un accès en lecture seule à une petite zone de rendu rectangulaire dans le tampon de trame pour traiter davantage les données dans le processeur. Les performances sont importantes car je dois effectuer cette opération à plusieurs reprises. J'ai cherché sur le Web et j'ai trouvé une approche comme l'utilisation de Pixel Buffer Object et de glMapBuffer, mais il semble qu'OpenGL ES 2.0 ne les prend pas en charge.

66
atisman

Depuis iOS 5.0, il existe désormais un moyen plus rapide de récupérer les données d'OpenGL ES. Il n'est pas évident, mais il s'avère que la prise en charge du cache de texture ajoutée dans iOS 5.0 ne fonctionne pas uniquement pour le téléchargement rapide des cadres de caméra vers OpenGL ES, mais il peut être utilisé à l'envers pour obtenir un accès rapide aux pixels bruts dans une texture OpenGL ES.

Vous pouvez en profiter pour récupérer les pixels pour un rendu OpenGL ES en utilisant un objet framebuffer (FBO) avec une texture attachée, cette texture ayant été fournie par le cache de texture. Une fois que vous avez rendu votre scène dans ce FBO, les pixels BGRA de cette scène seront contenus dans votre CVPixelBufferRef, il ne sera donc pas nécessaire de les tirer vers le bas à l'aide de glReadPixels().

C'est beaucoup, beaucoup plus rapide que d'utiliser glReadPixels() dans mes benchmarks. J'ai trouvé que sur mon iPhone 4, glReadPixels() était le goulot d'étranglement dans la lecture des images vidéo 720p pour l'encodage sur le disque. Cela limitait l'encodage à plus de 8-9 FPS. Remplacer cela par les lectures rapides du cache de texture me permet d'encoder une vidéo 720p à 20 FPS maintenant, et le goulot d'étranglement est passé de la lecture de pixels au traitement OpenGL ES et aux parties d'encodage de films réels du pipeline. Sur un iPhone 4S, cela vous permet d'écrire une vidéo 1080p à 30 FPS.

Mon implémentation peut être trouvée dans la classe GPUImageMovieWriter dans mon framework open source GPUImage , mais elle a été inspirée par article de Dennis Muhlestein sur le sujet et l'exemple d'application ChromaKey d'Apple (qui n'était que disponible à la WWDC 2011).

Je commence par configurer mon AVAssetWriter, ajouter une entrée et configurer une entrée de tampon de pixels. Le code suivant est utilisé pour configurer l'entrée du tampon de pixels:

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];

Une fois que j'ai cela, je configure le FBO sur lequel je rendrai mes images vidéo, en utilisant le code suivant:

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);
}

Cela extrait un tampon de pixels du pool associé à mon entrée d'écrivain d'actifs, crée et associe une texture à celle-ci et utilise cette texture comme cible pour mon FBO.

Une fois que j'ai rendu un cadre, je verrouille l'adresse de base du tampon de pixels:

CVPixelBufferLockBaseAddress(pixel_buffer, 0);

puis il suffit de l'introduire dans mon rédacteur d'actifs à encoder:

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);
}

Notez qu'à aucun moment ici je ne lis quoi que ce soit manuellement. De plus, les textures sont nativement au format BGRA, ce que les AVAssetWriters sont optimisés pour utiliser lors de l'encodage de la vidéo, il n'est donc pas nécessaire de faire de la couleur ici. Les pixels BGRA bruts sont simplement introduits dans l'encodeur pour créer le film.

Mis à part l'utilisation de ceci dans un AVAssetWriter, j'ai du code dans cette réponse que j'ai utilisé pour l'extraction de pixels bruts. Il connaît également une accélération significative dans la pratique par rapport à l'utilisation de glReadPixels(), bien que moins que ce que je vois avec le pool de mémoire tampon de pixels que j'utilise avec AVAssetWriter.

Il est dommage qu'aucune de ces informations ne soit documentée nulle part, car elle améliore considérablement les performances de capture vidéo.

131
Brad Larson

Concernant ce qu'Atisman a mentionné à propos de l'écran noir, j'ai également eu ce problème. Assurez-vous vraiment que tout va bien avec votre texture et d'autres paramètres. J'essayais de capturer la couche OpenGL d'AIR, ce que j'ai fait à la fin, le problème était que lorsque je n'ai pas défini "depthAndStencil" sur true par accident dans le manifeste des applications, ma texture FBO était à moitié hauteur (l'écran était divisé en deux et en miroir, je suppose à cause du paramétrage de la texture enveloppante). Et ma vidéo était noire.

C'était assez frustrant, car d'après ce que Brad publie, cela aurait dû fonctionner une fois que j'ai eu quelques données en texture. Malheureusement, ce n'est pas le cas, tout doit être "correct" pour que cela fonctionne - les données en texture ne sont pas une garantie pour voir des données égales dans la vidéo. Une fois que j'ai ajouté depthAndStencil, ma texture s'est fixée à pleine hauteur et j'ai commencé à obtenir l'enregistrement vidéo directement à partir de la couche OpenGL d'AIR, pas de glReadPixels ou quoi que ce soit :)

Donc oui, ce que Brad décrit fonctionne vraiment sans avoir besoin de recréer les tampons sur chaque image, il vous suffit de vous assurer que votre configuration est correcte. Si vous obtenez du noir, essayez de jouer avec les tailles de vidéo/texture peut-être ou avec d'autres paramètres (configuration de votre FBO?).

0
user2979732