前言
在上一篇理论文章中我们介绍了YUV到RGB之间转换的几种公式与一些优化算法,今天我们再来介绍一下RGB到YUV的转换,顺便使用Opengl ES做个实践,将一张RGB的图片通过Shader的方式转换YUV格式图,然后保存到本地。
可能有的童鞋会问,YUV转RGB是为了渲染显示,那么RGB转YUV的应用场景是什么?在做视频编码的时候我们可以使用MediaCodec搭配Surface就可以完成,貌似也没有用到RGB转YUV的功能啊,硬编码没有用到,那么软编码呢?一般我们做视频编码的时候都是硬编码优先,软编码兜底的原则,在遇到一些硬编码不可用的情况下可能就需要用到x264库进行软编码了,而此时RGB转YUV可能就派上用场啦。
RGB到YUV的转换公式
在前面 Opengl ES之YUV数据渲染 一文中我们介绍过YUV的几种兼容标准,下面我们看看RGB到YUV的转换公式:
RGB 转 BT.601 YUV
RGB 转 BT.709 YUV
或者也可以使用矩阵运算的方式进行转换,更加的便捷:
RGB转YUVRGB转YUV
先说一下RGB转YUV的过程,先将RGB数据按照公式转换为YUV数据,然后将YUV数据按照RGBA进行排布,这一步的目的是为了后续数据读取,最后使用读取YUV数据。
而对于OpenGL ES来说,目前它输入只认RGBA、lumiance、luminace alpha这几个格式,输出大多数实现只认RGBA格式,因此输出的数据格式虽然是YUV格式,但是在存储时我们仍然要按照RGBA方式去访问texture数据。
以NV21的YUV数据为例,它的内存大小为。如果是RGBA的格式存储的话,占用的内存空间大小是(因为 RGBA 一共4个通道)。很显然它们的内存大小是对不上的,那么该如何调整Opengl buffer的大小让RGBA的输出能对应上YUV的输出呢?我们可以设计输出的宽为,高为即可。
为什么是这样的呢?虽然我们的目的是将RGB转换成YUV,但是我们的输入和输出时读取的类型GLenum是依然是RGBA,也就是说:width x height x 4 = (width / 4) x (height * 3 / 2) * 4
而YUV数据在内存中的分布以下这样子的:
那么上面的排序如果进行了归一化之后呢,就变成了下面这样子了:
从上面的排布可以看出看出,在纹理坐标时,需要完成一次对整个纹理的采样,用于生成Y数据,当纹理坐标 时,同样需要再进行一次对整个纹理的采样,用于生成UV的数据。同时还需要将我们的视窗设置为
由于视口宽度设置为原来的 1/4 ,可以简单的认为相对于原来的图像每隔4个像素做一次采样,由于我们生成Y数据是要对每一个像素都进行采样,所以还需要进行3次偏移采样。
同理,生成对于UV数据也需要进行3次额外的偏移采样。
在着色器中offset变量需要设置为一个归一化之后的值:, 按照原理图,在纹理坐标 y < (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(Y0,Y1,Y2,Y3),整个范围采样结束时填充好 大小的缓冲区;当纹理坐标 y > (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(V0,U0,V0,U1),又因为 UV 缓冲区的高度为 height/2 ,VU plane 在垂直方向的采样是隔行进行,整个范围采样结束时填充好 大小的缓冲区。
主要代码
下面是Activity的主要代码逻辑:
以下是自定义SurfaceView的代码:
BaseOpengl的java代码:
将转换后的YUV数据读取保存好后,可以将数据拉取到电脑上使用这个软件查看是否真正转换成功。
参考
专栏系列
领取专属 10元无门槛券
私享最新 技术干货