使用OpenGL预览CameraX摄像头数据

前言

CameraX 是一个 Jetpack 支持库,旨在帮助您简化相机应用的开发工作。笔者看了下网上关于CameraX的资料虽然很多,但是很多基本上都是官网资料的翻版,学习的价值很没有直接看官网的高。

也有些博客介绍了CameraX结合OpenGL渲染的的例子,但好像都建立在Preview类的setOnPreviewOutputUpdateListener这个方法中进行处理,但是笔者更新CameraX版本之后发现setOnPreviewOutputUpdateListener这个
方法直接没了,完犊子了…

你看见我的尔康了吗

当然本文所介绍的方法随着CameraX的发展也会过时,但也希望能起到一点抛砖引玉的作用。。。。

show me the code

首先自定义一个OpenGL的渲染View,继承于GLSurfaceView,GLCameraView.java:


public class GLCameraView extends GLSurfaceView implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {

    private static final String LOG_TAG = "OpenGLCameraX";

    private Executor executor = Executors.newSingleThreadExecutor();

    private int textureId;
    private SurfaceTexture surfaceTexture;

    private int vPosition;
    private int vCoord;
    private int programId;

    private int textureMatrixId;
    private float[] textureMatrix = new float[16];

    protected FloatBuffer mGLVertexBuffer;
    protected FloatBuffer mGLTextureBuffer;

    public GLCameraView(Context context) {
        this(context, null);
    }

    public GLCameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(2);

        setRenderer(this);
        // 设置非连续渲染
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }


    @SuppressLint("UnsafeExperimentalUsageError")
    public void attachPreview(Preview preview) {
        preview.setSurfaceProvider(new Preview.SurfaceProvider() {
            @Override
            public void onSurfaceRequested(@NonNull SurfaceRequest request) {
                Surface surface = new Surface(surfaceTexture);
                request.provideSurface(surface, executor, new Consumer<SurfaceRequest.Result>() {
                    @Override
                    public void accept(SurfaceRequest.Result result) {
                        surface.release();
                        surfaceTexture.release();
                        Log.v(LOG_TAG, "--accept------");
                    }
                });
            }
        });
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        int[] ids = new int[1];

        // OpenGL相关
        GLES20.glGenTextures(1, ids, 0);
        textureId = ids[0];
        surfaceTexture = new SurfaceTexture(textureId);
        surfaceTexture.setOnFrameAvailableListener(this::onFrameAvailable);

        String vertexShader = OpenGLUtils.readRawTextFile(getContext(), R.raw.camera_vertex);
        String fragmentShader = OpenGLUtils.readRawTextFile(getContext(), R.raw.camera_frag);
        programId = OpenGLUtils.loadProgram(vertexShader, fragmentShader);

        vPosition = GLES20.glGetAttribLocation(programId, "vPosition");
        vCoord = GLES20.glGetAttribLocation(programId, "vCoord");

        textureMatrixId = GLES20.glGetUniformLocation(programId, "textureMatrix");

        // 4个顶点,每个顶点有两个浮点型,每个浮点型占4个字节
        mGLVertexBuffer = ByteBuffer.allocateDirect(4 * 4 * 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mGLVertexBuffer.clear();
        // 顶点坐标
        float[] VERTEX = {
                -1.0f, -1.0f,
                1.0f, -1.0f,
                -1.0f, 1.0f,
                1.0f, 1.0f
        };
        mGLVertexBuffer.put(VERTEX);

        // 纹理坐标
        mGLTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mGLTextureBuffer.clear();

        // 正常的纹理贴图坐标,但是贴出的图是上下颠倒的,所以需要修改一下
//        float[] TEXTURE = {
//                0.0f, 1.0f,
//                1.0f, 1.0f,
//                0.0f, 0.0f,
//                1.0f, 0.0f
//        };

        // 修复上下颠倒后的纹理贴图坐标
        float[] TEXTURE = {
                0.0f, 0.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f
        };
        mGLTextureBuffer.put(TEXTURE);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        // 清屏
        GLES20.glClearColor(1, 0, 0, 0);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // 更新纹理
        surfaceTexture.updateTexImage();
        surfaceTexture.getTransformMatrix(textureMatrix);
        GLES20.glUseProgram(programId);

        //变换矩阵
        GLES20.glUniformMatrix4fv(textureMatrixId, 1, false, textureMatrix, 0);

        // 传递坐标数据
        mGLVertexBuffer.position(0);
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGLVertexBuffer);
        GLES20.glEnableVertexAttribArray(vPosition);

        // 传递纹理坐标
        mGLTextureBuffer.position(0);
        GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGLTextureBuffer);
        GLES20.glEnableVertexAttribArray(vCoord);

        //绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        // 解绑纹理
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
    }

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

编写顶点着色器camera_vertex.glsl:


attribute vec4 vPosition;
attribute vec4 vCoord;
varying vec2 aCoord;

uniform mat4 textureMatrix;

void main(){
    gl_Position = vPosition;
    aCoord = (textureMatrix * vCoord).xy;
}

编写片段着色器camera_frag.glsl:


#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;

//采样点的坐标
varying vec2 aCoord;

//采样器
uniform samplerExternalOES vTexture;

void main(){
    //变量 接收像素值
    // texture2D:采样器 采集 aCoord的像素
    //赋值给 gl_FragColor 就可以了
    gl_FragColor = texture2D(vTexture,aCoord);
}

加载及编译着色器程序OpenGLUtils.java:


 public static String readRawTextFile(Context context, int rawId) {
        InputStream is = context.getResources().openRawResource(rawId);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuilder sb = new StringBuilder();
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }


    /**
     * 价值着色器并编译成GPU程序
     * @param vSource
     * @param fSource
     * @return
     */
    public static int loadProgram(String vSource, String fSource){
        /**
         * 顶点着色器
         */
        int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
        //加载着色器代码
        GLES20.glShaderSource(vShader,vSource);
        //编译(配置)
        GLES20.glCompileShader(vShader);

        //查看配置 是否成功
        int[] status = new int[1];
        GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS,status,0);
        if(status[0] != GLES20.GL_TRUE){
            //失败
            throw new IllegalStateException("load vertex shader:"+ GLES20.glGetShaderInfoLog(vShader));
        }

        /**
         *  片元着色器
         *  流程和上面一样
         */
        int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
        //加载着色器代码
        GLES20.glShaderSource(fShader,fSource);
        //编译(配置)
        GLES20.glCompileShader(fShader);

        //查看配置 是否成功
        GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS,status,0);
        if(status[0] != GLES20.GL_TRUE){
            //失败
            throw new IllegalStateException("load fragment shader:"+ GLES20.glGetShaderInfoLog(vShader));
        }


        /**
         * 创建着色器程序
         */
        int program = GLES20.glCreateProgram();
        //绑定顶点和片元
        GLES20.glAttachShader(program,vShader);
        GLES20.glAttachShader(program,fShader);
        //链接着色器程序
        GLES20.glLinkProgram(program);
        //获得状态
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS,status,0);
        if(status[0] != GLES20.GL_TRUE){
            throw new IllegalStateException("link program:"+ GLES20.glGetProgramInfoLog(program));
        }
        GLES20.glDeleteShader(vShader);
        GLES20.glDeleteShader(fShader);
        return program;
    }


结合CameraX用起来MainActivity.java:

public class MainActivity extends AppCompatActivity {


    private GLCameraView camera_preview;

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        camera_preview = findViewById(R.id.camera_preview);

        if (allPermissionsGranted()) {
            startCamera();
        } else {
            ActivityCompat.requestPermissions(
                    this, new String[]{Manifest.permission.CAMERA}, 100);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 100) {
            if (allPermissionsGranted()) {
                startCamera();
            } else {
                Toast.makeText(this, "没有相机权限", Toast.LENGTH_LONG).show();
            }
        }
    }

    private boolean allPermissionsGranted() {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
    }

    private void startCamera() {
        Executor executor = Executors.newSingleThreadExecutor();
        ListenableFuture<ProcessCameraProvider> processCameraProvider = ProcessCameraProvider.getInstance(this);
        processCameraProvider.addListener(new Runnable() {
            @Override
            public void run() {

                try {
                    ProcessCameraProvider cameraProvider = processCameraProvider.get();
                    Preview preview = new Preview.Builder()
                            .build();
                    camera_preview.attachPreview(preview);
                    cameraProvider.unbindAll();
                    cameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA,preview);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, ContextCompat.getMainExecutor(this));
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

关键代码点加了点注释,打完收工。

举一反三

1、目前的预览竖屏看起来挺正常的,但是横屏的时候预览界面明显发生变形了,这个问题怎么解决呢?有兴趣的童鞋可以了解下OpenGL的矩阵变换的相关知识,利用矩阵变换来解决这个问题。

2、预览使用的默认的比较低的分辨率,如果需要预览高分辨率需要怎么修改呢?

3、笔者在预览的时候测试了一下帧率,大概是每秒26帧作用,如果要做到预览每秒60帧又要怎么改呢?

4、入门OpenGL的童鞋应该知道VBOVAOFBO等相关概念,想进一步深入学习的童鞋也可以将VBOVAOFBO与CameraX结合起来做一个实践。

哔哔两句

CameraX虽然已经提出了两年多了,但是一直还没有发布正式版,貌似最近发布了一个beat版本,而且笔者在学习的过程中发现相关的api也一直在变化。
所以笔者觉得CameraX是未来,但不是现在。

虽然说CameraX还不稳定,甚至可能还存在着各种各样的问题,但是机会更加青睐的是那些未雨绸缪的人,持续关注学习CameraX的演进,本身就像跟着谷歌工程师学习的一个过程。

参考资料:《谷歌官方》

关注我,一起进步,人生不止coding!!!

微信扫码关注

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值