web-dev-qa-db-fra.com

vidéo ffmpeg à texture opengl

J'essaie de rendre les images saisies et converties à partir d'une vidéo en utilisant ffmpeg en une texture OpenGL à mettre sur un quad. J'ai à peu près épuisé Google et je n'ai pas trouvé de réponse, eh bien j'ai trouvé des réponses, mais aucune d'entre elles ne semble avoir fonctionné.

Fondamentalement, j'utilise avcodec_decode_video2() pour décoder le cadre, puis sws_scale() pour convertir le cadre en RVB, puis glTexSubImage2D() pour créer une texture openGL à partir de celui-ci mais ne peut pas ' Il semble que rien ne fonctionne.

Je me suis assuré que l'AVFrame "de destination" a 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 juste pour m'assurer qu'elles sont réellement rendues, ce qu'elles sont.

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

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

J'ai également glEnable(GL_TEXTURE_2D) et je l'ai testé en chargeant simplement dans un bmp normal.

[~ # ~] modifier [~ # ~]

Je reçois maintenant une image à l'écran, 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 et gluBuild2DMipmaps comme indiqué dans mon code). Je suis généralement presque exactement le même code que celui indiqué ci-dessus, seulement j'ai changé glTexSubImage2D En gluBuild2DMipmaps Et changé les types en GL_RGBA.

Voici à quoi ressemble le cadre:

Ffmpeg as OpenGL Texture garbled

MODIFIER ENCORE

Je viens de réaliser que je n'ai pas montré le code de 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 dans avgpicture_get_size En PIX_FMT_RGB24, Je l'ai fait aussi dans SwsContext et j'ai changé GluBuild2DMipmaps En GL_RGB Et j'obtiens une image légèrement meilleure mais on dirait qu'il me manque encore des lignes et c'est encore un peu étiré:

Ffmpeg Garbled OpenGL Texture 2

Un autre montage

Après avoir suivi les conseils de Macke et passé la résolution réelle à OpenGL, je reçois les images presque correctes mais toujours un peu asymétriques et en noir et blanc, il n'obtient maintenant que 6 images par seconde plutôt que 110 images par seconde:

enter image description here

P.S.

J'ai une fonction pour enregistrer les cadres dans l'image après sws_scale() et ils sortent bien en tant que couleur et tout, donc quelque chose dans OGL le rend en noir et blanc.

DERNIÈRE ÉDITION

Travail! D'accord, je le fais fonctionner maintenant, fondamentalement, je n'améliore pas la texture à une puissance de 2 et j'utilise simplement la résolution de la vidéo.

J'ai obtenu la texture s'affichant correctement avec une chance sur le bon glPixelStorei ()

glPixelStorei(GL_UNPACK_ALIGNMENT, 2);

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

Merci Macke et datenwolf pour toute votre aide.

41
Infiniti Fizz

La texture est-elle initialisée lorsque vous appelez glTexSubImage2D? Vous devez appeler glTexImage2D (pas Sub ) une fois 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

[~ # ~] modifier [~ # ~]

Vous ne fournissez pas de niveaux de mipmap. Avez-vous donc désactivé le mipmaping?

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

EDIT 2 l'image étant à l'envers n'est pas une surprise car la plupart des formats d'image ont l'origine en haut à gauche, tandis qu'OpenGL place l'origine de l'image de texture en bas la gauche. Cette bande que vous voyez là ressemble à une mauvaise foulée.

EDIT 3

J'ai fait ce genre de choses moi-même il y a environ un an. Je me suis é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 extraites à l'aide d'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;
}

J'utilise ceci dans un projet et cela fonctionne là-bas, donc ce code est testé, en quelque sorte.

9
datenwolf

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

Oui!

Le motif rayé est une indication évidente que vous ne correspondez pas à la taille des données (taille de ligne). (Étant donné que les couleurs sont correctes, RVB 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 doit la supporter si elle n'est pas ancienne).

Sinon, redimensionnez/complétez vos données avant de les télécharger en tant que texture 1024x512.

Mettre à jour

Je connais mieux OpenGL que les autres fonctions que vous appelez.

sxs_scale pourrait correspondre à 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 rembourrage (ce qui signifie, copiez une petite image (votre vidéo) dans une partie d'une grande texture (opengl)

Quelques autres conseils:

  • Avez-vous vraiment besoin de mipmaps? Générez-les uniquement si vous avez besoin de réduire l'échelle de votre texture en douceur (généralement nécessaire uniquement lorsqu'elle se trouve sur une géométrie 3D).
  • Évitez la génération de mipmap lors de l'exécution si vous effectuez le rendu d'une vidéo (en particulier, n'utilisez pas gluBuildMipMaps2D, car cela pourrait fonctionner dans le logiciel. Il existe d'autres moyens plus rapides, si vous avez besoin du mipmapping ( comme l'utilisation du paramètre de texture GL_GENERATE_MIPMAP). Seee ce fil pour plus d'informations.
  • Évitez d'appeler glTexImage à plusieurs reprises, car cela crée une nouvelle texture. glTexSubImage met simplement à jour une partie d'une texture, ce qui pourrait ê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 correspondent pas tout à fait, consultez glPixelStore pour définir les foulées de pixels et de lignes . Je soupçonne que les données fournies par sxs_scale/wmw ont un remplissage à la fin de chaque ligne (la ligne noire). Probablement pour que chaque ligne commence sur une limite paire de 8-16-32 octets.
5
Macke