Android Camera使用OpenGL ES 2.0和TextureView对预览进行实时二次处理(黑白滤镜)

本系列教程会有三篇文章讲解Android平台滤镜的实现方式,希望在阅读本文之前先阅读下述第一篇文档,因为第一篇讲过的知识,本文并不会细讲了。

下面进入正题:第二篇

本文使用TextureView作为布局界面,并且仍将使用SurfaceTexture来获取预览图像,需要注意的是TextureView内置了一个SurfaceTexture,也就是说本文需要两个SurfaceTexture。TextureView内置的SurfaceTexture用来配合EGL来将图像显示到屏幕上,自定义的SurfaceTexture用来接收Camera的预览图像来做二次处理(黑白滤镜)。这点可能比较难理解,后面通过代码进行详细讲解。

TextureView是没有配置OpenGL ES环境和Renderer线程,所以需要我们自己来初始化EGL和创建Renderer子线程。下面开始讲解。

1, 添加TextureView作为布局界面、创建SurfaceTexture以及开启相机

伪代码如下,这个不细讲了,可以参考我之前的博客 Android初始化OpenGL ES,并且分析Renderer子线程原理

    //实例化一个TextureView
    mTextureView = new TextureView(this);
    //设置SurfaceTexture监听函数
    mTextureView.setSurfaceTextureListener(mTextureListener);
    //在屏幕上显示TextureView
    setContentView(mTextureView);

    public TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
        //当SurfaceTexture可用时回调此方法,此时暂时用不到回调生成的surface
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            //获取外部纹理ID
            mOESTextureId = createOESTextureObject();
            //获取自定义的SurfaceTexture
            mOESSurfaceTexture = initOESTexture();
            //开启相机
            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
            mCamera = Camera.open(mCameraId)
            //设置camera参数
            ......
            //设置预览输出
            mCamera.setPreviewTexture(mOESSurfaceTexture);
            //开启预览
            mCamera.startPreview();
        }
        ......
    }

    //创建外部纹理
    public static int createOESTextureObject() {
        int[] tex = new int[1];
        GLES20.glGenTextures(1, tex, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return tex[0];
    }

    //根据外部纹理ID创建SurfaceTexture
    public SurfaceTexture initOESTexture() {
        mOESSurfaceTexture = new SurfaceTexture(mOESTextureId);
        mOESSurfaceTexture.setOnFrameAvailableListener(this);
        return mOESSurfaceTexture;
    }

    //监听SurfaceTexture的每帧数据
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        if (mHandler != null) {
            //因为没有GLSurfaceView的Renderer线程,所以我们需要自己通知子线程进行渲染图像
            mHandler.sendEmptyMessage(MSG_RENDER);
        }
    }   

2, 自定义Renderer线程

    public class CameraV1GLRenderer implements SurfaceTexture.OnFrameAvailableListener {
    //此init方法应该在Activity的OnCreate方法中调用
    public void init(TextureView textureView, int oesTextureId) {
        mTextureView = textureView;
        mOESTextureId = oesTextureId;
        //开启子线程
        mHandlerThread = new HandlerThread("Renderer Thread");
        mHandlerThread.start();
        //主线程与子线程需要通过Handler进行通信
        mHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_INIT:
                        initEGL();
                        return;
                    case MSG_RENDER:
                        drawFrame();
                        return;
                    case MSG_DEINIT:
                        return;
                    default:
                        return;
                }
            }
        };
        //初始化EGL环境
        mHandler.sendEmptyMessage(MSG_INIT);
    }

3, 初始化EGL环境

EGL是OpenGL ES与设备的系统屏幕进行通信的桥梁,因为TextureView是没有任何OpenGL ES相关的环境的,而上篇文章讲的GLSurfaceView是封装好了OpenGL ES相关的环境,包括EGL环境。当OpenGL ES需要绘制图像时,会找到EGL的EGLSurface,通过此对象请求SurfaceFlinger返回系统屏幕的图形访问接口,这个接口也就是屏幕的帧缓冲区,这样OpenGL就可以将图像渲染到屏幕的帧缓冲区中。

EGL主要需要四个对象,一个EGLDisplay描述EGL显示屏,一个EGLConfig描述帧缓冲区配置参数,一个EGLContext描述EGL上下文环境,一个EGLSurface描述EGL绘图表面。整个EGL初始化见下述代码。

    //定义所需变量    
    private EGL10 mEgl = null;
    private EGLDisplay mEGLDisplay = EGL10.EGL_NO_DISPLAY;
    private EGLContext mEGLContext = EGL10.EGL_NO_CONTEXT;
    private EGLConfig[] mEGLConfig = new EGLConfig[1];
    private EGLSurface mEglSurface;

    private void initEGL() {
        //获取系统的EGL对象
        mEgl = (EGL10) EGLContext.getEGL();

        //获取显示设备
        mEGLDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
        if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
            throw new RuntimeException("eglGetDisplay failed! " + mEgl.eglGetError());
        }

        //version中存放当前的EGL版本号,版本号即为version[0].version[1],如1.0
        int[] version = new int[2];

        //初始化EGL
        if (!mEgl.eglInitialize(mEGLDisplay, version)) {
            throw new RuntimeException("eglInitialize failed! " + mEgl.eglGetError());
        }

        //构造需要的配置列表
        int[] attributes = {
                //颜色缓冲区所有颜色分量的位数
                EGL10.EGL_BUFFER_SIZE, 32,
                //颜色缓冲区R、G、B、A分量的位数
                EGL10.EGL_RED_SIZE, 8,
                EGL10.EGL_GREEN_SIZE,8,
                EGL10.EGL_BLUE_SIZE, 8,
                EGL10.EGL_ALPHA_SIZE, 8,
                EGL10.EGL_NONE
        };
        int[] configsNum = new int[1];

        //EGL根据attributes选择最匹配的配置
        if (!mEgl.eglChooseConfig(mEGLDisplay, attributes, mEGLConfig, 1, configsNum)) {
            throw new RuntimeException("eglChooseConfig failed! " + mEgl.eglGetError());
        }

        //如本文开始所讲的,获取TextureView内置的SurfaceTexture作为EGL的绘图表面,也就是跟系统屏幕打交道
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        if (surfaceTexture == null)
            return;

        //根据SurfaceTexture创建EGL绘图表面
        mEglSurface = mEgl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig[0], surfaceTexture, null);

        //指定哪个版本的OpenGL ES上下文,本文为OpenGL ES 2.0
        int[] contextAttribs = {
                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL10.EGL_NONE
        };
        //创建上下文,EGL10.EGL_NO_CONTEXT表示不和别的上下文共享资源
        mEGLContext = mEgl.eglCreateContext(mEGLDisplay, mEGLConfig[0], EGL10.EGL_NO_CONTEXT, contextAttribs);

        if (mEGLDisplay == EGL10.EGL_NO_DISPLAY || mEGLContext == EGL10.EGL_NO_CONTEXT){
            throw new RuntimeException("eglCreateContext fail failed! " + mEgl.eglGetError());
        }

        //指定mEGLContext为当前系统的EGL上下文,你可能发现了使用两个mEglSurface,第一个表示绘图表面,第二个表示读取表面
        if (!mEgl.eglMakeCurrent(mEGLDisplay,mEglSurface, mEglSurface, mEGLContext)) {
            throw new RuntimeException("eglMakeCurrent failed! " + mEgl.eglGetError());
        }
    }

4. OpenGL ES代码编写

有了EGL环境,现在可开始OpenGL ES部分的代码编写,这一块可以参考上篇文章,内容基本一样,只不过通知渲染是由SurfaceTexture来做的。

    //监听SurfaceTexture的每帧数据
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        if (mHandler != null) {
            //因为没有GLSurfaceView的Renderer线程,所以我们需要自己通知自定义的Renderder子线程进行渲染图像
            mHandler.sendEmptyMessage(MSG_RENDER);
        }
    }

    //通过handlerMessage()方法调用此方法
    private void drawFrame() {
        //更新预览图像
        if (mOESSurfaceTexture != null) {
            mOESSurfaceTexture.updateTexImage();
            mOESSurfaceTexture.getTransformMatrix(transformMatrix);
        }
        //指定mEGLContext为当前系统的EGL上下文,这里比较消耗性能
        mEgl.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext);
        //设置视口
        GLES20.glViewport(0,0,mTextureView.getWidth(),mTextureView.getHeight());
        //清楚屏幕
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(0f, 0f, 0f, 1f);
        //绘制图像
        drawTexture(transformMatrix);
        //交换缓冲区,Android使用双缓冲机制,我们绘制的都是在后台缓冲区,通过交换将后台缓冲区变为前台显示区,下一帧的绘制仍然在后台缓冲区
        mEgl.eglSwapBuffers(mEGLDisplay, mEglSurface);
    }   

    //绘制图像,参考第一篇文章,有详细讲解
    public void drawTexture(float[] transformMatrix) {
        aPositionLocation = glGetAttribLocation(mShaderProgram, FilterEngine.POSITION_ATTRIBUTE);
        aTextureCoordLocation = glGetAttribLocation(mShaderProgram, FilterEngine.TEXTURE_COORD_ATTRIBUTE);
        uTextureMatrixLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_MATRIX_UNIFORM);
        uTextureSamplerLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_SAMPLER_UNIFORM);

        glActiveTexture(GLES20.GL_TEXTURE0);
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mOESTextureId);
        glUniform1i(uTextureSamplerLocation, 0);
        glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);

        if (mBuffer != null) {
            mBuffer.position(0);
            glEnableVertexAttribArray(aPositionLocation);
            glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 16, mBuffer);

            mBuffer.position(2);
            glEnableVertexAttribArray(aTextureCoordLocation);
            glVertexAttribPointer(aTextureCoordLocation, 2, GL_FLOAT, false, 16, mBuffer);

            glDrawArrays(GL_TRIANGLES, 0, 6);
        }
    }   

至此运行此程序Camera预览就会显示为黑白滤镜,如下图所示。

总结一下:首先创建TextureView和开启相机,之后创建一个外部纹理,根据此纹理ID创建一个SurfaceTexture,Camera将预览数据输出至此SurfaceTexture上。创建EGL环境来与系统屏幕进行通信,EGL使用TextureView内置的SurfaceTexture来表示绘图表面,之后OpenGL绘制的图像也就是渲染到此EglSurface上,通过eglSwapBuffers来显示OpenGL所绘制的图像。

代码地址(顺手给个Star啊):点击查看源码

作者:lb377463323
出处:http://blog.csdn.net/lb377463323
原文链接:http://blog.csdn.net/lb377463323/article/details/77096652
转载请注明出处!

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 使用 camera2 API 可以更加灵活、可定制和高效地完成 Android 相机应用开发,其相比 camera1 API 的性能有大幅提升。 在使用 camera2 API 完成预览和拍照前,需要进行以下几个步骤: 1. 获取 CameraManager 对象,查找可用的摄像头列表,并选择需要打开的摄像头。 2. 创建 CameraCaptureSession 对象,用于处理相机触发器的请求,并连接 CameraDevice 和 Surface。 3. 匹配预览和图片输出的 Surface,设置相应的尺寸和格式。 4. 创建 CaptureRequest 对象,设置相应的参数,如自动对焦模式、曝光模式等。 5. 使用 CameraCaptureSession 进行预览或拍照。 在预览时,可以使用 TextureView 或 SurfaceView 进行实时数据渲染,比如显示相机预览画面、拍照后处理和显示等,同时可以通过设置监听器动态获取相机输出的图像流数据。 在拍照时,需要创建 ImageReader 对象,设置输出数据的格式和尺寸,同时需要建立对应的 Surface,将其传入 CaptureRequest.Builder,设置请求类型并发起拍照请求。通过设置 ImageReader 的 OnImageAvailableListener 接口,即可接收到图片数据,并进行后续处理和保存。 以上是使用 camera2 API 完成预览和拍照的基本流程,实际开发中需要根据具体需求进行优化和调整。 ### 回答2: Android Camera2 API 是 Android 系统中相机功能的一种全新的 API,使用 Camera2 可以更灵活地操作相机设备并获得更高质量的照片。 使用 Camera2 实现预览非常简单,我们只需要实现一个 CameraDevice.StateCallback 接口实现类和一个 SurfaceView 主界面。在 StateCallback 的 onOpened 回调中获得 CameraDevice 的实例,然后通过 ImageReader 创建 SurfaceHolder,最后将 SurfaceHolder 通过 CameraDevice.createCaptureSession 接口跟 CameraDevice 进行绑定即可实现预览。 拍照的实现过程与预览类似,首先获得 CameraDevice 实例,然后创建一个 CaptureRequest.Builder 对象,将拍照设置参数通过 CaptureRequest.Builder.set 方法设置到 CaptureRequest.Builder 对象中,最后通过 CameraCaptureSession.capture 接口启动拍照操作即可。 当然,在使用 Camera2 API 进行操作相机时,还需要注意一些其他问题,比如不同的相机设备有不同的特性,需要针对不同的设备进行优化和适配,还需要保证应用的流畅性和稳定性,以达到更好的用户体验。 总之,使用 Camera2 API 实现预览和拍照是 Android 开发的一个重要技能,需要开发者深入了解该 API 的机制和使用方式,才能更好地实现优秀的相机应用。 ### 回答3: Android中的camera2是一种相机应用程序接口(API),旨在提高相机应用程序的功能和性能。相较于早期版本的camera API,camera2 API提供了更多的控制选项,允许开发者定制相机应用程序的功能,从而实现更好的用户体验。 使用camera2 API实现预览和拍照需要以下步骤: 1. 获取CameraManager对象。使用该对象可以获取系统中可用的相机列表,并在需要的时候打开指定相机。 2. 打开指定相机。调用CameraManager.openCamera()方法打开相机。 3. 创建CaptureSession。CaptureSession是与相机关联的一组输出Surface的集合。 4. 创建CaptureRequest。CaptureRequest是一个指定相机操作和设置的重要对象,可以通过它来设置各种模式、参数和目标Surface。 5. 创建Preview Request。处理预览界面。 6. 启动相机预览。启动前,可以使用CaptureRequest.Builder设置其他预览参数。 7. 拍照。当用户点击拍照按钮时,调用CaptureSession.capture()方法,即可拍照并接收回调。 8. 关闭相机。释放所有占用的资源,以便其他应用程序可以使用相机。 总之,在使用camera2 API实现预览和拍照时,需要使用许多类和方法。但只要开发者掌握了API中的基本概念和流程,就可以自由地使用该API,设计新型的相机应用程序,提供更好的功能和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值