最近在做android平台摄像头采集和视频渲染,当想要把性能做到极致的时候,总避不开使用GPU。

在视频采集、美颜以及其他前处理之后,需要将数据转换成I420格式,方便后续处理,如果使用CPU去做会导致性能瓶颈。

因为我们在前处理过程都是采用GPU去做,因此这个转换使用GPU去做不仅方便,而且能充分利用GPU的优势。

在编写OpenGL ES的shader前,先需要确定好fragment shader的输入和输出格式。

输入可以是一个包含RGBA的texture,或者是分别包含Y、U、V的三个texture,也可以使包含Y和UV的两个texture(UV分别放在texture rgba的r和a中,NV21和NV12都可以用这种方式)。

输出的texture不仅要包含所有的YUV信息,还要方便我们一次性读取I420格式数据(glReadPixels)。

因此输出数据的YUV紧凑地分布:

+---------+
    |         |
    |  Y      |
    |         |
    |         |
    +----+----+
    | U  | V  |
    |    |    |
    +----+----+

而对于OpenGL ES来说,目前它输入只认RGBA、lumiance、luminace alpha这几个格式,输出大多数实现只认RGBA格式,因此输出的数据格式虽然是I420格式,但是在存储时我们仍然要按照RGBA方式去访问texture数据。

对于上述存储布局,输出的texture宽度为width/4,高度为height+height/2。这样一张1280*720的图,需要申请的纹理大小为:360x1080。

先看看其fragment shader代码,一眼看去,简介明了:

// 在x方向上,一个像素的步长(纹理已经做过归一化,这个步长不是像素个数)
"uniform vec2 xUnit;\n"
// RGB to YUV的颜色转换系数
+ "uniform vec4 coeffs;\n"
+ "\n"
+ "void main() {\n"
// 虽然alpha通道值总是1,我们可以写成一个vec4xvec4的矩阵乘法,但是这样做实际
// 导致了较低帧率,这里用了vec3xvec3乘法。
// tc是texture coordinate,可以理解成输出纹理坐标
+ "  gl_FragColor.r = coeffs.a + dot(coeffs.rgb,\n"
+ "      sample(tc - 1.5 * xUnit).rgb);\n"
+ "  gl_FragColor.g = coeffs.a + dot(coeffs.rgb,\n"
+ "      sample(tc - 0.5 * xUnit).rgb);\n"
+ "  gl_FragColor.b = coeffs.a + dot(coeffs.rgb,\n"
+ "      sample(tc + 0.5 * xUnit).rgb);\n"
+ "  gl_FragColor.a = coeffs.a + dot(coeffs.rgb,\n"
+ "      sample(tc + 1.5 * xUnit).rgb);\n"
+ "}\n";

假设输出图片是1280x720大小的,那么GPU将并行地执行1280x720次main运算。

例如输出纹理坐标 tc = vec2(x,y) 点,该像素点有RGBA四个数值需要填充,而且都需要填充成Y(或者U、V),那么它需要知道R、G、B、A应该取实际纹理的哪一点,对于 Y 直接映射到纹理图片中,取左右各两个点即可。Y通道从0,0处绘制,UV按照实际的位置做左边转换。

作者:tkorays

来源:http://www.tkorays.com/2019/08/09/Convert-RGBA-to-YUV-by-GLES-Shader/

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频开发工作经验分享 || 视频版

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

NDK 学习进阶免费视频来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

最近在做android平台摄像头采集和视频渲染,当想要把性能做到极致的时候,总避不开使用GPU。在视频采集、美颜以及其他前处理之后,需要将数据转换成I420格式,方便后续处理,如果使用CP...
最近,有位读者大人在后台反馈:在参加一场面试的时候,面试官要求他用 shader 实现图像格式 RGB YUV ,他听了之后一脸懵,然后悻悻地对面试官说,他只用 shader YUV RGB ,不知道 RGB YUV 是个什么思路。 针对他的这个疑惑,今天专门写文章介绍一下如何 使用 OpenGL 实现 RGB YUV 的图像格式 换,帮助读者大人化解此类问题。 YUV 看图工具推荐 有读者大人让推荐一个 YUV 看图软件,由于手头的工具没法分享出来,又在 Github 上找了一圈发现这.
Shader 实现 RGB A I420 I420 格式的图像在视频解码中比较常见,像前面文章中提到的,在工程中一般会选择 使用 Shader RGB A YUV ,这样再 使用 glReadPixels 读取图像时可以有效降低传输数据量,提升性能,并且兼容性好。 所以,在读取 OpenGL 渲染结果时,先利用 Shader RGB A YUV 然后再进行读取,这种方式非常高效便捷。 例如 YUYV 格式相对 RGB A 数据量降为原来的 50% ,而采用 NV21 或者 I420 格式可以降低
GPU 实现 RGB -- YUV RGB --> YUV 换的公式是现成的,直接在 CPU 端 换的话,只需要遍历每个像素,得到新的 YUV 值,根据其内存分布规律,合理安排分布即可。然而在 CPU 端进行 换,存在的问题运行效率太低,无法满足高效 换的需求。我们将目光投向拥有流水线体系的支持高速浮点数计算的硬件——GPU. 换公式如下: GPU 上面的实现 考虑在 GP...
glReadPixels glReadPixels 是 OpenGL ES 的 API , OpenGL ES 2.0 和 3.0 均支持。 使用 非常方便,下面一行代码即可搞定,但是效率也是最低的。 glReadPixels(0, 0, outImage.width, outImage.height, GL_ RGB A, GL_UNSIGNED_BYTE, buffer); 当调用 glReadPixels 时,首先会影响 CPU 时钟周期,同时 GPU 会等待当前帧绘制完成,读取像素完成之后,才开始
在EasyX中,可以 使用 `setfillcolor(COLORREF)` 函数设置填充颜色,其中 `COLORREF` 类型的参数可以 使用 RGB A 表示法。具体地说,可以 使用 以下方式表示 RGB A 颜色值: ```c++ COLORREF rgb a = RGB (red, green, blue) | (alpha << 24); 其中,`red`、`green` 和 `blue` 分别表示红、绿、蓝三个通道的颜色值(范围为 0~255),`alpha` 表示透明度(范围为 0~255,0 表示完全透明,255 表示完全不透明)。通过将 `alpha` 左移 24 位,将其放到颜色值的高位中,就可以表示一个 RGB A 颜色值了。 例如,要设置一个红色不透明度为半透明的填充颜色,可以这样 : ```c++ int red = 255, green = 0, blue = 0, alpha = 128; COLORREF rgb a = RGB (red, green, blue) | (alpha << 24); setfillcolor( rgb a);