在OpenGL ES中,将YUV420转换为RGB的纹理。

9
我需要将YUV420P图像转换为RGB颜色空间,并使用Freescale iMX53处理器上的AMD GPU进行显示(OpenGL ES 2.0,EGL)。操作系统为Linux,无X11。为了实现这一目标,我应该能够创建一个包含YUV420P数据的适当图像:这可以是YUV420P / YV12图像类型或3个简单的8位图像,每个分量一个(Y,U,V)。
glTexImage2D被排除在外,因为它很慢,YUV420P帧是实时视频解码的结果,每秒25帧,使用glTexImage2D我们无法保持所需的帧速率。
有一个替代方案:eglCreateImageKHR / glEGLImageTargetTexture2DOES,唯一的问题是它们不能处理任何适用于YUV420 / YV12数据的图像格式。
EGLint attribs[] = {
  EGL_WIDTH, 800,
  EGL_HEIGHT, 480,
  EGL_IMAGE_FORMAT_FSL, EGL_FORMAT_YUV_YV12_FSL,
  EGL_NONE
};

EGLint const req_attribs[] = {
  EGL_RED_SIZE, 5,
  EGL_GREEN_SIZE, 6,
  EGL_BLUE_SIZE, 5,
  EGL_ALPHA_SIZE, 0,
  EGL_SAMPLES, 0,
  EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
  EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
  EGL_NONE
};

...

display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
eglChooseConfig(display, req_attribs, config, ARRAY_SIZE(config), &num_configs);
ctx = eglCreateContext(display, curr_config, NULL, NULL);
surface = eglCreateWindowSurface(display, curr_config, fb_handle, NULL);

...

EGLImageKHR yuv_img = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NEW_IMAGE_FSL, NULL, attribs); 
eglQueryImageFSL(display, yuv_img, EGL_CLIENTBUFFER_TYPE_FSL, (EGLint *)&ptr);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, yuv_img);

glEGLImageTargetTexture2DOES(...)失败。如果我将“attribs”中的适当行更改为以下内容:

EGL_IMAGE_FORMAT_FSL,EGL_FORMAT_RGB_565_FSL,

那么该图像可以分配给OpenGL ES纹理,但是它不适合保存8位数据(Y / U / V)或YUV420 / YV12数据。在搜索网络(包括Freescale社区论坛)时,我没有找到任何解决方案。

如何创建一个图像:

  • 快速创建;
  • 最终可以分配给已经存在的缓冲区(给出物理地址或虚拟地址);
  • 可以在片段/顶点着色器程序中使用以执行YUV --> RGB转换;

限制是为了避免由于性能原因而进行不必要的memcpy (...)。

1个回答

8
我已经在i.MX53上实现了几种YUV格式的内容,并且效果非常好。我有一篇发表的文章,虽然它是概括性的,涵盖了更多的Android平台:http://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis 我猜测你的问题可能是没有绑定到正确的纹理目标。应该像这样:
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, hEglImage[iTextureIndex]);

glBindTexture(GL_TEXTURE_EXTERNAL_OES, hTexture[iIndex]);   

而 eglImageAttributes 应该是以下之一:

EGLint eglImageAttributes[] = {EGL_WIDTH, iTextureWidth, EGL_HEIGHT, iTextureHeight, EGL_IMAGE_FORMAT_FSL, EGL_FORMAT_YUV_YV12_FSL, EGL_NONE};
EGLint eglImageAttributes[] = {EGL_WIDTH, iTextureWidth, EGL_HEIGHT, iTextureHeight, EGL_IMAGE_FORMAT_FSL, EGL_FORMAT_YUV_NV21_FSL, EGL_NONE};
EGLint eglImageAttributes[] = {EGL_WIDTH, iTextureWidth, EGL_HEIGHT, iTextureHeight, EGL_IMAGE_FORMAT_FSL, EGL_FORMAT_YUV_UYVY_FSL, EGL_NONE};

hEglImage[iTextureIndex] = eglCreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_NEW_IMAGE_FSL, NULL, eglImageAttributes);

struct EGLImageInfoFSL EglImageInfo;
eglQueryImageFSL(eglDisplay, hEglImage[iTextureIndex], EGL_CLIENTBUFFER_TYPE_FSL, (EGLint *)&EglImageInfo);

尽管Freescale i.MX53平台的这个功能使得视频的YUV到RGB颜色空间转换非常快,但它确实有几个限制:
  1. 它只支持这3种YUV格式。
  2. eglCreateImageKHR()必须分配缓冲区。 没有办法让它使用现有的缓冲区。 Freescale确认NULL指针不能是其他任何内容,这在技术上违反了Khronos规范。
Freescale已经解决了i.MX6平台上的这些问题,尽管架构确实有所不同。希望这可以帮助您。

非常感谢您的帮助,这是非常宝贵的信息。我对OpenGL(ES)还比较新,由于需要记住许多新细节来解决即使是简单问题,所以感到有些困惑。我的YUV420->RGB转换器现在可以工作了,尽管效率不太高,因为使用3个EGL_FORMAT_YUV_YV12_FSL图像来保存Y、U和V分量。我无法从单个EGL_FORMAT_YUV_YV12_FSL纹理中正确地获取Y、U和V分量,因此采用了另一种方法,现在它可以正常工作了... - tselmeci
顺便说一句,我很遗憾在iMX53上无法将现有缓冲区的地址传递给eglCreateImageKHR()。也许我会从相反的方向来解决这个问题:我创建许多图像,并将这些图像的物理地址传递给VPU,以将解码帧放入其中。我不确定它是否有效(例如,VPU需要DMA内存)... - tselmeci
这就是我所做的。让 EGL 分配缓冲区,然后使用这些缓冲区从 VPU 接收解码帧。我发现这样可以消除使用 IPU 的需要,并且效果更好。CPU 使用率非常低。您可以修改 Freescale 的 Gstreamer ISink 插件或 MXC-VPU 测试单元。 - ClayMontgomery
没有成功。VPU帧缓冲区的Y、Cb和Cr地址来自于eglQueryImageFSL。VPU解码一帧后,我从eglQueryImageFSL提供的虚拟地址执行memcpy到视频帧缓冲区,但它只是一遍又一遍地显示相同的垃圾。物理地址从0x00100000(1MB)开始,也许它们是GPU内存区域内的偏移量?现在我只是将这些地址传递给VPU... - tselmeci
好的,我已经弄清楚了! :) 必须使用“gpu_nommu”内核选项,这样物理地址才能在整个系统范围内访问(不仅由GPU使用其MMU)。 - tselmeci
好的观点。您还必须拥有来自Freescale的最新EGL驱动程序。我的版本是libEGL.so 270933 2011-11-16。您根本不需要任何memcpy()。 - ClayMontgomery

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接