opengl - ffmpeg video para abrir la textura
textures video-processing (2)
Intento renderizar fotogramas y convertirlos de un video usando ffmpeg a una textura OpenGL para ponerlos en un quad. Agoté bastante a Google y no encontré una respuesta. Bueno, encontré respuestas, pero ninguna de ellas parece haber funcionado.
Básicamente, estoy usando avcodec_decode_video2()
para decodificar el cuadro y luego sws_scale()
para convertir el cuadro a RGB y luego a glTexSubImage2D()
para crear una textura OpenGL a partir de él, pero parece que no funciona nada.
Me he asegurado de que el AVFrame "de destino" tenga una potencia de 2 dimensiones en la configuración del contexto de SWS. Aquí está mi código:
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();
Como puede ver en ese código, también estoy guardando los cuadros en archivos .ppm solo para asegurarme de que realmente están renderizando, lo cual es cierto.
El archivo que se está usando es .wmv a 854x480, ¿podría ser este el problema? ¿El hecho de que solo digo que sea 512x256?
PD. He visto esta pregunta sobre el desbordamiento de pila pero no sirvió.
Además, tengo glEnable(GL_TEXTURE_2D)
también y lo he probado simplemente cargando en un bmp normal.
EDITAR
Estoy obteniendo una imagen en la pantalla ahora, pero es un desastre confuso, supongo que algo tiene que ver con cambiar las cosas a una potencia de 2 (en la decodificación, swscontext
y gluBuild2DMipmaps
como se muestra en mi código). Normalmente soy casi exactamente el mismo código que el mostrado arriba, solo he cambiado glTexSubImage2D
a gluBuild2DMipmaps
y gluBuild2DMipmaps
cambiado los tipos a GL_RGBA
.
Así es como se ve el marco:
EDIT OTRA VEZ
Me acabo de dar cuenta de que no he mostrado el código de cómo se configura 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);
Ahora que he cambiado el PixelFormat
en avgpicture_get_size
a PIX_FMT_RGB24
, también lo hice en SwsContext
y cambié GluBuild2DMipmaps
a GL_RGB
y obtuve una imagen ligeramente mejor, pero parece que todavía me faltan líneas y aún me siento un poco estirado:
Otro Editar
Después de seguir los consejos de Macke y pasar la resolución real a OpenGL, obtengo los marcos casi correctos pero todavía un poco sesgados y en blanco y negro, también solo está obteniendo 6 fps ahora en vez de 110 fps:
PD
Tengo una función para guardar los cuadros en la imagen después de sws_scale()
y están saliendo bien como el color y todo para que algo en OGL lo esté haciendo en blanco y sws_scale()
.
ÚLTIMO EDICION
¡Trabajando! De acuerdo, lo tengo funcionando ahora, básicamente, no estoy rellenando la textura con una potencia de 2 y solo usando la resolución del video.
Tengo la textura que aparece correctamente con una conjetura afortunada en el glPixelStorei correcto ()
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
Además, si alguien más tiene la subimage()
muestra un problema en blanco como yo, debe rellenar la textura al menos una vez con glTexImage2D()
y entonces la uso una vez en el ciclo y luego uso glTexSubImage2D()
después de eso.
Gracias Macke y datenwolf por toda tu ayuda.
El archivo que se está usando es .wmv a 854x480, ¿podría ser este el problema? ¿El hecho de que solo digo que sea 512x256?
¡Sí!
El patrón de rayas es una indicación obvia de que no coinciden los tamaños de los datos (tamaño de fila). (Dado que los colores son correctos, RGB vs BGR versus BGRA y n-components es correcto).
Le está diciendo a OpenGL que la textura que está cargando es de 512x256 (que no es, AFAICT). Use las dimensiones reales (NPOT, su tarjeta debe ser compatible si no es antigua).
De lo contrario, cambie el tamaño / relleno de sus datos antes de cargarlos como una textura de 1024x512.
Actualizar
Estoy más familiarizado con OpenGL que con las otras funciones a las que llamas.
sxs_scale puede hacer lo que quieras (es decir, reducir la escala de la imagen a un tamaño de maceta). Sin embargo, escalar cada fotograma puede ser lento.
Yo usaría el relleno en su lugar (lo que significa, copia una imagen pequeña (tu video) en una parte de una textura grande (abierta)
Algunos otros consejos:
- ¿Realmente necesitas mipmaps? Solo genere estos datos si necesita reducir la escala de su textura sin problemas (generalmente solo es necesario cuando está en geometría 3d).
- Evite la generación de mipmap en tiempo de ejecución si está renderizando un video (especialmente, no use gluBuildMipMaps2D, ya que eso podría ejecutarse en software. Hay otras maneras de hacerlo más rápido, si necesita mipmapping (como usar el parámetro de textura GL_GENERATE_MIPMAP). este hilo para más información.
- Evite llamar a glTexImage repetidamente, ya que eso crea una nueva textura. glTexSubImage simplemente actualiza una parte de una textura, que podría funcionar mejor para usted.
- Si desea cargar la textura en un solo paso (que es preferible por razones de rendimiento), pero los datos no encajan, busque en
glPixelStore
para establecer los pasos de los píxeles y las filas. Sospecho que los datos proporcionados desde sxs_scale / wmw tienen algo de relleno al final de cada fila (la línea negra). Probablemente para que cada fila comience en un límite de 8-16-32 bytes.
¿La textura se inicializa cuando llamas aglTexSubImage2D
?Necesita llamar aglTexImage2D
(no Sub ) una vez para inicializar el objeto de textura.Use NULL para el puntero de datos, OpenGL luego inicializará una textura sin copiar datos.contestada
EDITAR
No estás suministrando niveles de mipmaping. Entonces, ¿deshabilitaste el mipmaping?
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILER, linear_interpolation ? GL_LINEAR : GL_NEAREST);
EDITAR 2 la imagen al revés no es una sorpresa ya que la mayoría de los formatos de imagen tienen el origen en la esquina superior izquierda, mientras que OpenGL coloca el origen de la imagen de la textura en la esquina inferior izquierda. Esa banda que ves allí parece una zancada equivocada.
EDIT 3
Hice este tipo de cosas hace aproximadamente un año. Me escribí un pequeño contenedor para ffmpeg, lo llamé aveasy https://github.com/datenwolf/aveasy
Y este es un código para poner los datos obtenidos usando aveasy en texturas 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;
}
Estoy usando esto en un proyecto y funciona allí, por lo que este código se prueba, más o menos.