美文网首页NDK
徒手撸一个抖音App(2) openGL显示摄像头

徒手撸一个抖音App(2) openGL显示摄像头

作者: ddssingsong | 来源:发表于2019-06-25 14:05 被阅读0次

    下面我们开始动手撸一个抖音APP,根据实际需求,会分为下面几步

    • openGL基础
    • 显示摄像头
    • 录制视频
    • 添加功能:设置录制视频速度
    • 添加功能:各种特效

    这里是第二部分,openGL显示摄像头

    1. 权限

    <!--openGL版本-->
        <uses-feature
            android:glEsVersion="0x00020000"
            android:required="true" />
        <!--摄像头权限-->
        <uses-permission android:name="android.permission.CAMERA" />
        <!--录音权限-->
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <!--写入文件权限-->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

    2. 初始化画布

    public class CaptureView extends GLSurfaceView implements GLSurfaceView.Renderer {
        // Render类
        private GLCameraDrawer glCameraDrawer;
        // 相机控制类
        private CameraHelper cameraHelper;
        // 要打开的摄像头的ID
        public static int cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
    
        public CaptureView(Context context) {
            super(context);
            init();
        }
    
        public CaptureView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            // 初始化
            glCameraDrawer = new GLCameraDrawer(getContext());
            // 创建摄像头资源
            cameraHelper = new CameraHelper();
            //设置EGL版本
            setEGLContextClientVersion(2);
            // 设置Render
            setRenderer(this);
            /**
             * 1. 连续渲染
             * 2. 按需渲染
             * 這裏设置按需渲染
             */
            setRenderMode(RENDERMODE_WHEN_DIRTY);
        }
        //=============================Render回调=========================================
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 初始化render
            glCameraDrawer.onSurfaceCreated(gl, config);
            // 打开摄像头
            cameraHelper.open(cameraId);
            // 获取预览宽高
            Point point = cameraHelper.getPreviewSize();
            glCameraDrawer.setDataSize(point.x, point.y);
            // 设置预览控件
            cameraHelper.setPreviewTexture(glCameraDrawer.getSurfaceTexture());
            // 渲染回调
            glCameraDrawer.getSurfaceTexture().setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                @Override
                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                    requestRender();
                }
            });
            // 开始预览画面
            cameraHelper.preview();
    
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 界面改变
            GLES20.glViewport(0, 0, width, height);
            glCameraDrawer.setViewSize(width, height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            // 画出界面
            glCameraDrawer.onDrawFrame(gl);
    
        }
    
        @Override
        public void onPause() {
            super.onPause();
            // 关闭摄像头预览
            cameraHelper.close();
        }
    
    
    

    3. 初始化相机控制类

    public class CameraHelper {
        private static final String TAG = "dds_CameraHelper";
        private Config mConfig;
        private Camera mCamera;
        private CameraSizeComparator sizeComparator;
        private Point mPreSize;
    
        public CameraHelper() {
            this.mConfig = new Config();
            mConfig.minPreviewWidth = 720;
            mConfig.minPictureWidth = 720;
            mConfig.rate = 1.778f;
            sizeComparator = new CameraSizeComparator();
        }
    
        // 打开相机
        public boolean open(int cameraId) {
            mCamera = Camera.open(cameraId);
            if (mCamera != null) {
                // 获取合适的图片尺寸和预览尺寸
                Camera.Parameters param = mCamera.getParameters();
                Camera.Size picSize = getPropPictureSize(param.getSupportedPictureSizes(), mConfig.rate, mConfig.minPictureWidth);
                Camera.Size preSize = getPropPreviewSize(param.getSupportedPreviewSizes(), mConfig.rate, mConfig.minPreviewWidth);
                param.setPictureSize(picSize.width, picSize.height);
                param.setPreviewSize(preSize.width, preSize.height);
                mCamera.setParameters(param);
    
    
                Camera.Size pre = param.getPreviewSize();
                Log.d(TAG, "preview height:" + pre.height + ",width:" + pre.width);
                // 注意,这里写反的话会导致界面被压缩
                mPreSize = new Point(pre.height, pre.width);
                return true;
            }
            return false;
        }
    
        // 开始预览
        public boolean preview() {
            if (mCamera != null) {
                mCamera.startPreview();
            }
            return true;
        }
    
        // 关闭摄像头
        public boolean close() {
            if (mCamera != null) {
                try {
                    mCamera.stopPreview();
                    mCamera.release();
                } catch (Exception e) {
                    e.printStackTrace();
                    return false;
                }
            }
            return true;
        }
    
        // 设置渲染的控件SurfaceTexture
        public void setPreviewTexture(SurfaceTexture texture) {
            if (mCamera != null) {
                try {
                    mCamera.setPreviewTexture(texture);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        // 获取预览的宽高
        public Point getPreviewSize() {
            return mPreSize;
        }
    
        private Camera.Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth) {
            Collections.sort(list, sizeComparator);
    
            int i = 0;
            for (Camera.Size s : list) {
                if ((s.height >= minWidth) && equalRate(s, th)) {
                    break;
                }
                i++;
            }
            if (i == list.size()) {
                i = 0;
            }
            return list.get(i);
        }
    
        private Camera.Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth) {
            Collections.sort(list, sizeComparator);
            int i = 0;
            for (Camera.Size s : list) {
                if ((s.height >= minWidth) && equalRate(s, th)) {
                    break;
                }
                i++;
            }
            if (i == list.size()) {
                i = 0;
            }
            return list.get(i);
        }
    
        private boolean equalRate(Camera.Size s, float rate) {
            float r = (float) (s.width) / (float) (s.height);
            if (Math.abs(r - rate) <= 0.03) {
                return true;
            } else {
                return false;
            }
        }
    
    
        private class CameraSizeComparator implements Comparator<Camera.Size> {
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                if (lhs.height == rhs.height) {
                    return 0;
                } else if (lhs.height > rhs.height) {
                    return 1;
                } else {
                    return -1;
                }
            }
        }
    
        class Config {
            float rate; //宽高比
            int minPreviewWidth;
            int minPictureWidth;
        }
    }
    
    

    4. 初始化GLCameraDrawer

    public class GLCameraDrawer implements GLSurfaceView.Renderer {
        private SurfaceTexture mSurfaceTexture;
        // OseFilter 使用openGL渲染画面
        private OseFilter baseFilter;
        private float[] mtx = new float[16];
        private Context mContext;
    
        public GLCameraDrawer(Context context) {
            baseFilter = new OseFilter();
            mContext = context;
        }
    
    
        public SurfaceTexture getSurfaceTexture() {
            return mSurfaceTexture;
        }
    
    
        //------------------------------------------------------------------------------
        private int width, height;
        private int dataWidth, dataHeight;
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            // 通过openGL创建一个纹理id
            int texture = createTextureID();
            // 初始化SurfaceTexture
            mSurfaceTexture = new SurfaceTexture(texture);
            // 注意:必须在gl线程操作openGL
            baseFilter.create(mContext);
            baseFilter.setTextureId(texture);
    
        }
    
        /**
         * 画布发生了改变
         */
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            setViewSize(width, height);
            // baseFilter.setMatrix(mtx);
        }
    
    
        /**
         * 开始画画吧
         */
        @Override
        public void onDrawFrame(GL10 gl) {
            if (mSurfaceTexture != null) {
                // 更新纹理图像为从图像流中提取的最近一帧
                mSurfaceTexture.updateTexImage();
            }
            // 绘制纹理
            baseFilter.draw();
        }
    
    
        //------------------------------------------------------------------------------
        
        public void setViewSize(int width, int height) {
            this.width = width;
            this.height = height;
            calculateMatrix();
        }
    
        public void setDataSize(int dataWidth, int dataHeight) {
            this.dataWidth = dataWidth;
            this.dataHeight = dataHeight;
            calculateMatrix();
        }
    
        private void calculateMatrix() {
            getShowMatrix(mtx, this.dataWidth, this.dataHeight, this.width, this.height);
    
            if (CaptureView.cameraId == 1) {
                // 前置摄像头旋转90度并
                flip(mtx, true, false);
                rotate(mtx, 90);
            } else {
                // 前置旋转270度
                rotate(mtx, 270);
            }
            baseFilter.setMatrix(mtx);
        }
    
        private static void getShowMatrix(float[] matrix, int imgWidth, int imgHeight, int viewWidth, int viewHeight) {
            if (imgHeight > 0 && imgWidth > 0 && viewWidth > 0 && viewHeight > 0) {
                float sWhView = (float) viewWidth / viewHeight;
                float sWhImg = (float) imgWidth / imgHeight;
                float[] projection = new float[16];
                float[] camera = new float[16];
                if (sWhImg > sWhView) {
                    Matrix.orthoM(projection, 0, -sWhView / sWhImg, sWhView / sWhImg, -1, 1, 1, 3);
                } else {
                    Matrix.orthoM(projection, 0, -1, 1, -sWhImg / sWhView, sWhImg / sWhView, 1, 3);
                }
                Matrix.setLookAtM(camera, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0);
                Matrix.multiplyMM(matrix, 0, projection, 0, camera, 0);
            }
        }
    
        private static float[] rotate(float[] m, float angle) {
            Matrix.rotateM(m, 0, angle, 0, 0, 1);
            return m;
        }
    
        private static float[] flip(float[] m, boolean x, boolean y) {
            if (x || y) {
                Matrix.scaleM(m, 0, x ? -1 : 1, y ? -1 : 1, 1);
            }
            return m;
        }
    
        //通过openGL创建一个纹理id
        private int createTextureID() {
            int[] texture = new int[1];
             // 创建纹理,生成你要操作的纹理对象的索引
            GLES20.glGenTextures(texture.length, texture, 0); 
            // 绑定纹理,告诉OpenGL下面代码中对2D纹理的任何设置都是针对索引为0的纹理的
            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]); 
    
            // glTexParameterF 是设置纹理贴图的参数属性
            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    
            // OpenGL——纹理过滤函数glTexParameterI() 确定如何把纹理象素映射成像素.
            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
            return texture[0];
    
        }
    
    }
    

    5. 开始编写OseFilter

    public class OseFilter {
        private static final String TAG = "dds_Filter";
    
        protected int mProgram;    // 程序句柄
    
        protected int mHPosition;  // 顶点坐标句柄
        protected int mHCoord;     // 纹理坐标句柄
        protected int mHMatrix;    // 总变换矩阵句柄
    
        protected int mHTexture;   // 默认纹理贴图句柄 片元句柄
    
    
        protected FloatBuffer mVertexBuffer; // 顶点坐标Buffer
        protected FloatBuffer mTextureBuffer; // 纹理坐标Buffer
    
    
        //单位矩阵
        public static final float[] OM = new float[]{
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
        };
    
        private int textureId = 0;
        private float[] matrix = Arrays.copyOf(OM, 16);
    
        private int mHCoordMatrix;
        private float[] mCoordMatrix = Arrays.copyOf(OM, 16);
    
    
        //顶点坐标
        private float pos[] = {
                -1.0f, 1.0f,
                -1.0f, -1.0f,
                1.0f, 1.0f,
                1.0f, -1.0f,
        };
    
        //纹理坐标
        private float[] coord = {
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 0.0f,
                1.0f, 1.0f,
        };
    
    
        public OseFilter() {
            //创建一个数据缓冲区
            //4个点 每个点两个数据(x,y) 数据类型float
            mVertexBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            mVertexBuffer.clear();
            mVertexBuffer.put(pos);
            mVertexBuffer.position(0);
    
            // 纹理 buffer
            mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            mTextureBuffer.clear();
            mTextureBuffer.put(coord);
            mTextureBuffer.position(0);
    
        }
    
    
        public final void setTextureId(int textureId) {
            this.textureId = textureId;
        }
    
        public void setMatrix(float[] matrix) {
            this.matrix = matrix;
        }
    
        public final void create(Context context) {
            // 读出顶点着色器:vertexSource 和 片源着色器:fragmentSource
            String vertexSource = OpenUtils.readRawTextFile(context, R.raw.camera_vertex);
            String fragmentSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);
    
            mProgram = createOpenGLProgram(vertexSource, fragmentSource);
            // 顶点
            mHPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");
            mHCoord = GLES20.glGetAttribLocation(mProgram, "vCoord");
            mHMatrix = GLES20.glGetUniformLocation(mProgram, "vMatrix");
            // 片元
            mHTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");
    
            //自定义
            mHCoordMatrix = GLES20.glGetUniformLocation(mProgram, "vCoordMatrix");
    
    
        }
    
    
        public void draw() {
            onClear();
    
            // 使用着色器程序
            onUseProgram();
    
            // 设置其他扩展数据
            onSetExpandData();
    
            // 绑定默认纹理
            onBindTexture();
    
            onDraw();
    
    
        }
    
        /**
         * 清除画布
         */
        protected void onClear() {
            GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        }
    
        /**
         * 使用着色器程序
         */
        protected void onUseProgram() {
            GLES20.glUseProgram(mProgram);
        }
    
        /**
         * 设置其他扩展数据
         */
        protected void onSetExpandData() {
            //3、变换矩阵
            GLES20.glUniformMatrix4fv(mHMatrix, 1, false, matrix, 0);
    
            GLES20.glUniformMatrix4fv(mHCoordMatrix, 1, false, mCoordMatrix, 0);
        }
    
        /**
         * 绑定默认纹理
         */
        protected void onBindTexture() {
            // 激活图层
    //        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    //        // 图像数据
    //        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    //        //传递参数 0:需要和纹理层GL_TEXTURE0对应
    //        GLES20.glUniform1i(mHTexture, 0);
    
    
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
            GLES20.glUniform1i(mHTexture, 0);
        }
    
        /**
         * 启用顶点坐标和纹理坐标进行绘制
         */
        protected void onDraw() {
            // 1、将顶点数据传入,确定形状
            GLES20.glVertexAttribPointer(mHPosition, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
            //传了数据之后 激活
            GLES20.glEnableVertexAttribArray(mHPosition);
    
    
            //2、将纹理坐标传入,采样坐标
            GLES20.glVertexAttribPointer(mHCoord, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer);
            GLES20.glEnableVertexAttribArray(mHCoord);
    
            //参数传完了 通知opengl 画画 从第0点开始 共4个点
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    
    
            GLES20.glDisableVertexAttribArray(mHPosition);
            GLES20.glDisableVertexAttribArray(mHCoord);
        }
    
        /**
         * 生成OpenGL Program
         *
         * @param vertexSource   顶点着色器代码
         * @param fragmentSource 片元着色器代码
         * @return 生成的OpenGL Program,如果为0,则表示创建失败
         */
        private static int createOpenGLProgram(String vertexSource, String fragmentSource) {
            // 1. 创建顶点着色器
            int vertex = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
            if (vertex == 0) {
                Log.e(TAG, "loadShader vertex failed");
                return 0;
            }
            // 2.创建片元着色器
            int fragment = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
            if (fragment == 0) {
                Log.e(TAG, "loadShader fragment failed");
                return 0;
            }
            // 3、创建着色器程序
            int program = GLES20.glCreateProgram();
            if (program != 0) {
                // 把着色器塞到程序当中
                GLES20.glAttachShader(program, vertex);
                GLES20.glAttachShader(program, fragment);
                // 链接着色器
                GLES20.glLinkProgram(program);
                int[] linkStatus = new int[1];
                // 获得程序是否配置成功
                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
                if (linkStatus[0] != GLES20.GL_TRUE) {
                    Log.e(TAG, "Could not link program:" + GLES20.glGetProgramInfoLog(program));
                    GLES20.glDeleteProgram(program);
                    program = 0;
                }
            }
            //因为已经塞到着色器程序中了,所以删了没关系
            GLES20.glDeleteShader(vertex);
            GLES20.glDeleteShader(fragment);
            return program;
        }
    
        /**
         * 加载着色器
         *
         * @param type       加载着色器类型
         * @param shaderCode 加载着色器的代码
         */
        private static int loadShader(int type, String shaderCode) {
            //1. 根据type创建顶点着色器或者片元着色器
            int shader = GLES20.glCreateShader(type);
            //2. 将着色器的代码加入到着色器中
            GLES20.glShaderSource(shader, shaderCode);
            //3. 编译着色器
            GLES20.glCompileShader(shader);
            return shader;
        }
    
    

    其中顶点着色器片元着色器如下

    camera_vertex

    attribute vec4 vPosition;
    attribute vec2 vCoord;
    uniform mat4 vMatrix;
    uniform mat4 vCoordMatrix;
    varying vec2 textureCoordinate;
    void main(){
        gl_Position = vMatrix*vPosition;
        textureCoordinate = (vCoordMatrix*vec4(vCoord,0,1)).xy;
    }
    

    camera_frag

    #extension GL_OES_EGL_image_external : require
    precision mediump float;
    varying vec2 textureCoordinate;
    uniform samplerExternalOES vTexture;
    
    void main(){
        gl_FragColor = texture2D(vTexture,textureCoordinate);
    }
    

    读取文件

     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();
        }
    

    代码

    本教程已发布到release,可下载相应的release的代码

    https://github.com/ddssingsong/AnyNdk

    相关文章

      网友评论

        本文标题:徒手撸一个抖音App(2) openGL显示摄像头

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