Android OpenGL使用GLSurfaceView预览视频
Android OpenGL使用GLSurfaceView預(yù)覽視頻
第一章 相關(guān)知識(shí)介紹
在介紹具體的功能之前,先對一些主要的類和方法進(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ù),大家可能會(huì)有一些問題,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)楹竺婵夏軙?huì)對Canmera視頻幀做一些處理,所以這里暫時(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.0setEGLContextClientVersion(2);// 設(shè)置與當(dāng)前GLSurfaceView綁定的RenderersetRenderer(this);// 設(shè)置渲染的模式setRenderMode(RENDERMODE_WHEN_DIRTY);}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {// TODO Auto-generated method stubLOG.logI("onSurfaceCreated...");mTextureID = GlUtil.createTextureID();mSurface = new SurfaceTexture(mTextureID);mSurface.setOnFrameAvailableListener(this);mDirectDrawer = new DirectDrawer(mTextureID);CameraCapture.get().openBackCamera();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {// TODO Auto-generated method stubLOG.logI("onSurfaceChanged...");// 設(shè)置OpenGL場景的大小,(0,0)表示窗口內(nèi)部視口的左下角,(w,h)指定了視口的大小GLES20.glViewport(0, 0, width, height);if (!CameraCapture.get().isPreviewing()) {CameraCapture.get().doStartPreview(mSurface);}}@Overridepublic void onDrawFrame(GL10 gl) {// TODO Auto-generated method stubLOG.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();}@Overridepublic void onPause() {// TODO Auto-generated method stubsuper.onPause();CameraCapture.get().doStopCamera();}@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {// TODO Auto-generated method stubLOG.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.+等版本,而且版本間的差別很大,不聲明版本號(hào),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í)候才會(huì)去渲染視圖,而CONTINUOUSLY的含義是視頻會(huì)一直連續(xù)的渲染。 - 在
onSurfaceCreated()函數(shù)中,創(chuàng)建一個(gè)渲染的紋理,這個(gè)紋理就是用來顯示Camera的圖像,所以需要新創(chuàng)建的SurfaceTexture綁定在一起,而SurfaceTexture是作為渲染的載體,另一方面需要和DirectDrawer綁定在一起,DirectDrawer是用來繪制圖像的,下面會(huì)具體介紹。最后是初始化Camera。 - 因?yàn)樵诔跏蓟臅r(shí)候這是了渲染的模式為
RENDERMODE_WHEN_DIRTY,所以我們就通知GLSurfaceView什么時(shí)候需要渲染圖像,而接口SurfaceTexture.OnFrameAvailableListener完成這項(xiàng)工作,函數(shù)onFrameAvailable()在有新數(shù)據(jù)到來時(shí),會(huì)被調(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 arrayprivate final int COORDS_PER_VERTEX = 2;private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertexprivate 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 membermPositionHandle = 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 coordinatesupdateVertices();setTexCoords();// initialize byte buffer for the draw listByteBuffer 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 verticesGLES20.glEnableVertexAttribArray(mPositionHandle);// Prepare the <insert shape here> coordinate dataGLES20.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 transformationGLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVP, 0);GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);// Disable vertex arrayGLES20.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)}; - 我們就有了一個(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ù)存儲(chǔ)字節(jié)順序可能不同,即大端小端問題。因此,為了保險(xiǎn)起見,在將數(shù)據(jù)傳遞給OpenGL之前,我們需要指明使用本機(jī)的存儲(chǔ)順序。?
此時(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),所以就省略了。接下來還會(huì)介紹一些有關(guān)GLSurfaceView的文章。
Android OpenGL渲染雙視頻
下載代碼
特別聲明文章轉(zhuǎn)載自:https://blog.csdn.net/a296777513/article/details/63685658
總結(jié)
以上是生活随笔為你收集整理的Android OpenGL使用GLSurfaceView预览视频的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 源码分析-GLSurfaceView的内
- 下一篇: 最好听的女孩的名字