OpenGLES:GLSurfaceView实现Android Camera预览

一.概述   

前几篇博文对OpenGL做了一些讲解,虽然只是一些基础的知识,但也足够玩出很多有趣的东东了

之前讲过OpenGL本身只是一个开源的图形渲染标准协议,所以OpenGL的学习应注重实战实操,多动手写代码才更有助于巩固所学知识

OpenGLES是OpenGL适配移动端嵌入式设备的版本,裁剪了OpenGL中低效能、冗余的部分,和OpenGL有一些差别,但是基本原理和绝大部分API都是一样的。

使用AndroidStudio进行OpenGLES开发,能十分方便地代码Coding、引用很多三方的工具类,非常便于OpenGL的学习、开发与调试。

二.主题

本篇博文主题:使用OpenGLES和GLSurfaceView实现在Android  Camera预览

是对前几篇OpenGL基础学习和我曾做过的Android Camera App开发预览部分的汇总

项目代码同步已经放在了Github上,博文最后有链接

要了解这篇博文内容,需要先具备Android两个方面的基础知识:

一. Android Camera Api2基础原理和开发技能,这部分网上有很多详细的讲解,

github上也有一个很好的Google官方Demo:GitHub-googlearchive/android-Camera2Basic,

可自行学习理解,

下文第四节,我对Camera2预览的Open流程也进行了专门的讲解

二. Android Surface、各种用于渲染显示的View(TextureView、SurfaceView、GLSurfaceView)、SurfaceTexture、SurfaceFlinger等渲染显示相关基础知识,这部分网上也有很多详细讲解。

这也是Android中很值得研究掌握的一块,原本打算先写一篇Android Surface相关博文后再讲解今天内容,但是写博客是一件很需要时间的事情,以后再写吧

本篇博文知识点较多且密集,但是如果耐心学习到最后,并且实现范例,一定会有不小的收获

现在正式切入主题。

三.开撸

有几个问题先提前解惑:

3.1 为什么使用GLSurfaceView?

GLSurfaceView 继承至 SurfaceView,除了拥有 SurfaceView 所有特性外,还加入了 EGL(OpenGL ES 和原生窗口系统之间的桥梁)的管理,并自带了一个单独的渲染线程。

概括下来就是:

  • 继承自 SurfaceView,拥有其所有特性
  • 加入了 EGL 管理,是 SurfaceView 应用 OpenGL ES 的典型场景
  • 有单独的渲染线程 GLThread
  • 单独出了 Renderer 接口负责实际渲染,不同的 Renderer 实现相当于不同的渲染策略,使用方式灵活(策略模式)

总结成一句话就是:

使用GLSurfaceView就能够使用OpenGLES开发各种图形渲染特效

3.2 EGL是什么?

Android 从很久之前的2.0版本开始,图形系统的底层渲染就全部都由OpenGL来负责,除了负责处理2D/3D API调用,还需负责处理Android SurfaceFlinger的实现过程和显存管理。

Android官方对EGL的解释:

OpenGL ES 定义了一个渲染图形的 API,但没有定义窗口系统。为了让 GLES 能够适合各种平台,GLES 将与知道如何通过操作系统创建和访问窗口的库结合使用。用于 Android 的库称为 EGL。如果要绘制纹理多边形,应使用 GLES 调用;如果要在屏幕上进行渲染,应使用 EGL 调用。

官图镇楼:

OpenGLES:GLSurfaceView实现Android Camera预览_第1张图片

  • Display(EGLDisplay) 是对实际显示设备的抽象。
  • Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer的抽象,包括Color Buffer, Stencil Buffer ,Depth Buffer.
  • Context (EGLContext) 存储OpenGL ES绘图的一些状态信息。

总结下来就是:

Android平台实现图形渲染的API是OpenGLES,但OpenGLES是跨平台的,在特定设备平台上使用需要一个中间层来适配该设备平台的窗口系统,这个中间层就是EGL。

如果还不理解,可以类比一下JNI就明白了

3.3 Android中如何进行OpenGLES开发

如上一节官图所示

OpenGLES的(javax.microedition.khronos.opengles)包定义了平台无关的OpenGL绘图指令(API),EGL(javax.microedition.khronos.egl)则定义了控制Displays,Contexts以及Surfaces 的统一的平台接口。

使用EGL的绘图有其固定的操作步骤,但是在Android平台中进行OpenGLES开发,无需使用(javax.microedition.khronos.egl)包来按照EGL的操作步骤进行。

Android平台中提供了一个(android.opengl)包,为GLSurfaceView提供了Display、Surface、Context 的管理,对应于OpenGLES的开发,只需在GLSurfaceView.Renderer里调用OpenGLES的API进行开发,并将GLSurfaceView.Renderer设置到GLSurfaceView就可以了。

四.OpenCamera

在了解GLES绘制相机预览之前,我还是先大概介绍下Camera Api2 OpenCamera的流程,毕竟只有先成功OpenCamera,才能在此基础上实现GLES渲染绘制。

前文中提到过,Camera2的预览实现暂时放在MainActivity中,这部分后续会从Activity中抽离独立。

具体过程如下:

OpenCamera流程:
1.权限请求


2.配置相机参数:setUpCameraOutputs()
   2.1 获取系统相机服务
   ① android.hardware.camera2.CameraManager:
   ② mCameraManager =(CameraManager)mContext.getSystemService(Context.CAMERA_SERVICE);

   2.2 通过 CameraId 获取到当前Camera特征:Characteristics
   2.3 通过 Characteristics 获取到各种参数,本项目当前只需获取 PictureSize
   2.4 实例化 ImageReader,设置监听:   
         mImageReader.setOnImageAvailableListener(mOnImageAvailableListener,mBackgroundHandler);
         预览、拍照数据都可以通过设置 ImageReader 监听获取到

3.openCamera()
   3.1 
CameraManager.openCamera(...)
     Camera Open的状态回调内部类作为参数传入
     ① android.hardware.camera2.CameraDevice;
     ② CameraDevice.StateCallback mStateCallback
     ③ mCameraManager.openCamera(mCameraId,mStateCallback,mBackgroundHandler);

    3.2 若Camera正常Open
      mStateCallbackonOpened()函数会被回调
      在其中做如下两个主要操作:
     
      3.2.1 创建CaptureRequest:

      ① mPreviewRequestBuilder  =cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      ② mPreviewRequest = mPreviewRequestBuilder.build();

      3.2.2 创建CaptureSession
      ① cameraDevice.createCaptureSession(Arrays.asList(surface),sessionsStateCallback,null);
      createCaptureSession参数:
      "Surface数组":本项目只有一个用于预览的Surface,由GLRender中生成的纹理对象创建
      "Sesseion创建状态回调":CameraCaptureSession.StateCallback sessionsStateCallback

4 开启预览
  若CaptureSession正常创建
  sessionsStateCallbackonConfigured(CameraCaptureSession session)函数会被回调

  在其中调用 setRepeatingRequest(...) 开启预览:
  ① SessionCameraCaptureSession.CaptureCallback mCaptureCallback 
  
② mCaptureSession.setRepeatingRequest(mPreviewRequest,mCaptureCallback,mBackgroundHandler);
   
    题外:mPreviewRequest在开启预览前可以设置各种配置:
    对焦模式、闪光灯模式、曝光区域、JPEG方向、JPEG质量....

5.Session Capture状态回调:
  每一个预览帧上来,都会执行如下两个状态回调函数
  同时分别返回CaptureRequestCaptureResult 和 TotalCaptureResult
  ① onCaptureProgressed(){...}
  ② onCaptureCompleted(){...}

  本项目无需在这两个函数中做什么操作。

Android Camera Api2  OpenCamera的具体流程就是这些,
接下来就是相机开启时GLES绘制预览的实现过程,
也是本文的重点。

四.GLES实现预览绘制

4.1 整体实现步骤如下:

  1. 添加GLSurfaceView到布局作为预览窗口;
  2. 新建一个继承GLSurfaceView.Renderer的Render,重写onSurfaceCreated()和onSurfaceChanged();
  3. Render中处理GL诸多事项,其中包括生成纹理对象,并使用纹理对象创建SurfaceTexture;
  4. 将Render设置给GLSurfaceView;
  5. SurfaceTexture调用setOnFrameAvailableListener()设置帧回调监听onFrameAvailable();
  6. 使用SurfaceTexture创建Surface;
  7. Camera在CreateCaptureSession时传入Surface;
  8. Camera预览开启后,每一帧回调在onFrameAvailable()中调用GLSurfaceView.requestRender()请求GL绘制;

4.2 Render中要做的事情

GLES绘制预览主要就在Render里,也就是"步骤3:Render处理GL诸多事项"

可以分为四个主要步骤:

  1. 顶点、纹理坐标操作:
  2. 着色器程序、对象操作
  3. 纹理操作
  4. 绘制

具体再细分如下:

1.顶点、纹理坐标操作
  (1).新建顶点坐标数组
  (2).新建纹理坐标数组
  (3).拷贝顶点坐标数据到Native Buffer
  (4).拷贝纹理坐标数据到Native Buffer
  (5).获取着色器中各属性的位置
  (6).解析顶点坐标数据缓冲,绑定到顶点着色器中的顶点坐标属性
  (7).解析纹理坐标数据缓冲,绑定到顶点着色器中的纹理坐标属性 


2.着色器程序、对象操作
  (1).创建顶点着色器文件,编写顶点着色器代码
  (2).创建片段着色器文件,编写片段着色器代码
  (1).创建着色器程序
  (2).加载顶点着色器文件源数据,编译、生成顶点着色器对象
  (3).加载片段着色器文件源数据,编译、生成片段着色器对象
  (4).绑定顶点着色器对象到着色器程序
  (5).绑定片段着色器对象到着色器程序
  (6).链接着色器程序到GL
  (7).检查着色器链接状态
  (8).使用着色器程序


3.纹理操作
  (1).创建纹理对象
  (2).使用纹理对象创建SurfaceTexture
  (3).激活GL纹理单元
  (4).绑定纹理对象到GL纹理单元
  (5).配置纹理采样和过滤方式
  (6).设置纹理采样器索引到片段着色器中纹理属性


4.绘制
  (1).SurfaceTexture更新纹理图像
  (2).SurfaceTexture更新纹理与窗口的变换矩阵
  (3).使能顶点着色器中的顶点坐标属性,从顶点缓冲接收顶点坐标数据
  (4).使能顶点着色器中的纹理坐标属性,从纹理缓冲接收纹理坐标数据
  (5).赋值变换矩阵到片段着色器中的矩阵属性
  (6).开始绘制
  (7).关闭使能顶点着色器中顶点坐标属性,停止从顶点缓冲接收顶点坐标数据
  (8).关闭使能顶点着色器中纹理坐标属性,停止从纹理缓冲接收纹理坐标数据

步骤有点多,但都是必须的,每一步都需要理解用熟。

六.代码实现

上一节很详细地阐述了实现原理和过程

纸上得来终觉浅,还是要手敲代码实现才来得实在

5.1 目录结构

文件不多,目录结构比较简单,如下图:

OpenGLES:GLSurfaceView实现Android Camera预览_第2张图片

目前Camera的功能实现暂时放在了Activity里面,这么做并不好,Camera的实现流程需要独立出来,MainActivity最好只处理View和生命周期控制。但是我们本次学习的重点是运用、巩固OpenGL所学知识,所以暂时没有对这块进行优化。

5.2 activity_main.xml

布局文件 activity_main.xml 中添加一个GLSurfaceView




    

5.4 着色器代码

顶点着色器:texture_vertex_shader.glsl

#version 300 es

//把顶点坐标给这个变量, 确定要画画的形状
layout (location = 0) in vec4 aPosVertex;

//接收纹理坐标,接收采样器采样图片的坐标
layout (location = 1) in vec4 aTexVertex;

//传给片元着色器 像素点
out vec2 texCoord;

//变换矩阵
uniform mat4 vMatrix;

void main()
{
    //内置变量 gl_Position ,我们把顶点数据赋值给这个变量 opengl就知道它要画什么形状了
    gl_Position = aPosVertex;
    //与矩阵相乘之后赋值给2维纹理坐标向量
    texCoord = (vMatrix * aTexVertex).xy;
}

 片段着色器:texture_fragtment_shader.glsl

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;

//纹理坐标,图片当中的坐标点
in vec2 texCoord;
//输出颜色,GL内建变量
out vec4 outColor;

//图片,采样器
uniform samplerExternalOES s_texture;

void main(){
    outColor = texture(s_texture, texCoord);
}

5.5 GLRender代码

GLRender继承至GLSurfaceView.Renderer,负责实现GLES绘制相机预览

GLSurfaceView.Renderer其实就是GLSurfaceView里的一个Interface,一共也只有三个接口函数,GLRender需要重写实现这三个函数:

    public interface Renderer {
        void onSurfaceCreated(GL10 gl, EGLConfig config);
        void onSurfaceChanged(GL10 gl, int width, int height);
        void onDrawFrame(GL10 gl);
    }

GLSurfaceView源码中有关于这三个函数的详细注解,可以查阅。

在这三个函数中做了这些操作:

void onSurfaceCreated(GL10 gl, EGLConfig config);
清除渲染颜色、
各种初始化操作

void onSurfaceChanged(GL10 gl, int width, int height);
初始化视窗
相机OpenCamera起点

void onDrawFrame(GL10 gl)
渲染绘制

除了OpenCamera需要放在onSurfaceChanged()里,其他具体的操作细节并不是固定的,可视具体编码情况而定。

GLRender中需要实现的事项在上一节中已详细列举,此处不再复述,
详细细节看代码逐行注释
全部代码如下:

/**
 * Create By Shawn.xiao at 2023/05/01
 */
package com.android.mycamera;

import android.content.Context;

import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.util.Size;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES30.*;

public class GLRender implements GLSurfaceView.Renderer {
    private static final String TAG = "test0523-GLRender";

    private Context mContext;

    private int BYTES_PER_FLOAT = 4;

    private String VERTEX_ATTRIB_POSITION = "aPosVertex";
    private int VERTEX_ATTRIB_POSITION_SIZE = 2;

    private String VERTEX_ATTRIB_TEXTURE_POSITION = "aTexVertex";
    private int VERTEX_ATTRIB_TEXTURE_POSITION_SIZE = 2;

    private String UNIFORM_TEXTURE = "s_texture";
    private String UNIFORM_VMATRIX = "vMatrix";

    private int mVertexLocation;
    private int mTextureLocation;
    private int mUTextureLocation;
    private int mVMatrixLocation;

    private Size mPreviewSize;

    private float[] mVertexCoord = {
            -1f, -1f,  //左下
            1f, -1f,   //右下
            -1f, 1f,   //左上
            1f, 1f,    //右上
    };

    //纹理坐标(s,t)
    /*纹理坐标需要经过变换
     * (1).顺时针旋转90°
     * (2).镜像
     */
    public float[] mTextureCoord = {
            0.0f, 0.0f,  //左下
            1.0f, 0.0f,  //右下
            0.0f, 1.0f,  //左上
            1.0f, 1.0f,  //右上
    };

    public float[] vMatrix = new float[16];

    private FloatBuffer mVertexCoordBuffer;
    private FloatBuffer mTextureCoordBuffer;

    private int mShaderProgram;

    //接收相机数据的纹理
    private int[] mTextureId = new int[1];
    //接收相机数据的 SurfaceTexture
    public SurfaceTexture mSurfaceTexture;

    public GLRender(Context context) {
        mContext = context;
    }

    public void initData(Size size) {
        mPreviewSize = size;
    }

    //向外提供 surfaceTexture 实例
    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.v(TAG, "onSurfaceCreated()");

        //设置清除渲染时的颜色
        /*
         * 白色:(1.0f, 1.0f, 1.0f, 0.0f)
         * 黑色:(0.0f, 0.0f, 0.0f, 1.0f)
         */
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        //创建并连接程序
        mShaderProgram = createAndLinkProgram("texture_vertex_shader.glsl", "texture_fragtment_shader.glsl");
        if (mShaderProgram != 0) {
            glUseProgram(mShaderProgram);
        }

        //初始化着色器中各变量属性
        initAttribLocation();
        //初始化顶点数据
        initVertexAttrib();
        //初始化纹理
        initTexture();

        //这么写不规范,先这么来吧,后续Camera流程要独立出来
        ((MainActivity) mContext).openCamera();
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.v(TAG, "onSurfaceChanged(): " + width + " x " + height);
        /* glViewport(GLint x,GLint y,GLsizei width,GLsizei height)
         * x、y是指距离左下角的位置,单位是像素
         * 如果不设置width,height,他们的值是布局默认大小,渲染占满整个布局
         */
        //glViewport(0, 0, width, height);
        glViewport(0, 450, mPreviewSize.getHeight(), mPreviewSize.getWidth());
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //Log.v(TAG,"onDrawFrame()");
        //surfaceTexture 获取新的纹理数据
        mSurfaceTexture.updateTexImage();
        mSurfaceTexture.getTransformMatrix(vMatrix);

        glClear(GL_COLOR_BUFFER_BIT);

        //允许顶点着色器中属性变量aPosVertex,接收来自缓冲区的顶点数据
        glEnableVertexAttribArray(mVertexLocation);
        //允许顶点着色器中属性变量 aTexVertex 接收来自缓冲区的纹理UV顶点数据
        glEnableVertexAttribArray(mTextureLocation);

        //矩阵赋值
        glUniformMatrix4fv(mVMatrixLocation, 1, false, vMatrix, 0);

        //开始绘制,绘制mVertexCoord.length/2即4个点
        //GL_TRIANGLE_STRIP 和 GL_TRIANGLE_FAN的绘制方式不同,需要注意
        glDrawArrays(GL_TRIANGLE_STRIP, 0, mVertexCoord.length / 2);

        //禁止顶点数组的句柄
        glDisableVertexAttribArray(mVertexLocation);
        glDisableVertexAttribArray(mTextureLocation);
    }

    private void initVertexAttrib() {
        mVertexCoordBuffer = getFloatBuffer(mVertexCoord);
        //把顶点数据缓冲区 绑定到顶点着色器中 接收顶点数据的属性变量 aPosVertex
        glVertexAttribPointer(mVertexLocation, VERTEX_ATTRIB_POSITION_SIZE, GL_FLOAT, false, 0, mVertexCoordBuffer);

        mTextureCoordBuffer = getFloatBuffer(mTextureCoord);
        //把UV顶点数据缓冲区 绑定到顶点着色器中 接收顶点数据的属性变量 aTexVertex
        glVertexAttribPointer(mTextureLocation, VERTEX_ATTRIB_TEXTURE_POSITION_SIZE, GL_FLOAT, false, 0, mTextureCoordBuffer);
    }

    public void initAttribLocation() {
        mVertexLocation = glGetAttribLocation(mShaderProgram, VERTEX_ATTRIB_POSITION);
        mTextureLocation = glGetAttribLocation(mShaderProgram, VERTEX_ATTRIB_TEXTURE_POSITION);
        mUTextureLocation = glGetUniformLocation(mShaderProgram, UNIFORM_TEXTURE);
        mVMatrixLocation = glGetUniformLocation(mShaderProgram, UNIFORM_VMATRIX);
    }

    public void initTexture() {
        //创建纹理对象
        glGenTextures(mTextureId.length, mTextureId, 0);
        //使用纹理对象创建surfaceTexture,提供给外部使用
        mSurfaceTexture = new SurfaceTexture(mTextureId[0]);
        //激活纹理:默认0号纹理单元,一般最多能绑16个,视GPU而定
        glActiveTexture(GL_TEXTURE0);
        //绑定纹理:将纹理放到当前单元的 GL_TEXTURE_BINDING_EXTERNAL_OES 目标对象中
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId[0]);
        //配置纹理:过滤方式
        glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        //将片段着色器的采样器(纹理属性:s_texture)设置为0号单元
        glUniform1i(mUTextureLocation, 0);
    }

    /*
     * 创建和链接着色器程序
     * 参数:顶点着色器、片段着色器程序ResId
     * 返回:成功创建、链接了顶点和片段着色器的着色器程序Id
     */
    public int createAndLinkProgram(String vertexShaderFN, String fragShaderFN) {
        //创建着色器程序
        int shaderProgram = glCreateProgram();
        if (shaderProgram == 0) {
            Log.e(TAG, "Failed to create mShaderProgram ");
            return 0;
        }

        //获取顶点着色器对象
        int vertexShader = loadShader(GL_VERTEX_SHADER, loadShaderSource(vertexShaderFN));
        if (0 == vertexShader) {
            Log.e(TAG, "Failed to load vertexShader");
            return 0;
        }

        //获取片段着色器对象
        int fragmentShader = loadShader(GL_FRAGMENT_SHADER, loadShaderSource(fragShaderFN));
        if (0 == fragmentShader) {
            Log.e(TAG, "Failed to load fragmentShader");
            return 0;
        }

        //绑定顶点着色器到着色器程序
        glAttachShader(shaderProgram, vertexShader);
        //绑定片段着色器到着色器程序
        glAttachShader(shaderProgram, fragmentShader);

        //链接着色器程序
        glLinkProgram(shaderProgram);
        //检查着色器链接状态
        int[] linked = new int[1];
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, linked, 0);
        if (linked[0] == 0) {
            glDeleteProgram(shaderProgram);
            Log.e(TAG, "Failed to link shaderProgram");
            return 0;
        }

        return shaderProgram;
    }

    /**
     * 加载着色器源,并编译
     *
     * @param type         顶点着色器(GL_VERTEX_SHADER)/片段着色器(GL_FRAGMENT_SHADER)
     * @param shaderSource 着色器源
     * @return 着色器
     */
    public int loadShader(int type, String shaderSource) {
        //创建着色器对象
        int shader = glCreateShader(type);
        if (shader == 0) {
            return 0;//创建失败
        }
        //加载着色器源
        glShaderSource(shader, shaderSource);
        //编译着色器对象
        glCompileShader(shader);
        //检查编译状态
        int[] compiled = new int[1];
        glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {
            //编译失败,执行:打印日志、删除链接到着色器程序的着色器对象、返回错误值
            Log.e(TAG, glGetShaderInfoLog(shader));
            glDeleteShader(shader);
            return 0;
        }

        return shader;
    }

    /*********************** 着色器、程序 ************************/
    public String loadShaderSource(String fname) {
        StringBuilder strBld = new StringBuilder();
        String nextLine;

        try {
            InputStream is = mContext.getResources().getAssets().open(fname);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            while ((nextLine = br.readLine()) != null) {
                strBld.append(nextLine);
                strBld.append('\n');
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return strBld.toString();
    }

    public FloatBuffer getFloatBuffer(float[] array) {
        //将顶点数据拷贝映射到 native 内存中,以便opengl能够访问
        FloatBuffer buffer = ByteBuffer
                .allocateDirect(array.length * BYTES_PER_FLOAT)//直接分配 native 内存,不会被gc
                .order(ByteOrder.nativeOrder())//和本地平台保持一致的字节序(大/小头)
                .asFloatBuffer();//将底层字节映射到FloatBuffer实例,方便使用

        buffer.put(array)//将顶点拷贝到 native 内存中
                .position(0);//每次 put position 都会 + 1,需要在绘制前重置为0

        return buffer;
    }
}

5.6 MainActivity

在MainActivity中需要做如下事情:

实例化GLSurfaceView、GLRender,进行配

全部代码如下:

/**
 * Create By Shawn.xiao at 2023/05/01
 */
package com.android.mycamera;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.media.ImageReader;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.Window;
import android.view.WindowManager;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private final String TAG = "test0523-MainActivity";

    private GLSurfaceView mGLSurfaceView;

    private Context mContext;

    private GLRender mGLRender;

    private List mOutputSizes;
    private Size mPictureSize;
    private Size mPreViewSize;

    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;
    private CaptureRequest mPreviewRequest;
    private CaptureRequest.Builder mPreviewRequestBuilder;
    private CameraCharacteristics mCharacteristics;

    private ImageReader mImageReader;

    private Surface surface;
    //private SurfaceTexture mSurfaceTexture;

    private int mImageFormat = ImageFormat.JPEG;

    private String mCameraId;

    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;

    public final int mMaxImages = 5;
    public final int REQUEST_CAMERA_PERMISSION = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setWindowFlag();

        setContentView(R.layout.activity_main);

        mContext = this;
        initView();
    }

    private void setWindowFlag() {
        Window window = getWindow();
        //隐藏顶部 StatuBar状态栏
        window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //隐藏底部 NavigationBar导航栏
        window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }


    private void initView() {
        mGLSurfaceView = findViewById(R.id.glSurfaceView);
        //设置GLES版本
        mGLSurfaceView.setEGLContextClientVersion(3);
        //创建Render对象,并将其设置到GLSurfaceView
        mGLRender = new GLRender(mContext);
        mGLSurfaceView.setRenderer(mGLRender);
        /**
         * 刷新方式
         * RENDERMODE_WHEN_DIRTY,手动刷新,调用requestRender()
         * RENDERMODE_CONTINUOUSLY,自动刷新,GPU一个fence,目前较为普遍的16ms自动回调一次onDrawFrame()
         *
         * 如果不设置,默认是自动刷新
         * */
        /*mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);*/
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //应该放在SurfaceTexture销毁的地方,暂时先放在这里
        closeCamera();
        stopBackgroundThread();
    }

    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void requestCameraPermission() {
        if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
            //do nothing frist shawn
        } else {
            requestPermissions(new String[]{Manifest.permission.CAMERA,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.READ_EXTERNAL_STORAGE},
                    REQUEST_CAMERA_PERMISSION);
        }
    }

    /**
     * Opens the camera
     */
    public void openCamera() {
        //检查权限
        if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission();
            return;
        }

        startBackgroundThread();
        //配置相机参数
        setUpCameraOutputs();

        try {
            Log.v(TAG, "openCamera mCameraId=" + mCameraId);
            //打开相机,设置状态回调
            mCameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets up member variables related to camera.
     */
    @SuppressWarnings("SuspiciousNameCombination")
    private void setUpCameraOutputs() {
        try {
            Log.v(TAG, "setUpCameraOutputs E");
            mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
            mCameraId = CameraUtils.getInstance().getCameraId();
            mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);

            mOutputSizes = CameraUtils.getInstance().getCameraOutputSizes(mCharacteristics, mCameraId, SurfaceTexture.class);
            mPictureSize = mOutputSizes.get(0);

            //PreviewSize应该是通过PictureSize和Screensize从mOutputSizes中找到比率匹配的Size数组,然后选最大或最合适的那个Size
            //这里先写成固定值实现功能
            mPreViewSize = new Size(1440,1080);
            mGLRender.initData(mPreViewSize);

            mImageReader = ImageReader.newInstance(mPictureSize.getWidth(), mPictureSize.getHeight(), mImageFormat, mMaxImages);
            mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
            Log.v(TAG, "setUpCameraOutputs X");
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //打开相机的状态回调
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Log.v(TAG, "mStateCallback onOpened()");

            // This method is called when the camera is opened,we start camera preview here.
            try {
                //从GLRender中获取到SurfaceTexture,也就是GLRender里创建的用于预览显示的纹理对象
                SurfaceTexture surfaceTexture = mGLRender.getSurfaceTexture();
                if (surfaceTexture == null) {
                    return;
                }
                surfaceTexture.setDefaultBufferSize(1440, 1080);
                //设置帧回调监听
                surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                    //每一帧回调都会走下面重写的函数
                    @Override
                    public void onFrameAvailable(final SurfaceTexture surfaceTexture) {
                        //请求绘制每一帧数据
                        mGLSurfaceView.requestRender();
                    }
                });
                //使用使用获取到的SurfaceTexture创建一个Surface
                surface = new Surface(surfaceTexture);

                mCameraDevice = cameraDevice;
                mPreviewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewRequestBuilder.addTarget(surface);
                mPreviewRequest = mPreviewRequestBuilder.build();

                //传入Surface数组和创建CaptureSession的状态回调
                cameraDevice.createCaptureSession(Arrays.asList(surface), sessionsStateCallback, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                Log.d(TAG, "Open Camera Failed!");
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            Log.v(TAG, "mStateCallback onDisconnected()");
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            Log.v(TAG, "mStateCallback onError()");
            cameraDevice.close();
            mCameraDevice = null;
            finish();
        }
    };

    CameraCaptureSession.StateCallback sessionsStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            if (null == mCameraDevice) return;

            mCaptureSession = session;
            try {
                // Auto focus should be continuous for camera preview.
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

                mPreviewRequest = mPreviewRequestBuilder.build();
                Log.v(TAG, "sessionsStateCallback setRepeatingRequest()");
                mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                Log.d(TAG, "相机访问异常");
            }
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            Log.d(TAG, "onConfigureFailed!");
        }
    };

    /**
     * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
     */
    private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                        @NonNull CaptureRequest request,
                                        @NonNull CaptureResult partialResult) {
            Log.v(TAG, "onCaptureProgressed onCaptureProgressed()");
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull TotalCaptureResult result) {
            Log.v(TAG, "onCaptureProgressed onCaptureCompleted()");
        }
    };


    /**
     * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
     * still image is ready to be saved.
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.v(TAG, "onImageAvailable()");
        }
    };

    /**
     * Closes the current {@link CameraDevice}.
     */
    private void closeCamera() {
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
    }
}

5.6 CameraUtils

CameraUtils:Camera功能类

不做过多阐述

代码如下:

/**
 * Create By Shawn.xiao at 2023/05/01
 */
package com.android.mycamera;

import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.params.StreamConfigurationMap;

import android.util.Size;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

import java.util.List;

public class CameraUtils {
    private static CameraUtils sInstance;

    private final String TAG = "CameraUtils";

    private final String BACK_CAMERA = "0";
    private final String FRONT_CAMERA = "1";

    public static CameraUtils getInstance() {
        if (sInstance == null) {
            synchronized (CameraUtils.class) {
                if (sInstance == null) {
                    sInstance = new CameraUtils();
                }
            }
        }
        return sInstance;
    }

    /**
     * 获取相机id
     */
    public String getCameraId() {
        //先写死返回后摄
        return BACK_CAMERA;
    }

    /**
     * 根据输出类获取指定相机的输出尺寸列表,降序排序
     *
     * @param cameraId 相机id
     * @param clz      输出类
     * @return
     */
    public List getCameraOutputSizes(CameraCharacteristics characteristics, String cameraId, Class clz) {
        StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

        List sizes = Arrays.asList(configs.getOutputSizes(clz));
        Collections.sort(sizes, new Comparator() {
            @Override
            public int compare(Size s1, Size s2) {
                return s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight();
            }
        });
        Collections.reverse(sizes);

        return sizes;
    }
}

七.实现效果

最终实现效果如下:

到此,使用 OpenGLES + GLSurfaceView 实现Android相机预览的过程就全部讲解完毕。

在此基础上,使用OpenGLES还能够实现很多有趣的视觉效果

这就是后面要持续做的事情了!

Github项目代码:GitHub - Shawn.xiao/GLCamera: GLSurfaceView Camera

你可能感兴趣的:(OpenGL/OpenGLES,android,图像处理,图形渲染,着色器,计算机视觉)