OpenGL标准并不支持YUV数据,ES扩展也只是多了一些跨线程共享Surface、绑定物理视窗的特性,也不支持YUV数据。
stackoverflow上有人已经给出了方法:
https://stackoverflow.com/questions/22456884/how-to-render-androids-yuv-nv21-camera-image-on-the-background-in-libgdx-with-o
想要直接渲染yuv数据,需要先了解一下yuv格式中数据的排列方式。摄像头会先存储画面的灰度数据,再进行颜色的采样
每个黑色点为一个像素点,每个红色点为颜色信息,每临近的4个像素点共用一个颜色数据,这个颜色数据是相邻4个点的平均值。
再现有宽为w高为h的一个视频,现在定义了一个data[] bytes变量并存储了这个视频的一帧数据。
- bytes数组中,先连续存储每个像素的灰度信息,每个灰度数据占一个字节,所以y通道占w*h个字节
- 然后顺序存储颜色数据,u和v分别占一个字节,每个颜色数据共占2个字节,所以uv通道占(w/2)(h/2)2=w*h/2个字节
根据这个存储特点,分别把y通道的数据和uv通道的数据生成两个opengl纹理,然后两个纹理作为渲染时的输入,在渲染过程中由GPU组装还原出来视频画面。
根据y通道生成纹理,y通道中每个像素占一个字节,正好对应OpenGL的GL_LUMINANCE_ALPHA
格式:
GLuint yTex;
glGenTextures(1,&yTex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, yTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bytes);
根据uv通道生成纹理,uv通道中每个像素占两个字节,可以用OpenglGL的GL_LUMINANCE_ALPHA
生成纹理:
GLuint uvTex;
glGenTextures(1,& uvTex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, uvTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width / 2, height / 2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data + (width * height));
两个纹理作为一个渲染管线的输入变量:
glUniform1i(engineHolder->posUniTextureY, 0);
glUniform1i(engineHolder->posUniTextureUV, 1);
使用的shader:
#extension GL_OES_EGL_image_external : require
precision highp float;
varying highp vec2 vTexCoord;
uniform sampler2D yTexture;
uniform sampler2D uvTexture;
vec4 getBaseColor(in vec2 coord){
float r,g,b,y,u,v;
y = texture2D(yTexture,coord).r;
vec4 uvColor = texture2D(uvTexture,coord);
u = uvColor.a - 0.5;
v = uvColor.r - 0.5;
r = y + 1.13983*v;
g = y - 0.39465*u - 0.58060*v;
b = y + 2.03211*u;
return vec4(r, g, b, 1.0);
}
void main(){
gl_FragColor = getBaseColor(mirrorCoord);
}
由于uv通道生成的纹理面积是y通道纹理的1/4,当OpenGL把uv纹理拉伸至视口大小时,正好一个颜色数据点的面积对于一个像素点的面积。
代码请见开源项目:https://github.com/ifinver/FinEngine