Camera 采集数据通过 GLSurfaceView 预览 (二)详解手机开发

上一篇文章Camera 采集数据通过 textureview 预览,手动对焦、自动对焦 (一)中使用了textureView进行预览,但是如果想做更多的功能,比如说增加水印、滤镜、离屏渲染等等,这使用GLSurfaceView预览更加合适,所以这篇文章是在上一篇的代码的基础上进行修改替换,并且这里不介绍GLSurfaceView和Android GLES的相关知识。(附带源码

自定义继承自GLSurfaceView的CameraGLSurfaceView

GLSurfaceView 继承自SurfaceView,都有自己独立的绘制线程,不一样的是Surfaceview是独立的线程在画布上绘制,而GLSurfaceView是GLThread绘制在独立的Surface,并且自己默认创建EGL上下文环境,利用了GPU的能力。

1. 初始化

private void init(Context context) { 
    setEGLContextClientVersion(2);// 设置EGL的版本,一定要设置 
    mRender = new CameraGLSufaceRender(context, this); // 自定义的渲染器 
    setRenderer(mRender); 
    setRenderMode(RENDERMODE_WHEN_DIRTY);// 绘制模式 
} 

绘制模式有两种:RENDERMODE_WHEN_DIRTY(按需渲染:调用requestRender才会渲染)、RENDERMODE_CONTINUOUSLY(持续渲染)

自定义Render

CameraGLSufaceRender继承自GLAbstractRender,GLAbstractRender也是自己定义的继承自GLSurfaceView.Renderer的抽象类,用来封装一些GLES常用的方法,例如加载链接着色器等等。

1. 生命周期

GLSurfaceView.Renderer有三个生命周期:
void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig);// surface创建完成
void onSurfaceChanged(GL10 gl10, int width, int height);// surface尺寸发生变化
void onDrawFrame(GL10 gl10);// surface的绘制工作(我们关注的主要部分)

2. 结合Render的生命周期做工作流程

(1)加载顶点着色器、片元着色器

 @Override 
protected String getVertexSource() {
    
    final String source = "attribute vec4 av_Position; " + 
                "attribute vec2 af_Position; " + 
                "varying vec2 v_texPo; " + 
                "void main() { " + 
                "    v_texPo = af_Position; " + 
                "    gl_Position = av_Position; " + 
                "}"; 
    return source; 
} 
 
@Override 
protected String getFragmentSource() {
    
    final String source = "#extension GL_OES_EGL_image_external : require /n" + 
                "precision mediump float; " + 
                "varying vec2 v_texPo; " + 
                "uniform samplerExternalOES s_Texture; " + 
                "void main() { " + 
                "   gl_FragColor = texture2D(s_Texture, v_texPo); " + 
                "} "; 
    return source; 
} 
  • 我们使用的外部的camera的外部纹理,所以这里用的不是sampler2D 而是samplerExternalOES
  • 记得 #extension GL_OES_EGL_image_external : require

(2)创建program,链接顶点、片元着色器

 protected void createProgram() {
    
    mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexSource()); 
    mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentSource()); 
    mProgram = GLES20.glCreateProgram(); 
    GLES20.glAttachShader(mProgram, mVertexShader); 
    GLES20.glAttachShader(mProgram, mFragmentShader); 
    GLES20.glLinkProgram(mProgram); 
    int [] status = new int[1]; 
    GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, status, 0); 
    if (status[0] != GLES20.GL_TRUE) {
    
        Log.e(TAG, "createProgam: link error"); 
        Log.e(TAG, "createProgam: " + GLES20.glGetProgramInfoLog(mProgram)); 
        GLES20.glDeleteProgram(mProgram); 
        return; 
    } 
} 

(3) 创建顶点坐标、纹理坐标

private float vertexData[] = {
    
            -1f, -1f, 0.0f, // 左下角 
            1f, -1f, 0.0f, // 右下角 
            -1f, 1f, 0.0f, // 左上角 
            1f, 1f, 0.0f,  // 右上角 
    }; 
 
    // 纹理坐标对应顶点坐标与后置摄像头映射 
    private float backTextureData[] = {
    
            1f, 1f, // 右上角 
            1f, 0f, // 右下角 
            0f, 1f, // 左上角 
            0f, 0f //  左下角 
    }; 
 
    // 纹理坐标对应顶点坐标与前置摄像头映射 
    private float frontTextureData[] = {
    
            0f, 1f, // 左上角 
            0f, 0f, //  左下角 
            1f, 1f, // 右上角 
            1f, 0f // 右下角 
    }; 
 
    // 每次取点的数量 
    private final int CoordsPerVertexCount = 3; 
    // 顶点坐标数量 
    private final int VertexCount = vertexData.length / CoordsPerVertexCount; 
    // 一次取出的大小 
    private final int VertexStride = CoordsPerVertexCount * 4; 
 
    // 每次取点的数量 
    private final int CoordsPerTextureCount = 2; 
    // 一次取出的大小 
    private final int TextureStride = CoordsPerTextureCount * 4; 

将坐标数据转换为FloatBuffer

this.vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) 
                .order(ByteOrder.nativeOrder()) 
                .asFloatBuffer() 
                .put(vertexData); 
this.vertexBuffer.position(0); 
 
this.backTextureBuffer = ByteBuffer.allocateDirect(backTextureData.length * 4) 
                .order(ByteOrder.nativeOrder()) 
                .asFloatBuffer() 
                .put(backTextureData); 
this.backTextureBuffer.position(0); 
 
this.frontTextureBuffer = ByteBuffer.allocateDirect(frontTextureData.length * 4) 
                .order(ByteOrder.nativeOrder()) 
                .asFloatBuffer() 
                .put(frontTextureData); 
this.frontTextureBuffer.position(0); 

以上的工作其实已经封装在GLAbstractRender了

(4)创建外部纹理id,后续可以通过id创建SurfaceTexture传给camera,提供传递预览数据进来

// 创建外部纹理 
protected int loadExternelTexture() { 
      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); 
      return texture[0]; 
} 

(5) 在onSurfaceCreated中,创建一系列的准备工作
当mSurfaceTexture发生数据变化,也就是camera数据变化时候,会通过onFrameAvailable回调进来,我们通知CameraGLSurfaceView重新绘制onRequestRender就可以了

public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
    
    createProgram(); 
    onCreate(); 
} 
 
protected void onCreate() {
    
        mTexture = loadExternelTexture();//创建外部纹理 
 
        av_Position = GLES20.glGetAttribLocation(mProgram, "av_Position"); 
        af_Position = GLES20.glGetAttribLocation(mProgram, "af_Position"); 
        s_Texture = GLES20.glGetUniformLocation(mProgram, "s_Texture"); 
        mSurfaceTexture = new SurfaceTexture(mTexture); 
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
    
            @Override 
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
    
                if (mRenderCallback != null) {
    
                    mRenderCallback.onRequestRender(); 
                } 
            } 
        }); 
        if (mRenderCallback != null) {
    
            mRenderCallback.onCreate(mSurfaceTexture); 
        } 
    } 

(6)在onDrawFrame执行绘制工作

public void onDrawFrame(GL10 gl10) { 
        GLES20.glUseProgram(mProgram); 
        GLES20.glViewport(0, 0, width, height); 
        onDraw(); 
    } 
@Override 
protected void onDraw() {
    
    if (mSurfaceTexture != null) {
    
        mSurfaceTexture.updateTexImage(); 
        // SurfaceTexture 的 updateTexImage() 将更新最新的图像转成GL中的纹理 
    } 
    GLES20.glEnableVertexAttribArray(av_Position); 
    GLES20.glEnableVertexAttribArray(af_Position); 
 
    // 设置顶点位置值 
    GLES20.glVertexAttribPointer(av_Position, CoordsPerVertexCount, GLES20.GL_FLOAT, false, VertexStride, vertexBuffer); 
    // 设置纹理位置值 
    if (isBackCamera) {
   //判断是前置还是后置 
        GLES20.glVertexAttribPointer(af_Position, CoordsPerTextureCount, GLES20.GL_FLOAT, false, TextureStride, backTextureBuffer); 
    } else {
    
       GLES20.glVertexAttribPointer(af_Position, CoordsPerTextureCount, GLES20.GL_FLOAT, false, TextureStride, frontTextureBuffer); 
    } 
 
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexture); 
    //GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture); 
    GLES20.glUniform1i(s_Texture, 0); 
    //绘制 GLES20.GL_TRIANGLE_STRIP:复用坐标 
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VertexCount); 
    GLES20.glDisableVertexAttribArray(av_Position); 
    GLES20.glDisableVertexAttribArray(af_Position); 
 
    if (mRenderCallback != null) {
    
        mRenderCallback.onDraw(); 
    } 
} 

前置摄像头和后置摄像头对应的纹理坐标是不一样的,所以需要根据不同摄像设置不同的纹理坐标

使用CameraGLSurfaceView

使用CameraGLSurfaceView,其实跟textureView没有太大区别,只是回调接口变化了

遇到的问题困惑

我在布局中沿用了上一篇文章的点击CameraGLSurfaceView手动对焦,将自定义FocusView移动到手指位置,这时候发现FocusView在GLSurfaceView的上有一些区域是无法显示完全的。

类似其他同学遇到的问题GLSurfaceView 的上层控件显示问题

猜测是否跟GLSurfaceView的显示机制有关,因为GLSurfaceView是通过让上层的界面透明从而显示。

我的解决办法是在布局的四个角落放置四个透明的textview。但还是找不到问题所在,希望知道的大神告诉我。

源码

云图网

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6267.html

(0)
上一篇 2021年7月17日 00:45
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论