42

我正在尝试将使用ffmpeg从视频中抓取和转换的帧渲染为要放在四边形上的OpenGL纹理。我已经用尽谷歌并没有找到答案,我找到了答案,但似乎没有一个有效。

基本上,我avcodec_decode_video2()用来解码帧,然后sws_scale()将帧转换为 RGB,然后glTexSubImage2D()从中创建一个 openGL 纹理,但似乎无法正常工作。

我已确保“目标”AVFrame 在 SWS 上下文设置中具有二维的能力。这是我的代码:

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

正如您在该代码中看到的那样,我还将帧保存到 .ppm 文件中,只是为了确保它们确实在渲染,它们就是这样。

正在使用的文件是 854x480 的 .wmv,这可能是问题所在吗?事实上我只是告诉它去 512x256?

PS 我看过这个Stack Overflow 问题,但没有帮助。

另外,我也有glEnable(GL_TEXTURE_2D),并且通过加载一个普通的 bmp 来测试它。

编辑

我现在在屏幕上显示图像,但它是乱码,我猜想与将事物更改为 2 的幂有关(在解码中,swscontextgluBuild2DMipmaps我的代码所示)。我通常与上面显示的代码几乎完全相同,只是我已更改glTexSubImage2DgluBuild2DMipmaps并将类型更改为GL_RGBA.

这是框架的样子:

ffmpeg as OpenGL Texture 乱码

再次编辑

刚刚意识到我还没有显示 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);

既然我已经将PixelFormatin更改avgpicture_get_size为,我也已将其更改为 ,并更改为,我得到了一个稍微好一点的图像,但看起来我仍然缺少线条并且它仍然有点拉伸:PIX_FMT_RGB24SwsContextGluBuild2DMipmapsGL_RGB

Ffmpeg 乱码 OpenGL 纹理 2

另一个编辑

在遵循 Macke 的建议并将实际分辨率传递给 OpenGL 之后,我得到了几乎正确的帧,但仍然有点偏斜并且是黑白的,现在它也只有 6fps 而不是 110fps:

在此处输入图像描述

附言

我有一个功能可以将帧保存到图像之后sws_scale(),它们作为颜色和一切都很好,所以 OGL 中的某些东西正在使它成为黑白。

最后编辑

在职的!好的,我现在可以正常工作了,基本上我不会将纹理填充到 2 的幂,而只是使用视频的分辨率。

我通过正确的 glPixelStorei() 幸运猜测正确显示了纹理

glPixelStorei(GL_UNPACK_ALIGNMENT, 2);

另外,如果其他subimage()人像我一样有显示空白的问题,你必须至少填充一次纹理,glTexImage2D()所以我在循环中使用它一次,然后再使用glTexSubImage2D()

感谢 Macke 和 datenwolf 提供的所有帮助。

4

2 回答 2

9

调用时纹理是否已初始化glTexSubImage2D?您需要调用glTexImage2D(而不是Sub)一次来初始化纹理对象。使用 NULL 作为数据指针,OpenGL 将在不复制数据的情况下初始化纹理。 回答

编辑

您没有提供 mipmaping 级别。那么你是否禁用了mipmaping?

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

编辑 2倒置的图像并不奇怪,因为大多数图像格式的原点位于左上角,而 OpenGL 将纹理图像的原点放在左下角。你在那里看到的那个条带看起来像是错误的行步。

编辑 3

大约一年前,我自己做过这种事情。我为 ffmpeg 写了一个小包装器,我称之为 aveasy https://github.com/datenwolf/aveasy

这是一些将使用 aveasy 获取的数据放入 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;
}

我在一个项目中使用它并且它在那里工作,所以这个代码经过测试,有点。

于 2011-06-27T16:32:07.177 回答
5

正在使用的文件是 854x480 的 .wmv,这可能是问题所在吗?事实上我只是告诉它去 512x256?

是的!

条纹模式明显表明您的数据大小(行大小)不匹配。(由于颜色是正确的,RGB vs BGR vs BGRA 和 n 分量是正确的。)

你告诉 OpenGL 你上传的纹理是 512x256(它不是,AFAICT)。使用真实尺寸(NPOT,如果它不是古老的,你的卡应该支持它)。

否则,在将数据上传为 1024x512 纹理之前调整数据大小/填充数据。

更新

我对 OpenGL 比您调用的其他函数更熟悉。

sxs_scale 可能是你想要的(即将图像缩小到一个罐子大小)。但是,缩放每一帧可能会很慢。

我会改用填充(这意味着,将小图像(您的视频)复制到大纹理的一部分(opengl)

其他一些提示:

  • 你真的需要mipmap吗?仅当您需要平滑地缩小纹理时才生成它们(通常仅在某些 3d 几何图形上需要)。
  • 如果您正在渲染视频,请避免在运行时生成 mipmap(特别是不要使用 gluBuildMipMaps2D,因为它可能会在软件中运行。如果您需要 mipmap ,还有其他更快的方法(例如使用 GL_GENERATE_MIPMAP 纹理参数)。见此线程以获取更多信息。
  • 避免重复调用 glTexImage,因为这会创建一个新的纹理。glTexSubImage 只是更新纹理的一部分,这可能对您更好。
  • 如果您想一步上传纹理(出于性能原因,这更可取),但数据不太合适,请查看glPixelStore设置像素和行步长。我怀疑从 sxs_scale/wmw 给出的数据在每行的末尾都有一些填充(黑线)。可能是这样每一行都从一个偶数 8-16-32 字节的边界开始。
于 2011-06-28T09:53:05.393 回答