安卓特效相機(二) EGL基礎(chǔ)

系列文章:

安卓特效相機(一) Camera2的使用
安卓特效相機(二) EGL基礎(chǔ)
安卓特效相機(三) OpenGL ES 特效渲染
安卓特效相機(四) 視頻錄制

上一篇文章已經(jīng)和大家講解了下Camera2的使用,能夠?qū)⑾鄼C捕捉到的畫面顯示到TextureView上,接下來就是要對這個畫面進行特效處理了晌缘。這里我們選用OpenGL ES去實現(xiàn)特效。

但是在講如何使用OpenGL ES去實現(xiàn)特效之前我們要先講下EGL

EGL

OpenGL作為一個被廣泛使用的跨平臺圖形庫,相信大家或多或少都有聽說。安卓上使用的是它針對手機、PDA和游戲主機等嵌入式設(shè)備而設(shè)計的子集OpenGL ES

為了實現(xiàn)跨平臺,OpenGL本體只包含了圖形運算方面的接口,也可以大致理解為只包含操作GPU的API。而作為和平臺強相關(guān)的本地窗口系統(tǒng)交互部分由于每個平臺都不一樣,而且沒有辦法抽離出統(tǒng)一的接口,所以不能包含進OpenGL本體里面系忙。例如Android上我們可以指定OpenGL繪制在哪個Surface上,但是IOS上并沒有這個東西,所以不能在OpenGL里面開一個接口接收Surface這個參數(shù)。

那OpenGL運行到實際的系統(tǒng)上的時候又是怎么和具體的系統(tǒng)做交互的呢?計算機領(lǐng)域有一句話:"計算機科學領(lǐng)域的任何問題都可以通過增加一個間接的中間層來解決"埃难。EGL就是安卓上的這個中間層,OpenGL通過它來和安卓系統(tǒng)的交互。我們可以用這個EGL來設(shè)置安卓上特有的一些配置項,比如之前說的Surface涤久。順帶提一嘴在IOS里面這個中間層叫做EAGL涡尘。

EGL的使用

EGL的使用其實套路比較死板,沒有比較死記整個流程和api,只要大概知道每個接口的作用就好∠煊兀可以寫一個自己的工具類,配置上默認值,在需要的時候再去修改考抄。

在代碼里面安卓經(jīng)過這么多年的迭代已經(jīng)給我們提供了幾個版本的EGL,比如EGL10、EGL11栓拜、EGL14座泳。它們用法基本一樣,我這里就用EGL14舉例了惠昔。

EGL使用的整個流程如下:

1.png

初始化EGL

首先我們要創(chuàng)建EGLDisplay并且初始化EGL環(huán)境:

mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

if (mEGLDisplay == EGL14.EGL_NO_DISPLAY){
    throw new RuntimeException("can't get eglGetDisplay");
}

if (!EGL14.eglInitialize(mEGLDisplay, null, 0, null, 0)) {
    throw new RuntimeException("eglInitialize failed");
}

EGLDisplay指的是物理的顯示設(shè)備比如我們的手機屏幕,我們可以通過傳入屏幕設(shè)備的id去獲取到設(shè)備句柄,絕大多數(shù)情況下我們傳入EGL14.EGL_DEFAULT_DISPLAY獲取默認的屏幕就好,而且一般情況下我們的手機也只有一個屏幕。

如果拿不到設(shè)備就會返回EGL_NO_DISPLAY挑势。

拿到EGLDisplay之后就可以調(diào)用EGL14.eglInitialize去初始化EGL環(huán)境镇防。第一個參數(shù)是EGLDisplay,然后可以通過后面的參數(shù)獲取系統(tǒng)中EGL的實現(xiàn)版本號,做一些兼容處理,這里我們沒有使用什么高級特性,不需要獲取,傳null和0就好。如果需要獲取的話可以傳入一個數(shù)組,并且指定major版本和minor想要存放到數(shù)組的哪個位置去獲取潮饱。

選擇EGLConfig

在使用OpenGL的時候EGLDisplay支持的配置有很多種,例如顏色可能支持ARGB888来氧、RGB888、RGB444香拉、RGB565等,我們可以通過eglGetConfigs拿到EGLDisplay支持的所有配置,然后選擇我們需要的啦扬。

public static native boolean eglGetConfigs(
    EGLDisplay dpy,
    EGLConfig[] configs,
    int configsOffset,
    int config_size,
    int[] num_config,
    int num_configOffset
);

不過如果直接去遍歷所有的配置找我們需要的那個,代碼寫起來比較麻煩。所以EGL提供了一個eglChooseConfig方法,我們輸入關(guān)心的屬性,其他的屬性讓EGL自己匹配就好凫碌∑苏保可能會匹配出多個EGLConfig,這個時候隨便選一個都可以:

private EGLConfig chooseEglConfig(EGLDisplay display) {
    int[] attribList = {
            EGL14.EGL_BUFFER_SIZE, 32,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
            EGL14.EGL_NONE
    };
    EGLConfig[] configs = new EGLConfig[1];
    int[] numConfigs = new int[1];
    if (!EGL14.eglChooseConfig(
            display,
            attribList,
            0,
            configs,
            0,
            configs.length,
            numConfigs,
            0)) {
        throw new RuntimeException("eglChooseConfig failed");
    }
    return configs[0];
}

...

mEGLConfig = chooseEglConfig(mEGLDisplay);

創(chuàng)建EGLContext

EGLContext是OpenGL的線程相關(guān)上下文環(huán)境,我們在OpenGL中創(chuàng)建的數(shù)據(jù)如圖片、頂點盛险、著色器等最后獲取到的只是一個id,它的具體內(nèi)容其實依賴這個EGLContext瞄摊。所以接下來我們需要將它創(chuàng)建出來。

mEGLContext = createEglContext(mEGLDisplay, mEGLConfig);
if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
    throw new RuntimeException("eglCreateContext failed");
}

private EGLContext createEglContext(EGLDisplay display, EGLConfig config) {
    int[] contextList = {
            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
    };
    return EGL14.eglCreateContext(
            display,
            config,
            EGL14.EGL_NO_CONTEXT,
            contextList,
            0);
}

這個上下文環(huán)境是線程相關(guān)的,一般來講OpenGL的操作都在同一個線程中進行,但是有些復(fù)雜的業(yè)務(wù)場景可能需要多線程,于是可以在eglCreateContext的第三個參數(shù)里面?zhèn)魅雜hare_context做到多線程共享苦掘。如果不需要多線程共享的話傳入EGL14.EGL_NO_CONTEXT就好

創(chuàng)建并指定EGLSurface

我們在EGLDisplay指定了屏幕,在EGLContext里面提供了上下文環(huán)境,最后一步就是要指定一個EGLSurface告訴OpenGL應(yīng)該往哪里畫東西换帜。

很多安卓程序員可能就算寫過一些簡單的OpenGL程序,也沒有直接使用過EGL。因為我們熟悉的GLSurfaceView已經(jīng)幫我們封裝好了,我們只需要在Render.onDrawFrame里面直接使用OpenGL的接口繪圖就好了,繪制的圖形就好顯示在GLSurfaceViews上鹤啡。

但是由于我們這里使用TextureView替代SurfaceView,并且也沒有啥GLTextureView幫我們封裝好EGL惯驼。需要自己去配置EGL并用TextureView的SurfaceTexture去創(chuàng)建并指定EGLSurface讓預(yù)覽畫面繪制到TextureView上:

public EGLSurface createEGLSurface(Surface surface) {
        int[] attribList = {
                EGL14.EGL_NONE
        };
        return EGL14.eglCreateWindowSurface(
                mEGLDisplay,
                mEGLConfig,
                surface,
                attribList,
                0);
    }

...

public void makeCurrent(EGLSurface eglSurface) {
    EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext);
}

...

public void initEGL(SurfaceTexture surface) {
    ...
    EGLSurface eglSurface = createEGLSurface(new Surface(surface));
    makeCurrent(eglSurface);
    ...
}

我們可以用eglCreateWindowSurface創(chuàng)建EGLSurface然后用eglMakeCurrent指定OpenGL繪制的結(jié)果最后輸出到這個EGLSurface上。

其實TextureView和SurfaceView都可以用來顯示預(yù)覽畫面,它們各有優(yōu)缺點递瑰。SurfaceView在WMS中有對應(yīng)的WindowState實際上是多開了個窗口浮在應(yīng)用的上面,因為這個Surface不在View hierachy中祟牲,它的顯示也不受View的屬性控制,所以不能進行平移泣矛,縮放等變換疲眷。而TextureView不會創(chuàng)建多個窗口,所以可以用view的屬性去控制它,但是渲染的性能的話會比SurfaceView稍微低一點。

總結(jié)

總結(jié)一下EGL的三大模塊和相關(guān)方法如下:

2.png

完整的EGL初始化代碼如下,大多數(shù)情況不需要修改就可以直接拷貝去用了:

private void initEGL(SurfaceTexture surface) {
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
            throw new RuntimeException("can't get eglGetDisplay");
        }

        if (!EGL14.eglInitialize(mEGLDisplay, null, 0, null, 0)) {
            throw new RuntimeException("eglInitialize failed");
        }

        mEGLConfig = chooseEglConfig(mEGLDisplay);
        mEGLContext = createEglContext(mEGLDisplay, mEGLConfig);
        if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
            throw new RuntimeException("eglCreateContext failed");
        }

        EGLSurface eglSurface = createEGLSurface(new Surface(surface));
        makeCurrent(eglSurface);
    }


    private EGLConfig chooseEglConfig(EGLDisplay display) {
        int[] attribList = {
            EGL14.EGL_BUFFER_SIZE, 32,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
            EGL14.EGL_NONE
        };
        EGLConfig[] configs = new EGLConfig[1];
        int[] numConfigs = new int[1];
        if (!EGL14.eglChooseConfig(
            display,
            attribList,
            0,
            configs,
            0,
            configs.length,
            numConfigs,
            0)) {
            throw new RuntimeException("eglChooseConfig failed");
        }
        return configs[0];
    }

    private EGLContext createEglContext(EGLDisplay display, EGLConfig config) {
        int[] contextList = {
            EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
        };
        return EGL14.eglCreateContext(
            display,
            config,
            EGL14.EGL_NO_CONTEXT,
            contextList,
            0);
    }

    public EGLSurface createEGLSurface(Surface surface) {
        int[] attribList = {
            EGL14.EGL_NONE
        };
        return EGL14.eglCreateWindowSurface(
            mEGLDisplay,
            mEGLConfig,
            surface,
            attribList,
            0);
    }

    public void makeCurrent(EGLSurface eglSurface) {
        EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末您朽,一起剝皮案震驚了整個濱河市狂丝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哗总,老刑警劉巖几颜,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異讯屈,居然都是意外死亡蛋哭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門涮母,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谆趾,“玉大人躁愿,你說我怎么就攤上這事』ε睿” “怎么了彤钟?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長跷叉。 經(jīng)常有香客問我逸雹,道長,這世上最難降的妖魔是什么云挟? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任梆砸,我火速辦了婚禮,結(jié)果婚禮上园欣,老公的妹妹穿的比我還像新娘帖世。我一直安慰自己,他們只是感情好俊庇,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布狮暑。 她就那樣靜靜地躺著,像睡著了一般辉饱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拣展,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天彭沼,我揣著相機與錄音,去河邊找鬼备埃。 笑死姓惑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的按脚。 我是一名探鬼主播于毙,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辅搬!你這毒婦竟也來了唯沮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤堪遂,失蹤者是張志新(化名)和其女友劉穎介蛉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溶褪,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡币旧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了猿妈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吹菱。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡巍虫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鳍刷,到底是詐尸還是另有隱情垫言,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布倾剿,位于F島的核電站筷频,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏前痘。R本人自食惡果不足惜凛捏,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芹缔。 院中可真熱鬧坯癣,春花似錦、人聲如沸最欠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芝硬。三九已至蚜点,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拌阴,已是汗流浹背绍绘。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留迟赃,地道東北人陪拘。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像纤壁,于是被迫代替她去往敵國和親左刽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

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