美文网首页
使用OpenGL预览CameraX摄像头数据

使用OpenGL预览CameraX摄像头数据

作者: FlyerGo | 来源:发表于2021-04-08 09:04 被阅读0次

    前言

    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的演进,本身就像跟着谷歌工程师学习的一个过程。

    参考资料:《谷歌官方》

    vx公号:思想觉悟
    关注我,一起进步,人生不止coding!!!

    相关文章

      网友评论

          本文标题:使用OpenGL预览CameraX摄像头数据

          本文链接:https://www.haomeiwen.com/subject/hojmkltx.html