我有一个来自捕获卡的10位YUV(V210)视频帧,我想在GLSL着色器中解压缩这些数据并最终转换为RGB以进行屏幕输出.我在Linux上使用Quadro 4000卡(OpenGL 4.3).
我正在使用以下设置上传纹理:
视频帧:720x486像素
在128字节对齐的内存中物理占用933120个字节(步幅为1920)
纹理当前上传为480x486像素(步幅/ 4 x高度),因为这与数据的字节数匹配
GL_RGB10_A2的internalFormat
GL_RGBA的格式
GL_UNSIGNED_INT_2_10_10_10_REV的类型
过滤当前设置为GL_NEAREST
为清晰起见,这是upload命令:
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,bytes);
数据本身包装如下:
UYVA | YUYA | VYUA | YVYA
或者在这里查看Blackmagic的插图:http://i.imgur.com/PtXBJbS.png
每个纹素总共为32位("R,G,B"通道各10位,alpha为2位).复杂的地方是将6个像素打包到128位的这个块中.这些块只是重复上述模式直到帧结束.
我知道可以使用texture2D(tex,coord).rgb访问每个纹素的组件,但由于每个纹素的顺序不相同(例如UYV vs YUY),我知道必须操纵纹理坐标来解释那.
但是,我不知道如何处理这样一个事实:这个纹理中只有比GL知道更多的像素,我相信这意味着我必须考虑放大/缩小以及min/mag过滤(我需要双线性)在我的着色器内部.输出窗口需要能够是任何大小(比纹理更小,相同或更大),因此着色器不应具有与之相关的任何常量.
我怎么能做到这一点?
我建议先写一个只进行像素重新排序的着色器,并保持插值.
它确实需要额外的视频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
使用整数索引a 即可选择R/G/B/A值.我希望GLSL支持同样的东西.
所以现在你有了Y.对U和V重复上述过程.
一旦你开始工作,你可以尝试通过巧妙地在一个纹理中更有效地包装上述信息来优化,而不是三个不同的纹理.或者,您可以尝试考虑线性函数,在舍入后生成列索引.这将为您节省许多纹理查找.
但也许整个优化在你的场景中没有实际意义.只需让最简单的案例先行.
我特意没有为你编写着色器代码,因为我对DirectX非常熟悉.但这应该让你开始.
祝好运!
这是完成的着色器,包含所有通道和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); }
如果图像将在屏幕上放大,那么您可能希望进行双线性过滤,但这需要在着色器中执行.