最近在做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);