Android 仿抖音之使用OpenGL显示摄像头

前言

在上一篇博客中,简单介绍了一下有关于OpenGL的基础内容,没看过的,可以看一下OpenGL ES基础,如果对里面有很多内容还是不懂的话,就百度一下吧,里面我都是简单说了一下大概内容,从这一篇开始,用仿抖音的项目来一步步具体介绍怎么在Android中使用OpenGL。

首先抖音其实就是录制前处理和录制后特效的处理,今天先来第一步使用OpenGL显示摄像头,为后面的工作做准备。

需求

使用OpenGL显示摄像头,分析一下需求,其实也就是两步,第一步采集摄像头数据,第二步将摄像头数据显示到屏幕上

采集摄像头数据

使用Android的Camera就可以实现采集

将摄像头数据显示到屏幕上

这里显示到屏幕上,其实有很多方法,比如ANative_window等等,但是这个是使用OpenGL实现,使用OpenGL怎么实现呢,在上一篇博客中说到了OpenGL的绘制流程,基本就是按照那个流程进行实现的。

下面是项目结构中一个不太规范的类图,下面根据这种图来实现具体的代码


简单点说,我们都知道SurfaceView实质是将底层显存Surface显示到界面上,而GLSurfaceView实际就是在这个基础之上增加了OpenGL环境,DouyinView实际就相当于一块画布,而ScreenFilter中是封装了如何使用画笔去画当前摄像头采集到的内容,而DouyinRender渲染器,就是将ScreenFilter中的画渲染到画布上。

创建工程

创建一个JNI工程,就是在一开始创建项目的时候,勾选上 Include C++ support,这样就创建好了


配置文件 AndroidManifest.xml


OpenGL的使用还需要有设备制造商提供支持,以下是设备的支持情况,项目里都是用来2.0

OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持这个API规范。

OpenGL ES 2.0 :Android 2.2(API 8)和更高的版本支持这个API规范。

OpenGL ES 3.0 :Android 4.3(API 18)和更高的版本支持这个API规范。

OpenGL ES 3.1 : Android 5.0(API 21)和更高的版本支持这个API规范。

DouyinView.java

DouyinRender.java


这里使用的Render,是实现了OpenGL配置好EGL的渲染器,后面会自己来配置,这里先使用这个,上面也有提到过,OpenGL的操作都是在GLThread线程中完成,所以这里的onSurfaceCreated,onSurfaceChanged,onDrawFrame都是在GLThread中实现的,下面单独看看每个方法的使用


CameraHelper是封装了对Camera的一系列操作



创建着色器

AS里面有个插件可以支持GLSL的高亮显示,在plugin里面搜索 GLSL Support

顶点着色器


片元着色器


这里我们需要画的是矩形,也就是两个三角形,给了4个点的坐标,这4个点,会执行4次顶点着色器,依次将顶点传递给gl_Position,当4个点都传递完成之后,会进行光栅化,将一个矩形变换成一个个的片元,然后再去使用gl_FragColor去着色

因为SurfaceTexture是Android中的,并不是OpenGL的,所以需要使用额外扩展的采样器samplerExternalOES,而不是普通的sample2D采样器,在使用samplerExternalOES时候,需要再添加一句:#extension GL_OES_EGL_image_external : require


ScreenFilter.java

public ScreenFilter(Context context) {

        //把camera_vertext内容读出来

        String vertexSource= OpenUtils.readRawTextFile(context, R.raw.camera_vertex);

        String fragSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);

        //通过字符串创建着色器程序

        //使用opengl

        //一.顶点着色器

        //1.1创建顶点着色器

        int vSharderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);

        //1.2绑定代码到着色器中

        GLES20.glShaderSource(vSharderId,vertexSource);

        //1.3编译着色器

        GLES20.glCompileShader(vSharderId);

        //1.4主动获取成功失败

        int[] status=new int[1];

        GLES20.glGetShaderiv(vSharderId,GLES20.GL_COMPILE_STATUS,status,0);

        if (status[0] != GLES20.GL_TRUE){

            throw new IllegalStateException("ScreenFitler 顶点着色器配置失败!");

        }

        //二.创建片元着色器

        int fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);

        //绑定代码到着色器中

        GLES20.glShaderSource(fShaderId,fragSource);

        //编译

        GLES20.glCompileShader(fShaderId);

        GLES20.glGetShaderiv(fShaderId,GLES20.GL_COMPILE_STATUS,status,0);

        if (status[0] != GLES20.GL_TRUE){

            throw new IllegalStateException("ScreenFilter 片元着色器配置失败");

        }

        //三、把着色器塞到程序里面去,GPU

        mProgram = GLES20.glCreateProgram();

        GLES20.glAttachShader(mProgram,vSharderId);

        GLES20.glAttachShader(mProgram,fShaderId);

        //连接着色器

        GLES20.glLinkProgram(mProgram);

        //获取程序是否配置成功

  GLES20.glGetProgramiv(mProgram,GLES20.GL_LINK_STATUS,status,0);

        if (status[0] != GLES20.GL_TRUE){

            throw  new IllegalStateException("Screen Filter 着色器程序连接失败");

        }

        //因为着色器已经塞到了GPU程序中,所以可以删除了

        GLES20.glDeleteShader(vSharderId);

        GLES20.glDeleteShader(fShaderId);

        //获得着色器程序中的变量的索引,通过这个索引对其进行赋值

        vPosition = GLES20.glGetAttribLocation(mProgram,"vPosition");

        vCoord = GLES20.glGetAttribLocation(mProgram,"vCoord");

        vMatrix = GLES20.glGetUniformLocation(mProgram,"vMatrix");

        vTexture =  GLES20.glGetUniformLocation(mProgram,"vTexture");

        //创建一个数据缓冲区

        //OpenGL的中顶点的位置坐标

        mVertextBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();

        mVertextBuffer.clear();

        float[] v = {-1.0f,-1.0f,

                1.0f,-1.0f,

                -1.0f,1.0f,

                1.0f,1.0f

        };

        mVertextBuffer.put(v);

        //采样器采样图片的坐标

        mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();

        mTextureBuffer.clear();

//        float[] f={0.0f,1.0f,

//                1.0f,1.0f,

//                0.0f,0.0f,

//                1.0f,0.0f

//        };

//

        //顺时针旋转90度

//        float[] f={1.0f,1.0f,

//                1,0,

//                0,1,

//                0,0

//

//

//        };

//        镜像

        float[] f={1.0f,0.0f,

                1.0f,1.0f,

                0.0f,0.0f,

                0.0f,1.0f

        };

        mTextureBuffer.put(f);

    }

    /**

    * 使用着色器程序开始画画

    * @param texture

    * @param mtx

    */

    public void onDrawFrame(int texture,float[] mtx){

        //1.设置窗口大小

        GLES20.glViewport(0,0,mWidth,mHeight);

        //2.使用着色器程序

        GLES20.glUseProgram(mProgram);

        /**

        * 3. 画顶点

        */

        //3.1传入顶点数据,确定形状

        mVertextBuffer.position(0);

        //size:2表示xy两个数据

        GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,mVertextBuffer);

        //3.2激活

        GLES20.glEnableVertexAttribArray(vPosition);

        //4,片元,纹理

        mTextureBuffer.position(0);

        GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,mTextureBuffer);

        GLES20.glEnableVertexAttribArray(vCoord);

        //5.变换矩阵

        GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);

        //片元

        //激活图层

        GLES20.glActiveTexture(GLES20.GL_TEXTURE);

        //图像数据

        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);

        //传递参数

        GLES20.glUniform1i(vTexture,0);

        //参数传完了,通知OpenGL画画,从第0个点开始,共4个点

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);

    }

Tips

    因为OpenGL中如果显示不正确,错误一般都比较难找,所以在代码里有在一些地方去获取是否配置正确,来方便找错

    记录我遇到的两个bug,如果遇到问题可做参考,第一个是在获取索引的时候,类型弄错了。第二个是在顶点着色器中,获取像素点aCoord进行矩阵变换的时候,写成了纹理坐标 * 矩阵,这样出来的结果是错的,一定要是 矩阵 * 纹理坐标。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容