Android 系統(tǒng)下 OpenGL 三種離屏渲染技術(shù)
經(jīng)常有同學(xué)問我如何將 OpenGL 渲染出的結(jié)果保存成mp4文件,今天我們就來聊聊這個話題。
離屏渲染 OpenGL
要想將OpenGL渲染出的結(jié)果保存成mp4文件,我們需要使用一種稱為離屏渲染的技術(shù)。
什么是離屏渲染呢?顧名思意,就是讓OpenGL不將渲染的結(jié)果直接輸出到屏幕上,而是輸出到一個中間緩沖區(qū)(一塊GPU空間),然后再將中間緩沖區(qū)的內(nèi)容輸出到屏幕或編碼器等目標(biāo)上,這就稱為離屏渲染。
使用離屏渲染有什么好處呢? 比如我們可以讓OpenGL將渲染的結(jié)果先輸出到一個幀緩沖區(qū)中,然后再把幀緩沖區(qū)的內(nèi)容送給編碼器進(jìn)行編碼,最后寫到mp4文件中,這樣就達(dá)到我們的目標(biāo)了。 當(dāng)然在將結(jié)果保存成mp4文件的同時,還能讓它在屏幕上顯示出來就更好了,那么如何做到這一點(diǎn)呢?
在Android系統(tǒng)下,有三種方法可以實(shí)現(xiàn),下面我們就來一一介紹一下。
Android下離屏渲染的三種方式
在Android系統(tǒng)下可以使用三種方法實(shí)現(xiàn)同時將OpenGL的內(nèi)容輸出給多個目標(biāo)(屏幕和編碼器)。第一種方法是二次渲染法;第二種方法是使用FBO;第三種是使用BlitFramebuffer。
首先我們來看看如何通過二次渲染法實(shí)現(xiàn)將OpenGL渲染結(jié)果送給屏幕和編碼器。
二次渲染法
想通過二次渲染法實(shí)現(xiàn)OpenGL渲染結(jié)果送屏幕和編碼器,我們必須使用SurfaceView,而不能使用GLSurfaceView。
之所以如此,是因?yàn)镚LSurfaceView有自己的專有渲染線程,這雖然減少了開發(fā)者使用OpenGL的復(fù)雜度,但也同時降低了開發(fā)者對EGL的控制力,顯得不夠靈活,從而無法將OpenGL的渲染結(jié)果輸出給多個目標(biāo)。
為了解決這個問題,我們需要使用 SurfaceView+自己創(chuàng)建渲染線程 這個組合,在自己創(chuàng)建的渲染線程中使用EGL API,通過多次渲染并將結(jié)果輸送給多個目標(biāo)Surface來實(shí)現(xiàn)二次渲染,其架構(gòu)如下圖所示:
在上圖中,我們可以看到有 SurfaceView, MediaCodec, Camera, OpenGL/EGL等組件。
其中SurfaceView用于展示OpenGL的渲染結(jié)果,而且它還實(shí)現(xiàn)了兩個重要的事件處理方法,surfaceCreated和surfaceChanged。
surfaceCreated方法是在SurfaceView中的Surface創(chuàng)建好后被調(diào)用。在該方法中,首先創(chuàng)建一個渲染線程,并在渲染線程中創(chuàng)建EGL環(huán)境。
而surfaceChanged方法是在Surface發(fā)生變化時被調(diào)用,在該方法中我們會創(chuàng)建FBO環(huán)境,關(guān)于這方面的內(nèi)容我在講解FBO時再向你做詳細(xì)介紹。
MediaCodec 用于編碼,它也有一個Surface用于接收需要編碼的數(shù)據(jù) 。
Camera 用于采集視頻數(shù)據(jù),當(dāng)采集到視頻數(shù)據(jù)后,它會通知渲染線程,渲染線程通過SurfaceTexture從BufferQueue中取走數(shù)據(jù),并交由OpenGL處理。
OpenGL/EGL用于渲染,它收到視頻幀后調(diào)用Shader程序進(jìn)行渲染,之后將渲染后的結(jié)果輸出給SurfaceView的Surface ,讓其在屏幕上顯示;接下來,調(diào)用EGL的eglMakeCurrent方法,將默認(rèn)Surface從SurfaceView的Surface改為MediaCodec的Surface,然后再次調(diào)用Shader程序進(jìn)行渲染,并將渲染后的結(jié)果輸出給MediaCodec的Surface進(jìn)行編碼。
也就是說,二次渲染就是調(diào)用兩次Shader程序進(jìn)行渲染,每次渲染后的結(jié)果輸送給不同的目標(biāo)Surface,因此稱為二次渲染法。
當(dāng)然,如果你需要將渲染結(jié)果輸出給多個目標(biāo)也是可以的,那就需要調(diào)用多次Shader程序,每次將渲染結(jié)果輸送給不同的目標(biāo)Surface就好了。
FBO法
上面的方法雖然能夠?qū)⑼粋€源輸送給不同的目標(biāo),但每次都要調(diào)用OpenGL進(jìn)行重繪,效率上確實(shí)不高。尤其是當(dāng)我們要渲染的模型特別復(fù)雜的時候,會嚴(yán)重影響效率,有沒有更好的解決辦法呢?
OpenGL為我們提供了一種高效的辦法,即FBO(FrameBufferObject)。下面我們來看一下如何通過FBO高效的向多個目標(biāo)輸送渲染結(jié)果,其架構(gòu)圖如下所示:
其架構(gòu)圖與我們上面展示的二次渲染架構(gòu)圖很類似,只不過通過OpenGL渲染的結(jié)果不再直接送給不同的Surface,而是將結(jié)果輸出到FBO中,這里你可以先將FBO想像為一塊特殊的空間(關(guān)于FBO的細(xì)節(jié),我會單獨(dú)寫一篇文章進(jìn)行講解)。
然后通過另外一種Shader程序(這種Shader程序是專門用于處理FBO紋理的)再次進(jìn)行渲染。當(dāng)然,這種Shader程序由于處理的是紋理,所以會比第一次使用的Shader程序效率高很多。
渲染成功后將結(jié)果輸出給屏幕,然后切換Surface再次調(diào)用Shader(第二個Shader),這次渲染的結(jié)果將會輸送給MediaCodec的Surface進(jìn)行編碼。
因此,通過FBO方法我們只需要對原模型渲染一次,將結(jié)果保存到FBO。之后再對FBO中的內(nèi)容進(jìn)行多次渲染,通過這種方式來提高效率。
注意,之所以通過這種方式可以提高效率,原因就在于OpenGL對FBO紋理的渲染效率遠(yuǎn)遠(yuǎn)高于對原模型的渲染效率。
BlitFramebuffer法
通過上面的描述,我們?nèi)匀挥X得不夠高效。有沒有更好的方法,只渲染一次就可以將結(jié)果輸送給多個目標(biāo)呢?到了OpenGL3.0出現(xiàn)了一種更高效的方法,即BlitFramebuffer。
它是如何工作的呢?其工作架構(gòu)圖所下所示:
該方法不再使用FBO做緩存,而是像二次渲染法一樣,先將渲染的內(nèi)容輸出到當(dāng)前Surface中,但并不展示到屏幕上。
相當(dāng)于把當(dāng)前的Surface當(dāng)作一個緩沖區(qū),然后切換Surface,此時MediaCodec的Surface變成了當(dāng)前Surface,接下來利用OpenGL3.0提供的API BlitFramebuffer從原來的Surface拷貝數(shù)據(jù)到當(dāng)前Surface中,再調(diào)用EGL的eglSwapBuffers將Surface中的內(nèi)容送編碼器編碼。
之后再將當(dāng)前Surface切回原來的Surface,也就是SurfaceView的Surface,同樣調(diào)用EGL的eglSwapBuffers方法,將其內(nèi)容顯示到屏幕上。
至此,實(shí)現(xiàn)了OpenGL僅渲染一次,卻可以輸出給多個目標(biāo),這種方法是最高效的。
小結(jié)
本文向你介紹了三種離屏渲染的方法,其中最高效的是BlitFramebuffer方法,但它是在OpenGL3.0才有的API,因此只有OpenGL3.0才可以使用該方法;其次是FBO方法,但其復(fù)雜度要比二次渲染法高很多,并且效率是有提高與原模型的復(fù)雜高有很大關(guān)系,如果原模型復(fù)雜度并不高,那么它與二次渲染法的效率并不多。
此外,本篇文章中還有很多細(xì)節(jié)并沒有介紹到,比如如何使用FBO,如何在自己創(chuàng)建的渲染線程中構(gòu)建EGL環(huán)境,如何使用BlitFramebuffer等,這些內(nèi)容我將在后面的文章中再向你做詳細(xì)講解。
參考資料
《系統(tǒng)玩轉(zhuǎn)OpenGL+AI,實(shí)現(xiàn)各種酷炫視頻特效》
原文鏈接:https://zhuanlan.zhihu.com/p/676879474
-- END --
進(jìn)技術(shù)交流群,掃碼添加我的微信:Byte-Flow
獲取相關(guān)資料和源碼
推薦:
Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能
全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了
面試官:如何利用 Shader 實(shí)現(xiàn) RGBA 到 NV21 圖像格式轉(zhuǎn)換?
