系列文章:
安卓特效相機(一) 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使用的整個流程如下:
初始化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)方法如下:
完整的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);
}
}