0x1 Introduction
本文我们将在GPU上实现yuv420p到rgb的转换,转换代码采用opengl es glsl实现。该转换代码在GPU上运行,为了更好地展示这个实现过程和体现转换效果,我们在Android平台上实现一个app应用。
详细代码请参考 YUVRender
YUV2RGB转换的算法原理如下,其中Y/U/V是从yuv420p数据中读取的三个分量。
B=1.164(Y−16)+2.018(U−128)
G=1.164(Y−16)−0.813(V−128)−0.391(U−128)
R=1.164(Y−16)+1.596(V−128)
0x2 Shader代码
0x21 vertex shader代码
vertex shader代码首先输入顶点坐标和纹理坐标。
输出变量gl_Position用于在计算顶点位置以后将其写入裁剪坐标。
输出变量vtexcoord是输入给fragment shader作为纹理坐标的。
0x22 fragment shader代码
fragment shader代码首先输入texture坐标, 该坐标首先经过了vertex shader处理,
然后又经过了Primitive Assembly阶段的clip和插值处理。
然后输入三个sampler2D,这三个sampler2D分别对应yuv420p的Y/U/V数据,通过texture2D这个内置的纹理访问函数,我们可以读取对应纹理坐标下的Y/U/V数据。
接下面的代码就是执行YUV到RGB的转换矩阵了,具体的矩阵可以参考前面的介绍。
0x3 具体实现
0x31 texture的创建
为了让GPU能读取到YUV的数据,需要创建3个texture, 这三个texture分别存放Y/U/V分量。
0x32 yuv420p数据的读取
我们读取的yuv420p数据是foreman.qcif,分辨率是qcif(176x144), 从数据布局来看,首先是176x144大小的Y分量,然后是88x72大小的U分量, 最后是88x72大小的V分量。下面是读取YUV数据的详细代码。
0x33 texture的更新
YUV数据保持在pixels数组中,通过调用glTexImage2D把YUV数据输入到GPU driver中。
因为需要给YUV的三个texture分别输入,所以需要循环3次。
0x34 更新其他参数
下面的代码是把顶点坐标和纹理坐标输入给GPU driver。
0x4 调试中碰到的问题
代码写好以后,一运行,不能出来正确的图像,一直是绿屏。下面介绍一下解决绿屏的过程。
首先检查api调用是否正确,通过启用swiftshader,我们可以打印出所有的opengl es api调用log, 检查该app调用的opengl es api,没有发现问题。
再怀疑是因为texture没有被正确地送入到GPU中,通过dump glTexImage2D()输入的pixel,发现也是正确的。
最后在fragment shader中写hard code, 强制输出某种颜色,也是正确的,说明整个流程没有问题。
最后怀疑是texture的坐标没有正确地设置,在打印glVertexAttribPointer()的输入参数,发现这个时候的texture坐标都是0,明显不是正确的值,一步一步往上debug, 发现是因为Java ByteBuffer的order没有被设置成本机字节顺序(ByteOrder.nativeOrder()), 设置了以后,texture的坐标就正确了。
解决了绿屏的问题以后,yuv420p的数据能在Android上正确显示了,显示效果如下