我有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过滤(我需要双线性)。输出窗口需要能够是任何大小(比纹理更小,相同或更大),因此着色器不应该有任何与之相关的常量。
我该如何做到这一点?
答案 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非常熟悉。但这应该让你开始。
祝你好运!