经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » OpenGL » 查看文章
解密视频魔法:将ExternalOES纹理转化为TEXTURE_2D纹理
来源:cnblogs  作者:bjxiaxueliang  时间:2023/12/22 16:21:22  对本文有异议

在使用OpenGL ES进行图形图像开发时,我们常使用GL_TEXTURE_2D纹理类型,它提供了对标准2D图像的处理能力。这种纹理类型适用于大多数场景,可以用于展示静态贴图、渲染2D图形和进行图像处理等操作。
另外,有时我们需要从Camera或外部视频源读取数据帧并进行处理。这时,我们会使用GL_TEXTURE_EXTERNAL_OES纹理类型。其专门用于对外部图像或实时视频流进行处理,可以直接从 BufferQueue 中接收的数据渲染纹理多边形,从而提供更高效的视频处理和渲染性能。

在实际应用中,我们通常将GL_TEXTURE_2DGL_TEXTURE_EXTERNAL_OES这两种纹理类型分开使用,并且它们互不干扰。实际上,这种情况占据了80%的使用场景。我们可以根据具体需求选择合适的纹理类型进行处理和渲染。
然而,有时候我们也会遇到一些特殊情况,需要将GL_TEXTURE_EXTERNAL_OES纹理转化为GL_TEXTURE_2D纹理进行视频处理或计算。这种情况可能出现在需要对视频数据进行特殊的图像处理或者与GL_TEXTURE_2D纹理类型的其他渲染操作进行交互时。

当以上情况出现时,我们该如何处理呢?难道是直接将GL_TEXTURE_EXTERNAL_OES纹理赋值给GL_TEXTURE_2D纹理使用(经过实验这种方式是不可用的)?
这里对此情况,先给出解决方案,一般我们可以通过一些技术手段,如离屏渲染FrameBuffer帧缓冲区对象,将GL_TEXTURE_EXTERNAL_OES纹理转换为GL_TEXTURE_2D纹理,并进行后续的处理和计算。
而此篇文章主要记录,我是如何通过FrameBuffer帧缓冲区对象,将GL_TEXTURE_EXTERNAL_OES纹理数据转化为GL_TEXTURE_2D纹理数据的!

  • 首先 回顾一下GL_TEXTURE_2D纹理与GL_TEXTURE_EXTERNAL_OES纹理;
  • GL_TEXTURE_EXTERNAL_OES纹理数据通过FrameBuffer转化为GL_TEXTURE_2D纹理数据

一、TEXTURE_2DEXTERNAL_OES

在正式研究 “GL_TEXTURE_EXTERNAL_OES纹理数据转化为GL_TEXTURE_2D纹理数据” 之前,先要搞清楚:

  • 什么是GL_TEXTURE_2D纹理?
  • 什么又是GL_TEXTURE_EXTERNAL_OES纹理?
  • GL_TEXTURE_2D纹理与GL_TEXTURE_EXTERNAL_OES纹理有什么样的区别?

1.1 GL_TEXTURE_2D纹理

GL_TEXTURE_2D 提供了对标准2D图像的处理能力,可以存储静态的贴图图像或者帧缓冲区的渲染结果
其使用二维的纹理坐标系,通过将纹理坐标映射到纹理图像上的对应位置,可以实现纹理贴图、纹理过滤、纹理环绕等操作

GL_TEXTURE_2D纹理的特点包括:

  • 使用二维纹理坐标系进行操作;
  • 使用glTexImage2D函数加载纹理数据;
  • 通过纹理过滤和纹理环绕等方式进行纹理的采样和处理;

GL_TEXTURE_2D纹理:创建、绑定、采样、加载纹理图像

  1. public static int createDrawableTexture2D(Context context, int drawableId) {
  2. // 生成纹理ID
  3. int[] textures = new int[1];
  4. GLES30.glGenTextures(1, textures, 0);
  5. // 绑定纹理
  6. GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
  7. // 纹理采样方式
  8. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
  9. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
  10. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
  11. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
  12. // texImage2D加载图像数据
  13. InputStream is = context.getResources().openRawResource(drawableId);
  14. Bitmap bitmapTmp;
  15. try {
  16. bitmapTmp = BitmapFactory.decodeStream(is);
  17. } finally {
  18. try {
  19. is.close();
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. GLUtils.texImage2D(GLES30.GL_TEXTURE_2D,0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, GLUtils.getType(bitmapTmp), 0 );
  25. bitmapTmp.recycle();
  26. return textures[0];
  27. }

GL_TEXTURE_2D纹理:Shader处理阶段(片元着色器)

  1. precision mediump float;
  2. varying vec2 v_texture_coord;
  3. uniform sampler2D MAIN;
  4. void main() {
  5. vec4 color=texture2D(MAIN, v_texture_coord);
  6. gl_FragColor=color;
  7. }

GL_TEXTURE_2D纹理:纹理渲染

  1. GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);
  2. GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

1.2 GL_TEXTURE_EXTERNAL_OES纹理类型

根据AOSP: SurfaceTexture 文档描述,GL_TEXTURE_EXTERNAL_OES 是一种特殊的纹理类型,主要用于处理外部图像或视频数据,如从摄像头捕捉的实时图像和外部视频流。
GL_TEXTURE_EXTERNAL_OES 相对于 GL_TEXTURE_2D 最大的特点就是 GL_TEXTURE_EXTERNAL_OES可直接从 BufferQueue 中接收的数据渲染纹理多边形

GL_TEXTURE_EXTERNAL_OES纹理类型的特点包括:

  • 需采用特殊的采样器类型纹理着色器扩展
  • 使用二维纹理坐标系进行操作,与GL_TEXTURE_2D相似。
  • 专门用于处理外部图像或视频数据,可直接从 BufferQueue 中接收的数据渲染纹理多边形,从而提供更高效的视频处理和渲染性能。

对于此,官方文档中提供了一个 Grafika 的连续拍摄案例工程,并给出了如下参考流程图。
Google官方Grafika案例流程

通过阅读 Grafika 的连续拍摄案例,我们得知:

  • 首先,需创建一个OES纹理ID,用于接收Camera图像数据
  1. // GL_TEXTURE_EXTERNAL_OES: 纹理创建、绑定、采样
  2. public static int createTextureOES() {
  3. // 创建OES纹理ID
  4. int[] textures = new int[1];
  5. GLES30.glGenTextures(1, textures, 0);
  6. TextureUtil.checkGlError("glGenTextures");
  7. // 绑定OES纹理ID
  8. int texId = textures[0];
  9. GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
  10. TextureUtil.checkGlError("glBindTexture " + texId);
  11. // OES纹理采样
  12. GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
  13. GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
  14. GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
  15. GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
  16. TextureUtil.checkGlError("glTexParameter");
  17. return texId;
  18. }
  • 完成OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture,将SurfaceTexture设定为预览的PreviewTexture;
  1. // 传入一个OES纹理ID
  2. SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
  3. // 将 SurfaceTexture 设置为预览的 PreviewTexture
  4. Camera.setPreviewTexture(mSurfaceTexture);
  • 或者通过SurfaceTexture创建Surface,将Surface对象传递给MediaPlayerMediaCodec进行视频帧数据获取;
  1. // 传入一个OES纹理ID
  2. SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
  3. // 创建 Surface
  4. Surface mSurface = new Surface(mSurfaceTexture);
  5. // 将 Surface 设置给 MediaPlayer 外部视频播放器,获取视频帧数据
  6. MediaPlayer.setSurface(surface);
  1. // GL_TEXTURE_EXTERNAL_OES纹理:Shader处理阶段(片元着色器)
  2. #extension GL_OES_EGL_image_external : require
  3. precision mediump float;
  4. varying vec2 v_texture_coord;
  5. uniform samplerExternalOES MAIN;
  6. void main() {
  7. vec4 color=texture2D(MAIN, v_texture_coord);
  8. gl_FragColor=color;
  9. }

GL_TEXTURE_EXTERNAL_OES纹理:纹理渲染

  1. // 纹理渲染阶段:GL_TEXTURE_EXTERNAL_OES纹理
  2. GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, texId);
  3. GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

1.3 关于两者的区别 我的个人理解

关于两者的区别,我的个人理解:
GL_TEXTURE_2D纹理类型与GL_TEXTURE_EXTERNAL_OES纹理类型,在数据来源纹理数据的存储格式上存在差异。

  • 数据来源方面
    一个来源于glTexImage2D加载的二维图像数据
    一个来源与图像消费者Surface对应的BufferQueue
  • 纹理存储格式
    GL_TEXTURE_EXTERNAL_OES数据来源于外部视频源或Camera,其数据格式可能为YUV或RGB;
    GL_TEXTURE_2D的数据格式则依赖于开发中setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)的配置,可能是RGBA8888,也可能是RGBA4444等等。

由于两者数据来源和纹理存储格式的差异,两种纹理类型是不能直接进行转化的。

  • 首先,其在纹理采样阶段Shader处理阶段纹理渲染阶段均不同程度的存在差异(这一点在上一节的两者对比的代码举例中可以证明)。
  • 其次,如果需要在处理和计算阶段将GL_TEXTURE_EXTERNAL_OES纹理转换GL_TEXTURE_2D纹理,通常需要使用离屏渲染帧缓冲区对象等技术手段。

二、EXTERNAL_OES转化为TEXTURE_2D纹理数据

这里直接介绍转化过程

OES纹理数据转化TEXTURE2D纹理数据

  • 首先,需创建一个OES纹理ID(相关代码举例在前文已经给出);
  • 完成OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture(相关代码举例在前文已经给出);
  • 通过SurfaceTexture创建Surface,将Surface对象传递给MediaPlayer,获取Sdcard中对应路径的视频帧数据获取(相关代码举例在前文已经给出);
  • 创建FRAMEBUFFER帧缓冲区,并绑定GL_TEXTURE_2D空白纹理对象;
  1. public static int createEmptyTexture2DBindFrameBuffer(int[] frameBuffer, int texPixWidth, int texPixHeight) {
  2. // 创建纹理ID
  3. int[] textures = new int[1];
  4. GLES30.glGenTextures(1, textures, 0);
  5. // 绑定GL_TEXTURE_2D纹理
  6. GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
  7. // 纹理采样
  8. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
  9. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
  10. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
  11. GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
  12. // 创建一个空的2D纹理对象,指定其基本参数,并绑定到对应的纹理ID上
  13. GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, texPixWidth, texPixHeight,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
  14. // 取消绑定纹理
  15. GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
  16. /**
  17. * 帧缓冲区
  18. */
  19. // 创建帧缓冲区
  20. GLES30.glGenFramebuffers(1, frameBuffer, 0);
  21. // 将帧缓冲对象绑定到OpenGL ES上下文的帧缓冲目标上
  22. GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
  23. // 使用GLES30.GL_COLOR_ATTACHMENT0将纹理作为颜色附着点附加到帧缓冲对象上
  24. GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textures[0], 0);
  25. // 取消绑定缓冲区
  26. GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
  27. return textures[0];
  28. }
  • GL_TEXTURE_EXTERNAL_OES纹理渲染FRAMEBUFFER帧缓冲区中;
  1. // 激活纹理
  2. GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
  3. // 将所需的纹理对象绑定到Shader中纹理单元0上
  4. GLES30.glUniform1i(mOesTextureIdHandle, 0);
  5. // 绑定纹理
  6. GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
  7. // 绑定FRAMEBUFFER缓冲区
  8. GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, tex2DFrameBufferId);
  9. // 绘制矩形
  10. GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
  11. // 取消FRAMEBUFFER的绑定
  12. GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
  • 最后,绘制渲染GL_TEXTURE_2D纹理,完成纹理图像的显示。
  1. // 激活纹理
  2. GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
  3. // 将所需的纹理对象绑定到Shader中纹理单元0上
  4. GLES30.glUniform1i(mTex2DIdHandle, 0);
  5. // 绑定纹理
  6. GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, tex2DId);
  7. // 绘制矩形
  8. GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);

三、源码下载

ExternalOES纹理数据 转换为 TEXTURE-2D纹理数据:
https://download.csdn.net/download/aiwusheng/88650498

工程代码截图

参考

AOSP:SurfaceTexture
https://source.android.google.cn/docs/core/graphics/arch-st?hl=zh-c

Github:Google Grafika
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java

OpenGL渲染管线:
https://xiaxl.blog.csdn.net/article/details/121467207

纹理ID 离屏渲染 写入到Surface中:
https://xiaxl.blog.csdn.net/article/details/131682521

MediaCodeC与OpenGL硬编码录制mp4:
https://xiaxl.blog.csdn.net/article/details/72530314

原文链接:https://www.cnblogs.com/xiaxveliang/p/17921279.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号