29 votes

ffmpeg video vers texture opengl

J'essaie de rendre des images saisies et converties d'une vidéo à l'aide de ffmpeg en une texture OpenGL à placer sur un quad. J'ai pratiquement épuisé google sans trouver de réponse, enfin j'ai trouvé des réponses mais aucune ne semble avoir fonctionné.

En gros, j'utilise avcodec_decode_video2() pour décoder la trame et ensuite sws_scale() pour convertir l'image en RGB et ensuite glTexSubImage2D() pour créer une texture openGL à partir de celui-ci mais je n'arrive pas à faire fonctionner quoi que ce soit.

Je me suis assuré que l'AVFrame "de destination" avait une puissance de 2 dimensions dans la configuration du contexte SWS. Voici mon code :

SwsContext *img_convert_ctx = sws_getContext(pCodecCtx->width,
                pCodecCtx->height, pCodecCtx->pix_fmt, 512,
                256, PIX_FMT_RGB24, SWS_BICUBIC, NULL,
                NULL, NULL);

//While still frames to read
while(av_read_frame(pFormatCtx, &packet)>=0) {
    glClear(GL_COLOR_BUFFER_BIT);

    //If the packet is from the video stream
    if(packet.stream_index == videoStream) {
        //Decode the video
        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

        //If we got a frame then convert it and put it into RGB buffer
        if(frameFinished) {
            printf("frame finished: %i\n", number);
            sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

            glBindTexture(GL_TEXTURE_2D, texture);
            //gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pCodecCtx->width, pCodecCtx->height, GL_RGB, GL_UNSIGNED_INT, pFrameRGB->data);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0,0, 512, 256, GL_RGB, GL_UNSIGNED_BYTE, pFrameRGB->data[0]);
            SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, number);
            number++;
        }
    }

    glColor3f(1,1,1);
    glBindTexture(GL_TEXTURE_2D, texture);
    glBegin(GL_QUADS);
        glTexCoord2f(0,1);
        glVertex3f(0,0,0);

        glTexCoord2f(1,1);
        glVertex3f(pCodecCtx->width,0,0);

        glTexCoord2f(1,0);
        glVertex3f(pCodecCtx->width, pCodecCtx->height,0);

        glTexCoord2f(0,0);
        glVertex3f(0,pCodecCtx->height,0);

    glEnd();

Comme vous pouvez le voir dans ce code, j'enregistre également les images dans des fichiers .ppm pour m'assurer qu'elles sont effectivement rendues, ce qui est le cas.

Le fichier utilisé est un .wmv en 854x480, cela pourrait-il être le problème ? Le fait que je lui demande simplement de passer en 512x256 ?

P.S. J'ai regardé ceci Question sur Stack Overflow mais ça n'a pas aidé.

Aussi, j'ai glEnable(GL_TEXTURE_2D) et je l'ai testé en chargeant simplement un fichier bmp normal.

EDIT

J'obtiens une image sur l'écran maintenant mais c'est un désordre confus, je suppose que quelque chose à voir avec le changement des choses à une puissance de 2 (dans le décodage), swscontext y gluBuild2DMipmaps comme indiqué dans mon code). En général, j'utilise presque exactement le même code que celui présenté ci-dessus, sauf que j'ai modifié glTexSubImage2D a gluBuild2DMipmaps et changé les types en GL_RGBA .

Voici à quoi ressemble le cadre :

Ffmpeg as OpenGL Texture garbled

EDIT ENCORE

Je viens de réaliser que je n'ai pas montré le code de la configuration de pFrameRGB :

//Allocate video frame for 24bit RGB that we convert to.
AVFrame *pFrameRGB;
pFrameRGB = avcodec_alloc_frame();

if(pFrameRGB == NULL) {
    return -1;
}

//Allocate memory for the raw data we get when converting.
uint8_t *buffer;
int numBytes;
numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
buffer = (uint8_t *) av_malloc(numBytes*sizeof(uint8_t));

//Associate frame with our buffer
avpicture_fill((AVPicture *) pFrameRGB, buffer, PIX_FMT_RGB24,
    pCodecCtx->width, pCodecCtx->height);

Maintenant que j'ai changé le PixelFormat sur avgpicture_get_size a PIX_FMT_RGB24 J'ai fait ça en SwsContext également et a changé GluBuild2DMipmaps a GL_RGB et j'obtiens une image légèrement meilleure, mais on dirait qu'il manque encore des lignes et qu'elle est encore un peu étirée :

Ffmpeg Garbled OpenGL Texture 2

Une autre édition

Après avoir suivi les conseils de Macke et transmis la résolution réelle à OpenGL, j'obtiens des images presque correctes mais toujours un peu déformées et en noir et blanc, et je n'obtiens que 6 images par seconde au lieu de 110 :

enter image description here

P.S.

J'ai une fonction pour sauvegarder les cadres dans une image après sws_scale() et elles sortent bien en couleur et tout, donc quelque chose dans OGL les rend en N&B.

DERNIER ÉDIT

Le travail ! Ok, j'ai réussi à le faire fonctionner maintenant. En fait, je n'ai pas rempli la texture à la puissance 2 et j'ai utilisé la résolution de la vidéo.

J'ai réussi à faire apparaître la texture correctement avec une estimation chanceuse du glPixelStorei() correct.

glPixelStorei(GL_UNPACK_ALIGNMENT, 2);

Aussi, si quelqu'un d'autre a le subimage() montrant un problème de blanc comme moi, vous devez remplir la texture au moins une fois avec glTexImage2D() et donc je l'utilise une fois dans la boucle et ensuite j'utilise glTexSubImage2D() après cela.

Merci Macke et datenwolf pour toute votre aide.

6voto

datenwolf Points 85093

La texture est-elle initialisée lorsque vous appelez glTexSubImage2D ? Vous devez appeler glTexImage2D (pas Sous ) un pour initialiser l'objet texture. Utilisez NULL pour le pointeur de données, OpenGL initialisera alors une texture sans copier les données. répondu à

EDIT

Vous ne fournissez pas les niveaux de mipmaping. Vous avez donc désactivé le mipmaping ?

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILER, linear_interpolation ? GL_LINEAR : GL_NEAREST);

EDIT 2 Le fait que l'image soit à l'envers n'est pas une surprise, car l'origine de la plupart des formats d'image se trouve en haut à gauche, alors qu'OpenGL place l'origine de l'image de texture en bas à gauche. Cette bande que vous voyez là ressemble à une mauvaise rangée.

EDIT 3

J'ai fait ce genre de choses moi-même il y a environ un an. J'ai écrit un petit wrapper pour ffmpeg, je l'ai appelé aveasy. https://github.com/datenwolf/aveasy

Et voici du code pour mettre les données récupérées avec aveasy dans des textures OpenGL :

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <GL/glew.h>

#include "camera.h"
#include "aveasy.h"

#define CAM_DESIRED_WIDTH 640
#define CAM_DESIRED_HEIGHT 480

AVEasyInputContext *camera_av;
char const *camera_path = "/dev/video0";
GLuint camera_texture;

int open_camera(void)
{
    glGenTextures(1, &camera_texture);

    AVEasyInputContext *ctx;

    ctx = aveasy_input_open_v4l2(
        camera_path,
        CAM_DESIRED_WIDTH,
        CAM_DESIRED_HEIGHT,
        CODEC_ID_MJPEG,
        PIX_FMT_BGR24 );
    camera_av = ctx;

    if(!ctx) {
        return 0;
    }

    /* OpenGL-2 or later is assumed; OpenGL-2 supports NPOT textures. */
    glBindTexture(GL_TEXTURE_2D, camera_texture[i]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(
        GL_TEXTURE_2D,  
        0,
        GL_RGB, 
        aveasy_input_width(ctx),
        aveasy_input_height(ctx),
        0,
        GL_BGR,
        GL_UNSIGNED_BYTE,
        NULL );

    return 1;
}

void update_camera(void)
{
    glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE );
    glPixelStorei( GL_UNPACK_LSB_FIRST,  GL_TRUE  );
    glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 );
    glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0);
    glPixelStorei( GL_UNPACK_SKIP_ROWS, 0);
    glPixelStorei( GL_UNPACK_ALIGNMENT, 1);

    AVEasyInputContext *ctx = camera_av;
    void *buffer;

    if(!ctx)
        return;

    if( !( buffer = aveasy_input_read_frame(ctx) ) )
        return;

    glBindTexture(GL_TEXTURE_2D, camera_texture);
    glTexSubImage2D(
        GL_TEXTURE_2D,
        0,
        0,
        0,
        aveasy_input_width(ctx),
        aveasy_input_height(ctx),
        GL_BGR,
        GL_UNSIGNED_BYTE,
        buffer );
}

void close_cameras(void)
{
    aveasy_input_close(camera_av);
    camera_av=0;
}

Je l'utilise dans un projet et il y fonctionne, donc ce code est testé, en quelque sorte.

3voto

Macke Points 13474

Le fichier utilisé est un .wmv en 854x480. 854x480, cela pourrait-il être le problème ? Le fait que je lui dise simplement de passer en 512x256 ?

Oui !

Le motif rayé est une indication évidente que vous ne faites pas correspondre les tailles des données (taille des lignes). (Puisque les couleurs sont correctes, RGB vs BGR vs BGRA et n-composants est correct).

Vous dites à OpenGL que la texture que vous téléchargez est 512x256 (ce qui n'est pas le cas, AFAICT). Utilisez les dimensions réelles (NPOT, votre carte devrait le supporter si elle n'est pas ancienne).

Sinon, redimensionnez/passez vos données avant en le téléchargeant comme une texture 1024x512.

Mise à jour

Je suis plus familier avec OpenGL qu'avec les autres fonctions que vous appelez.

sxs_scale pourrait vous permettre d'obtenir ce que vous voulez (c'est-à-dire réduire l'image à la taille d'un pot). Cependant, la mise à l'échelle de chaque image peut être lente.

J'utiliserais plutôt le padding (ce qui signifie, copier une petite image (votre vidéo) dans une partie d'une grande texture (opengl).

Quelques autres conseils :

  • Avez-vous vraiment besoin des mipmaps ? Ne les générez que si vous avez besoin de réduire l'échelle de votre texture de manière fluide (en général, cela n'est nécessaire que lorsque la texture se trouve sur une géométrie 3D).
  • Évitez la génération de mipmap au moment de l'exécution si vous effectuez le rendu d'une vidéo (en particulier, n'utilisez pas gluBuildMipMaps2D, car cela pourrait s'exécuter dans le logiciel. Il y a d'autres moyens qui sont plus rapides, si vous avez besoin de mipmapping (en utilisant par exemple le paramètre de texture GL_GENERATE_MIPMAP). Voir ce fil pour plus d'informations.
  • Evitez d'appeler glTexImage de façon répétée, car cela crée une nouvelle texture. glTexSubImage met juste à jour une partie de la texture, ce qui peut être mieux pour vous.
  • Si vous souhaitez télécharger la texture en une seule étape (ce qui est préférable pour des raisons de performances), mais que les données ne sont pas tout à fait adaptées, vous pouvez vous tourner vers glPixelStore pour définir les pas des pixels et des lignes. Je soupçonne que les données fournies par sxs_scale/wmw ont un certain remplissage à la fin de chaque ligne (la ligne noire). Probablement pour que chaque ligne commence sur une frontière égale de 8-16-32 octets.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X