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

news2024/12/26 12:29:02

严以律己,宽以待人

引言

终于到该章节了,还记得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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JMX Exporter源码解读+生产环境最佳实践+解决其抓取指标超时问题

文章目录 背景第一版配置-查询所有MBean第二版配置-配置白名单第三版配置-增加Cache第四版配置-修改jmx_exorter源码禁用默认jvm导出第五版配置-基于第四版excludeObjectNameAttributes第六版配置-修改jmx_exorter源码includeObjectNameAttributes配置基于release-1.0.1分支修改…

前端(3)——快速入门JaveScript

参考: 罗大富 JavaScript 教程 | 菜鸟教程 JavaScript 教程 1. JaveScript JavaScript 简称 JS JavaScript 是一种轻量级、解释型、面向对象的脚本语言。它主要被设计用于在网页上实现动态效果,增加用户与网页的交互性。作为一种客户端脚本语言&#…

人工智能:塑造未来的工作与生活

目录 人工智能技术的应用前景与影响 人工智能的历史与现状 人工智能的应用领域 人工智能的前景与挑战 个人视角:人工智能的应用前景与未来 人工智能在生活中的潜力 面对人工智能带来的挑战 我的观点与建议 结语 人工智能技术的应用前景与影响 随着人工智能…

东土国产自主智能控制器,亮相第七届长三角科技成果交易博览会

近日,第七届长三角科技成果交易博览会(以下简称“长三角科交会”)在上海汽车会展中心开幕。为展示嘉定新城产业集聚成果,宣传新城核心区投资环境,新城公司连续第六届参加长三角科交会。 在此次展会上,新城…

AUTOSAR_EXP_ARAComAPI的7章笔记(4)

☞返回总目录 相关总结:本地 / 网络多绑定用例总结 7.3.2 本地/网络多绑定用例 在前一节中,我们看到了的一种多绑定特殊变体,现在来看,也可认为是一种真实情况的变体。 假设有一个与上一章节相似的情景,唯一的区别…

ubuntu将firewall-config导出为.deb文件

firewall-config ubuntu是canonial 公司维护的,用wireshark测过,开机会给他们公司发遥测(开了ufw阻塞所有连接也一样,canonial在里面把代码改了)firewall-config是fedora(爱好者维护,公益版本)自带的防火墙…

LabVIEW中坐标排序与旋转 参见附件snippet程序

LabVIEW中坐标排序与旋转 参见附件snippet程序LabVIEW中坐标排序与旋转 参见附件snippet程序 - 北京瀚文网星科技有限公司 在LabVIEW中处理坐标排序的过程,尤其是按顺时针或逆时针排列坐标点,常见的应用包括处理几何形状、路径规划等任务。下面我将为您…

基于微信小程序的校园超市购物系统设计与实现,LW+源码+讲解

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本超市购物系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

如何使用EasyExcel生成多列表组合填充的复杂Excel示例

作者:Funky_oaNiu 一、(需求)生成的表格效果:二、搞一个模板文件三、建立对应的表格实体类四、开始填充五、Vue3前端发起请求下载六、官方文档及AI问答 一、(需求)生成的表格效果: 其中只有顶部…

AdaBoost 二分类问题

代码功能 生成数据集: 使用 make_classification 创建一个模拟分类问题的数据集。 数据集包含 10 个特征,其中 5 个是有用特征,2 个是冗余特征。 数据集划分: 将数据分为训练集(70%)和测试集(3…

权限相关知识

1.Linux权限的概念 在说Linux权限的概念之前我来问大家一个问题,你们觉得什么是权限? 权限平时的体现呢,就比如不是校长的亲戚就不能逛办公室,没充会员的爱奇艺看不了VIP影视剧,没成会员的的蛋糕店拿不到会员价等等等…

Python爬虫项目 | 一、网易云音乐热歌榜歌曲

文章目录 1.文章概要1.1 实现方法1.2 实现代码1.3 最终效果 2.具体讲解2.1 使用的Python库2.2 代码说明2.2.1 创建目录保存文件2.2.2 爬取网易云音乐热歌榜单歌曲 2.3 过程展示 3 总结 1.文章概要 学习Python爬虫知识,实现简单的一个小案例,网易云音乐热…

苍穹外卖-后端部分

软件开发整体介绍 前端搭建 在非中文目录中双击nginx.exe然后浏览器访问localhost即可 后端搭建 基础准备 导入初始文件 使用git进行版本控制 创建本地仓库和远程仓库,提交Git 连接数据库 连接数据库把资料中的文件放入运行即可 前后端联调测试 苍穹外卖项目接口文档…

3D电子商务是什么?如何利用3D技术提升销售转化?

在数字化浪潮席卷全球的今天,网上购物已成为消费者日常生活中不可或缺的一部分。然而,尽管其便捷性无可比拟,但传统电商模式中的“看不见、摸不着”问题始终困扰着消费者与商家。商品是否符合期望、尺寸是否合适、颜色是否真实……这些不确定…

EXCEL延迟退休公式

如图: A B为手工输入 C2EOMONTH(A2,B2*12) D2EOMONTH(C2,IF(C2>DATEVALUE("2025-1-1"),INT((DATEDIF(DATEVALUE("2025-1-1"),C2,"m")4)/4),0)) E2EOMONTH(A2,B2*12IF(EOMONTH(A2,B2*12)>DATEVALUE("2025-1-1"),INT(…

OpenSSL 自签名

参考文档:unigui开发人员工作手册2021 参考文章:保姆级OpenSSL下载及安装教程-CSDN博客 下载 Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions 进入后向下拉找到下载位置,建议下载二进制版本的精简版&#xff0c…

DevOps工程技术价值流:加速业务价值流的落地实践与深度赋能

DevOps的兴起,得益于敏捷软件开发的普及与IT基础设施代码化管理的革新。敏捷宣言虽已解决了研发流程中的诸多挑战,但代码开发仅是漫长价值链的一环,开发前后的诸多问题仍亟待解决。与此同时,虚拟化和云计算技术的飞跃,…

R语言贝叶斯分析:INLA 、MCMC混合模型、生存分析肿瘤临床试验、间歇泉喷发时间数据应用|附数据代码...

全文链接:https://tecdat.cn/?p38273 多模态数据在统计学中并不罕见,常出现在观测数据来自两个或多个潜在群体或总体的情况。混合模型常用于分析这类数据,它利用不同的组件来对数据中的不同群体或总体进行建模。本质上,混合模型是…

Python酷库之旅-第三方库Pandas(218)

目录 一、用法精讲 1021、pandas.DatetimeIndex.inferred_freq属性 1021-1、语法 1021-2、参数 1021-3、功能 1021-4、返回值 1021-5、说明 1021-6、用法 1021-6-1、数据准备 1021-6-2、代码示例 1021-6-3、结果输出 1022、pandas.DatetimeIndex.indexer_at_time方…

从基础到进阶,Dockerfile 如何使用环境变量

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 什么是 Dockerfile 环境变量?🔖1. `ENV` 指令🔖2. `ARG` 指令🔖语法:🔖使用 `ARG` 的例子:📝 如何使用环境变量提高 Dockerfile 的灵活性🔖1. 动态配置环境🔖2. 配置不同的运行环境🔖3. 多…