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

严以律己,宽以待人

引言

终于到该章节了,还记得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的Camera及Camera2都可以使用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);
    ...

    // 激活纹理编号0
    GLES20.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预览

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();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mCameraFilter.surfaceCreated();
        mTextureId = mCameraFilter.getTextureId();
        mSurfaceTexture = new SurfaceTexture(mTextureId);
        mView.mMainHandler.post(() -> mView.surfaceTextureCreated(mSurfaceTexture));
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mCameraFilter.surfaceChanged(width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // 更新最新纹理
        mSurfaceTexture.updateTexImage();
        // 获取SurfaceTexture变换矩阵
        mSurfaceTexture.getTransformMatrix(mDisplayProjectionMatrix);
        // 将SurfaceTexture绘制到GLSurfaceView上
        mCameraFilter.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/918981.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

独立站干货:WordPress主机推荐

WordPress作为全球最受欢迎的独立站建设平台,提供了灵活性和强大的功能,使得建站变得简单而高效。本文将为您详细介绍WordPress建站的流程,并推荐几款实测后觉得好用的主机商。 WordPress建站流程 域名注册 首先需要注册一个域名&#xff0c…

细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的方法

目录 一、DMA基础知识 1、DMA简介 (1)DMA控制器 (2)DMA流 (3)DMA请求 (4)仲裁器 (5)DMA传输属性 2、源地址和目标地址 3、DMA传输模式 4、传输数据量的大小 5、数据宽度 6、地址指针递增 7、DMA工作模式 8、DMA流的优先级别 9、FIFO或直接模式 10、单次传输或突…

基于Spring Boot+Vue的多媒体素材管理系统的设计与实现

一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构:B/S架构 运行环境:win10/win11、jdk17 前端: 技术:框架Vue.js;UI库:ElementUI; 开发工具&…

如何禁用关闭奇安信天擎开机自启动教程

前言 公司要求我们员工每个电脑上都要安装奇安信防护软件,但是身为开发,这个软件占内存不说,还禁用我们电脑上todesk等远程软件,因为我们给客户部署的项目,部署的有软件服务,经常需要用到todesk等远程软件…

[Docker#8] 容器配置 | Mysql | Redis | C++ | 资源控制 | 命令对比

目录 一:Mysql 容器化安装 二:Redis 容器化安装 Redis 简介 Redis 容器创建 三:C容器制作 四:容器资源更新 常见问题 一:Mysql 容器化安装 进入 mysql 的镜像网站,查找 mysql 的镜像 mysql docker…

CentOS 修改服务器登录密码的完整指南

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] &#x1f4f1…

深入理解Redis(七)----Redis实现分布式锁

基于Redis的实现方式 1、选用Redis实现分布式锁原因: (1)Redis有很高的性能; (2)Redis命令对此支持较好,实现起来比较方便 2、使用命令介绍: (1)SETNX SETNX …

Uniapp运行环境判断和解决跨端兼容性详解

Uniapp运行环境判断和解决跨端兼容性 开发环境和生产环境 uniapp可通过process.env.NODE_ENV判断当前环境是开发环境还是生产环境,一般用于链接测试服务器或者生产服务器的动态切换。在HX中,点击运行编译出来的代码是开发环境,点击发行编译…

WPF MVVM框架

一、MVVM简介 MVC Model View Control MVP MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和模型(Model),具有低…

【nginx】client timed out和send_timeout的大小设置

websocket连接会断开,抓包检查后发现是中间的代理服务器nginx断开的,同时将后端和浏览器都断开了。将nginx日志调到debug级别后,有下面的断开信息。 [info] 125923#125923: *34 client timed out (110: Connection timed out) while proxyin…

python视频编辑中的蒙版技术:创意与技术相结合

在数字视频编辑的世界里,蒙版技术是一种强大的工具,它允许我们在视频帧上进行精确的编辑和效果叠加。通过蒙版,我们可以控制哪些部分的视频内容被显示或隐藏,从而创造出各种视觉效果和过渡。在本文中,我们将探讨如何使…

前端算法:树(力扣144、94、145、100、104题)

目录 一、树(Tree) 1.介绍 2.特点 3.基本术语 4.种类 二、树之操作 1.遍历 前序遍历(Pre-order Traversal):访问根节点 -> 遍历左子树 -> 遍历右子树。 中序遍历(In-order Traversal&#xf…

【代码审计】常见漏洞专项审计-业务逻辑漏洞审计

❤️博客主页: iknow181 🔥系列专栏: 网络安全、 Python、JavaSE、JavaWeb、CCNP 🎉欢迎大家点赞👍收藏⭐评论✍ 0x01 漏洞介绍 1、 原理 业务逻辑漏洞是一类特殊的安全漏洞,业务逻辑漏洞属于设计漏洞而非实…

Spring Boot汽车资讯:数字化时代的驾驶

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…

Redis的缓存穿透、缓存雪崩、缓存击穿问题及有效解决方案

目录 一、缓存穿透 1.简介 2.解决方案 3.修改前的代码 4.修改过后的代码 二、缓存雪崩 1.简介 2.解决方案 三、缓存击穿 1.简介 2.解决方案 3.用代码来实现互斥锁来解决缓存击穿 4.用代码来实现逻辑过期解决缓存击穿 四、缓存穿透和缓存击穿的区别 一、缓存穿透 …

FPGA 第7讲 简单组合逻辑译码器

时间:2024.11.15 一、学习内容 1.译码器 译码是编码的逆过程,在编码时,每一种二进制代码,都赋予了特定的含义,即都表示了一个确定的信号或者对象。把代码状态的特定含义翻译出来的过程叫做译码,实现译码操…

如何在 Ubuntu 上安装 Jupyter Notebook

本篇文章将教你在 Ubuntu 服务器上安装 Jupyter Notebook,并使用 Nginx 和 SSL 证书进行安全配置。 我将带你一步步在云服务器上搭建 Jupyter Notebook 服务器。Jupyter Notebook 在数据科学和机器学习领域被广泛用于交互式编码、可视化和实验。在远程服务器上运行…

【Pikachu】XML外部实体注入实战

若天下不定&#xff0c;吾往&#xff1b;若世道不平&#xff0c;不回&#xff01; 1.XXE漏洞实战 首先写入一个合法的xml文档 <?xml version "1.0"?> <!DOCTYPE gfzq [<!ENTITY gfzq "gfzq"> ]> <name>&gfzq;</name&…

Docker安装稳定版本nginx-1.26.2

Linux 安装稳定版本nginx-1.20.2 1、下载镜像、场景配置文件目录 [rootTseng ~]# docker pull nginx:1.26.2 1.26.2: Pulling from library/nginx 2d429b9e73a6: Pull complete 40a0d865309c: Pull complete a949b43e642c: Pull complete 8a756fb620a9: Pull complete 93…

训练误差or测试误差与特征个数之间的关系--基于R语言实现

a 生成数据集&#xff0c;数据由 Y X β ϵ YX\beta\epsilon YXβϵ产生&#xff0c;其中 p 20 &#xff0c; n 1000 p20&#xff0c;n1000 p20&#xff0c;n1000 #way1 set.seed(1) p 20 n 1000 x matrix(rnorm(n*p), n, p) B rnorm(p) B[3] 0 B[4] 0 B[9] 0 B[19…