OpenGL ES總結(四)OpenGL 渲染視訊畫面
前一篇介紹是渲染一張圖片,今天是在MediaPlayer播放過程中,渲染視訊,看下Agenda:
- 與渲染圖片的區別
- 建立SurfaceTexture
- 設定shader(著色器)
- 建立紋理座標
- UV座標介紹
- UV紋理座標設定與貼圖規則是什麼?
- 視訊播放
與渲染圖片的區別
渲染視訊畫面和渲染圖片不同,視訊需要不斷地重新整理,每當有新的一幀來時,我們都應該更新紋理,然後重新繪製。我們使用SurfaceTexture來設定MediaPlayer的setSurface.
建立一個紋理時,視訊的每一幀都可以看成圖片,也就是要不斷的更新紋理
主要的原因是,MediaPlayer的輸出往往不是RGB格式(一般是YUV),而GLSurfaceView需要RGB格式才能正常顯示,另外,獲取每一幀的資料並沒有那麼方便。
所以,我們建立的紋理應該稍有不同,SurfaceTexture在
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乾貨,原始碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼或者長按識別二維碼,即可關注。