Android OpenGL使用GLSurfaceView預(yù)覽視頻

前言

一年之前做過一些即時(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_DIRTYRENDERMODE_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)到屏幕
    image
  • GL_LINE_STRIP:連續(xù)的連線肾档,第n個(gè)頂點(diǎn)與第n-1個(gè)頂點(diǎn)繪制一條直線
    image
  • GL_LINE_LOOP:與上一個(gè)相同,但是需要首尾相聯(lián)接
    image
  • GL_LINES:形成對的獨(dú)立的線段
    image
  • GL_TRIANGLE_STRIP:繪制一系列的三角形辫继,先是頂點(diǎn)v0怒见,v1,v2姑宽,然后是v2遣耍,v1,v3(注意規(guī)律)炮车,然后v2舵变,v3,v4等瘦穆。該規(guī)律確保所有的三角形都以相同的方向繪制
    image
  • GL_TRIANGLE_FANGL_TRANGLE_STRIP類似纪隙,但其縣繪制v0,v1扛或,v2绵咱,再是v0,v2熙兔,v3悲伶,然后v0,v3黔姜,v4等拢切。
    image

(4) 需要注意的是蒂萎,在這個(gè)類中秆吵,定義了mMVP這個(gè)數(shù)組,這個(gè)數(shù)組的功能是對視頻幀數(shù)據(jù)進(jìn)行轉(zhuǎn)換的五慈,例如旋轉(zhuǎn)圖像等纳寂。

第三章 總結(jié)

到此為止主穗,使用GLSurfaceView預(yù)覽Camera的介紹就完了,這篇文章毙芜,僅僅介紹了CameraGLSurfaceViewDirectDrawer這兩個(gè)類忽媒,但是如何對Camera進(jìn)行操作的并沒有介紹,這不是本文的重點(diǎn)腋粥,所以就省略了晦雨。接下來還會介紹一些有關(guān)GLSurfaceView的文章。

Android OpenGL渲染雙視頻

下載代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隘冲,一起剝皮案震驚了整個(gè)濱河市闹瞧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展辞,老刑警劉巖奥邮,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異罗珍,居然都是意外死亡洽腺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門覆旱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蘸朋,“玉大人,你說我怎么就攤上這事扣唱《纫海” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵画舌,是天一觀的道長堕担。 經(jīng)常有香客問我,道長曲聂,這世上最難降的妖魔是什么霹购? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮朋腋,結(jié)果婚禮上齐疙,老公的妹妹穿的比我還像新娘。我一直安慰自己旭咽,他們只是感情好贞奋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著穷绵,像睡著了一般轿塔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天勾缭,我揣著相機(jī)與錄音揍障,去河邊找鬼。 笑死俩由,一個(gè)胖子當(dāng)著我的面吹牛毒嫡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播幻梯,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼兜畸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碘梢?” 一聲冷哼從身側(cè)響起膳叨,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痘系,沒想到半個(gè)月后菲嘴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汰翠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年龄坪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片复唤。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡健田,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佛纫,到底是詐尸還是另有隱情妓局,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布呈宇,位于F島的核電站好爬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏甥啄。R本人自食惡果不足惜存炮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜈漓。 院中可真熱鬧穆桂,春花似錦、人聲如沸融虽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽有额。三九已至般又,卻和暖如春彼绷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倒源。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留句狼,地道東北人笋熬。 一個(gè)月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像腻菇,于是被迫代替她去往敵國和親胳螟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容