Android音視頻(二)預覽攝像頭畫面

利用OpenGL生成紋理並綁定到SurfaceTexture上,而後把Camera的預覽數據設置到SurfaceTexture中,OpenGL拿到攝像頭數據並顯示出來。bash

1. 頂點與片元着色器

片元着色器:ide

#extension GL_OES_EGL_image_external:require
precision mediump float;
varying vec2 a_position;
uniform samplerExternalOES vTexture;
void main(){
    gl_FragColor = texture2D(vTexture,a_position);
}
複製代碼
  1. 注意前面要加 #extension GL_OES_EGL_image_external:require聲明。
  2. 以前的sampler2D 變成了 samplerExternalOES

頂點着色器:ui

attribute vec4 v_position;
attribute vec2 f_position;
varying vec2 a_position;
void main(){
    gl_Position = v_position;
    a_position = f_position;
}
複製代碼

2. 建立Program

mProgram = ShaderUtil.createProgram(ShaderUtil.getRawResource(mContext, R.raw.demo2_vertex_shader),
                ShaderUtil.getRawResource(mContext, R.raw.demo2_fragment_shader));
        if (mProgram == 0){
            throw new RuntimeException("create program fail");
        }
        v_position = GLES20.glGetAttribLocation(mProgram, "v_position");
        f_positon = GLES20.glGetAttribLocation(mProgram, "f_position");
複製代碼

3. 生成並綁定紋理

int[] textures = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        textureId = textures[0];

        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);

        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
        //建立Camera預覽的SurfaceTexture
        mSurfaceTexture = new SurfaceTexture(textureId);
        if (mOnSurfaceCreateListener != null){
            mOnSurfaceCreateListener.onSurfaceCreate(mSurfaceTexture);
        }

        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
複製代碼

須要注意使用的是GLES11Ext.GL_TEXTURE_EXTERNAL_OES:this

GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
複製代碼

在建立紋理後咱們將紋理id做爲SurfaceTexture的構造參數傳入,並經過回調通知外層處理Camera:spa

mSurfaceTexture = new SurfaceTexture(textureId);
 if (mOnSurfaceCreateListener != null){
         mOnSurfaceCreateListener.onSurfaceCreate(mSurfaceTexture);
  }
複製代碼

4. 刷新顯示

//將紋理圖像更新爲圖像流中的最新幀。只有在擁有紋理的OpenGL ES上下文在調用線程上是最新的時,才      能夠調用此方法。
//它將隱式地將其紋理綁定到GL_TEXTURE_EXTERNAL_OES紋理目標
       mSurfaceTexture.updateTexImage();
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(1.0f, 0, 0, 1f);

        GLES20.glUseProgram(mProgram);

        GLES20.glEnableVertexAttribArray(v_position);
        GLES20.glVertexAttribPointer(v_position, 2, GLES20.GL_FLOAT, false, 8, mVertexBuffer);

        GLES20.glEnableVertexAttribArray(f_positon);
        GLES20.glVertexAttribPointer(f_positon, 2, GLES20.GL_FLOAT, false, 8, mFragmentBuffer);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glBindTexture(mProgram, 0);
複製代碼

注意要先調用紋理圖像更新爲圖像流中的最新幀。:線程

mSurfaceTexture.updateTexImage();
複製代碼

外部GLSurfaceView處理邏輯:code

public Demo2SurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setEGLContextClientVersion(2);
        Demo2Renderer demo2Renderer = new Demo2Renderer(context);
        //Camera 管理類
        mDemo2Camera = new Demo2Camera(context);
        demo2Renderer.setOnSurfaceCreateListener(new Demo2Renderer.OnSurfaceCreateListener() {
            @Override
            public void onSurfaceCreate(SurfaceTexture surfaceTexture) {
                mDemo2Camera.initCamera(surfaceTexture, cameraId);
                //註冊當一個新的圖像幀可用於SurfaceTexture時要調用的回調。
                surfaceTexture.setOnFrameAvailableListener(Demo2SurfaceView.this);
            }
        });
        setRenderer(demo2Renderer);
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }

    public void onDestory() {
        if (mDemo2Camera != null){
            mDemo2Camera.stopPreview();
        }
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        //手動請求刷新
        requestRender();
    }
複製代碼

再來看下這個Demo2Camera管理類:orm

public Demo2Camera(Context context){
        this.width = DisplayUtil.getScreenWidth(context);
        this.height = DisplayUtil.getScreenHeight(context);
    }

    public void initCamera(SurfaceTexture surfaceTexture,int cameraId){
        this.mSurfaceTexture = surfaceTexture;
        setCameraParm(cameraId);
    }

    private void setCameraParm( int cameraId) {
        try {
            mCamera = Camera.open(cameraId);
           //將SurfaceTexture設置爲預覽surface
            mCamera.setPreviewTexture(mSurfaceTexture);

            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setFlashMode("off");
            parameters.setPreviewFormat(ImageFormat.NV21);

            Camera.Size size = getFitSize(parameters.getSupportedPictureSizes());
            parameters.setPictureSize(size.width, size.height);

            size = getFitSize(parameters.getSupportedPreviewSizes());
            parameters.setPreviewSize(size.width, size.height);
            
            mCamera.setParameters(parameters);
            //開始預覽
            mCamera.startPreview();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private Camera.Size getFitSize(List<Camera.Size> sizes) {
        if(width < height) {
            int t = height;
            height = width;
            width = t;
        }

        for(Camera.Size size : sizes) {
            if(1.0f * size.width / size.height == 1.0f * width / height) {
                return size;
            }
        }
        return sizes.get(0);
    }
複製代碼

關鍵處理邏輯在:cdn

mCamera.setPreviewTexture(mSurfaceTexture);
複製代碼

此時將Camera、SurfaceTexture、OpenGL鏈接在一塊兒了,看下預覽的畫面。 ci

顯示圖像
what? 如今顯示怎麼是這樣的?方向歪到姥姥家了,咳咳,那下面開始調整方向。

5. 調整預覽方向

改造頂點着色器:

attribute vec4 v_position;
attribute vec2 f_position;
uniform mat4 uMatrix;
varying vec2 a_position;
void main(){
    gl_Position = v_position * uMatrix;
    a_position = f_position;
}
複製代碼

增長矩陣:

uniform mat4 uMatrix;
複製代碼

Renderer構造方法中置空矩陣:

Matrix.setIdentityM(matrix, 0);
複製代碼

查找到矩陣對應着色器中的id:

uMatrix = GLES20.glGetUniformLocation(mProgram, "uMatrix");
複製代碼
@Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        setAngle(-90, 0, 0, 1);
        setAngle(180, 1, 0, 0);
        setAngle(180, 0, 1, 0);
    }

    public void setAngle(float angle, float x, float y, float z){
        Matrix.rotateM(matrix, 0, angle, x, y, z);
    }
複製代碼

Matrix.rotateM(matrix, o, a, x, y, z): a: 正數:逆時針旋轉 負數:順時針旋轉 x、y、z:分別表示相應座標軸

矩陣賦值:

...
GLES20.glUseProgram(mProgram);
GLES20.glUniformMatrix4fv(uMatrix, 1, false, matrix, 0);
...
複製代碼

攝像頭顯示