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 的幂有关(在解码中,swscontext
和 gluBuild2DMipmaps
,如图所示我的代码)。我的代码通常与上面显示的几乎完全相同,只是我将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 纹理的主要内容,如果未能解决你的问题,请参考以下文章
Android - 获取视频帧的 FFmpeg 替代方案。 (由于许可)