ffmpeg 视频到 opengl 纹理

Posted

技术标签:

【中文标题】ffmpeg 视频到 opengl 纹理【英文标题】:ffmpeg video to opengl texture 【发布时间】:2011-09-23 15:26:15 【问题描述】:

我正在尝试渲染从使用 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?

附:我看过这个Stack Overflow question,但没有帮助。

另外,我也有glEnable(GL_TEXTURE_2D),并通过加载正常的 bmp 对其进行了测试。

编辑

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

这是框架的样子:

再次编辑

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

现在我已经将avgpicture_get_size 中的PixelFormat 更改为PIX_FMT_RGB24,我也在SwsContext 中完成了该操作,并将GluBuild2DMipmaps 更改为GL_RGB,我得到了一个稍微好一点的图像,但它看起来我还是少了几行,而且还是有点牵强:

另一个编辑

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

附言

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

最后编辑

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

幸运地猜到了正确的 glPixelStorei(),我得到了正确显示的纹理

glPixelStorei(GL_UNPACK_ALIGNMENT, 2);

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

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

【问题讨论】:

投票结束。 IMO,这个问题已经失控了。您需要比 SO 在这里支持的更多的手持设备。有很多问题需要覆盖,并且不断地重新编辑问题并不是很好。我建议尝试邮件列表或论坛,或 SO OpenGL 聊天室。 好的,感谢 glPixelStorei,问题现在已经解决了。我已经删除了 cmets 等,这些对其他有同样问题的人解决这个问题没有帮助。 另外,由于问题是关于将 ffmpeg 框架获取到 opengl 纹理,我不明白这个问题是如何失控的,因为所有问题和答案都是关于将 ffmpeg 框架上传到一个opengl纹理,无论它是关于性能(gluBuild2DMipmaps(),glTexSubImage2D()),像素存储(glPixelStorei()等)ffmpeg的执行(sws_scale,SwsContext)还是与问题有关的任何其他事情。编辑问题对于遇到相同问题的其他人很有用,因为他们可以轻松查看为解决问题所采取的步骤。 @InfinitiFizz:嘿,我正在尝试通过创建一个浏览器插件来实现同样的目标,但是,我得到的只是一个黑屏,你能指导我吗? 【参考方案1】:

纹理是在你初始化的时候初始化的吗? 打电话给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;

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

【讨论】:

是的,很抱歉,我实际上在使用 Sub 后又回到 glTexImage2D 以确保这不是问题。 @Infinity Fizz:请参阅我的第一次编辑以了解您问题的另一个可能原因。 我认为这很难说,我们需要更多的上下文。我想你只是看到回来?当您尝试简单的调试纹理时会发生什么?当您不设置渲染纹理时会发生什么,彩色矩形是否正确出现?您对 glGetError 的调用在哪里? 嗨,我已将其更改为使用 gluBuild2DMipmaps,只是为了排除 mipmapping 未被禁用的可能性,现在它可以工作了!好吧,我得到了一个纹理,但它是实际帧的乱码版本,但我可以正确地挑选出图像。我不知道该怎么做才能正确获得框架。我正在输出到 RGBA 并在 gluBuild2DMipmaps() 中使用 GL_UNSIGNED_BYTE。我已经尝试过 GL_RGB 并弄乱了 swscontext、解码和 gluBuild2DMipmaps 的高度和宽度,但没有任何效果。我在上面的原始问题中发布了图像是什么样的 啊,这真是太酷了 datenwolf,但我真的很想尝试自己做,但如果情况变得更糟,我会看看,谢谢 :-)【参考方案2】:

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

是的!

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

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

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

更新

比起你调用的其他函数,我更熟悉 OpenGL。

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

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

其他一些提示:

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

【讨论】:

啊,好吧,这很有道理,但我真的不知道还能做什么。如果我尝试将 854x480 放入 gluBuild2DMipmaps 中,我只会得到 6fps,而我之前大约是 110fps,猜测这与 2 事物的力量有关,现在视频似乎也是黑白的(参见原始问题中的图像现在)。当您说“在上传之前调整数据大小/填充数据”时,您的意思是在 sws_scale() 阶段执行此操作吗?正如我应该将 Sws_Context 更改为只获取 512x256 的图像,或者将其填充为 1024x512,我将如何做呢? @Infinity Fizz:gluBuild2DMimaps2D 经历了 2 个瓶颈:首先它创建了输入图像的图像金字塔(即它对其进行下采样),然后它通过昂贵的 glTexImage2D 而不是更快的 glTexSubImage2D。跨度> @datenwolf 这些都不是主要瓶颈。核心问题是 gluBuild2DMimaps2D 将纹理重新采样为 2 的幂,然后在 CPU 上构建图像金字塔。 @fintelia: “生成图像金字塔”(这是我写的)在 CPU 上的操作。而且 glTexImage2D 是一项昂贵的操作。它们都是瓶颈,图像金字塔的生成是较大的(相差几个数量级); glTexImage2D 也不容忽视。

以上是关于ffmpeg 视频到 opengl 纹理的主要内容,如果未能解决你的问题,请参考以下文章

纹理不填充图形 - OpenGL

OpenGL ES 1.1 - alpha 蒙版

OpenGL全景视频

Android - 获取视频帧的 FFmpeg 替代方案。 (由于许可)

OSX 上的 AVFoundation:来自视频的 OpenGL 纹理,无需访问像素数据

使用 NDK、OpenGL ES 和 FFmpeg 的 Android 视频播放器