经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » OpenGL » 查看文章
Opengl ES之YUV数据渲染
来源:cnblogs  作者:思想觉悟  时间:2022/11/23 18:51:36  对本文有异议

YUV回顾

记得在音视频基础知识介绍中,笔者专门介绍过YUV的相关知识,可以参考:
《音视频基础知识-YUV图像》

YUV数据量相比RGB较小,因此YUV适用于传输,但是YUV图不能直接用于显示,需要转换为RGB格式才能显示,因而YUV数据渲染实际上就是使用Opengl ES将YUV数据转换程RGB数据,然后显示出来的过程。

也就是说Opengl ES之所以能渲染YUV数据其实就是使用了Opengl强大的并行计算能力,快速地将YUV数据转换程了RGB数据。

本文首发于微信公总号号:思想觉悟

更多关于音视频、FFmpeg、Opengl、C++的原创文章请关注微信公众号:思想觉悟

YUV的格式比较多,我们今天就以YUV420SP为例,而YUV420SP又分为NV12NV21两种,因此今天我们的主题就是如何使用Opengl ES对NV12NV21数据进行渲染显示。

在着色器中使用texture2D对YUV数据进行归一化处理后Y数据的映射范围是0到1,而U和V的数据映射范围是-0.5到0.5。

因为YUV格式图像 UV 分量的默认值分别是 127 ,Y 分量默认值是 0 ,8 个 bit 位的取值范围是 0 ~ 255,由于在 shader 中纹理采样值需要进行归一化,所以 UV 分量的采样值需要分别减去 0.5 ,确保 YUV 到 RGB 正确转换。

YUV数据准备

首先我们可以使用ffmpeg命令行将一张png图片转换成YUV格式的图片:

  1. ffmpeg -i 图片名称.png -s 图片宽x图片高 -pix_fmt nv12或者nv21 输出名称.yuv)

通过上面这个命令行我们就可以将一张图片转换成yuv格式的图片,此时我们可以使用软件YUVViewer看下你转换的图片对不对,如果本身转换出来的图片就是错的,那么后面的程序就白搭了...

注意:转换图片的宽高最好是2的幂次方,笔者测试了下发现如果宽高不是2的幂次方的话虽然能正常转换,但是查看的时候要么有色差,要么有缺陷,也有可能正常。

又或者你可以极客一点,直接使用ffmpeg代码解码视频的方式获得YUV数据并保存,这个可以参考笔者之前的文章:

《FFmpeg连载3-视频解码》

同时在上面的文章中笔者也介绍了通过ffplay命令行的方式查看YUV数据的方法。

YUV数据渲染

YUV 渲染步骤:

  • 生成 2 个纹理,分别用于承载Y数据和UV数据,编译链接着色器程序;

NV21和NV12格式的YUV数据是只有两个平面的,它们的排列顺序是YYYY UVUV或者YYYY VUVU因此我们的片元着色器需要两个纹理采样。

  • 确定纹理坐标及对应的顶点坐标;
  • 分别加载 NV21 的两个 Plane 数据到 2 个纹理,加载纹理坐标和顶点坐标数据到着色器程序;
  • 绘制。

YUV与RGB的转换格式图:

YUV与RGB的转换公式

在OpenGLES的内置矩阵实际上是一列一列地构建的,比如YUV和RGB的转换矩阵的构建是:

  1. // 标准转换,舍弃了部分小数精度
  2. mat3 convertMat = mat3(1.0, 1.0, 1.0, //第一列
  3. 0.0,-0.3381.732 //第二列
  4. 1.371,-0.698 0.0);//第三列

OpenGLES 实现 YUV 渲染需要用到 GL_LUMINANCE 和 GL_LUMINANCE_ALPHA 格式的纹理,其中 GL_LUMINANCE 纹理用来加载 NV21 Y Plane 的数据,GL_LUMINANCE_ALPHA 纹理用来加载 UV Plane 的数据。

废话少说,show me the code

YUVRenderOpengl.h

  1. #ifndef NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H
  2. #define NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H
  3. #include "BaseOpengl.h"
  4. class YUVRenderOpengl: public BaseOpengl{
  5. public:
  6. YUVRenderOpengl();
  7. virtual ~YUVRenderOpengl();
  8. virtual void onDraw() override;
  9. // 设置yuv数据
  10. virtual void setYUVData(void *y_data,void *uv_data, int width, int height, int yuvType);
  11. private:
  12. GLint positionHandle{-1};
  13. GLint textureHandle{-1};
  14. GLint y_textureSampler{-1};
  15. GLint uv_textureSampler{-1};
  16. GLuint y_textureId{0};
  17. GLuint uv_textureId{0};
  18. };
  19. #endif //NDK_OPENGLES_LEARN_YUVRENDEROPENGL_H

YUVRenderOpengl.cpp

  1. #include "YUVRenderOpengl.h"
  2. #include "../utils/Log.h"
  3. // 顶点着色器
  4. static const char *ver = "#version 300 es\n"
  5. "in vec4 aPosition;\n"
  6. "in vec2 aTexCoord;\n"
  7. "out vec2 TexCoord;\n"
  8. "void main() {\n"
  9. " TexCoord = aTexCoord;\n"
  10. " gl_Position = aPosition;\n"
  11. "}";
  12. // 片元着色器 nv12
  13. //static const char *fragment = "#version 300 es\n"
  14. // "precision mediump float;\n"
  15. // "out vec4 FragColor;\n"
  16. // "in vec2 TexCoord;\n"
  17. // "uniform sampler2D y_texture; \n"
  18. // "uniform sampler2D uv_texture;\n"
  19. // "void main()\n"
  20. // "{\n"
  21. // "vec3 yuv;\n"
  22. // "yuv.x = texture(y_texture, TexCoord).r;\n"
  23. // "yuv.y = texture(uv_texture, TexCoord).r-0.5;\n"
  24. // "yuv.z = texture(uv_texture, TexCoord).a-0.5;\n"
  25. // "vec3 rgb =mat3( 1.0,1.0,1.0,\n"
  26. // "0.0,-0.344,1.770,1.403,-0.714,0.0) * yuv;\n"
  27. // "FragColor = vec4(rgb, 1);\n"
  28. // "}";
  29. /**
  30. * 仅仅是以下两句不同而已
  31. * "yuv.y = texture(uv_texture, TexCoord).r-0.5;\n"
  32. * "yuv.z = texture(uv_texture, TexCoord).a-0.5;\n"
  33. */
  34. // 片元着色器nv21 仅仅是
  35. static const char *fragment = "#version 300 es\n"
  36. "precision mediump float;\n"
  37. "out vec4 FragColor;\n"
  38. "in vec2 TexCoord;\n"
  39. "uniform sampler2D y_texture; \n"
  40. "uniform sampler2D uv_texture;\n"
  41. "void main()\n"
  42. "{\n"
  43. "vec3 yuv;\n"
  44. "yuv.x = texture(y_texture, TexCoord).r;\n"
  45. "yuv.y = texture(uv_texture, TexCoord).a-0.5;\n"
  46. "yuv.z = texture(uv_texture, TexCoord).r-0.5;\n"
  47. "vec3 rgb =mat3( 1.0,1.0,1.0,\n"
  48. "0.0,-0.344,1.770,1.403,-0.714,0.0) * yuv;\n"
  49. "FragColor = vec4(rgb, 1);\n"
  50. "}";
  51. // 使用绘制两个三角形组成一个矩形的形式(三角形带)
  52. // 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
  53. const static GLfloat VERTICES[] = {
  54. 0.5f,-0.5f, // 右下
  55. 0.5f,0.5f, // 右上
  56. -0.5f,-0.5f, // 左下
  57. -0.5f,0.5f // 左上
  58. };
  59. // 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
  60. //由于对一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
  61. const static GLfloat TEXTURE_COORD[] = {
  62. 1.0f,1.0f, // 右下
  63. 1.0f,0.0f, // 右上
  64. 0.0f,1.0f, // 左下
  65. 0.0f,0.0f // 左上
  66. };
  67. YUVRenderOpengl::YUVRenderOpengl() {
  68. initGlProgram(ver,fragment);
  69. positionHandle = glGetAttribLocation(program,"aPosition");
  70. textureHandle = glGetAttribLocation(program,"aTexCoord");
  71. y_textureSampler = glGetUniformLocation(program,"y_texture");
  72. uv_textureSampler = glGetUniformLocation(program,"uv_texture");
  73. LOGD("program:%d",program);
  74. LOGD("positionHandle:%d",positionHandle);
  75. LOGD("textureHandle:%d",textureHandle);
  76. LOGD("y_textureSampler:%d",y_textureSampler);
  77. LOGD("uv_textureSampler:%d",uv_textureSampler);
  78. }
  79. YUVRenderOpengl::~YUVRenderOpengl() {
  80. }
  81. void YUVRenderOpengl::setYUVData(void *y_data, void *uv_data, int width, int height, int yuvType) {
  82. // 准备y数据纹理
  83. glGenTextures(1, &y_textureId);
  84. glActiveTexture(GL_TEXTURE2);
  85. glUniform1i(y_textureSampler, 2);
  86. // 绑定纹理
  87. glBindTexture(GL_TEXTURE_2D, y_textureId);
  88. // 为当前绑定的纹理对象设置环绕、过滤方式
  89. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  90. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  91. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  92. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  93. glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, y_data);
  94. // 生成mip贴图
  95. glGenerateMipmap(GL_TEXTURE_2D);
  96. glBindTexture(GL_TEXTURE_2D, y_textureId);
  97. // 解绑定
  98. glBindTexture(GL_TEXTURE_2D, 0);
  99. // 准备uv数据纹理
  100. glGenTextures(1, &uv_textureId);
  101. glActiveTexture(GL_TEXTURE3);
  102. glUniform1i(uv_textureSampler, 3);
  103. // 绑定纹理
  104. glBindTexture(GL_TEXTURE_2D, uv_textureId);
  105. // 为当前绑定的纹理对象设置环绕、过滤方式
  106. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  107. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  108. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  109. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  110. // 注意宽高
  111. // 注意要使用 GL_LUMINANCE_ALPHA
  112. glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width/2, height/2, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, uv_data);
  113. // 生成mip贴图
  114. glGenerateMipmap(GL_TEXTURE_2D);
  115. glBindTexture(GL_TEXTURE_2D, uv_textureId);
  116. // 解绑定
  117. glBindTexture(GL_TEXTURE_2D, 0);
  118. }
  119. void YUVRenderOpengl::onDraw() {
  120. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  121. glClear(GL_COLOR_BUFFER_BIT);
  122. glUseProgram(program);
  123. // 激活纹理
  124. glActiveTexture(GL_TEXTURE2);
  125. // 绑定纹理
  126. glBindTexture(GL_TEXTURE_2D, y_textureId);
  127. glUniform1i(y_textureSampler, 2);
  128. // 激活纹理
  129. glActiveTexture(GL_TEXTURE3);
  130. // 绑定纹理
  131. glBindTexture(GL_TEXTURE_2D, uv_textureId);
  132. glUniform1i(uv_textureSampler, 3);
  133. /**
  134. * size 几个数字表示一个点,显示是两个数字表示一个点
  135. * normalized 是否需要归一化,不用,这里已经归一化了
  136. * stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
  137. */
  138. // 启用顶点数据
  139. glEnableVertexAttribArray(positionHandle);
  140. glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES);
  141. // 纹理坐标
  142. glEnableVertexAttribArray(textureHandle);
  143. glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD);
  144. // 4个顶点绘制两个三角形组成矩形
  145. glDrawArrays(GL_TRIANGLE_STRIP,0,4);
  146. glUseProgram(0);
  147. // 禁用顶点
  148. glDisableVertexAttribArray(positionHandle);
  149. if(nullptr != eglHelper){
  150. eglHelper->swapBuffers();
  151. }
  152. glBindTexture(GL_TEXTURE_2D, 0);
  153. }

注意看着色器代码的注释,NV12和NV21的渲染仅仅是着色器代码有细小差别而已。

YUVRenderActivity.java

  1. public class YUVRenderActivity extends BaseGlActivity {
  2. // 注意改成你自己图片的宽高
  3. private int yuvWidth = 640;
  4. private int yuvHeight = 428;
  5. private String nv21Path;
  6. private String nv12Path;
  7. private Handler handler = new Handler(Looper.getMainLooper());
  8. @Override
  9. protected void onCreate(@Nullable Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. // 注意申请磁盘写权限
  12. // 拷贝资源
  13. nv21Path = getFilesDir().getAbsolutePath() + "/nv21.yuv";
  14. FileUtils.copyAssertToDest(this,"nv21.yuv",nv21Path);
  15. nv12Path = getFilesDir().getAbsolutePath() + "/nv12.yuv";
  16. FileUtils.copyAssertToDest(this,"nv12.yuv",nv12Path);
  17. }
  18. @Override
  19. public BaseOpengl createOpengl() {
  20. YUVRenderOpengl yuvRenderOpengl = new YUVRenderOpengl();
  21. return yuvRenderOpengl;
  22. }
  23. @Override
  24. protected void onResume() {
  25. super.onResume();
  26. handler.postDelayed(new Runnable() {
  27. @Override
  28. public void run() {
  29. // 注意nv12和nv21的偏远着色器有点不一样的,需要手动改下调试 YUVRenderOpengl.cpp
  30. // if(!TextUtils.isEmpty(nv12Path)){
  31. // loadYuv(nv12Path,BaseOpengl.YUV_DATA_TYPE_NV12);
  32. // }
  33. if(!TextUtils.isEmpty(nv21Path)){
  34. loadYuv(nv21Path,BaseOpengl.YUV_DATA_TYPE_NV21);
  35. }
  36. }
  37. },200);
  38. }
  39. @Override
  40. protected void onStop() {
  41. handler.removeCallbacksAndMessages(null);
  42. super.onStop();
  43. }
  44. private void loadYuv(String path,int yuvType){
  45. try {
  46. InputStream inputStream = new FileInputStream(new File(path));
  47. Log.v("fly_learn_opengl","---length:" + inputStream.available());
  48. byte[] yData = new byte[yuvWidth * yuvHeight];
  49. inputStream.read(yData,0,yData.length);
  50. byte[] uvData = new byte[yuvWidth * yuvHeight / 2];
  51. inputStream.read(uvData,0,uvData.length);
  52. Log.v("fly_learn_opengl","---read:" + (yData.length + uvData.length) + "available:" + inputStream.available());
  53. myGLSurfaceView.setYuvData(yData,uvData,yuvWidth,yuvHeight);
  54. } catch (Exception e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }

这个主要看懂loadYuv方法,对于YUV数据的读取即可。

思考

都说YUV的格式较多,本文我们介绍了如何使用Opengl ES渲染YUV420SP数据,那么对于YUV420P数据,使用Opengl ES如何渲染呢?欢迎关注评论解答交流。

专栏系列

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形绘制
Opengl ES之四边形绘制
Opengl ES之纹理贴图
Opengl ES之VBO和VAO
Opengl ES之EBO
Opengl ES之FBO
Opengl ES之PBO

关注我,一起进步,人生不止coding!!!
微信扫码关注

原文链接:https://www.cnblogs.com/goFlyer/p/16917665.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号