1. 程式人生 > >OpenGL ES總結(四)OpenGL 渲染視訊畫面

OpenGL ES總結(四)OpenGL 渲染視訊畫面

前一篇介紹是渲染一張圖片,今天是在MediaPlayer播放過程中,渲染視訊,看下Agenda:

  • 與渲染圖片的區別
  • 建立SurfaceTexture
  • 設定shader(著色器)
  • 建立紋理座標
    • UV座標介紹
    • UV紋理座標設定與貼圖規則是什麼?
  • 視訊播放

與渲染圖片的區別

渲染視訊畫面和渲染圖片不同,視訊需要不斷地重新整理,每當有新的一幀來時,我們都應該更新紋理,然後重新繪製。我們使用SurfaceTexture來設定MediaPlayer的setSurface.

建立一個紋理時,視訊的每一幀都可以看成圖片,也就是要不斷的更新紋理

主要的原因是,MediaPlayer的輸出往往不是RGB格式(一般是YUV),而GLSurfaceView需要RGB格式才能正常顯示,另外,獲取每一幀的資料並沒有那麼方便。
所以,我們建立的紋理應該稍有不同,SurfaceTexture在

《Android Multimedia框架總結(三)MediaPlayer中建立到setDataSource過程》就曾詳細介紹過,這裡貼出來:

SurfaceTexture: SurfaceTexture是從Android3.0(API 11)加入的一個新類。這個類跟SurfaceView很像,可以從video decode裡面獲取影象流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收影象流之後,不需要顯示出來。SurfaceTexture不需要顯示到螢幕上,因此我們可以用SurfaceTexture接收來自decode出來的影象流,然後從SurfaceTexture中取得影象幀的拷貝進行處理,處理完畢後再送給另一個SurfaceView用於顯示即可。

建立SurfaceTexture

public VideoTexture(Context context, int textureId) {
       mContext = context;
       mTexureId = textureId;
       mSurfaceTexture = new SurfaceTexture(textureId);
       mSurface = new Surface(mSurfaceTexture);
       // 初始化頂點座標與著色資料
       initVertexData();
       // 初始化著色器
       initShader();
}

在onDrawFrame中

mSurfaceTexture.updateTexImage();
checkGlError("onDrawFrame start");
mSurfaceTexture.getTransformMatrix(mSTMatrix);
GLES30.glUseProgram(mProgram);
checkGlError("glUseProgram");

GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(mTarget, mTexId);

其中mTarget是 GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GLES11Ext.GL_TEXTURE_EXTERNAL_OES的到底是做什麼的?
我們知道視訊解碼的輸出格式是YUV的(YUV420sp),那麼這個擴充套件紋理的作用就是實現YUV格式到RGB的自動轉化,我們就不需要再為此寫YUV轉RGB的程式碼。

我們用上面的textureId是構造時傳入的,

// 生成紋理ID
int[] textures = new int[2];
GLES20.glGenTextures(//
    2, // 產生的紋理id的數量
    textures, // 紋理id的陣列
    0 // 偏移量
);

mVideoTexture = new VideoTexture(mContext, textures[0]);

這個textureId是用於去建立一個SurfaceTexture,然後用surfaceTexture去建立一個Surface ,再把surface送給mediaPlayer進行輸出。
updateTexImage,就是來更新紋理,其中getTransformMatrix的目的,是讓新的紋理和紋理座標系能夠正確的對應,mSTMatrix的定義是和mMVPMatrix完全一樣的。

    private float[] mMVPMatrix = new float[16];
    private float[] mSTMatrix = new float[16];

設定shader(著色器):

    public void initShader() {
        // 載入頂點著色器的指令碼內容
        String mVertexShader = ShaderUtil.readRawTextFile(mContext, R.raw.vertex_oes);
        // 載入片元著色器的指令碼內容
        String mFragmentShader = ShaderUtil.readRawTextFile(mContext, R.raw.frag_oes);
        // 基於頂點著色器與片元著色器建立程式
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        // 獲取程式中頂點位置屬性引用id
        maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
        // 獲取程式中頂點紋理座標屬性引用id
        maTextureCoordHandle = GLES30.glGetAttribLocation(mProgram,
                "aTextureCoord");
        // 獲取程式中總變換矩陣引用id
        muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
        muSTMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uSTMatrix");

    }

其中vextex_oes.glsl

uniform mat4 uMVPMatrix; //總變換矩陣
uniform mat4 uSTMatrix;
attribute vec4 aPosition;  //頂點位置
attribute vec4 aTextureCoord;    //頂點紋理座標
varying vec2 vTextureCoord;  //用於傳遞給片元著色器的變數
void main()
{
   gl_Position = uMVPMatrix * aPosition; //根據總變換矩陣計算此次繪製此頂點位置
   vTextureCoord = (uSTMatrix * aTextureCoord).xy;//將接收的紋理座標傳遞給片元著色器
}                      

frag_oes.glsl

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTextureCoord; //接收從頂點著色器過來的引數
uniform samplerExternalOES sTexture;//紋理內容資料
void main()
{
   //給此片元從紋理中取樣出顏色值
   gl_FragColor = texture2D(sTexture, vTextureCoord); 
}              

建立紋理座標:

private final float[] mTriangleVerticesData = {
     // X, Y, Z, U, V
     -3.2f, -1.5f, 0, 0.f, 0.f, //
     3.2f, -1.5f, 0, 1.f, 0.f, //
     -3.2f, 1.5f, 0, 0.f, 1.f, //
     3.2f, 1.5f, 0, 1.f, 1.f, //
};

其中有X,Y,Z,還有U,V,那麼什麼是UV座標?

對於三維模型,有兩個最重要的座標系統,一是頂點的位置(X,Y,Z)座標,另一個就是UV座標。什麼是UV?簡單的說,就是貼圖影射到模型表面的依據。 完整的說,其實應該是UVW(因為XYZ已經用過了,所以另選三個字母表示)。U和V分別是圖片在顯示器水平、垂直方向上的座標,取值一般都是0~1,也就是(水平方向的第U個畫素/圖片寬度,垂直方向的第V個畫素/圖片高度)。那W呢?貼圖是二維的,何來三個座標?嗯嗯,W的方向垂直於顯示器表面,一般 用於程式貼圖或者某些3D貼圖技術(記住,確實有三維貼圖這種概念!),對於遊戲而言不常用到,所以一般我們就簡稱UV了。

所有的圖象檔案都是二維的一個平面。水平方向是U,垂直方向是V,通過這個平面的,二維的UV座標系。我們可以定點陣圖象上的任意一個象素。但是一個問題是如何把這個二維的平面貼到三維的NURBS表面和多邊形表面呢? 對於NURBS表面。由於他本身具有UV引數,儘管這個UV值是用來定位表面上的點的引數,但由於它也是二維的,所以很容易通過換算把表面上的點和平面圖象上的象素對應起來。所以把圖象貼帶NURBS是很直接的一件事。但是對於多變形模型來講,貼圖就變成一件麻煩的事了。所以多邊形為了貼圖就額外引進了一個UV座標,以便把多邊形的頂點和圖象檔案上的象素對應起來,這樣才能在多邊形表面上定位紋理貼圖。所以說多邊形的頂點除了具有三維的空間座標外。還具有二維的UV座標。

UV” 這裡是指u,v紋理貼圖座標的簡稱(它和空間模型的X, Y, Z軸是類似的). 它定義了圖片上每個點的位置的資訊. 這些點與3D模型是相互聯絡的, 以決定表面紋理貼圖的位置. UV就是將影象上每一個點精確對應到模型物體的表面. 在點與點之間的間隙位置由軟體進行影象光滑插值處理. 這就是所謂的UV貼圖.
那為什麼用UV座標而不是標準的投影座標呢? 通常給物體紋理貼圖最標準的方法就是以planar(平面),cylindrical(圓柱), spherical(球形),cubic(方盒)座標方式投影貼圖.
Planar projection(平面投影方式)是將影象沿x,y或z軸直接投影到物體. 這種方法使用於紙張, 佈告, 書的封面等 - 也就是表面平整的物體.平面投影的缺點是如果表面不平整, 或者物體邊緣彎曲, 就會產生如圖A的不理想接縫和變形. 避免這種情況需要建立帶有alpha通道的影象, 來掩蓋臨近的平面投影接縫, 而這會是非常煩瑣的工作. 所以不要對有較大厚度的物體和不平整的表面運用平面投影方式. 對於立方體可以在x, y方向分別進行平面投影, 但是要注意邊緣接縫的融合. 或者採用無縫連續的紋理, 並使用cubic投影方式. 多數軟體有圖片自動縮放功能, 使影象與表面吻合. 顯然, 如果你的影象與表面形狀不同, 自動縮放就會改變影象的比例以吻合表面. 這通常會產生不理想的效果, 所以製作貼圖前先測量你的物體尺寸.

uv紋理座標設定與貼圖規則是什麼?

當opengl對一個四方形進行貼圖時,會定義紋理貼圖座標,一串陣列。

當紋理對映啟動後繪圖時,你必須為OpenGL ES提供其他資料,即頂點陣列中各頂點的紋理座標。紋理座標定義了影象的哪一部分將被對映到多邊形。

視訊播放

try {
    mMediaPlayer = new MediaPlayer();
    mMediaPlayer.setSurface(mSurface);
    mMediaPlayer.setLooping(true);

    String url = Environment.getExternalStorageDirectory() + "/節目.mp4";
    Log.w("videoTexture", " datasource file url " + url);
    mMediaPlayer.setDataSource(mContext, Uri.parse(url));
    mMediaPlayer.prepare();
    mMediaPlayer.start();
} catch (Exception e) {
      e.printStackTrace();
}

最後效果圖:

這裡寫圖片描述

第一時間獲得部落格更新提醒,以及更多android乾貨,原始碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼或者長按識別二維碼,即可關注。

這裡寫圖片描述
如果你覺得好,隨手點贊,也是對筆者的肯定,也可以分享此公眾號給你更多的人,原創不易