做好的Demo截圖
前言
講了這么多拖刃,可能有人要問了,播放視頻用個(gè)android封裝的VideoView或者用MediaPlayer+SurfaceView來進(jìn)行播放視頻不就得了嗎扎筒,干嘛還要整這么麻煩综慎。OK涣仿,為了回答這個(gè)問題勤庐,我們先看看OpenGL ES干什么的示惊,它是OpenGL三維圖形API的子集,圖形硬件的一種軟件接口愉镰,針對(duì)手機(jī)米罚、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì)。我想如果是做游戲類開發(fā)的肯定對(duì)一些圖形庫(kù)不會(huì)陌生丈探,其實(shí)很多游戲引擎內(nèi)部都是封裝的像OpenGL录择,DirectX 3D之類的圖形庫(kù),然后開發(fā)者就可以通過這些圖形庫(kù)碗降,來開發(fā)很多好玩的東西隘竭,如游戲,動(dòng)畫等等讼渊,那么我現(xiàn)在用Opengl es來繪制視頻是不是也可以定制很多有意思的東西动看,比如開發(fā)一個(gè)左右分屏視頻播放器,然后在虛擬現(xiàn)實(shí)(VR)頭盔上來觀看2d的視頻爪幻,如果用opengl去繪制菱皆,那簡(jiǎn)直分分中搞定,因?yàn)檫@里挨稿,每一幀的視頻在opengl 看來只是一張紋理貼圖而已仇轻,那么我想把這個(gè)貼圖貼在哪里就貼在哪里∧谈剩總之篷店,用opengl可以開發(fā)出很多有意思的二維,三維的圖形應(yīng)用出來臭家。
正文
說了這么多疲陕,咱們開始吧吭产,
/***
* 在這個(gè)類里面對(duì)視頻紋理進(jìn)行繪制工作,繼承了 {@link TextureSurfaceRenderer},
* 并實(shí)現(xiàn)了{(lán)@link SurfaceTexture.OnFrameAvailableListener}
*/
public class VideoTextureSurfaceRenderer extends TextureSurfaceRenderer implements
SurfaceTexture.OnFrameAvailableListener
{
public static final String TAG = VideoTextureSurfaceRenderer.class.getSimpleName();
/**繪制的區(qū)域尺寸*/
private static float squareSize = 1.0f;
private static float squareCoords[] = {
-squareSize, squareSize, 0.0f, // top left
-squareSize, -squareSize, 0.0f, // bottom left
squareSize, -squareSize, 0.0f, // bottom right
squareSize, squareSize, 0.0f // top right
};
/**繪制次序*/
private static short drawOrder[] = {
0, 1, 2,
0, 2, 3
};
/**
* 用來緩存紋理坐標(biāo)鸭轮,因?yàn)榧y理都是要在后臺(tái)被繪制好臣淤,然
* 后不斷的替換最前面顯示的紋理圖像
*/
private FloatBuffer textureBuffer;
/**紋理坐標(biāo)*/
private float textureCoords[] = {
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f
};
/**生成的真實(shí)紋理數(shù)組*/
private int[] textures = new int[1];
/**著色器腳本程序的handle(句柄)*/
private int shaderProgram;
/**squareCoords的的頂點(diǎn)緩存*/
private FloatBuffer vertexBuffer;
/**繪制次序的緩存*/
private ShortBuffer drawOrderBuffer;
/**矩陣來變換紋理坐標(biāo),(具體含義下面再解釋)*/
private float[] videoTextureTransform;
/**當(dāng)前的視頻幀是否可以得到*/
private boolean frameAvailable = false;
private Context context;
private SurfaceTexture videoTexture窃爷; // 從視頻流捕獲幀作為Opengl ES 的Texture
/**
* @param texture 從TextureView獲取到的SurfaceTexture邑蒋,目的是為了配置EGL 的native window
*/
public VideoTextureSurfaceRenderer(Context context, SurfaceTexture texture, int width, int height)
{
super(texture, width, height); //先調(diào)用父類去做EGL初始化工作
this.context = context;
videoTextureTransform = new float[16];
}
// 代碼略
}
對(duì)上面的代碼再稍稍解釋一下吧,在繪制之前按厘,我們首先選定一塊區(qū)域來讓圖像就在這塊區(qū)域來繪制医吊,則有如下定義:
private static float squareSize = 1.0f;
private static float squareCoords[] = {
-squareSize, squareSize, 0.0f, // top left
-squareSize, -squareSize, 0.0f, // bottom left
squareSize, -squareSize, 0.0f, // bottom right
squareSize, squareSize, 0.0f // top right
};
上幅圖來解釋解釋:
當(dāng)
squareSize=1.0
時(shí),square的面積就是整個(gè)手機(jī)屏幕逮京,若squareSize=0.5f
則每個(gè)邊長(zhǎng)都為屏幕的一半卿堂。數(shù)組的坐標(biāo)順序?yàn)椋鹤笊?>左下->右下->右上。
然后接著定義一個(gè)紋理坐標(biāo)數(shù)組:
private float textureCoords[] = {
0.0f, 1.0f, 0.0f, 1.0f, //左上
0.0f, 0.0f, 0.0f, 1.0f, //左下
1.0f, 0.0f, 0.0f, 1.0f, //右下
1.0f, 1.0f, 0.0f, 1.0f //右上
};
接著上圖:
如上圖懒棉,這就是2D OpenGL ES紋理坐標(biāo)草描,它由四個(gè)列向量(s, t, 0, 1)組成,其中s, t ∈[0, 1]。
不知道大家有沒有發(fā)現(xiàn)策严,繪制區(qū)域(quareCoords[])的頂點(diǎn)數(shù)組坐標(biāo)順序跟紋理數(shù)組坐標(biāo)的順序都是從 左上方開始->到右上方結(jié)束穗慕,為什么要這樣做呢? 其實(shí)目的就是為了統(tǒng)一方向妻导,因?yàn)槲覀冎朗謾C(jī)屏幕的坐標(biāo)是左上角為原點(diǎn)(0.0, 0.0)逛绵,所以為了以后不必要的轉(zhuǎn)換工作,最好將繪制的順序的都統(tǒng)一起來倔韭。
然后則看看紋理的繪制次序drawOrder[] = {0,1,2, 0, 2, 3}
; 這又是個(gè)什么次序呢术浪? 好,再來個(gè)圖看看
其實(shí)寿酌,opengl es 在繪制一個(gè)多邊形時(shí)胰苏,都是用一個(gè)基本的圖元即三角形拼湊出來的,比如一個(gè)矩形它可以用(0->1->2)和(0->2->3)的次序份名,通過兩個(gè)三角形給拼湊出來的碟联,當(dāng)然也可是其它的組合比如(1,3僵腺,2)(1鲤孵,3,0)等等辰如,總之得用兩個(gè)三角形拼湊成一個(gè)矩形普监,不過建議還是都按找同一鐘次序,比如都是按照順時(shí)針或都按照逆時(shí)針來拼湊。
OK凯正,接下來再貼出省略的代碼:
/**
* 重寫父類方法毙玻,初始化組件
*/
@Override
protected void initGLComponents()
{
setupVertexBuffer();
setupTexture();
loadShaders();
}
/***
* 設(shè)置頂點(diǎn)緩存
*/
private void setupVertexBuffer()
{
/** Draw Order buffer*/
ByteBuffer orderByteBuffer = ByteBuffer.allocateDirect(drawOrder. length * 2);
orderByteBuffer.order(ByteOrder.nativeOrder()); //Modifies this buffer's byte order
drawOrderBuffer = orderByteBuffer.asShortBuffer(); //創(chuàng)建此緩沖區(qū)的視圖,作為一個(gè)short緩沖區(qū).
drawOrderBuffer.put(drawOrder);
drawOrderBuffer.position(0); //下一個(gè)要被讀或?qū)懙脑氐乃饕壬ⅲ瑥? 開始
// Initialize the texture holder
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
}
ByteBuffer.allocateDirect(drawOrder. length * 2)
表示的是直接從系統(tǒng)中分配大小為 (drawOrder. length * 2)的內(nèi)存桑滩,2 代表一個(gè)short型占兩個(gè)字節(jié),(int占4個(gè)字節(jié))允睹。
/**接著初始化紋理*/
private void setupTexture()
{
ByteBuffer texturebb = ByteBuffer.allocateDirect(textureCoords.length * 4);
texturebb.order(ByteOrder.nativeOrder());
textureBuffer = texturebb.asFloatBuffer();
textureBuffer.put(textureCoords);
textureBuffer.position(0);
// 啟用紋理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//1表示只需要一個(gè)紋理索引
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]);
videoTexture = new SurfaceTexture(textures[0]);
videoTexture.setOnFrameAvailableListener(this);
}
關(guān)于glBindTexure(int target运准, int texture)
中的參數(shù)target,表示指定這是一張什么類型的紋理缭受,在此是GLES11Ext.GL_BLEND_EQUATION_RGB_OES
胁澳,也可以是常用的2D紋理如GLES20.GL_TEXTURE_2D
等;第二個(gè)參數(shù)texture
米者,在程序第一次使用這個(gè)參數(shù)時(shí)韭畸,這個(gè)函數(shù)會(huì)創(chuàng)建一個(gè)新的對(duì)象,并把這個(gè)對(duì)象分配給它蔓搞,之后這個(gè)texture就成了一個(gè)活動(dòng)的紋理對(duì)象胰丁。如果texture=0
則OpenGL就停止使用紋理對(duì)象,并返回到初始的默認(rèn)紋理败明。
/**加載頂點(diǎn)與片段著色器*/
private void loadShaders()
{
final String vertexShader = RawResourceReader.readTextFileFromRawResource(context, R.raw.vetext_sharder);
final String fragmentShader = RawResourceReader.readTextFileFromRawResource(context, R.raw.fragment_sharder);
final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
shaderProgram = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[]{"texture","vPosition","vTexCoordinate","textureTransform"});
}
關(guān)于可編程著色器在計(jì)算機(jī)圖形領(lǐng)域本身就是個(gè)很大的主題隘马,已超出了本文的范圍太防。那么就稍稍解釋下妻顶,OpenGL著色語言(OpenGL shading Language),它是一種編程語言,用于創(chuàng)建可編程的著色器蜒车。在opengl es 2.0以前使用的是一種“固定功能管線“讳嘱,而2.0以后就是使用的著這鐘可“編程的管線“,這種管線又分為頂點(diǎn)處理管線與片段處理管線(涉及到OpenGL的渲染管線的一些機(jī)制酿愧,大家自行的查吧)沥潭。
我現(xiàn)在就說說這個(gè)GLSL著色器是怎么使用的吧:有關(guān)使用GLSL創(chuàng)建著色器流程如下圖(參照OpenGL 編程指南):
大家可以參照這幅圖來看看程序中shader的使用。
在本項(xiàng)目的res目錄下新建一個(gè)raw文件夾嬉挡,然后分別創(chuàng)建vertext_shader.glsl和fragment_sharder.glsl文件钝鸽,代碼如下:
vertext_sharder:
attribute vec4 vPosition; //頂點(diǎn)著色器輸入變量由attribute來聲明
attribute vec4 vTexCoordinate;
//uniform表示一個(gè)變量的值由應(yīng)用程序在著色器執(zhí)行之前指定,
//并且在圖元處理過程中不會(huì)發(fā)生任何變化庞钢。mat4表示一個(gè)4x4矩陣
uniform mat4 textureTransform;
varying vec2 v_TexCoordinate; //片段著色器輸入變量用arying來聲明
void main () {
v_TexCoordinate = (textureTransform * vTexCoordinate).xy;
gl_Position = vPosition;
}
fragment_shader
/**使用GL_OES_EGL_image_external擴(kuò)展處理拔恰,來增強(qiáng)GLSL*/
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES texture; //定義擴(kuò)展的的紋理取樣器amplerExternalOES
varying vec2 v_TexCoordinate;
void main () {
vec4 color = texture2D(texture, v_TexCoordinate);
gl_FragColor = color;
}
著色器語言非常的類似C語言,也是從main函數(shù)開始執(zhí)行的基括。其中的很多語法颜懊,變量等等,還是大家自行的查查,這不是幾句能說明白的河爹。著色器的創(chuàng)建及編譯過程的代碼都在項(xiàng)目里的一個(gè)util包下的三個(gè)工具類匠璧,RawRourceReader,ShaderHelper,TextureHelper類中,我就不再貼出來了咸这,有興趣大家可以fork或clone下來看看夷恍。
ok,終于初始化完了媳维,太不容易了裁厅,冏。
好吧侨艾,繪制工作開始跑起來执虹。
@Override
protected boolean draw()
{
synchronized (this)
{
if (frameAvailable)
{
videoTexture .updateTexImage();
videoTexture .getTransformMatrix(videoTextureTransform);
frameAvailable = false;
}
else
{
return false;
}
}
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glViewport(0, 0, width, height);
this.drawTexture();
return true;
}
當(dāng)視頻的幀可得到時(shí),調(diào)用videoTexture .updateTexImage()
方法唠梨,這個(gè)方法的官網(wǎng)解釋如下:
<font color=#008000 >Update the texture image to the most recent frame from the image stream. This may only be called while the OpenGL ES context that owns the texture is current on the calling thread. It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.</font>
大意是袋励,從的圖像流中更新紋理圖像到最近的幀中。這個(gè)函數(shù)僅僅當(dāng)擁有這個(gè)紋理的Opengl ES上下文當(dāng)前正處在繪制線程時(shí)被調(diào)用当叭。它將隱式的綁定到這個(gè)擴(kuò)展的GL_TEXTURE_EXTERNAL_OES 目標(biāo)紋理(為什么上面的片段著色代碼中要擴(kuò)展這個(gè)OES茬故,可能就是應(yīng)為這個(gè)吧)。
而videoTexture .getTransformMatrix(videoTextureTransform)
這又是什么意思呢蚁鳖?當(dāng)對(duì)紋理用amplerExternalOES
采樣器采樣時(shí)磺芭,應(yīng)該首先使用getTransformMatrix(float[])查詢得到的矩陣來變換紋理坐標(biāo),每次調(diào)用updateTexImage()的時(shí)候醉箕,可能會(huì)導(dǎo)致變換矩陣發(fā)生變化钾腺,因此在紋理圖像更新時(shí)需要重新查詢,該矩陣將傳統(tǒng)的2D OpenGL ES紋理坐標(biāo)列向量(s,t,0,1)讥裤,其中s放棒,t∈[0,1],變換為紋理中對(duì)應(yīng)的采樣位置己英。該變換補(bǔ)償了圖像流中任何可能導(dǎo)致與傳統(tǒng)OpenGL ES紋理有差異的屬性间螟。例如,從圖像的左下角開始采樣损肛,可以通過使用查詢得到的矩陣來變換列向量(0,0,0,1)厢破,而從右上角采樣可以通過變換(1,1,0,1)來得到。
關(guān)于 GLES20.glViewport(int x, int y, int width, int height) 治拿,也是個(gè)比較重要的函數(shù)摩泪,這個(gè)函數(shù)大家網(wǎng)上查查。
ok忍啤,接下來到了真正的繪制紋理的時(shí)候了加勤。代碼如下:
private void drawTexture() {
// Draw texture
GLES20.glUseProgram(shaderProgram); //繪制時(shí)使用著色程序
int textureParamHandle = GLES20.glGetUniformLocation(shaderProgram, "texture"); //返回一個(gè)于著色器程序中變量名為"texture"相關(guān)聯(lián)的索引
int textureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinate");
int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
int textureTransformHandle = GLES20.glGetUniformLocation(shaderProgram, "textureTransform");
//在用VertexAttribArray前必須先激活它
GLES20.glEnableVertexAttribArray(positionHandle);
//指定positionHandle的數(shù)據(jù)值可以在什么地方訪問仙辟。 vertexBuffer在內(nèi)部(NDK)是個(gè)指針,指向數(shù)組的第一組值的內(nèi)存
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glBindTexture(GLES20.GL_TEXTURE0, textures[0]);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//指定一個(gè)當(dāng)前的textureParamHandle對(duì)象為一個(gè)全局的uniform 變量
GLES20.glUniform1i(textureParamHandle, 0);
GLES20.glEnableVertexAttribArray(textureCoordinateHandle);
GLES20.glVertexAttribPointer(textureCoordinateHandle, 4, GLES20.GL_FLOAT, false, 0, textureBuffer);
GLES20.glUniformMatrix4fv(textureTransformHandle, 1, false, videoTextureTransform, 0);
//GLES20.GL_TRIANGLES(以無數(shù)小三角行的模式)去繪制出這個(gè)紋理圖像
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawOrderBuffer);
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureCoordinateHandle);
}
好了所有代碼就分析到這里了鳄梅,前兩篇鏈接TextureView+SurfaceTexture+OpenGL ES來播放視頻(一) 叠国, TextureView+SurfaceTexture+OpenGL ES來播放視頻(二) , 項(xiàng)目地址在here