音視頻開發之旅(14) OpenGL ES 實時濾鏡

目錄

  1. OES是什麼?SurfaceTeture是什麼?
  2. 實時濾鏡的流程
  3. 具體實踐
  4. 遇到的問題
  5. 收穫

一、基本知識介紹

**外部紋理是什麼? **

    public static native void glBindTexture(
        int target,
        int texture
    );

我們在前面幾篇中target一直是GL_TEXTURE_2D,即一張紋理圖片,直接進行渲染。而在播放視頻或者Camera預覽 對數據進行濾鏡、特效處理後在渲染到屏幕上而不是直接渲染到屏幕,這是就需要用到OES外部紋理,GLES11Ext.GL_TEXTURE_EXTERNAL_OES

SurfaceTeture是什麼?

我們在前面章節介紹Camera預覽時使用的是SurfaceHolder進行的preview。

SurfaceTexture 類是在 Android 3.0 中引入的,它對圖像流的處理並不直接顯示,而是轉爲GL外部紋理,可用於圖像流數據的二次處理(如Camera濾鏡,特效等)

SurfaceTexture從圖像流(Camera預覽、視頻解碼)中獲得幀數據,調用updateTexImage(),根據內容流中最近的圖像更新SurfaceTexture對應的GL紋理對象,接下來,就可以像操作普通GL紋理一樣操作它了。

二、流程

Camera採集數據後不直接顯示到屏幕上,而是先對這個圖像做濾鏡處理,即先渲染在一個外部紋理上,處理完之後在顯示在屏幕上。

OpenGL紋理繪製的基本流程。

  1. 創建Camera
  2. 創建和加載glsl,創建編譯鏈接program, 獲取location
  3. 創建外部紋理、綁定紋理、設置參數
  4. 根據創建的紋理id生成一個SurfaceTexture
  5. 設置Camera通過SurfaceTeture方式而不是SurfaceHolder進行預覽
  6. Camera將預覽數據輸出至此SurfaceTexture上,將一幀預覽數據推送給外部紋理上
  7. 給此紋理添加濾鏡
  8. 處理後的數據通過OpenGL ES繪製出來

三、實踐:Camera預覽添加實時濾鏡(原圖、黑白、冷暖色)

1. 創建Camera

private void initCamera(int cameraId) {
        curCameraId = cameraId;
        mCamera = Camera.open(curCameraId);
        Log.d(TAG, "initCamera: Camera Open ");


        Camera.Parameters parameters = mCamera.getParameters();
        Camera.Size closelyPreSize = CameraUtil.getCloselyPreSize(true, SystemUtils.getDisplayWidth(), SystemUtils.getDisplayHeight(), parameters.getSupportedPreviewSizes());
        Log.i(TAG, "initCamera: closelyPreSizeW=" + closelyPreSize.width + " closelyPreSizeH=" + closelyPreSize.height);
        parameters.setPreviewSize(closelyPreSize.width, closelyPreSize.height);
        //這裏把Camera的寬高做下反轉
        mPreviewWidth = closelyPreSize.height;
        mPreviewHeight = closelyPreSize.width;

        mCamera.setParameters(parameters);
    }

2. 加載glsl,創建program,獲取location

省略

3. 創建外部紋理

    private int genOesTextureId() {
        int[] textureObjectId = new int[1];
        GLES20.glGenTextures(1, textureObjectId, 0);
        //綁定紋理
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureObjectId[0]);
        //設置放大縮小。設置邊緣測量
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST_MIPMAP_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        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 textureObjectId[0];
    }

int[] textureObjectId = new int[1];
        GLES20.glGenTextures(1, textureObjectId, 0);
         綁定外部紋理
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureObjectId[0]);
         設置放大縮小。設置邊緣測量
     GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST_MIPMAP_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        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);

4. 根據創建的紋理id生成一個SurfaceTexture

mSurfaceTexture = new SurfaceTexture(mTextureId);

5. 設置Camera通過SurfaceTeture方式而不是SurfaceHolder進行預覽

mCamera.setPreviewTexture(mSurfaceTexture);

6. Camera將預覽數據輸出至此SurfaceTexture上,將一幀預覽數據推送給外部紋理上

public void onDrawFrame(GL10 gl) {
                //每次繪製後,都通知texture刷新
                if (mSurfaceTexture != null) {              

                     mSurfaceTexture.updateTexImage();
                }
                
            }

7. OpenGL ES就可以操作此紋理,加濾鏡

 private void onBindFilter() {
        GLES20.glUniform1i(mUFilterIndex, mIndex);

        switch (mIndex){
            case 0:
                //原圖效果,不同處理
                break;
            case 1:
                //黑白濾鏡

                GLES20.glUniform3fv(uColor,1,ImageBgData.GRAY_FILTER_COLOR_DATA,0);
                break;
            case 2:
                //暖色濾鏡
                GLES20.glUniform3fv(uColor,1,ImageBgData.WARM_FILTER_COLOR_DATA,0);

                break;
            case 3:
                //冷色濾鏡
                GLES20.glUniform3fv(uColor,1,ImageBgData.COOL_FILTER_COLOR_DATA,0);

                break;
        }
    }

8. 處理後的數據通過OpenGL ES繪製出來

 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + getTextureType());
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, getTextureId());
        GLES20.glUniform1i(mUTexture, getTextureType());

//設置定點數據
        GLES20.glEnableVertexAttribArray(mAPosition);
        GLES20.glVertexAttribPointer(
                mAPosition,
                2,
                GLES20.GL_FLOAT,
                false,
                0,
                mVerBuffer);
        //
        GLES20.glEnableVertexAttribArray(mACoord);
        GLES20.glVertexAttribPointer(
                mACoord,
                2,
                GLES20.GL_FLOAT,
                false,
                0,
                mTextureCoordinate);
        //繪製三角形帶
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        GLES20.glDisableVertexAttribArray(mAPosition);
        GLES20.glDisableVertexAttribArray(mACoord);

代碼託管在Github上

四、遇到的問題

  1. 如何設置給Camera設置SurfaceTexure而不是SurfaceHolde
  2. 畫面預覽被放大了,進行正交投影 矩陣變換前 把視圖的寬高比反轉下,因爲camera的width大於height
  3. 在片元着色器中定義了uniform vec3 u_Color;但是寫program中綁定數據時卻用了glVertexAttrib3fv(這個只用於頂點着色器,如果把color定義在頂點着色器,然後通過varying傳遞也可以)
    修正爲 GLES20.glUniform3fv(uColor,1,ImageBgData.COOL_FILTER_COLOR_DATA,0);即可正常切換預覽

五、資料

[Android視頻編輯器(四)通過OpenGL給視頻增加不同濾鏡效果]
[專欄:Android圖像處理之實時濾鏡]
[專欄:圖像處理]
[Android Camera使用OpenGL ES 2.0和GLSurfaceView對預覽進行實時二次處理(黑白濾鏡)]

六、收穫

  1. 瞭解OES概念
  2. 瞭解FBO、VBO、PBO概念
  3. 通過實踐實時濾鏡熟悉流程

感謝你的閱讀

下一篇我們開始進入粒子系統,逐步實現網易雲鯨雲特效。歡迎關注公衆號“音視頻開發之旅”,一起學習成長。

歡迎交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章