解密视频魔法:将ExternalOES纹理转化为TEXTURE_2D纹理
在使用OpenGL ES进行图形图像开发时,我们常使用
GL_TEXTURE_2D纹理类型,它提供了对标准2D图像的处理能力。这种纹理类型适用于大多数场景,可以用于展示静态贴图、渲染2D图形和进行图像处理等操作。
另外,有时我们需要从Camera或外部视频源读取数据帧并进行处理。这时,我们会使用GL_TEXTURE_EXTERNAL_OES纹理类型。其专门用于对外部图像或实时视频流进行处理,可以直接从 BufferQueue 中接收的数据渲染纹理多边形,从而提供更高效的视频处理和渲染性能。
在实际应用中,我们通常将GL_TEXTURE_2D和GL_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_2D 与 EXTERNAL_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纹理:创建、绑定、采样、加载纹理图像
public static int createDrawableTexture2D(Context context, int drawableId) {
// 生成纹理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 纹理采样方式
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// texImage2D加载图像数据
InputStream is = context.getResources().openRawResource(drawableId);
Bitmap bitmapTmp;
try {
bitmapTmp = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D,0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, GLUtils.getType(bitmapTmp), 0 );
bitmapTmp.recycle();
return textures[0];
}
GL_TEXTURE_2D纹理:Shader处理阶段(片元着色器)
precision mediump float;
varying vec2 v_texture_coord;
uniform sampler2D MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_2D纹理:纹理渲染
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);
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 的连续拍摄案例工程,并给出了如下参考流程图。

通过阅读 Grafika 的连续拍摄案例,我们得知:
- 首先,需创建一个
OES纹理ID,用于接收Camera图像数据;
// GL_TEXTURE_EXTERNAL_OES: 纹理创建、绑定、采样
public static int createTextureOES() {
// 创建OES纹理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
TextureUtil.checkGlError("glGenTextures");
// 绑定OES纹理ID
int texId = textures[0];
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
TextureUtil.checkGlError("glBindTexture " + texId);
// OES纹理采样
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
TextureUtil.checkGlError("glTexParameter");
return texId;
}
- 完成
OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture,将SurfaceTexture设定为预览的PreviewTexture;
// 传入一个OES纹理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 将 SurfaceTexture 设置为预览的 PreviewTexture
Camera.setPreviewTexture(mSurfaceTexture);
- 或者通过
SurfaceTexture创建Surface,将Surface对象传递给MediaPlayer或MediaCodec进行视频帧数据获取;
// 传入一个OES纹理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 创建 Surface
Surface mSurface = new Surface(mSurfaceTexture);
// 将 Surface 设置给 MediaPlayer 外部视频播放器,获取视频帧数据
MediaPlayer.setSurface(surface);
- 前文已经提到
GL_TEXTURE_EXTERNAL_OES纹理类型 可直接从Surface对应的BufferQueue中获取视频流数据; - 在获取到视频帧数据后:
一方面,可通过OpenGL的渲染管线,将GL_TEXTURE_EXTERNAL_OES纹理渲染到GLSurfaceView上,完成图像数据的预览;
另一方面,可将GL_TEXTURE_EXTERNAL_OES纹理,通过离屏渲染的形式,写入到 MediaCodeC,硬编码生成MP4视频。
// GL_TEXTURE_EXTERNAL_OES纹理:Shader处理阶段(片元着色器)
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 v_texture_coord;
uniform samplerExternalOES MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_EXTERNAL_OES纹理:纹理渲染
// 纹理渲染阶段:GL_TEXTURE_EXTERNAL_OES纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, texId);
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纹理ID(相关代码举例在前文已经给出); - 完成
OES纹理ID创建后,通过oesTextureId创建一个图像消费者SurfaceTexture(相关代码举例在前文已经给出); - 通过
SurfaceTexture创建Surface,将Surface对象传递给MediaPlayer,获取Sdcard中对应路径的视频帧数据获取(相关代码举例在前文已经给出); - 创建
FRAMEBUFFER帧缓冲区,并绑定GL_TEXTURE_2D空白纹理对象;
public static int createEmptyTexture2DBindFrameBuffer(int[] frameBuffer, int texPixWidth, int texPixHeight) {
// 创建纹理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 绑定GL_TEXTURE_2D纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 纹理采样
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// 创建一个空的2D纹理对象,指定其基本参数,并绑定到对应的纹理ID上
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, texPixWidth, texPixHeight,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
// 取消绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
/**
* 帧缓冲区
*/
// 创建帧缓冲区
GLES30.glGenFramebuffers(1, frameBuffer, 0);
// 将帧缓冲对象绑定到OpenGL ES上下文的帧缓冲目标上
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
// 使用GLES30.GL_COLOR_ATTACHMENT0将纹理作为颜色附着点附加到帧缓冲对象上
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textures[0], 0);
// 取消绑定缓冲区
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
return textures[0];
}
- 将
GL_TEXTURE_EXTERNAL_OES纹理渲染到FRAMEBUFFER帧缓冲区中;
// 激活纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 将所需的纹理对象绑定到Shader中纹理单元0上
GLES30.glUniform1i(mOesTextureIdHandle, 0);
// 绑定纹理
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
// 绑定FRAMEBUFFER缓冲区
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, tex2DFrameBufferId);
// 绘制矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
// 取消FRAMEBUFFER的绑定
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
- 最后,绘制
渲染GL_TEXTURE_2D纹理,完成纹理图像的显示。
// 激活纹理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 将所需的纹理对象绑定到Shader中纹理单元0上
GLES30.glUniform1i(mTex2DIdHandle, 0);
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, tex2DId);
// 绘制矩形
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
解密视频魔法:将ExternalOES纹理转化为TEXTURE_2D纹理的更多相关文章
- 函数纹理(国际象棋棋盘纹理&粗布纹理)MFC
函数纹理(国际象棋棋盘纹理&粗布纹理)MFC实现 源码百度云下载 国际象棋棋盘纹理(效果图见最后) //国际象棋纹理函数 //g(u, v) = a , 向下取整(8u)+向下取整(8v) ...
- 4.QOpenGLWidget-对三角形进行纹理贴图、纹理叠加
在上章3.QOpenGLWidget-通过着色器来渲染渐变三角形,我们为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像.但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多 ...
- 一文详解 纹理采样与Mipmap纹理——构建山地渲染效果
在开发一些相对较大的场景时,例如:一片铺满相同草地纹理的丘陵地形,如果不采用一些技术手段,就会出现远处的丘陵较近处的丘陵相比更加的清晰的视觉效果,而这种效果与真实世界中近处的物体清晰远处物体模糊的效果 ...
- three.js学习:纹理Texture之平面纹理
index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- 解决 cocos2dx iOS/mac 设置纹理寻址模式后纹理变黑的问题
sprite:getTexture():setTexParameters(gl.LINEAR,gl.LINEAR,gl.REPEAT,gl.REPEAT) 在安卓设备上,设置了纹理自定义寻址模式,纹理 ...
- OpenGL蓝宝书第七章:立体天空和纹理折射、双纹理(下)
对照了蓝宝书,才知道红宝书的长处. reflect函数的原理在红宝书中有说明,仅仅有对照了红宝书,才知道红宝书的定位:高级工具书. 蓝宝书作为入门级书籍,以较快的速度让读者敲到代码去思考,总遗留了须要 ...
- base64随机字符混淆加密、解密-美拍视频地址解密,反推加密算法
用火车头测试采集美拍的数据时无意中发现美拍的视频地址是一段加了混淆字符串的base64代码.如下图 于是好奇之下研究了下解密算法.具体过程省略800字.发现美拍的视频解密是通过js完成,于是找到了具体 ...
- 某学院m3u8视频解密获取分析实战分享
[免责声明]本文来源于作者个人学习整理,仅供学习交流使用,不构成商业目的.所有资源均系本人个人学习或网络收集,仅提供一个展示.介绍.观摩学习的博文,不对其内容的准确性.可靠性.正当性.安全性.合法性等 ...
- 短视频技术详解:Android端的短视频开发技术
在 <如何快速实现移动端短视频功能?>中,我们主要介绍了当前短视频的大热趋势以及开发一个短视频应用所涉及到的功能和业务.在本篇文章中,我们主要谈一谈短视频在Android端上的具体实现技术 ...
- 【视频开发】伽马校正(gamma correction)学习笔记
我相信几乎所有做图像处理方面的人都听过伽马校正(Gamma Correction)这一个名词,但真正明白它是什么.为什么要有它.以及怎么用它的人其实不多.我也不例外. 最初我查过一些资料,但很多文章 ...
随机推荐
- 通过商品API接口获取到数据后的分析和应用
一.如果你想要分析商品API接口获取到的数据,可以按照如下的步骤进行: 了解API接口返回值的格式,如JSON格式.XML格式.CSV格式等,选择适合你的数据分析方式. 使用API请求工具(如Post ...
- Solution -「洛谷 P5072」「YunoOI 2015」盼君勿忘
Description Link. 无修支持查询:查询一个区间 \([l,r]\) 中所有子序列分别去重后的和 \(\bmod\ p\) Solution 这是数据结构一百题的第50题(一半了哦)的纪 ...
- MySQL系列3:缓冲池Buffer Pool的设计思想
1. 回顾 上一篇我们主要讲了InnoDB的存储引擎,其中主要的一个组件就是缓存池Buffer Pool,缓存了磁盘的真实数据,然后基于缓存做增删改查操作,同时配合了后续的redo log.刷磁盘等机 ...
- 基于三菱Q系列cc-Link的卧式自动燃煤蒸汽锅炉控制系统
系统说明: 方案选用: 本系统最终采用三菱Q系列+FX3U系列方案 工艺流程: 触摸屏设计: 程序设计: 本文章为原创作品,未经允许,请勿转载,否则将会追究法律责任.
- ASP.NET 6启动时自动创建MongoDB索引
大家好,我是Edison. 最近,在使用MongoDB时,碰到这样的一个需求:针对某个Collection手动在开发环境创建了索引,但在测试环境和生产环境不想再手动操作了,于是就想着通过代码的方式在A ...
- SpringBoot2.7升级到3.0的实践分享
背景 最近把项目中的技术框架做一次升级,最重要的就是SpringBoot从2.7.x升级到3.0.x,当然还会有一些周边的框架也会连带着升级,比如Mybatis Plus,SpringCloud等,话 ...
- qq群匿名聊怎么用
qq群匿名聊怎么用 1 2 3 4 5 分步阅读 匿名的意思就是不认识.群匿名聊当然是把群里的马甲一下变成不认识的人,再在一起聊天.是不是觉得有点吃饱了没事干,但是当下该功能还是比较实用的,群匿名聊可 ...
- 原创基于Scrum框架产研团队运作20问
学习完了 Scrum,实际使用中,是否遇到/思考过下面的问题? Product Owner的老板是谁.谁来给 Product Owner打绩效.考核的标准是啥? Scrum Master 的老板是谁. ...
- 【Dotnet 工具箱】基于 .NET 6 和 Angular 构建项目任务管理平台
1.Reha 时间管理大师 Rhea 是一个基于 C# 和 .NET 6 开发的在线任务管理平台,类似于 禅道.Jira.Redmine, 滴答清单等. 支持多视图多维度统一管理任务.多级结构,工作区 ...
- Go方法特性详解:简单性和高效性的充分体现
本文深入探讨了Go语言中方法的各个方面,包括基础概念.定义与声明.特性.实战应用以及性能考量.文章充满技术深度,通过实例和代码演示,力图帮助读者全面理解Go方法的设计哲学和最佳实践. 关注[TechL ...