如何使用GLSL正确解开V210视频帧?

时间:2013-12-01 22:11:42

标签: opengl video glsl video-capture

我有10位YUV(V210)视频帧来自采集卡,我想在GLSL着色器中解压缩这些数据,最终转换为RGB进行屏幕输出。我在Linux上使用Quadro 4000卡(OpenGL 4.3)。

我使用以下设置上传纹理:

  

视频帧:720x486像素

     

在128字节对齐的内存中实际占用933120个字节(步幅为1920)

     

纹理当前上传为480x486像素(步幅/ 4 x高度),因为这与数据的字节数匹配

     

GL_RGB10_A2的内部格式

     

GL_RGBA的格式

     

GL_UNSIGNED_INT_2_10_10_10_REV

的类型      

过滤当前设置为GL_NEAREST

为清晰起见,这是上传命令:

  

int stride =((m_videoWidth + 47)/ 48)* 128;

     

glTexImage2D(GL_TEXTURE_2D,0,GL_RGB10_A2,stride / 4,m_videoHeight,0,GL_RGBA,GL_UNSIGNED_INT_2_10_10_10_REV,字节);

数据本身就像这样包装:

U Y V A | Y U Y A | V Y U A | Y V Y A

或者在此处查看Blackmagic的插图:http://i.imgur.com/PtXBJbS.png

每个纹素总共为32位(“R,G,B”通道各10位,alpha为2位)。复杂的地方是将6个像素打包到128位的这个块中。这些块只是重复上述模式,直到帧结束。

我知道每个纹素的组件都可以用texture2D(tex,coord).rgb访问,但由于每个纹素的顺序不一样(例如UYV vs YUY),我知道必须操纵纹理坐标为此负责。

然而,我不知道如何处理这样一个事实:这个纹理中只有比GL知道更多的像素,我相信这意味着我必须考虑扩大/缩小以及最小我的着色器内部/ mag过滤(我需要双线性)。输出窗口需要能够是任何大小(比纹理更小,相同或更大),因此着色器不应该有任何与之相关的常量。

我该如何做到这一点?

2 个答案:

答案 0 :(得分:7)

这是完成的着色器,包含所有通道和RGB转换(但不执行过滤):

#version 130
#extension GL_EXT_gpu_shader4 : enable
in vec2 texcoord;
uniform mediump sampler2D tex;
out mediump vec4 color;

// YUV offset
const vec3 yuvOffset = vec3(-0.0625, -0.5, -0.5);

// RGB coefficients
// BT.601 colorspace
const vec3 Rcoeff = vec3(1.1643,  0.000,  1.5958);
const vec3 Gcoeff = vec3(1.1643, -0.39173, -0.81290);
const vec3 Bcoeff = vec3(1.1643,  2.017,  0.000);

// U Y V A | Y U Y A | V Y U A | Y V Y A

int GROUP_FOR_INDEX(int i) {
  return i / 4;
}

int SUBINDEX_FOR_INDEX(int i) {
  return i % 4;
}

int _y(int i) {
  return 2 * i + 1;
}

int _u(int i) {
  return 4 * (i/2);
}

int _v(int i) {
  return 4 * (i / 2) + 2;
}

int offset(int i) {
  return i + (i / 3);
}

vec3 ycbcr2rgb(vec3 yuvToConvert) {
  vec3 pix;
  yuvToConvert += yuvOffset;
  pix.r = dot(yuvToConvert, Rcoeff);
  pix.g = dot(yuvToConvert, Gcoeff);
  pix.b = dot(yuvToConvert, Bcoeff);
  return pix;
}

void main(void) {
  ivec2 size = textureSize2D(tex, 0).xy; // 480x486
  ivec2 sizeOrig = ivec2(size.x * 1.5, size.y); // 720x486

  // interpolate 0,0 -> 1,1 texcoords to 0,0 -> 720,486
  ivec2 texcoordDenorm = ivec2(texcoord * sizeOrig);

  // 0 1 1 2 3 3 4 5 5 6 7 7 etc.
  int yOffset = offset(_y(texcoordDenorm.x));
  int sourceColumnIndexY = GROUP_FOR_INDEX(yOffset);

  // 0 0 1 1 2 2 4 4 5 5 6 6 etc.
  int uOffset = offset(_u(texcoordDenorm.x));
  int sourceColumnIndexU = GROUP_FOR_INDEX(uOffset);

  // 0 0 2 2 3 3 4 4 6 6 7 7 etc.
  int vOffset = offset(_v(texcoordDenorm.x));
  int sourceColumnIndexV = GROUP_FOR_INDEX(vOffset);

  // 1 0 2 1 0 2 1 0 2 etc.
  int compY = SUBINDEX_FOR_INDEX(yOffset);

  // 0 0 1 1 2 2 0 0 1 1 2 2 etc.
  int compU = SUBINDEX_FOR_INDEX(uOffset);

  // 2 2 0 0 1 1 2 2 0 0 1 1 etc.
  int compV = SUBINDEX_FOR_INDEX(vOffset);

  vec4 y = texelFetch(tex, ivec2(sourceColumnIndexY, texcoordDenorm.y), 0);
  vec4 u = texelFetch(tex, ivec2(sourceColumnIndexU, texcoordDenorm.y), 0);
  vec4 v = texelFetch(tex, ivec2(sourceColumnIndexV, texcoordDenorm.y), 0);

  vec3 outColor = ycbcr2rgb(vec3(y[compY], u[compU], v[compV]));

  color = vec4(outColor, 1.0);
}

如果图像将在屏幕上放大,那么您可能希望进行双线性过滤,但这需要在着色器中执行。

答案 1 :(得分:5)

我建议先写一个只进行像素重新排序的着色器,并保持插值。

它确实需要额外的视频RAM和另一个渲染通道,但它并不一定要慢:如果包含重新缩放,则需要计算4个中间像素的内容,然后在它们之间进行插值。如果为插值创建单独的着色器,则可以像使用硬件插值返回单个纹理查找的结果一样简单。

为颜色样本重新排列设置正确的着色器后,您可以随时将其转换为函数。

那么如何编写重新排列着色器?

您的输入如下:

  U Y V A | Y U Y A | V Y U A | Y V Y A

为简单起见,假设您只想读Y.那么您可以制作一个非常简单的1d纹理(720列x 1行)。每个纹理单元格都有两个值:列偏移从哪里读取值。其次,我们需要在该单元格中Y样本的位置:

  U Y V A | Y U Y A | V Y U A | Y V Y A


  0      1      2      3      4      5   ..... // out column (0-720)

  0      1      1      2      3      3   ..... // source column index (0-480)
  1      0      2      1      0      2   ..... // Y sample index in column (range 0-2)

要获取Y(亮度)值,请使用屏幕x位置索引行纹理。然后你知道要读取哪个源纹素。然后取第二个组件,并使用它来获取正确的样本。在DirectX中,您只需使用整数索引vec4/float4即可选择R / G / B / A值。我希望GLSL支持同样的。

所以现在你有了Y.对U和V重复上述过程。

一旦你开始工作,你可以尝试通过巧妙地在一个纹理中更有效地包装上述信息来优化,而不是三个不同的纹理。或者,您可以尝试考虑线性函数,在舍入后生成列索引。这将为您节省许多纹理查找。

但也许整个优化在你的场景中没有实际意义。只需让最简单的案例先行。

我故意没有为你编写着色器代码,因为我对DirectX非常熟悉。但这应该让你开始。

祝你好运!