1. 程式人生 > >android平臺下OpenGL ES 3.0使用GLSurfaceView對相機Camera預覽實時處理

android平臺下OpenGL ES 3.0使用GLSurfaceView對相機Camera預覽實時處理

OpenGL ES 3.0學習實踐

相機預覽資料格式

android相機輸出的原始資料格式一般都是NV21(實際上就是YUV420SP的格式)或者NV12(實際上就是YUV420P的格式),筆者的小米MIX 2S的預設輸出格式就是NV21的,關於格式的問題,後續部落格再詳細說明。

前面的部落格以及說明了如何通過OpenGL ES來渲染影象,一般YUV格式的資料是無法直接用OpenGL ES來渲染的,而在OpenGL中使用的絕大部分紋理ID都是RGBA的格式,在OpenGL ES 3.0的擴充套件#extension GL_OES_EGL_image_external_essl3定義了一個紋理的擴充套件型別,即GL_TEXTURE_EXTERNAL_OES,否則整個轉換過程將會非常複雜。同時這種紋理目標對紋理的使用方式也會有一些限制,紋理繫結需要繫結到型別GL_TEXTURE_EXTERNAL_OES上,而不是型別GL_TEXTURE_2D上,對紋理設定引數也要使用GL_TEXTURE_EXTERNAL_OES

型別,生成紋理與設定紋理引數的程式碼如下:

int[] tex = new int[1];
//建立一個紋理
GLES30.glGenTextures(1, tex, 0);
//繫結到外部紋理上
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
//設定紋理過濾引數
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);

在實際的渲染過程中繫結紋理的程式碼如下:

GLES30.glActiveTexture(GL_TEXTURE0);
GLES30.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
GLES30.glUniform1i(uniformSamplers, 0);

在OpenGL ES的頂點著色器中,任何需要從紋理中取樣的OpenGL ES的頂點著色器都需要宣告其對此擴充套件的使用,使用指令如下:

//這行是OpenGL ES 2.0中的宣告
#extension GL_OES_EGL_image_external : require
//這行是OpenGL ES 3.0中的宣告
#extension GL_OES_EGL_image_external_essl3 : require

上面的過程就是使用這種擴充套件型別的紋理ID從建立到設定引數,再到真正的渲染整個過程,接下來再根據一個完整的示例看一下具體的旋轉角度問題,因為在使用攝像頭的時候很容易在預覽的時候會出現倒立、映象等問題。

開始專案實踐

先不多說,直接實踐看效果,基於之前的專案工程,新建CameraSurfaceRenderer.java檔案:

/**
 * @anchor: andy
 * @date: 2018-11-09
 * @description: 基於相機
 */
public class CameraSurfaceRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "TextureRenderer";

    private final FloatBuffer vertexBuffer, mTexVertexBuffer;

    private final ShortBuffer mVertexIndexBuffer;

    private int mProgram;

    private int textureId;

    /**
     * 頂點座標
     * (x,y,z)
     */
    private float[] POSITION_VERTEX = new float[]{
            0f, 0f, 0f,     //頂點座標V0
            1f, 1f, 0f,     //頂點座標V1
            -1f, 1f, 0f,    //頂點座標V2
            -1f, -1f, 0f,   //頂點座標V3
            1f, -1f, 0f     //頂點座標V4
    };

    /**
     * 紋理座標
     * (s,t)
     */
    private static final float[] TEX_VERTEX = {
            0.5f, 0.5f, //紋理座標V0
            1f, 1f,     //紋理座標V1
            0f, 1f,     //紋理座標V2
            0f, 0.0f,   //紋理座標V3
            1f, 0.0f    //紋理座標V4
    };

    /**
     * 索引
     */
    private static final short[] VERTEX_INDEX = {
            0, 1, 2,  //V0,V1,V2 三個頂點組成一個三角形
            0, 2, 3,  //V0,V2,V3 三個頂點組成一個三角形
            0, 3, 4,  //V0,V3,V4 三個頂點組成一個三角形
            0, 4, 1   //V0,V4,V1 三個頂點組成一個三角形
    };

    private float[] transformMatrix = new float[16];

    /**
     * 渲染容器
     */
    private GLSurfaceView mGLSurfaceView;

    /**
     * 相機ID
     */
    private int mCameraId;

    /**
     * 相機例項
     */
    private Camera mCamera;

    /**
     * Surface
     */
    private SurfaceTexture mSurfaceTexture;

    /**
     * 矩陣索引
     */
    private int uTextureMatrixLocation;

    private int uTextureSamplerLocation;

    public CameraSurfaceRenderer(GLSurfaceView glSurfaceView) {
        this.mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        this.mGLSurfaceView = glSurfaceView;
        mCamera = Camera.open(mCameraId);
        setCameraDisplayOrientation(mCameraId, mCamera);

        //分配記憶體空間,每個浮點型佔4位元組空間
        vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //傳入指定的座標資料
        vertexBuffer.put(POSITION_VERTEX);
        vertexBuffer.position(0);

        mTexVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(TEX_VERTEX);
        mTexVertexBuffer.position(0);

        mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
                .order(ByteOrder.nativeOrder())
                .asShortBuffer()
                .put(VERTEX_INDEX);
        mVertexIndexBuffer.position(0);
    }

    private void setCameraDisplayOrientation(int cameraId, Camera camera) {
        Activity targetActivity = (Activity) mGLSurfaceView.getContext();
        android.hardware.Camera.CameraInfo info =
                new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = targetActivity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0: degrees = 0; break;
            case Surface.ROTATION_90: degrees = 90; break;
            case Surface.ROTATION_180: degrees = 180; break;
            case Surface.ROTATION_270: degrees = 270; break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);
    }

    /**
     * 載入外部紋理
     * @return
     */
    public int loadTexture() {
        int[] tex = new int[1];
        //建立一個紋理
        GLES30.glGenTextures(1, tex, 0);
        //繫結到外部紋理上
        GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
        //設定紋理過濾引數
        GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
        GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
        GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
        //解除紋理繫結
        GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return tex[0];
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        //設定背景顏色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //編譯
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_camera_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_camera_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);

        uTextureMatrixLocation = GLES30.glGetUniformLocation(mProgram, "uTextureMatrix");
        //獲取Shader中定義的變數在program中的位置
        uTextureSamplerLocation = GLES30.glGetUniformLocation(mProgram, "yuvTexSampler");

        //載入紋理
        textureId = loadTexture();
        //載入SurfaceTexture
        loadSurfaceTexture(textureId);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        //使用程式片段
        GLES30.glUseProgram(mProgram);

        //更新紋理影象
        mSurfaceTexture.updateTexImage();
        mSurfaceTexture.getTransformMatrix(transformMatrix);

        //啟用紋理單元0
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        //繫結外部紋理到紋理單元0
        GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
        //將此紋理單元床位片段著色器的uTextureSampler外部紋理取樣器
        GLES30.glUniform1i(uTextureSamplerLocation, 0);

        //將紋理矩陣傳給片段著色器
        GLES30.glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);

        GLES30.glEnableVertexAttribArray(0);
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);

        GLES30.glEnableVertexAttribArray(1);
        GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, mTexVertexBuffer);

        // 繪製
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);

    }

    public boolean loadSurfaceTexture(int textureId) {
        //根據紋理ID建立SurfaceTexture
        mSurfaceTexture = new SurfaceTexture(textureId);
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                mGLSurfaceView.requestRender();
            }
        });
        //設定SurfaceTexture作為相機預覽輸出
        try {
            mCamera.setPreviewTexture(mSurfaceTexture);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        //開啟相機預覽
        mCamera.startPreview();
        return true;
    }
}

頂點著色器:

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aTextureCoord;
//紋理矩陣
uniform mat4 uTextureMatrix;
out vec2 yuvTexCoords;
void main() { 
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     //只保留x和y分量
     yuvTexCoords = (uTextureMatrix * aTextureCoord).xy;
}

片段著色器:

#version 300 es
//OpenGL ES3.0外部紋理擴充套件
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
     vFragColor = texture(yuvTexSampler,yuvTexCoords);
}

注意: 外部紋理擴充套件在OpenGL ES 2.0OpenGL ES 3.0中不太一樣。

//OpenGL ES2.0外部紋理擴充套件
#extension GL_OES_EGL_image_external : require

在啟動的Activity中處理初始化的部分:

private void setupView() {
     //例項化一個GLSurfaceView
     mGLSurfaceView = new GLSurfaceView(this);
     mGLSurfaceView.setEGLContextClientVersion(3);
     mGLSurfaceView.setRenderer(new CameraSurfaceRenderer(mGLSurfaceView));
     setContentView(mGLSurfaceView);
}

來個剪刀手看看效果:

簡單的濾鏡處理

通過紋理將相機採集的資料渲染到GLSurfaceView的過程中,我們也可以新增各種濾鏡。

給相機新增黑白濾鏡,修改片段著色器

#version 300 es
//OpenGL ES3.0外部紋理擴充套件
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
     vec4 vCameraColor = texture(yuvTexSampler,yuvTexCoords);
     float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
     vFragColor = vec4(fGrayColor, fGrayColor, fGrayColor, 1.0);
}

可以參照下面的幾個轉換公式:

標清電視使用的標準BT.601

[ Y U V ] = [ 0.299 0.587 0.114 0.14713 0.28886 0.436 0.615 0.51499 0.10001 ] [ R G B ] \begin{bmatrix} Y^{'} \\ U \\ V \\ \end{bmatrix} = \begin{bmatrix} 0.299 & 0.587 & 0.114\\ -0.14713 & -0.28886 & 0.436 \\ 0.615 & -0.51499 & -0.10001 \end{bmatrix} \begin{bmatrix} R \\ G \\ B \\ \end{bmatrix}