Android Camera系列(三):GLSurfaceView+Camera

news2024/9/23 3:12:18

人类的悲欢并不相通—鲁迅

本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等,项目结构代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会

Alt

本章我们来讲解GLSurfaceView进行Camera预览,基于第一篇Android Camera系列(一):SurfaceView+Camera的成果,我们已经对Camera进行了封装,CameraManager拿来直接使用就好

一.GLSurfaceView使用

GLSurfaceView实际上就是继承了SurfaceView,并在其内部封装了EGL环境管理和渲染线程,使得我们可以直接使用opengl的API接口对图像进行变换等操作,如:黑白滤镜、美颜等各种复杂的滤镜效果

  1. 自定义CameraGLSurfaceView继承GLSurfaceView
  2. 实现SurfaceTexture.OnFrameAvailableListener接口,并在onFrameAvailable回调中请求每一帧数据进行渲染,也就是说Camera的预览需要我们自己绘制完成
  3. GLSurfaceView提供了绘制接口Renderer,我们需要定义CameraSurfaceRenderer实现该接口,并在GLSurfaceView初始化时设置自定义的渲染类。在onSurfaceCreated回调中创建外部纹理SurfaceTexture,并设置OnFrameAvailableListener监听Camera数据回调
  4. 实现自定义CameraCallback接口,监听Camera状态
  5. 一定要实现onResumeonPause接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug的源头
public class CameraGLSurfaceView extends GLSurfaceView implements SurfaceTexture.OnFrameAvailableListener, CameraCallback {

    private static final String TAG = CameraGLSurfaceView.class.getSimpleName();

    private Context mContext;
    private SurfaceTexture mSurfaceTexture;
    private CameraHandler mCameraHandler;
    private boolean hasSurface; // 是否存在摄像头显示层
    private CameraManager mCameraManager;
    private int mRatioWidth = 0;
    private int mRatioHeight = 0;
    private int mGLSurfaceWidth;
    private int mGLSurfaceHeight;
    private CameraSurfaceRenderer mRenderer;

    public CameraGLSurfaceView(Context context) {
        super(context);
        init(context);
    }

    public CameraGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mCameraHandler = new CameraHandler(this);

        mCameraManager = new CameraManager(context);
        mCameraManager.setCameraCallback(this);

        setEGLContextClientVersion(2);
        mRenderer = new CameraSurfaceRenderer(mCameraHandler);
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    private void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        requestRender();
    }

    /**
     * Connects the SurfaceTexture to the Camera preview output, and starts the preview.
     */
    private void handleSetSurfaceTexture(SurfaceTexture st) {
        Logs.i(TAG, "handleSetSurfaceTexture.");
        mSurfaceTexture = st;
        hasSurface = true;
        mSurfaceTexture.setOnFrameAvailableListener(this);
        openCamera();
    }

    /**
     *
     * @param width
     * @param height
     */
    private void handleSurfaceChanged(int width, int height) {
        Logs.i(TAG, "handleSurfaceChanged.");
        mGLSurfaceWidth = width;
        mGLSurfaceHeight = height;
        setAspectRatio();
    }

    /**
     * 打开摄像头并预览
     */
    public void onResume() {
        super.onResume();
        if (hasSurface) {
            // 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()
            // 并不会调用,需要在此处初始化摄像头
            openCamera();
        }
    }

    /**
     * 停止预览并关闭摄像头
     */
    public void onPause() {
        super.onPause();
        closeCamera();
    }

    public void onDestroy() {
        mCameraHandler.invalidateHandler();
    }

    /**
     * 打开摄像头
     */
    private void openCamera() {
        if (mSurfaceTexture == null) {
            Logs.e(TAG, "mSurfaceTexture is null.");
            return;
        }
        if (mCameraManager.isOpen()) {
            Logs.w(TAG, "Camera is opened!");
            return;
        }
        mCameraManager.openCamera();
        if (mCameraManager.isOpen()) {
            mCameraManager.startPreview(mSurfaceTexture);
        }
    }

    private void closeCamera() {
        mCameraManager.releaseCamera();
        queueEvent(() -> mRenderer.notifyPausing());
        mSurfaceTexture = null;
    }

    @Override
    public void onOpen() {

    }

    @Override
    public void onOpenError(int error, String msg) {

    }

    @Override
    public void onPreview(int previewWidth, int previewHeight) {
        Logs.i(TAG, "onPreview " + previewWidth + " " + previewHeight);
        queueEvent(() -> mRenderer.setCameraPreviewSize(previewWidth, previewHeight));
        setAspectRatio();
    }

    @Override
    public void onPreviewError(int error, String msg) {

    }

    @Override
    public void onClose() {

    }

    private void setAspectRatio() {
        int previewWidth = mCameraManager.getPreviewWidth();
        int previewHeight = mCameraManager.getPreviewHeight();
        if (mGLSurfaceWidth > mGLSurfaceHeight) {
            setAspectRatio(previewWidth, previewHeight);
        } else {
            setAspectRatio(previewHeight, previewWidth);
        }
    }

    /**
     * Handles camera operation requests from other threads.  Necessary because the Camera
     * must only be accessed from one thread.
     * <p>
     * The object is created on the UI thread, and all handlers run there.  Messages are
     * sent from other threads, using sendMessage().
     */
    static class CameraHandler extends Handler {
        public static final int MSG_SET_SURFACE_TEXTURE = 0;

        public static final int MSG_SURFACE_CHANGED = 1;

        private WeakReference<CameraGLSurfaceView> mWeakGLSurfaceView;

        public CameraHandler(CameraGLSurfaceView view) {
            mWeakGLSurfaceView = new WeakReference<>(view);
        }

        /**
         * Drop the reference to the activity.  Useful as a paranoid measure to ensure that
         * attempts to access a stale Activity through a handler are caught.
         */
        public void invalidateHandler() {
            mWeakGLSurfaceView.clear();
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            int what = msg.what;

            CameraGLSurfaceView view = mWeakGLSurfaceView.get();
            if (view == null) {
                return;
            }

            switch (what) {
                case MSG_SET_SURFACE_TEXTURE:
                    view.handleSetSurfaceTexture((SurfaceTexture) msg.obj);
                    break;
                case MSG_SURFACE_CHANGED:
                    view.handleSurfaceChanged(msg.arg1, msg.arg2);
                    break;
                default:
                    throw new RuntimeException("unknown msg " + what);
            }
        }
    }

    /**
     * Renderer object for our GLSurfaceView.
     * <p>
     * Do not call any methods here directly from another thread -- use the
     * GLSurfaceView#queueEvent() call.
     */
    static class CameraSurfaceRenderer implements GLSurfaceView.Renderer {

        private CameraGLSurfaceView.CameraHandler mCameraHandler;

        private final float[] mSTMatrix = new float[16];
        private FullFrameRect mFullScreen;
        // width/height of the incoming camera preview frames
        private boolean mIncomingSizeUpdated;
        private int mIncomingWidth;
        private int mIncomingHeight;
        private int mTextureId = -1;
        private SurfaceTexture mSurfaceTexture;

        public CameraSurfaceRenderer(CameraGLSurfaceView.CameraHandler cameraHandler) {
            mCameraHandler = cameraHandler;

            mTextureId = -1;

            mIncomingSizeUpdated = false;
            mIncomingWidth = mIncomingHeight = -1;
        }

        /**
         * Notifies the renderer thread that the activity is pausing.
         * <p>
         * For best results, call this *after* disabling Camera preview.
         */
        public void notifyPausing() {
            if (mSurfaceTexture != null) {
                Logs.d(TAG, "renderer pausing -- releasing SurfaceTexture");
                mSurfaceTexture.release();
                mSurfaceTexture = null;
            }
            if (mFullScreen != null) {
                mFullScreen.release(false);     // assume the GLSurfaceView EGL context is about
                mFullScreen = null;             //  to be destroyed
            }
            mIncomingWidth = mIncomingHeight = -1;
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Logs.i(TAG, "onSurfaceCreated. " + Thread.currentThread().getName());
            // Set up the texture blitter that will be used for on-screen display.  This
            // is *not* applied to the recording, because that uses a separate shader.
            mFullScreen = new FullFrameRect(
                    new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));

            mTextureId = mFullScreen.createTextureObject();

            // Create a SurfaceTexture, with an external texture, in this EGL context.  We don't
            // have a Looper in this thread -- GLSurfaceView doesn't create one -- so the frame
            // available messages will arrive on the main thread.
            mSurfaceTexture = new SurfaceTexture(mTextureId);

            mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            gl.glViewport(0, 0, width, height);
            mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            if (mSurfaceTexture == null) return;

            mSurfaceTexture.updateTexImage();

            if (mIncomingWidth <= 0 || mIncomingHeight <= 0) {
                return;
            }
            if (mIncomingSizeUpdated) {
                mFullScreen.getProgram().setTexSize(mIncomingWidth, mIncomingHeight);
                mIncomingSizeUpdated = false;
            }

            mSurfaceTexture.getTransformMatrix(mSTMatrix);
            mFullScreen.drawFrame(mTextureId, mSTMatrix);
        }

        public void setCameraPreviewSize(int width, int height) {
            mIncomingWidth = width;
            mIncomingHeight = height;
            mIncomingSizeUpdated = true;
        }
    }
}

1.Camera操作时机

与SurfaceView和TextureView不同,GLSurfaceView中并没有地方获取SurfaceTexture的地方,虽然Renderer接口有onSurfaceCreated回调但是并没有SurfaceTexture,而是要求我们自己创建外部纹理ID用于Camera预览数据的回调。至于这个外部纹理如何又显示到GLSurfaceView上的,这个章节就不先介绍了,后续我们进行完整的opengl环境搭建再进一步讨论。

Renderer中的所有回调接口都是运行在独立的线程中的,这也就是为什么我们要单独定义个类,而不是让CameraGLSurfaceView直接实现该接口,让他和别的接口方法隔离

GLSurfaceViewsetRenderer接口源码可以看到会启动一个GLThread线程,Renderer接口都是运行在该线程中

    public void setRenderer(Renderer renderer) {
        ...
        mRenderer = renderer;
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }

因此我们要把创建好的外部纹理通过Handler传递给UI线程,UI线程在获取到SurfaceTexture后打开摄像头,记得在onResume中也同样打开一次摄像头

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            mFullScreen = new FullFrameRect(
                    new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT));
            // 创建外部纹理ID
            mTextureId = mFullScreen.createTextureObject();
            // 在此EGL上下文中创建具有外部纹理的SurfaceTexture
            mSurfaceTexture = new SurfaceTexture(mTextureId);
            // 将SurfaceTexture传递给UI线程
            mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SET_SURFACE_TEXTURE, mSurfaceTexture));
        }

我们重写surfaceDestroyed,在该回调和onPause中关闭摄像头

注意surfaceDestroyedSurfaceHolder.Callback的方法,该方法是运行在UI线程中的

2. GLSurfaceView计算大小

  1. 和SurfaceView和TextureView一样,我们在onPreview回调中设置TextureView的大小和比例
  2. onSurfaceChanged回调中设置GL画布大小,偶发预览变形大多是没有在此调用glViewport方法导致
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            gl.glViewport(0, 0, width, height);
            mCameraHandler.sendMessage(mCameraHandler.obtainMessage(CameraHandler.MSG_SURFACE_CHANGED, width, height));
        }

二.最后

本文介绍了Camera+GLSurfaceView的基本操作及关键代码。本章内容也不是很多,介绍了如何用GLSurfaceView预览Camera数据的流程。我们没有对opengl的API进行过多的讲解,以及Renderer接口是如何将数据渲染到GLSurfaceView中的。在后续的章节我会讲解opengl在Android中的使用,希望聪明的你在回看该章会有种醍醐灌顶的感觉吧。

lib-camera库包结构如下:

说明
cameracamera相关操作功能包,包括Camera和Camera2。以及各种预览视图
encoderMediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制
glesopengles操作相关
permission权限相关
util工具类

每个包都可独立使用做到最低的耦合,方便白嫖

github地址:https://github.com/xiaozhi003/AndroidCamera,https://gitee.com/xiaozhi003/android-camera

参考:

  1. https://github.com/afei-cn/CameraDemo
  2. https://github.com/saki4510t/UVCCamera
  3. https://github.com/google/grafika

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

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

相关文章

Telephony SMS

1、短信的协议架构 如下图,参考3GPP 23.040 4.9节 Protocols and protocol architecture 1、SM-AL : 应用层 2、SM-TL :传输层 3、SM-RL :中继层 4、SM-LL :链路层 由于我们只关注手机终端,因此只需要关注SM-TL这一层即可 2、SM-TL分类 短信的协议架构参考3GPP 23.04…

猛兽财经:在股价创下历史新高后,5个因素将使Netflix股价进一步上涨

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 股价创三年来新高后&#xff0c; Netflix股价还会继续上涨 作为流媒体领域无可争议的领导者&#xff0c;Netflix(NFLX)的股价在上周再次创下了新高&#xff08;每股超过了700美元&#xff0c;这一涨幅已经超过了2021年底创…

[Linux] 项目自动化构建工具-make/Makefile

标题&#xff1a;[Linux] 项目自动化构建工具-make/Makefile 水墨不写bug 目录 一、什么是make/makefile 二、make/makefile语法 补充&#xff08;多文件标识&#xff09;&#xff1a; 三、make/makefile原理 四、make/makefile根据时间对文件选择操作 正文开始&#xff…

基于SpringBoot的校园闲置物品租售系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 用户管理界面 …

华为云征文|Flexus云服务X实例应用,通过QT连接华为云MySQL,进行数据库的操作,数据表的增删改查

引出 4核12G-100G-3M规格的Flexus X实例使用测评第3弹&#xff1a;Flexus云服务X实例应用&#xff0c;通过QT连接华为云MySQL&#xff0c;进行数据库的操作&#xff0c;数据表的增删改查 什么是Flexus云服务器X实例 官方解释&#xff1a; Flexus云服务器X实例是新一代面向中…

【python】如何用python代码快速生成二维码

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【算法思想·二叉树】思路篇

本文参考labuladong算法笔记[东哥带你刷二叉树&#xff08;思路篇&#xff09; | labuladong 的算法笔记] 本文承接 【算法思想二叉树】纲领篇&#xff0c;先复述一下前文总结的二叉树解题总纲&#xff1a; 二叉树解题的思维模式分两类&#xff1a; 1、是否可以通过遍历一遍二…

数据结构——单链表相关操作

zhuzhu1、结构框图&#xff1a; 2、增删改查&#xff1a; 定义链表节点和对象类型 /*************************************************************************> File Name: link.h> Author: yas> Mail: rage_yashotmail.com> Created Time: Tue 03 Sep 2024…

ServiceStage集成Sermant实现应用的优雅上下线

作者&#xff1a;聂子雄 华为云高级软件工程师 摘要 优雅上下线旨在确保服务在进行上下线操作时&#xff0c;能够平滑过渡&#xff0c;避免对业务造成影响&#xff0c;保证资源的高效利用。Sermant基于字节码增强的技术实现了应用优雅上下线能力&#xff0c;应用发布与运维平…

摩博会倒计时!OneOS操作系统抢先了解!

2024年第二十二届中国国际摩托车博览会&#xff08;摩博会&#xff09;临近&#xff0c;中移物联OneOS与智能硬件领域佼佼者恒石智能宣布强强合作&#xff0c;与9月13日至16日在重庆国家会展中心共同展现多款Model系列芯片&#xff08;Model3、Model4、Model3C、Model3A&#x…

I2C软件模拟时序的基本要素

目录 前言 一、关于I2C 二、正文 1.引脚的配置 2.I2C的起始和终止时序 3.发送一个字节 4.接收一个字节 5.应答信号 6.指定地址写和指定地址读 总结 前言 环境&#xff1a; 芯片&#xff1a;STM32F103C8T6 Keil&#xff1a;V5.24.2.0 本文主要参考江科大教程&#…

系统架构师考试学习笔记第三篇——架构设计高级知识(11)软件可靠性基础知识

本章知识点&#xff1a; 第11课时主要学习软件可靠性基本概念、建模、管理、设计、测试和评价等内容。本课时内容侧重于概念知识,根据以往全国计算机技术与软件专业技术资格(水平)考试的出题规律,考查的知识点多来源于教材,扩展内容较少。根据考试大纲,本课时知识点会涉及单项选…

注册安全分析报告:央视网

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

day47——面向对象特征之继承

一、继承&#xff08;inhert&#xff09; 面向对象三大特征&#xff1a;封装、继承、多态 继承&#xff1a;所谓继承&#xff0c;是类与类之间的关系。就是基于一个已有的类&#xff0c;来创建出一个新类的过程叫做继承。主要提高代码的复用性。 1.1 继承的作用 1> 实现…

16、修改Markdown Preview Enhanced默认样式

前言 vscode的markdown preview enhanced插件的主题并不一定符合每个人的审美&#xff0c;所以有的时候需要自定义,笔者根据网上大佬的文章整合了下自定义修改Markdown Preview Enhanced默认样式的方法&#xff0c;模板在文章中&#xff0c;大家可以直接使用&#xff0c;希望能…

【数据结构】反射,枚举你必须知道的相关知识

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解关于反射以及枚举&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/7D225 &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 目录 &#x1f…

获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

大家都知道很多Anti Cheat会封硬件序列号&#xff0c;所以本文探索一下如何get and modify序列号。 这个服务是比较贵的: 于是有了研究一下的想法。 思路: 1. 通过厂商自带的程序刷新固件。 2. 自己写驱动修改。 思路1不讨论&#xff0c;要拿到厂商去修改&#xff0c;很不方…

台球助教陪练预约系统源码开发

随着科技的发展和人们对生活质量要求的提高&#xff0c;体育运动的数字化趋势日益明显。台球作为一种集休闲娱乐与竞技于一体的运动项目&#xff0c;在全球范围内拥有广泛的爱好者群体。为了更好地满足这部分人群的需求&#xff0c;开发一个高效的台球助教陪练预约系统变得尤为…

国家超算互联网入选国家数据局“全国一体化算力网应用优秀案例”

在2024年8月29日举行的中国国际大数据产业博览会上&#xff0c;国家数据局公布了首批“全国一体化算力网应用优秀案例”。 这一举措是在经过严格的评审过程后&#xff0c;挑选了包括“国家超算互联网”在内的25个创新平台和方案&#xff0c;它们代表了当前算力网建设的先进与创…

【ssh】环境问题汇总

问题1.同时显示两个不同的 Conda 环境&#xff0c;如图 (base) 环境 是 Conda 安装后默认激活的环境。 (ani) 是手动创建的另一个 Conda 环境。 解决&#xff1a;执行conda deactivate。如果 (ani) 环境多次激活&#xff0c;需要多次执行 conda deactivate 才能回到 base 环境…