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

字节流动

共 20282字,需浏览 41分钟

 ·

2023-09-19 05:57

本文使用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(0f0f0f1f);
        //绘制图像
        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, 1false, transformMatrix, 0);

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

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

            glDrawArrays(GL_TRIANGLES, 06);
        }
    }   

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

总结一下:首先创建TextureView和开启相机,之后创建一个外部纹理,根据此纹理ID创建一个SurfaceTexture,Camera将预览数据输出至此SurfaceTexture上。

创建EGL环境来与系统屏幕进行通信,EGL使用TextureView内置的SurfaceTexture来表示绘图表面,之后OpenGL绘制的图像也就是渲染到此EglSurface上,通过eglSwapBuffers来显示OpenGL所绘制的图像

代码地址(顺手给个Star啊):https://github.com/lb377463323/GraphicsTestBed

作者:lb377463323  
出处:
http://blog.csdn.net/lb377463323  
原文链接:
http://blog.csdn.net/lb377463323/article/details/77096652  


-- END --


进技术交流群,扫码添加我的微信:Byte-Flow



获取相关资料和源码



推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜

浏览 944
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报