Android OpenGLES2.0开发(八):Camera预览

embedded/2024/11/17 23:43:25/

严以律己,宽以待人

引言

终于到该章节了,还记得Android OpenGLES2.0开发(一):艰难的开始章节说的吗?写这个系列的初衷就是因为每次用到GLSurfaceView+Camera预览时,总是Ctrl+CCtrl+V从来没有研究过里面的代码,也不知道如何修改。经过前面章节的铺垫,现在可以自信的说Camera+OpenGL ES轻松拿捏。

外部纹理

上一篇中我们已经讲过如何显示一张图片,而Camera预览其实也是显示一张一张的图片。我们将Camera的预览帧数据转化为Bitmap传给OpenGL ES就可以了。但是这种方式就失去了使用OpenGL ES效率高的优势,NV21转Bitmap是CPU操作极其耗性能。

那么有没有更好的方式?答案是有的,我们可以将NV21数据直接传给OpenGL ES进行处理预览,这样操作就快了很多。我们知道OpenGL ES显示的是RGBA的数据,相当于OpenGL ES要将NV21转为RGBA,效率肯定比上面的情况好很多,但是操作略微复杂,这也不是最终方案,有没有更简单的方式呢?

Android的CameraCamera2都可以使用SurfaceTexture作为预览载体,但是它们所使用的SurfaceTexture传入的OpenGL ES texture object name必须为GLES11Ext.GL_TEXTURE_EXTERNAL_OESGL_TEXTURE_EXTERNAL_OES是一种特殊的纹理类型,只用于处理外部图像或视频数据,如从摄像头捕捉的实时图像和外部视频流

GL_TEXTURE_EXTERNAL_OES的特点:

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

纹理渲染

Android Camera系列(三):GLSurfaceView+Camera这篇文章我们详细介绍了Camera使用GLSurfaceView进行预览操作,但唯独缺失了使用OpenGL ES绘制部分,而本篇是时候填坑了。

开始编写代码前,我们需要将上一篇的Image类拷贝一份命名为CameraFilter

1. 修改纹理着色器

首先,我们需要修改我们的着色器,将顶点着色器修改为:

// 顶点着色器代码
private final String vertexShaderCode ="uniform mat4 uMVPMatrix;\n" +// 顶点坐标"attribute vec4 vPosition;\n" +"uniform mat4 uTexPMatrix;\n" +// 纹理坐标"attribute vec4 vTexCoordinate;\n" +"varying vec2 aTexCoordinate;\n" +"void main() {\n" +"  gl_Position = uMVPMatrix * vPosition;\n" +"  aTexCoordinate = (uTexPMatrix * vTexCoordinate).xy;\n" +"}";

顶点着色器中的代码和渲染图片顶点着色器代码基本一致,增加了一个uTexPMatrix变量,这个是用来对纹理坐标进行变换的矩阵

uTexPMatrix纹理顶点变换的矩阵其实可以不用,我们只用顶点变换矩阵也是可以的,但是我们就需要对Camera前后置旋转变换要做一个处理。而SurfaceTexture中会自带一个变换矩阵,我们拿来直接用就不用处理Camera的前后置及旋转方向的问题了。

// 片段着色器代码
private final String fragmentShaderCode ="#extension GL_OES_EGL_image_external : require\n" +"precision mediump float;\n" +"uniform samplerExternalOES vTexture;\n" +"varying vec2 aTexCoordinate;\n" +"void main() {\n" +"  gl_FragColor = texture2D(vTexture, aTexCoordinate);\n" +"}\n";

片段着色器中我们不再使用sampler2D采样,而是使用samplerExternalOES纹理采样器,并且要在头部增加使用扩展纹理的声明#extension GL_OES_EGL_image_external : require

2. 设置顶点坐标和纹理坐标

上一篇中我们已经正确设置了坐标,所以这两个顶点坐标保持不变

3. 初始化

初始化我们不再需要传入Bitmap

public CameraFilter() {
...
}

4. 创建外部纹理

我们需要在surfaceCreated中创建外部纹理,相机预览使用EXTERNAL_OES纹理,创建方式与2D纹理创建基本相同

public void surfaceCreated() {// 加载顶点着色器程序...// 创建纹理句柄textureId = createTexture();
}public int createTexture() {int[] texture = new int[1];GLES20.glGenTextures(1, texture, 0);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);// 取消绑定纹理GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);return texture[0];
}

由于我们创建的是扩展纹理,所以绑定的时候我们也需要绑定到扩展纹理上才可以正常使用,GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture[0])

5. 计算变换矩阵

surfaceChanged中计算变换矩阵,由于视图的变换现在交给了纹理顶点坐标,所以我们顶点坐标矩阵使用原始矩阵即可。

public void surfaceChanged(int width, int height) {GLES20.glViewport(0, 0, width, height);// 获取原始矩阵,与原始矩阵相乘坐标不变Matrix.setIdentityM(mMVPMatrix, 0);
}

现在出现了两个矩阵,一个是顶点变换矩阵,一个是纹理变换矩阵。只要有顶点坐标都可以进行变换,但是我们最好控制变量,不要两个同时变换。如果还想继续使用顶点坐标矩阵变换,那么可以删除纹理矩阵参数。

5. 渲染

我们修改draw方法中的部分代码

  • 将纹理变换矩阵传入给顶点着色器
  • glBindTexture改为GLES11Ext.GL_TEXTURE_EXTERNAL_OES
public void draw(float[] texMatrix) {...// 将纹理坐标变换矩阵传递给顶点着色器GLES20.glUniformMatrix4fv(vTexPMatrixHandle, 1, false, texMatrix, 0);...// 激活纹理编号0GLES20.glActiveTexture(GLES20.GL_TEXTURE0);// 绑定纹理GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);// 设置纹理采样器编号,该编号和glActiveTexture中设置的编号相同GLES20.glUniform1i(texHandle, 0);// 绘制GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);// 取消绑定纹理GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);...
}

Camera_137">Camera预览

GLSurfaceView的模板代码我就不再这里列了,我这里只把Renderer中的代码再贴出来,看下他是如何使用CameraFilter进行渲染的

static class MyRenderer implements Renderer {private CameraFilter mCameraFilter;private int mTextureId;private SurfaceTexture mSurfaceTexture;private CameraGLSurfaceView mView;private final float[] mDisplayProjectionMatrix = new float[16];public MyRenderer(CameraGLSurfaceView glSurfaceView) {mView = glSurfaceView;mCameraFilter = new CameraFilter();}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mCameraFilter.surfaceCreated();mTextureId = mCameraFilter.getTextureId();mSurfaceTexture = new SurfaceTexture(mTextureId);mView.mMainHandler.post(() -> mView.surfaceTextureCreated(mSurfaceTexture));}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mCameraFilter.surfaceChanged(width, height);}@Overridepublic void onDrawFrame(GL10 gl) {// 更新最新纹理mSurfaceTexture.updateTexImage();// 获取SurfaceTexture变换矩阵mSurfaceTexture.getTransformMatrix(mDisplayProjectionMatrix);// 将SurfaceTexture绘制到GLSurfaceViewmCameraFilter.draw(mDisplayProjectionMatrix);}
}

Renderer的代码比较简单,在对应的生命周期中调用CameraFilter的生命周期方法即可。我们需要注意的就是onDrawFrame方法中的功能:

  • mSurfaceTexture.updateTexImage:
    从OpenGL上下文线程,即当前渲染线程中更新图像数据流最近的一帧纹理图像。调用该方法我们可以获取一帧新的图像用来渲染。
  • mSurfaceTexture.getTransformMatrix:
    获取刚那一帧纹理数据的变换矩阵,我们只需将该矩阵传入着色器就可以获取一个方向正确的预览视图。

为什么getTransformMatrix获取到的矩阵可以获取到正确的预览方向?原因在于Camera在初始化时设置了正确的预览方向,他会把正确的方向映射给SurfaceTexture。所以我们不需要再写复杂的投影变化了,直接用getTransformMatrix获取的变换矩阵即可。

请添加图片描述
预览效果如上,搞定手工!

最后

该篇章主要讲解了OpenGL ES对外部纹理Camera预览数据如何进行渲染,因为有了前面的基础,在对外部纹理渲染时我们只修改了部分代码即可实现。Camera的相关操作我们在Android Camera系列中有详细的讲解,这里没有在重复说明,回顾前面的篇章本篇享用更佳。

OpenGL ES系列:https://github.com/xiaozhi003/OpenGLDemo.git
Camera系列:https://github.com/xiaozhi003/AndroidCamera.git


http://www.ppmy.cn/embedded/138064.html

相关文章

Halcon 3D平面度

平面度是对表面形状的一种度量,用于指示该表面上的所有点是否都在同一个平面上。平面度在几何尺寸和公差(GD&T)中用平行四边形表示,当两个表面必须装配在一起形成紧密密封时,平面度就特别有用。 使用平面度公差是…

蓝桥杯c++算法学习【3】之思维与贪心(重复字符串、翻硬币、乘积最大、皮亚诺曲线距离【难】:::非常典型的必刷例题!!!)

别忘了请点个赞收藏关注支持一下博主喵!!! 关注博主,更多蓝桥杯nice题目静待更新:) 思维与贪心 一、重复字符串 【问题描述】 如果一个字符串S恰好可以由某个字符串重复K次得到,我们就称S是K次重复字 符串…

工业大数据分析与应用:开启智能制造新时代

在全球工业4.0浪潮的推动下,工业大数据分析已经成为推动智能制造、提升生产效率和优化资源配置的重要工具。通过收集、存储、处理和分析海量工业数据,企业能够获得深刻的业务洞察,做出更明智的决策,并实现生产流程的全面优化。本文…

QT5.14*解决QSslSocket::connectToHostEncrypted: TLS initialization faile

qDebug()<<"QSslSocket"<<QSslSocket::sslLibraryBuildVersionString();通过上述代码在QT控制台查看对应需要的SSL版本&#xff0c;QT5.14.*输出的内容为&#xff1a; OpenSSL 1.1.1d 10 Sep 2019从官方下载openssl安装包即可&#xff0c;在官网找了很…

pycharm连接oracle数据库查询数据

查询当前python版本在 Terminal中使用命令 pip version Python-oracledb 的默认精简模式可以连接到 Oracle 数据库 12.1 或更高版本。如果要连接到 Oracle 数据库 11.2&#xff0c;则需要通过在代码中调用 oracledb.init_oracle_client() 来启用厚模式。否则会提示版本不支持。…

web——upload-labs——第四关——.htaccess文件绕过

先尝试直接上传一个普通的一句话木马 显示此文件不允许上传&#xff0c;这道题并没有提示不允许上传什么后缀的文件&#xff0c;经过尝试&#xff0c;基本上所有后缀能够被解析为php语句执行的文件都不能成功上传。试试正常的图片能不能上传&#xff1a; 我们再来试试图片马能不…

20241115在飞凌的OK3588-C的核心板上跑Linux R4时拿大文件到电脑的方法

20241115在飞凌的OK3588-C的核心板上跑Linux R4时拿大文件到电脑的方法 2024/11/15 15:26 缘起&#xff1a;使用SONY 405的机芯&#xff0c;以1080p60录像了半小时&#xff0c;3.5GB的mp4视频要拿到电脑上播放确认。 方法&#xff1a;1、拷贝到TF卡。记住&#xff0c;对于FAT32…

力扣-Hot100-二叉树其二【算法学习day.33】

前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&am…