一 、YUV422数据格式
结合上图可以看出YUV422的特点如下:
①在横向上是两个亮度(Y)共享一组色度(UV), 所以UV的宽度是Y的1/2, 高度相等。
②在内存中YUV422是交替连续存储的, 即内存中只需要一个地址保存每一帧的视频数据,在内存中的分布如下:
二 、GLFW渲染YUV422
2.1 定义顶点数据
YUV422和之前文章中渲染YUV420P一样, 定义四个顶点数据, 然后用索引来绘制两个三角形。
float vertex_coord_data[] = {
-1.f, -1.f, 0.f, 0.f, 1.f,
-1.f, 1.f, 0.f, 0.f, 0.f,
1.f, 1.f, 0.f, 1.f, 0.f,
1.f, -1.f, 0.f, 1.f, 1.f,
};
uint32_t vertx_index_data[] = {
0, 1, 2,
2, 3, 0
};
uint32_t m_vertex_buffer, m_index_buffer;
glGenBuffers(1, &m_vertex_buffer);
glGenBuffers(1, &m_index_buffer);
glGenVertexArrays(1, &m_vertex_array);
glBindVertexArray(m_vertex_array);
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_coord_data), vertex_coord_data, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertx_index_data), vertx_index_data, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(m_vertex_array);
2.2 创建YUYV纹理
因为YUYV在内存中是交替分布(packed)的, 所以这里建一张纹理就可以了。 为了方便计算, 使用 OpenGL建纹理时指定RGBA像素格式,这样刚好存储 YUYV 一个完整的像素单元, 纹理宽度就是YUYV帧宽度的一半。
glGenTextures(1, &m_tex_yuyv);
glBindTexture(GL_TEXTURE_2D, m_tex_yuyv);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
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_RGBA, m_tex_width/2, m_tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glGenerateMipmap(GL_TEXTURE_2D);
m_shader_parse.setInt("tex_yuyv", 0);
2.3 上行YUYV数据
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex_yuyv);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2 , m_tex_height, GL_RGBA, GL_UNSIGNED_BYTE, yuyv);
m_shader_parse.setInt("tex_yuyv", 0);
2.4 渲染YUYV
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex_yuyv);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
2.5 着色器
顶点着色器和YUV420P的顶点shader一致,因为数据是 YUYV 格式,然后纹理定义的是RGBA, 所以纹理采样一次就可以得到两个像素的采样值。
/顶点着色器///
#version 330 core
layout(location = 0) in vec3 vertex_pos;
layout(location = 1) in vec2 tex_pos;
out vec2 tex_uv;
void main()
{
gl_Position = vec4(vertex_pos, 1.f);
tex_uv = vec2(tex_pos);
}
/像素着色器///
#version 330 core
out vec4 frage_color1;
in vec2 tex_uv;
uniform sampler2D tex_yuyv;
uniform vec4 color_vec0;
uniform vec4 color_vec1;
uniform vec4 color_vec2;
uniform vec3 color_range_min;
uniform vec3 color_range_max;
uniform int rt_width;
void main()
{
vec4 yuyv = texture2D(tex_yuyv, tex_uv);
vec2 yy = yuyv.xz;
vec2 cbcr = yuyv.yw;
vec2 tex_size = textureSize(tex_yuyv, 0);
float factor = tex_size.x/rt_width;
float cur_y = (tex_uv.x * factor) < 0.5f ? yy.x : yy.y;
vec3 yuv = vec3(cur_y, cbcr);
yuv = clamp(yuv, color_range_min, color_range_max);
vec3 rgb = vec3(0.f);
rgb.r = dot(color_vec0.xyz, yuv) + color_vec0.w;
rgb.g = dot(color_vec1.xyz, yuv) + color_vec1.w;
rgb.b = dot(color_vec2.xyz, yuv) + color_vec2.w;
frage_color1 = vec4(rgb, 1.f);
}
2.6 设置渲染用到的参数
之前都在 shader 里面固定了,现在改为在 CPU 计算好转换矩阵再上传到 GPU里面去
m_shader_parse.setVec3("color_range_min", color_range_min);
GLenum errorcode = glGetError();
m_shader_parse.setVec3("color_range_max", color_range_max);
errorcode = glGetError();
m_shader_parse.setVec4("color_vec0", color_vec0);
errorcode = glGetError();
m_shader_parse.setVec4("color_vec1", color_vec1);
errorcode = glGetError();
m_shader_parse.setVec4("color_vec2", color_vec2);
errorcode = glGetError();
m_shader_parse.setInt("rt_width", m_tex_width);
三、代码地址以及存在的问题(已解决)
经过上述方式用 GLFW 渲染 YUYV后, 画面变得有些模糊, 我猜原因应该是片元着色器中采样没对, 但是查了较多资料也没找到解决方法,各位老师看过后能不能纠正一下哪里不正确。
代码地址为:https://github.com/pengguoqing/samples_code/tree/master/OpenGL/yuv422
本demo的渲染效果如下, 对比字体可以发现渲染结果变模糊了
正确的YUV420P的渲染效果
原因和解决方案:shader 中使用 texture 采样时会自动进行插值,这就会导致 YUV 不是严格严格匹配的,于是模糊就发生了。完整方案在后续这篇文章中有详细介绍: OpenGL使用FBO与PBO上行纹理 (YUYV).