设为首页 加入收藏

TOP

Opengl ES之RGB转NV21(一)
2023-07-23 13:31:15 】 浏览:158
Tags:Opengl RGB NV21

前言

在上一篇理论文章中我们介绍了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

Y  =  0.257R + 0.504G + 0.098B + 16
Cb = -0.148R - 0.291G + 0.439B + 128
Cr =  0.439R - 0.368G - 0.071B + 128

RGB 转 BT.709 YUV

Y  =  0.183R + 0.614G + 0.062B + 16
Cb = -0.101R - 0.339G + 0.439B + 128
Cr =  0.439R - 0.399G - 0.040B + 128

或者也可以使用矩阵运算的方式进行转换,更加的便捷:

RGB转YUV

RGB转YUV

先说一下RGB转YUV的过程,先将RGB数据按照公式转换为YUV数据,然后将YUV数据按照RGBA进行排布,这一步的目的是为了后续数据读取,最后使用glReadPixels读取YUV数据。

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

以NV21的YUV数据为例,它的内存大小为width x height * 3 / 2。如果是RGBA的格式存储的话,占用的内存空间大小是width x height x 4(因为 RGBA 一共4个通道)。很显然它们的内存大小是对不上的,
那么该如何调整Opengl buffer的大小让RGBA的输出能对应上YUV的输出呢?我们可以设计输出的宽为width / 4,高为height * 3 / 2即可。

为什么是这样的呢?虽然我们的目的是将RGB转换成YUV,但是我们的输入和输出时读取的类型GLenum是依然是RGBA,也就是说:width x height x 4 = (width / 4) x (height * 3 / 2) * 4

而YUV数据在内存中的分布以下这样子的:

width / 4
|--------------|
|              |
|              | h
|      Y       |
|--------------|            
|   U   |  V   |
|       |      |  h / 2
|--------------|

那么上面的排序如果进行了归一化之后呢,就变成了下面这样子了:

(0,0) width / 4  (1,0)
|--------------|
|              |
|              |  h
|      Y       |
|--------------|  (1,2/3)          
|   U   |  V   |
|       |      |  h / 2
|--------------|
(0,1)           (1,1)

从上面的排布可以看出看出,在纹理坐标y < (2/3)时,需要完成一次对整个纹理的采样,用于生成Y数据,当纹理坐标 y > (2/3)时,同样需要再进行一次对整个纹理的采样,用于生成UV的数据。
同时还需要将我们的视窗设置为glViewport(0, 0, width / 4, height * 1.5);

由于视口宽度设置为原来的 1/4 ,可以简单的认为相对于原来的图像每隔4个像素做一次采样,由于我们生成Y数据是要对每一个像素都进行采样,所以还需要进行3次偏移采样。

同理,生成对于UV数据也需要进行3次额外的偏移采样。

在着色器中offset变量需要设置为一个归一化之后的值:1.0/width, 按照原理图,在纹理坐标 y < (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(Y0,Y1,Y2,Y3),整个范围采样结束时填充好 width*height 大小的缓冲区;
当纹理坐标 y > (2/3) 范围,一次采样(加三次偏移采样)4 个 RGBA 像素(R,G,B,A)生成 1 个(V0,U0,V0,U1),又因为 UV 缓冲区的高度为 height/2 ,VU plane 在垂直方向的采样是隔行进行,整个范围采样结束时填充好 width*height/2 大小的缓冲区。

主要代码

RGBtoYUVOpengl.cpp


#include "../utils/Log.h"
#include "RGBtoYUVOpengl.h"

// 顶点着色器
static const char *ver = "#version 300 es\n"
                         "in vec4 aPosition;\n"
                         "in vec2 aTexCoord;\n"
                         "out vec2 v_texCoord;\n"
                         "void main() {\n"
                         "  v_texCoord = aTexCoord;\n"
                         "  gl_Position = aPosition;\n"
                         "}";

// 片元着色器
static const char *fragment = "#version 300 es\n"
                              "precision mediump float;\n"
                              "in vec2 v_texCoord;\n"
                              "layout(location = 0) out vec4 outColor;\n"
                              "uniform sampler2D s_TextureMap;\n"
                              "uniform float u_Offset;\n"
                              "const vec3 COEF_Y = vec3(0.299, 0.587, 0.114);\n"
                              "const vec3 COEF_U = vec3(-0.147, -0.289, 0.436);\n"
                              "const vec3 COEF_V = vec3(0.615, -0.515, -0.100);\n"
                              "const float UV_DIVIDE_LINE = 2.0 / 3.0;\n"
                              "void main(){\n"
                              "    vec2 texelOffset = vec2(u_Offset, 0.0);\n"
                              "    if (v_texCoord.   y <= UV_DIVIDE_LINE) {\n"
                              "        vec2 texCoord = vec2(v_texCoord.   x, v_texCoord.   y * 3.0 / 2.0);\n"
                              "        vec4 color0 = texture(s_TextureMap, texCoord);\n&
首页 上一页 1 2 3 4 5 6 下一页 尾页 1/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Android网络请求(4) 网络请求框架.. 下一篇华为运动健康服务Health Kit 6.9...

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目