自定义GLSurfaceView+Opengl es(使用了VBO)实现摄像头预览

https://blog.csdn.net/llxyy299/article/details/82659683这篇文章中记录了自己创建EGL环境,自己实现一个GLSurfaceView,但是还没有记录怎么用,这篇文章来记录怎么使用自定义的GLSurfaceView。

1、首先创建一个CameraHelper类,里面封装了安卓相机的一些api:

package com.leilu.mycamera.camera;

import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;

import java.io.IOException;
import java.util.List;

/**
 * Created by ll on 2018/9/13.
 */

public class CameraHelper {
    private Camera mCamera;
    private int mCameraId = 0;
    private int mDesireWidth, mDesireHeight;
    private boolean mIsInit;
    private SurfaceTexture mSurfaceTexture;
    private boolean mIsPortrait = true;

    public CameraHelper() {

    }

    public boolean init(int cameraId, int desireWidth, int desireHeight, SurfaceTexture surfaceTexture) {
        if (mCamera == null) {
            checkCameraID(cameraId);
            mCameraId = cameraId;
            mDesireWidth = desireWidth;
            mDesireHeight = desireHeight;
            mSurfaceTexture = surfaceTexture;

            mCamera = Camera.open(mCameraId);
            Camera.Parameters parameters = mCamera.getParameters();
            Camera.Size picSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPictureSizes());
            if (picSize != null) {
                parameters.setPictureSize(picSize.width, picSize.height);
            }
            Log.i("==", "picSize:" + picSize.width + "   " + picSize.height);
            Camera.Size preSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPreviewSizes());
            if (preSize != null) {
                parameters.setPreviewSize(preSize.width, preSize.height);
            }
            Log.i("==", "preSize:" + preSize.width + "   " + preSize.height);
            mCamera.setParameters(parameters);
            try {
                mCamera.setPreviewTexture(surfaceTexture);
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        mIsInit = true;
        return true;
    }

    public boolean startPreview() {
        if (!mIsInit || mCamera == null) {
            return false;
        }
        mCamera.startPreview();
        return true;
    }

    public boolean stopPreview() {
        if (!mIsInit || mCamera == null) {
            return false;
        }
        mCamera.stopPreview();
        return true;
    }

    public boolean switchCamera(int cameraId) {
        release();
        if (!init(cameraId, mDesireWidth, mDesireHeight, mSurfaceTexture)) {
            return false;
        }
        startPreview();
        return true;
    }

    private Camera.Size getFitSize(int desireWidth, int desireHeight,
                                   List<Camera.Size> preSizeList) {
        int reqTmpWidth;
        int reqTmpHeight;
        // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
        if (mIsPortrait) {
            reqTmpWidth = desireHeight;
            reqTmpHeight = desireWidth;
        } else {
            reqTmpWidth = desireWidth;
            reqTmpHeight = desireHeight;
        }
        //先查找preview中是否存在与控件相同宽高的尺寸
        for (Camera.Size size : preSizeList) {
            if ((size.width == reqTmpWidth) && (size.height == reqTmpHeight)) {
                return size;
            }
        }

        // 得到与传入的宽高比最接近的size
        float reqRatio = ((float) reqTmpWidth) / reqTmpHeight;
        float curRatio, deltaRatio;
        float deltaRatioMin = Float.MAX_VALUE;
        Camera.Size retSize = null;
        for (Camera.Size size : preSizeList) {
            curRatio = ((float) size.width) / size.height;
            deltaRatio = Math.abs(reqRatio - curRatio);
            if (deltaRatio < deltaRatioMin) {
                deltaRatioMin = deltaRatio;
                retSize = size;
            }
        }

        return retSize;
    }

    private void checkCameraID(int cameraId) {
        int count = Camera.getNumberOfCameras();
        if (cameraId < 0 || cameraId >= count) {
            throw new IllegalArgumentException("Nonsupport cameraID !");
        }
    }


    public void release() {
        stopPreview();
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
        mIsInit = false;
    }
}

2、 创建一个CameraRender类来实现Opengl ES的渲染代码:

package com.leilu.mycamera.camera;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;

import com.leilu.floatwindow.R;
import com.leilu.floatwindow.ShaderUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


/**
 * Created by ll on 2018/9/13.
 */

public class CameraRender {

    private static final float[] VERTEX_POSITION = {
            -1, -1,
            1, -1,
            -1, 1,
            1, 1
    };
    private static final float[] FRAGMENT_POSITION = {
            0, 1,
            1, 1,
            0, 0,
            1, 0
    };


    private SurfaceTexture mSurfaceTexture;

    private int mProgram;
    private int mExtTextureHandle;
    private int mTexturePositionHandle;
    private int mVertexPositionHandle;
    private int mProjectionMatrixHandle;

    private int[] mVertexBufferIds;
    private int[] mFragmentBufferIds;
    private int[] mExtTextureIds;
    private float[] mProjectionMatrix = new float[16];

    private Context mContext;

    public CameraRender(Context context) {
        mContext = context.getApplicationContext();
        Matrix.setIdentityM(mProjectionMatrix, 0);
    }

    public void init(SurfaceTexture.OnFrameAvailableListener onFrameAvailable) {
        if (mSurfaceTexture == null) {
            mProgram = ShaderUtil.createProgram(ShaderUtil.getRawResource(mContext, R.raw.camera_vertex_shader),
                    ShaderUtil.getRawResource(mContext, R.raw.camera_fragment_shader));
            if (mProgram == -1) {
                return;
            }
            GLES20.glUseProgram(mProgram);
            // 获取纹理句柄
            mExtTextureHandle = GLES20.glGetAttribLocation(mProgram, "sTexture");
            mTexturePositionHandle = GLES20.glGetAttribLocation(mProgram, "texturePosition");
            mVertexPositionHandle = GLES20.glGetAttribLocation(mProgram, "vertexPosition");
            mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, "projectionMatrix");

            // 创建vbo
            mVertexBufferIds = createVBO(VERTEX_POSITION);
            mFragmentBufferIds = createVBO(FRAGMENT_POSITION);

            // 创建纹理
            mExtTextureIds = createTexture();
            mSurfaceTexture = new SurfaceTexture(mExtTextureIds[0]);
            mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailable);
        }
    }


    private int[] createVBO(float[] datas) {
        FloatBuffer floatBuffer = ByteBuffer.allocateDirect(datas.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        floatBuffer.put(datas);
        floatBuffer.position(0);

        int[] vbo = new int[1];
        GLES20.glGenBuffers(vbo.length, vbo, 0);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[0]);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, datas.length * 4, null, GLES20.GL_STATIC_DRAW);
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, datas.length * 4, floatBuffer);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        floatBuffer.clear();
        return vbo;
    }

    private int[] createTexture() {
        int[] textures = new int[1];
        GLES20.glGenTextures(textures.length, textures, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return textures;
    }

    public void setViewPort(int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    public void setProjectionMatrix(boolean isBack) {
        Matrix.setIdentityM(mProjectionMatrix, 0);
        if (isBack) {
            Matrix.setRotateM(mProjectionMatrix, 0, 270, 0, 0, 1);
        } else {
            Matrix.setRotateM(mProjectionMatrix, 0, 90, 0, 0, 1);
        }
    }

    public void draw() {
        if (mProgram == -1) {
            return;
        }

        mSurfaceTexture.updateTexImage();

        // 矩阵
        GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);

        // 绑定顶点坐标
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferIds[0]);
        GLES20.glEnableVertexAttribArray(mVertexPositionHandle);
        GLES20.glVertexAttribPointer(mVertexPositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        // 绑定片元坐标
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mFragmentBufferIds[0]);
        GLES20.glEnableVertexAttribArray(mTexturePositionHandle);
        GLES20.glVertexAttribPointer(mTexturePositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        // 绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mExtTextureIds[0]);
        GLES20.glUniform1f(mExtTextureHandle, 0);

        // 绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        GLES20.glDisableVertexAttribArray(mVertexPositionHandle);
        GLES20.glDisableVertexAttribArray(mTexturePositionHandle);
    }

    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    public void release() {
        if (mExtTextureIds != null && mExtTextureIds[0] != 0) {
            GLES20.glDeleteTextures(1, mExtTextureIds, 0);
        }
        if (mFragmentBufferIds != null && mFragmentBufferIds[0] != 0) {
            GLES20.glDeleteBuffers(1, mFragmentBufferIds, 0);
        }
        if (mVertexBufferIds != null && mVertexBufferIds[0] != 0) {
            GLES20.glDeleteBuffers(1, mVertexBufferIds, 0);
        }
        GLES20.glDeleteProgram(mProgram);
    }

}

 3、创建一个CameraView类,该类直接继承了自定义的GLSurfaceView:

package com.leilu.mycamera.camera;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;

import com.leilu.mycamera.EGLThread;
import com.leilu.mycamera.MyGLSurfaceView;

import static android.opengl.GLES10.glClear;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClearColor;
import static com.leilu.mycamera.EGLThread.RENDERMODE_WHEN_DIRTY;

/**
 * Created by ll on 2018/9/13.
 */

public class CameraView extends MyGLSurfaceView implements EGLThread.OnEGLThreadListener, SurfaceTexture.OnFrameAvailableListener {

    private static final int FRONT_CAMERA_ID = 1;
    private static final int BACK_CAMERA_ID = 0;

    private CameraHelper mCameraHelper;
    private CameraRender mCameraRender;
    private int mCameraId = BACK_CAMERA_ID;

    public CameraView(Context context) {
        this(context, null);
    }

    public CameraView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setRenderMode(RENDERMODE_WHEN_DIRTY);
        setRenderListener(this);
        mCameraHelper = new CameraHelper();
        mCameraRender = new CameraRender(context);
    }

    @Override
    public void onCreate() {
        mCameraRender.init(this);
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        requestRender();
    }

    @Override
    public void onSurfaceChanged(Object surface, int width, int height) {
        mCameraRender.setViewPort(width, height);
        SurfaceTexture surfaceTexture = mCameraRender.getSurfaceTexture();
        if (surfaceTexture != null) {
            mCameraHelper.init(mCameraId, width, height, surfaceTexture);
            open();
        }
    }

    @Override
    public void onDrawFrame() {
        glClear(GL_COLOR_BUFFER_BIT);
        glClearColor(0, 0, 0, 1);
        mCameraRender.draw();
    }

    public void open() {
        mCameraHelper.startPreview();
        mCameraRender.setProjectionMatrix(mCameraId == 0);
    }

    public void close() {
        mCameraHelper.stopPreview();
    }

    @Override
    protected void onDetachedFromWindow() {
        mCameraHelper.release();
        mCameraHelper = null;
        mCameraRender.release();
        mCameraRender = null;
        super.onDetachedFromWindow();
    }

    public void swithCamera() {
        if (mCameraId == BACK_CAMERA_ID) {
            mCameraId = FRONT_CAMERA_ID;
        } else {
            mCameraId = BACK_CAMERA_ID;
        }
        if (mCameraHelper.switchCamera(mCameraId)) {
            mCameraRender.setProjectionMatrix(mCameraId == 0);
        }
    }
}

上面三个类创建完成以后使用就非常简单了,直接在布局里面嵌入CameraView这个控件即可:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.leilu.mycamera.camera.CameraView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="open"
        android:text="打开" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="switchCamera"
        android:text="切换" />


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="close"
        android:text="关闭" />

</LinearLayout>

 

package com.leilu.mycamera;

import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;

import com.leilu.floatwindow.R;
import com.leilu.mycamera.camera.CameraView;


/**
 * Created by ll on 2018/9/13.
 */

public class CameraActivity extends Activity {

    private CameraView mCameraView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        mCameraView = findViewById(R.id.surface_view);
    }

    public void open(View view) {
        mCameraView.open();
    }

    public void close(View view) {
        mCameraView.close();
    }

    public void switchCamera(View view) {
        mCameraView.swithCamera();
    }
}

此时就可以预览摄像头了。

预留的问题:

1、切换摄像头的时候画面会旋转一下,这个问题下次解决

2、在创建自定义GLSurfaceView的时候说到可以实现纹理共享,在这里还没有用到,下篇文章中就使用共享纹理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值