前言
一年之前做過一些即時(shí)通信視頻相關(guān)的工作匿情,主要是做視頻渲染這一部分的工作倍靡,由于2016畢業(yè)來到了華為堕澄,華為對研究生的安排就是“哪里需要去哪里”膜蛔,和你專業(yè)和擅長的沒有太大的關(guān)系瀑志,所以一直在適應(yīng)當(dāng)下的工作涩搓,現(xiàn)在基本上可以勝任現(xiàn)在的工作,可以抽出一些時(shí)間來總結(jié)一下之前了解過的OpenGL相關(guān)知識劈猪。
第一章 相關(guān)知識介紹
在介紹具體的功能之前昧甘,先對一些主要的類和方法進(jìn)行一些介紹,這樣可以更好的理解整個(gè)程序
1.1 GLSurfaceView
在谷歌的官方文檔中是這樣解釋GLSurfaceView的:
An implementation of SurfaceView that uses the dedicated surface for displaying OpenGL rendering.
大意是GLSurfaceView是一個(gè)繼承了SurfaceView類战得,它是專門用來顯示OpenGL的渲染充边。通俗的來說,GLSurfaceView可以用來顯示視頻常侦、圖像和3D模型等視圖浇冰,在接下來的章節(jié)中主要是使用它來顯示Camera視頻數(shù)據(jù),大家可能會有一些問題聋亡,SurfaceView也可用來預(yù)覽Camera肘习,那么這兩者有什么區(qū)別嗎?GLSurfaceView能夠真正做到讓Camera的數(shù)據(jù)和顯示分離坡倔,我們就可以在此基礎(chǔ)上對視頻數(shù)據(jù)做一些處理漂佩,例如美圖,增加特效等罪塔。
1.2 GLSurfaceView.Renderer
如果說GLSurfaceView是畫布投蝉,那么僅僅有一張白紙是沒用的,我們還需要一支畫筆征堪,Renderer的功能就是這里說的畫筆墓拜。Renderer是一個(gè)接口,主要包含3個(gè)抽象的函數(shù):onSurfaceCreated
请契、onDrawFrame
咳榜、onSurfaceChanged
夏醉,從名字就可以看出,分別是在SurfaceView創(chuàng)建涌韩、視圖大小發(fā)生改變和繪制圖形時(shí)調(diào)用畔柔。
1.3 Camera
從Android 5.0開始(API Level 21),可以完全控制安卓設(shè)別相機(jī)的新API Camera2(android.hardware.Camera2)
被引進(jìn)來了臣樱。雖然新的Camera2不管在功能上還是友好度上都強(qiáng)于舊的Camera靶擦,但是我們這里還是使用的舊的Camera扶檐,由于新的Camera2暫時(shí)還沒有找到可以獲取視頻幀的接口藏古,因?yàn)楹竺婵夏軙anmera視頻幀做一些處理,所以這里暫時(shí)還是使用舊的Camera不瓶。
第二章 開始繪制
2.1 CameraGLSurfaceView
public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener {
private Context mContext;
private SurfaceTexture mSurface;
private int mTextureID = -1;
private DirectDrawer mDirectDrawer;
public CameraGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// 設(shè)置OpenGl ES的版本為2.0
setEGLContextClientVersion(2);
// 設(shè)置與當(dāng)前GLSurfaceView綁定的Renderer
setRenderer(this);
// 設(shè)置渲染的模式
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
LOG.logI("onSurfaceCreated...");
mTextureID = GlUtil.createTextureID();
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
mDirectDrawer = new DirectDrawer(mTextureID);
CameraCapture.get().openBackCamera();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
LOG.logI("onSurfaceChanged...");
// 設(shè)置OpenGL場景的大小,(0,0)表示窗口內(nèi)部視口的左下角棚放,(w,h)指定了視口的大小
GLES20.glViewport(0, 0, width, height);
if (!CameraCapture.get().isPreviewing()) {
CameraCapture.get().doStartPreview(mSurface);
}
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
LOG.logI("onDrawFrame...");
// 設(shè)置白色為清屏
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// 清除屏幕和深度緩存
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// 更新紋理
mSurface.updateTexImage();
mDirectDrawer.draw();
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
CameraCapture.get().doStopCamera();
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// TODO Auto-generated method stub
LOG.logI("onFrameAvailable...");
this.requestRender();
}
}
這個(gè)類主要做了以下幾件事情:
- 實(shí)現(xiàn)Renderer這個(gè)接口枚粘,并且實(shí)現(xiàn)GLSurfaceView的初始化。在
CameraGLSurfaceView
的構(gòu)造函數(shù)中設(shè)置了GLSurfaceView的版本:setEGLContextClientVersion(2)
飘蚯,如果沒有這個(gè)設(shè)置馍迄,GLSurfaceView是什么也繪制不出來的,因?yàn)锳ndroid支持OpenGL ES1.1局骤、2.0以及3.+等版本攀圈,而且版本間的差別很大,不聲明版本號峦甩,GLSurfaceView是不知道使用哪個(gè)版本進(jìn)行渲染赘来;設(shè)置Renderer與當(dāng)前的View綁定,然后再設(shè)置渲染的模式為RENDERMODE_WHEN_DIRTY
凯傲。渲染模式的設(shè)置也很關(guān)鍵犬辰,渲染模式有兩種:RENDERMODE_WHEN_DIRTY
和RENDERMODE_CONTINUOUSLY
。DIRYT的含義是只有當(dāng)被通知的時(shí)候才會去渲染視圖泣洞,而CONTINUOUSLY的含義是視頻會一直連續(xù)的渲染忧风。 - 在
onSurfaceCreated()
函數(shù)中默色,創(chuàng)建一個(gè)渲染的紋理球凰,這個(gè)紋理就是用來顯示Camera的圖像,所以需要新創(chuàng)建的SurfaceTexture
綁定在一起腿宰,而SurfaceTexture
是作為渲染的載體呕诉,另一方面需要和DirectDrawer
綁定在一起,DirectDrawer
是用來繪制圖像的吃度,下面會具體介紹甩挫。最后是初始化Camera。 - 因?yàn)樵诔跏蓟臅r(shí)候這是了渲染的模式為
RENDERMODE_WHEN_DIRTY
椿每,所以我們就通知GLSurfaceView什么時(shí)候需要渲染圖像伊者,而接口SurfaceTexture.OnFrameAvailableListener
完成這項(xiàng)工作英遭,函數(shù)onFrameAvailable()
在有新數(shù)據(jù)到來時(shí),會被調(diào)用亦渗,在其中調(diào)用requestRender()挖诸,就可以完成新數(shù)據(jù)的渲染。 - 在
onSurfaceChanged()
函數(shù)中法精,設(shè)置了OpenGL窗口的大小,(0,0)表示窗口內(nèi)部視口的左下角多律,(w,h)指定了視口的大小搂蜓;打開Camera的預(yù)覽狼荞。 - 最后,在
onDrawFrame()
函數(shù)中繪制更新的紋理帮碰。
2.2 DirectDrawer
這個(gè)類非常重要相味,負(fù)責(zé)將SurfaceTexture(紋理的句柄)內(nèi)容繪制到屏幕上。
public class DirectDrawer {
private FloatBuffer vertexBuffer, mTextureCoordsBuffer;
private ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mTextureCoordHandle;
private int mMVPMatrixHandle;
private short drawOrder[] = {0, 2, 1, 0, 3, 2}; // order to draw vertices
// number of coordinates per vertex in this array
private final int COORDS_PER_VERTEX = 2;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
private float mVertices[] = new float[8];
private float mTextureCoords[] = new float[8];
private float mTextHeightRatio = 0.1f;
private int texture;
public float[] mMVP = new float[16];
public void resetMatrix() {
mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, mMVP);
}
public DirectDrawer(int texture) {
String vertextShader = TextResourceReader.readTextFileFromResource(MyApplication.getContext()
, R.raw.video_vertex_shader);
String fragmentShader = TextResourceReader.readTextFileFromResource(MyApplication.getContext()
, R.raw.video_normal_fragment_shader);
mProgram = GlUtil.createProgram(vertextShader, fragmentShader);
if (mProgram == 0) {
throw new RuntimeException("Unable to create program");
}
//get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
GlUtil.checkLocation(mPositionHandle, "vPosition");
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
GlUtil.checkLocation(mTextureCoordHandle, "inputTextureCoordinate");
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
GlUtil.checkLocation(mMVPMatrixHandle, "uMVPMatrix");
this.texture = texture;
// initialize vertex byte buffer for shape coordinates
updateVertices();
setTexCoords();
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, mMVP);
}
public void draw() {
GLES20.glUseProgram(mProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
// get handle to vertex shader's vPosition member
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the <insert shape here> coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, mTextureCoordsBuffer);
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVP, 0);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
}
public static void mat4f_LoadOrtho(float left, float right, float bottom, float top, float near, float far, float[] mout) {
float r_l = right - left;
float t_b = top - bottom;
float f_n = far - near;
float tx = -(right + left) / (right - left);
float ty = -(top + bottom) / (top - bottom);
float tz = -(far + near) / (far - near);
mout[0] = 2.0f / r_l;
mout[1] = 0.0f;
mout[2] = 0.0f;
mout[3] = 0.0f;
mout[4] = 0.0f;
mout[5] = 2.0f / t_b;
mout[6] = 0.0f;
mout[7] = 0.0f;
mout[8] = 0.0f;
mout[9] = 0.0f;
mout[10] = -2.0f / f_n;
mout[11] = 0.0f;
mout[12] = tx;
mout[13] = ty;
mout[14] = tz;
mout[15] = 1.0f;
}
public void updateVertices() {
final float w = 1.0f;
final float h = 1.0f;
mVertices[0] = -w;
mVertices[1] = h;
mVertices[2] = -w;
mVertices[3] = -h;
mVertices[4] = w;
mVertices[5] = -h;
mVertices[6] = w;
mVertices[7] = h;
vertexBuffer = ByteBuffer.allocateDirect(mVertices.length * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer().put(mVertices);
vertexBuffer.position(0);
}
public void setTexCoords() {
mTextureCoords[0] = 0;
mTextureCoords[1] = 1 - mTextHeightRatio;
mTextureCoords[2] = 1;
mTextureCoords[3] = 1 - mTextHeightRatio;
mTextureCoords[4] = 1;
mTextureCoords[5] = 0 + mTextHeightRatio;
mTextureCoords[6] = 0;
mTextureCoords[7] = 0 + mTextHeightRatio;
mTextureCoordsBuffer = ByteBuffer.allocateDirect(mTextureCoords.length * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer().put(mTextureCoords);
mTextureCoordsBuffer.position(0);
}
}
這個(gè)類的主要功能就是繪制圖像收毫。
(1) 定義Vertex Shader(頂點(diǎn)著色器攻走,用來繪制圖形的形狀)、Fragment Shader(片段著色器此再,用來繪制圖形的顏色或者紋理)和Program(OpenGL ES對象昔搂,包含了用來繪制一個(gè)或者多個(gè)形狀的shader),然后接下來都是圍繞著這三個(gè)變量输拇,最后通過調(diào)用OpenGL方法進(jìn)行繪制摘符。具體的過程可以參考前面的博客 使用OpenGL ES顯示圖形
(2) 既然我們需要預(yù)覽Camera的視頻數(shù)據(jù),那么我們可以知道現(xiàn)實(shí)的區(qū)域的形狀大部分都是四邊形策吠,但是在OpenGL中只有提供了繪制三角形的方法逛裤,我們就需要將兩個(gè)三角形拼接成一個(gè)正方形,所以需要定義一個(gè)大小為8的數(shù)組猴抹,如下面代碼所示:
static float squareCoords[] = {
-1.0f, 1.0f, // 左上點(diǎn)
-1.0f, -1.0f, // 左下點(diǎn)
1.0f, -1.0f, // 右下點(diǎn)
1.0f, 1.0f, // 有上點(diǎn)
};
此時(shí)带族,我們就有了一個(gè)四邊形的4個(gè)點(diǎn)的數(shù)據(jù)了。但是蟀给,OpenGL并不是對數(shù)組的數(shù)據(jù)直接進(jìn)行操作的蝙砌,而是在直接內(nèi)存中,即操作的數(shù)據(jù)需要保存到NIO里面的Buffer對象中跋理。而我們上面生命的float[]對象保存在數(shù)組中择克,因此我們需要將float[]對象轉(zhuǎn)換為Java.nio.Buffer對象,代碼如下:
public void updateVertices() {
final float w = 1.0f;
final float h = 1.0f;
mVertices[0] = -w;
mVertices[1] = h;
mVertices[2] = -w;
mVertices[3] = -h;
mVertices[4] = w;
mVertices[5] = -h;
mVertices[6] = w;
mVertices[7] = h;
vertexBuffer = ByteBuffer.allocateDirect(mVertices.length * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer().put(mVertices);
vertexBuffer.position(0);
}
注意前普,ByteBuffer和FloatBuffer以及IntBuffer都是繼承自抽象類java.nio.Buffer肚邢。
另外,OpenGL在底層的實(shí)現(xiàn)是C語言拭卿,與Java默認(rèn)的數(shù)據(jù)存儲字節(jié)順序可能不同骡湖,即大端小端問題贱纠。因此,為了保險(xiǎn)起見响蕴,在將數(shù)據(jù)傳遞給OpenGL之前并巍,我們需要指明使用本機(jī)的存儲順序。
此時(shí)换途,我們順利地將float[]轉(zhuǎn)為了FloatBuffer懊渡,后面繪制三角形的時(shí)候,直接通過成員變量mTriangleBuffer即可军拟。
(3) 最后就是將準(zhǔn)備好的數(shù)據(jù)繪制到屏幕上剃执,OpenGL 提供了兩個(gè)繪制的方法glDrawArrays(int mode, int first, int count)
和glDrawElements(int mode,int count, int type, Buffer indices)
兩個(gè)方法,在這里我們使用的第二種繪制的方法懈息,關(guān)于mode有幾種模式供我們選擇:
-
GL_POINTS
:繪制獨(dú)立的點(diǎn)到屏幕
-
GL_LINE_STRIP
:連續(xù)的連線肾档,第n個(gè)頂點(diǎn)與第n-1個(gè)頂點(diǎn)繪制一條直線
-
GL_LINE_LOOP
:與上一個(gè)相同,但是需要首尾相聯(lián)接
-
GL_LINES
:形成對的獨(dú)立的線段
-
GL_TRIANGLE_STRIP
:繪制一系列的三角形辫继,先是頂點(diǎn)v0怒见,v1,v2姑宽,然后是v2遣耍,v1,v3(注意規(guī)律)炮车,然后v2舵变,v3,v4等瘦穆。該規(guī)律確保所有的三角形都以相同的方向繪制
-
GL_TRIANGLE_FAN
和GL_TRANGLE_STRIP
類似纪隙,但其縣繪制v0,v1扛或,v2绵咱,再是v0,v2熙兔,v3悲伶,然后v0,v3黔姜,v4等拢切。
(4) 需要注意的是蒂萎,在這個(gè)類中秆吵,定義了mMVP這個(gè)數(shù)組,這個(gè)數(shù)組的功能是對視頻幀數(shù)據(jù)進(jìn)行轉(zhuǎn)換的五慈,例如旋轉(zhuǎn)圖像等纳寂。
第三章 總結(jié)
到此為止主穗,使用GLSurfaceView預(yù)覽Camera的介紹就完了,這篇文章毙芜,僅僅介紹了CameraGLSurfaceView
和DirectDrawer
這兩個(gè)類忽媒,但是如何對Camera進(jìn)行操作的并沒有介紹,這不是本文的重點(diǎn)腋粥,所以就省略了晦雨。接下來還會介紹一些有關(guān)GLSurfaceView的文章。