【Android 音視頻開發打怪升級:OpenGL渲染視頻畫面篇】4、深刻了解OpenGL之EGL

【聲 明】

首先,這一系列文章均基於本身的理解和實踐,可能有不對的地方,歡迎你們指正。
其次,這是一個入門系列,涉及的知識也僅限於夠用,深刻的知識網上也有許許多多的博文供你們學習了。
最後,寫文章過程當中,會借鑑參考其餘人分享的文章,會在文章最後列出,感謝這些做者的分享。android

碼字不易,轉載請註明出處!git

教程代碼:【Github傳送門

目錄

1、Android音視頻硬解碼篇:
2、使用OpenGL渲染視頻畫面篇
3、Android FFmpeg音視頻解碼篇
  • 1,FFmpeg so庫編譯
  • 2,Android 引入FFmpeg
  • 3,Android FFmpeg視頻解碼播放
  • 4,Android FFmpeg+OpenSL ES音頻解碼播放
  • 5,Android FFmpeg+OpenGL ES播放視頻
  • 6,Android FFmpeg簡單合成MP4:視屏解封與從新封裝
  • 7,Android FFmpeg視頻編碼

本文你能夠了解到

EGL做爲OpenGL與本地窗口渲染的中間橋樑,不少時候是不會被剛入門OpenGL的開發者關注的,甚至有點忽略了。隨着學習的深刻,EGL將是不得不面對的東西。本文將介紹EGL是什麼,有什麼用,以及如何使用EGL。github

1、EGL是什麼

做爲Android開發者,EGL彷彿是一個很陌生的東西,爲何?api

都怪Android的GLSurfaceView封裝的太好了。哈哈哈數組

1,爲何onDrawFrame會不斷的回調呢?

前面的文章就介紹過,OpenGL是基於線程的,直到目前爲止,咱們並無深入的認識到這個問題,但咱們知道的是,當咱們繼承GLSurfaceView.Renderer時,系統會回調如下方法:緩存

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
}

override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
}

override fun onDrawFrame(gl: GL10?) {
}
複製代碼

而且onDrawerFrame方法是會被不斷的調用,咱們就是在這裏面實現了OpenGL的繪製流程。app

這裏咱們就能夠猜想,可以不斷被調用的,有沒有可能就是一個while循環的線程呢?框架

答案是:Yes。ide

若是你去看一下GLSurfaceView的源碼,你會找到一個叫GLThread的線程,在線程中就初始化了EGL相關的內容。而且在合適的時機,分別調用了Renderer中的三個方法。函數

那麼,EGL到底是個什麼東西呢?

2,EGL是個啥?

咱們知道OpenGL是一組能夠操做GPU的API,然而僅僅可以操做GPU,並不可以將圖像渲染到設備的顯示窗口上。那麼,就須要一箇中間層,鏈接OpenGL與設備窗口,而且最好是跨平臺的。

因而EGL出現了,由Khronos Group提供的一組平臺無關的API。

3,EGL的一些基礎知識
  • EGLDisplay

EGL定義的一個抽象的系統顯示類,用於操做設備窗口。

  • EGLConfig

EGL配置,如rgba位數

  • EGLSurface

渲染緩存,一塊內存空間,全部要渲染到屏幕上的圖像數據,都要先緩存在EGLSurface上。

  • EGLContext

OpenGL上下文,用於存儲OpenGL的繪製狀態信息、數據。

初始化EGL的過程其實就是配置以上幾個信息的過程。

2、如何使用EGL

單單看上面的介紹,其實仍是比較難理解EGL究竟有什麼做用,或者應該怎麼樣去使用EGL。


請你們先思考一個問題

若是同時有兩個GLSurfaceView在渲染視頻畫面,OpenGL爲何可以正確的把畫面分別繪製到兩個GLSurfaceView中?

仔細回想一下OpenGL ES的每一個API,有沒有哪一個API是指定當前畫面是渲染到哪一個GLSurfaceView的?

沒有!


請帶着這個疑問,閱讀下面的內容。

1,封裝EGL核心API

首先,對EGL初始化的核心(第一節中介紹的4個)內容進行封裝,命名爲 EGLCore

const val FLAG_RECORDABLE = 0x01

private const val EGL_RECORDABLE_ANDROID = 0x3142

class EGLCore {

    private val TAG = "EGLCore"

    // EGL相關變量
    private var mEGLDisplay: EGLDisplay = EGL14.EGL_NO_DISPLAY
    private var mEGLContext = EGL14.EGL_NO_CONTEXT
    private var mEGLConfig: EGLConfig? = null

    /** * 初始化EGLDisplay * @param eglContext 共享上下文 */
    fun init(eglContext: EGLContext?, flags: Int) {
        if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("EGL already set up")
        }

        val sharedContext = eglContext ?: EGL14.EGL_NO_CONTEXT

        // 1,建立 EGLDisplay
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("Unable to get EGL14 display")
        }

        // 2,初始化 EGLDisplay
        val version = IntArray(2)
        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
            mEGLDisplay = EGL14.EGL_NO_DISPLAY
            throw RuntimeException("unable to initialize EGL14")
        }

        // 3,初始化EGLConfig,EGLContext上下文
        if (mEGLContext === EGL14.EGL_NO_CONTEXT) {
            val config = getConfig(flags, 2) ?: throw RuntimeException("Unable to find a suitable EGLConfig")
            val attr2List = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
            val context = EGL14.eglCreateContext(
                mEGLDisplay, config, sharedContext,
                attr2List, 0
            )
            mEGLConfig = config
            mEGLContext = context
        }
    }

    /** * 獲取EGL配置信息 * @param flags 初始化標記 * @param version EGL版本 */
    private fun getConfig(flags: Int, version: Int): EGLConfig? {
        var renderableType = EGL14.EGL_OPENGL_ES2_BIT
        if (version >= 3) {
            // 配置EGL 3
            renderableType = renderableType or EGLExt.EGL_OPENGL_ES3_BIT_KHR
        }

        // 配置數組,主要是配置RAGA位數和深度位數
        // 兩個爲一對,前面是key,後面是value
        // 數組必須以EGL14.EGL_NONE結尾
        val attrList = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            //EGL14.EGL_DEPTH_SIZE, 16,
            //EGL14.EGL_STENCIL_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE, renderableType,
            EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
            EGL14.EGL_NONE
        )
        //配置Android指定的標記
        if (flags and FLAG_RECORDABLE != 0) {
            attrList[attrList.size - 3] = EGL_RECORDABLE_ANDROID
            attrList[attrList.size - 2] = 1
        }
        val configs = arrayOfNulls<EGLConfig>(1)
        val numConfigs = IntArray(1)

        //獲取可用的EGL配置列表
        if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
                configs, 0, configs.size,
                numConfigs, 0)) {
            Log.w(TAG, "Unable to find RGB8888 / $version EGLConfig")
            return null
        }
        
        //使用系統推薦的第一個配置
        return configs[0]
    }

    /** * 建立可顯示的渲染緩存 * @param surface 渲染窗口的surface */
    fun createWindowSurface(surface: Any): EGLSurface {
        if (surface !is Surface && surface !is SurfaceTexture) {
            throw RuntimeException("Invalid surface: $surface")
        }

        val surfaceAttr = intArrayOf(EGL14.EGL_NONE)

        val eglSurface = EGL14.eglCreateWindowSurface(
                                        mEGLDisplay, mEGLConfig, surface,
                                        surfaceAttr, 0)

        if (eglSurface == null) {
            throw RuntimeException("Surface was null")
        }

        return eglSurface
    }

    /** * 建立離屏渲染緩存 * @param width 緩存窗口寬 * @param height 緩存窗口高 */
    fun createOffscreenSurface(width: Int, height: Int): EGLSurface {
        val surfaceAttr = intArrayOf(EGL14.EGL_WIDTH, width,
                                                 EGL14.EGL_HEIGHT, height,
                                                 EGL14.EGL_NONE)

        val eglSurface = EGL14.eglCreatePbufferSurface(
                                        mEGLDisplay, mEGLConfig,
                                        surfaceAttr, 0)

        if (eglSurface == null) {
            throw RuntimeException("Surface was null")
        }

        return eglSurface
    }

    /** * 將當前線程與上下文進行綁定 */
    fun makeCurrent(eglSurface: EGLSurface) {
        if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("EGLDisplay is null, call init first")
        }
        if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
            throw RuntimeException("makeCurrent(eglSurface) failed")
        }
    }

    /** * 將當前線程與上下文進行綁定 */
    fun makeCurrent(drawSurface: EGLSurface, readSurface: EGLSurface) {
        if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("EGLDisplay is null, call init first")
        }
        if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
            throw RuntimeException("eglMakeCurrent(draw,read) failed")
        }
    }

    /** * 將緩存圖像數據發送到設備進行顯示 */
    fun swapBuffers(eglSurface: EGLSurface): Boolean {
        return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface)
    }

    /** * 設置當前幀的時間,單位:納秒 */
    fun setPresentationTime(eglSurface: EGLSurface, nsecs: Long) {
        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs)
    }

    /** * 銷燬EGLSurface,並解除上下文綁定 */
    fun destroySurface(elg_surface: EGLSurface) {
        EGL14.eglMakeCurrent(
            mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
            EGL14.EGL_NO_CONTEXT
        )
        EGL14.eglDestroySurface(mEGLDisplay, elg_surface);
    }

    /** * 釋放資源 */
    fun release() {
        if (mEGLDisplay !== EGL14.EGL_NO_DISPLAY) {
            // Android is unusual in that it uses a reference-counted EGLDisplay. So for
            // every eglInitialize() we need an eglTerminate().
            EGL14.eglMakeCurrent(
                mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
                EGL14.EGL_NO_CONTEXT
            )
            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
            EGL14.eglReleaseThread()
            EGL14.eglTerminate(mEGLDisplay)
        }

        mEGLDisplay = EGL14.EGL_NO_DISPLAY
        mEGLContext = EGL14.EGL_NO_CONTEXT
        mEGLConfig = null
    }
}
複製代碼

以上是最基礎,最簡潔的EGL初始化封裝了,基本上每一個方法都是必要的。

具體來看下:

  • 初始化init,分爲3個步驟:

    • 經過eglGetDisplay建立EGLDisplay
    • 經過eglInitialize初始化了EGLDisplay
    • 經過eglCreateContext初始化EGLContext

其中,在初始化EGLCongtext的時候,調用了getConfig方法。

  • 配置上下文getConfig:

    • 根據選擇的EGL版本,配置版本標誌
    • 初始化配置列表,配置渲染的rgba位數和深度位數,兩個爲一對,前面一個爲類型,後面爲值,而且必須以EGL14.EGL_NONE做爲結尾。
    • 配置Android特有的屬性EGL_RECORDABLE_ANDROID
    • 根據以上配置的信息,經過eglChooseConfig,系統會返回符合的配置信息列表,通常使用返回第一個配置信息。

Android 指定的標誌EGL_RECORDABLE_ANDROID
告訴EGL它建立的surface必須和視頻編解碼器兼容。
沒有這個標誌,EGL可能會使用一個MediaCodec不能理解的Buffer。
這個變量在api26之後系統才自帶有,爲了兼容,咱們本身寫好這個值0x3142。

  • 建立EGLSurface,分爲兩種模式:

    • 可顯示窗口,使用eglCreateWindowSurface建立。
    • 離屏(不可見)窗口,使用eglCreatePbufferSurface建立。

第一種是最經常使用的,一般將頁面上的SurfaceView持有的Surface,或SurfaceTexture傳遞進去進行綁定。這樣OpenGL處理的圖像數據就能夠顯示在屏幕上。

第二種用於離屏渲染,也就是將OpenGL處理的圖像數據保存在緩存中,不會顯示到屏幕上,可是整個渲染流程和普通模式同樣,這樣能夠很好的處理一些用戶不須要看見的圖像數據。

  • 綁定OpenGL渲染線程與繪製上下文:makeCurrent

    • 使用eglMakeCurrent來實現綁定。

到這裏,使用EGLCore中封裝的方法就能夠初始化EGL了。可是仍是沒有回答上邊提到的問題。

答案就在glMakeCurrent中。

glMakeCurrent這個方法,實現了設備顯示窗口(EGLDisplay)、 OpenGL 上下文(EGLContext)、圖像數據緩存(GLSurface) 、當前線程的綁定。

注意這裏的:「當前線程的綁定」。


如今來回答上面提出的問題:爲何OpenGL能夠在多個GLSurfaceView中正確繪製?

在EGL初始化之後,即渲染環境(EGLDisplay、EGLContext、GLSurface)準備就緒之後,須要在渲染線程(繪製圖像的線程)中,明確的調用glMakeCurrent。這時,系統底層會將OpenGL渲染環境綁定到當前線程。

在這以後,只要你是在渲染線程中調用任何OpenGL ES的API(好比生產紋理ID的方法GLES20.glGenTextures),OpenGL會自動根據當前線程,切換上下文(也就是切換OpenGL的渲染信息和資源)。

換而言之,若是你在非調用glMakeCurrent的線程中去調用OpenGL的API,系統將找不到對應的OpenGL上下文,也就找不到對應的資源,可能會致使異常出錯。

這也就是爲何有文章說,OpenGL渲染必定要在OpenGL線程中進行。

實際上,GLSurfaceView#Renderer的三個回調方法,都是在GLThread中進行調用的。


  • 交換緩存數據,並顯示圖像:swapBuffers

    • eglSwapBuffers是EGL提供的用來將EGLSurface數據顯示到設備屏幕上的方法。在OpenGL繪製完圖像化,調用該方法,才能真正顯示出來。
  • 解綁數據緩存表面,以及釋放資源

    • 當頁面上的Surface被銷燬(好比App到後臺)的時候,須要將資源解綁。
    • 當頁面退出時,這時SurfaceView被銷燬,須要釋放全部的資源。

上面的僅僅作了核心API的封裝,接下來要新建一個類來調用它。

2,調用EGL核心方法

這裏,新建一個EGLSurfaceHolder,用於操做EGLCore

class EGLSurfaceHolder {

    private val TAG = "EGLSurfaceHolder"

    private lateinit var mEGLCore: EGLCore

    private var mEGLSurface: EGLSurface? = null

    fun init(shareContext: EGLContext? = null, flags: Int) {
        mEGLCore = EGLCore()
        mEGLCore.init(shareContext, flags)
    }

    fun createEGLSurface(surface: Any?, width: Int = -1, height: Int = -1) {
        mEGLSurface = if (surface != null) {
            mEGLCore.createWindowSurface(surface)
        } else {
            mEGLCore.createOffscreenSurface(width, height)
        }
    }

    fun makeCurrent() {
        if (mEGLSurface != null) {
            mEGLCore.makeCurrent(mEGLSurface!!)
        }
    }

    fun swapBuffers() {
        if (mEGLSurface != null) {
            mEGLCore.swapBuffers(mEGLSurface!!)
        }
    }

    fun destroyEGLSurface() {
        if (mEGLSurface != null) {
            mEGLCore.destroySurface(mEGLSurface!!)
            mEGLSurface = null
        }
    }

    fun release() {
        mEGLCore.release()
    }
}
複製代碼

代碼很簡單,最重要的就是持有了EGLSurface(固然了,你也能夠把EGLSurface也放在EGLCore中),並開放了更簡潔的EGL操做方法給外部進行調用。

3,模擬GLSurfaceView,使用EGL實現渲染

爲了更好的認識EGL,這裏經過模擬GLSurfaceView來了解如何使用EGL。

  • 自定義一個渲染器CustomerRender
class CustomerGLRenderer : SurfaceHolder.Callback {

    //OpenGL渲染線程
    private val mThread = RenderThread()

    //頁面上的SurfaceView弱引用
    private var mSurfaceView: WeakReference<SurfaceView>? = null

    //全部的繪製器
    private val mDrawers = mutableListOf<IDrawer>()

    init {
        //啓動渲染線程
        mThread.start()
    }
    
    /** * 設置SurfaceView */
    fun setSurface(surface: SurfaceView) {
        mSurfaceView = WeakReference(surface)
        surface.holder.addCallback(this)

        surface.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener{
            override fun onViewDetachedFromWindow(v: View?) {
                mThread.onSurfaceStop()
            }

            override fun onViewAttachedToWindow(v: View?) {
            }
        })
    }

    /** * 添加繪製器 */
    fun addDrawer(drawer: IDrawer) {
        mDrawers.add(drawer)
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        mThread.onSurfaceCreate()
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
        mThread.onSurfaceChange(width, height)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
        mThread.onSurfaceDestroy()
    }
}
複製代碼

主要以下:

  1. 一個自定義的渲染線程RenderThread
  2. 一個SurfaceView的弱引用
  3. 一個繪製器列表

初始化時,啓動渲染線程。而後就是將SurfaceView生命週期轉發給渲染線程,沒有其餘了。

  • 定義渲染狀態
/** * 渲染狀態 */
enum class RenderState {
    NO_SURFACE, //沒有有效的surface
    FRESH_SURFACE, //持有一個未初始化的新的surface
    SURFACE_CHANGE, // surface尺寸變化
    RENDERING, //初始化完畢,能夠開始渲染
    SURFACE_DESTROY, //surface銷燬
    STOP //中止繪製
}
複製代碼

根據這幾個狀態,在RenderThread中,切換線程的執行狀態。

渲染狀態切換流程圖

說明以下:

  1. 線程start,進入while(true)循環時,狀態爲NO_SURFACE,線程進入等待(hold on);
  2. Surface create後,狀態變爲 FRESH_SURFACE ;
  3. Surface change後,進入 SURFACE_CHANGE 狀態;
  4. 執行完 SURFACE_CHANGE 後,自動進入 RENDERING 狀態;
  5. 在沒有其餘中斷的狀況下,每隔20ms執行一遍Render渲染畫面;
  6. 若是Surface 銷燬,從新進入 NO_SURFACE 狀態;若有新surface,從新執行2-5;
  7. 若是SurfaceView銷燬,進入 STOP 狀態,渲染線程退出,end。
  • 執行渲染循環
inner class RenderThread: Thread() {
    
    // 渲染狀態
    private var mState = RenderState.NO_SURFACE
    
    private var mEGLSurface: EGLSurfaceHolder? = null

    // 是否綁定了EGLSurface
    private var mHaveBindEGLContext = false

    //是否已經新建過EGL上下文,用於判斷是否須要生產新的紋理ID
    private var mNeverCreateEglContext = true

    private var mWidth = 0
    private var mHeight = 0

    private val mWaitLock = Object()

//------------第1部分:線程等待與解鎖-----------------

    private fun holdOn() {
        synchronized(mWaitLock) {
            mWaitLock.wait()
        }
    }

    private fun notifyGo() {
        synchronized(mWaitLock) {
            mWaitLock.notify()
        }
    }

//------------第2部分:Surface聲明週期轉發函數------------

    fun onSurfaceCreate() {
        mState = RenderState.FRESH_SURFACE
        notifyGo()
    }

    fun onSurfaceChange(width: Int, height: Int) {
        mWidth = width
        mHeight = height
        mState = RenderState.SURFACE_CHANGE
        notifyGo()
    }

    fun onSurfaceDestroy() {
        mState = RenderState.SURFACE_DESTROY
        notifyGo()
    }

    fun onSurfaceStop() {
        mState = RenderState.STOP
        notifyGo()
    }

//------------第3部分:OpenGL渲染循環------------

    override fun run() {
        // 【1】初始化EGL
        initEGL()
        while (true) {
            when (mState) {
                RenderState.FRESH_SURFACE -> {
                    //【2】使用surface初始化EGLSurface,並綁定上下文
                    createEGLSurfaceFirst()
                    holdOn()
                }
                RenderState.SURFACE_CHANGE -> {
                    createEGLSurfaceFirst()
                    //【3】初始化OpenGL世界座標系寬高
                    GLES20.glViewport(0, 0, mWidth, mHeight)
                    configWordSize()
                    mState = RenderState.RENDERING
                }
                RenderState.RENDERING -> {
                    //【4】進入循環渲染
                    render()
                }
                RenderState.SURFACE_DESTROY -> {
                    //【5】銷燬EGLSurface,並解綁上下文
                    destroyEGLSurface()
                    mState = RenderState.NO_SURFACE
                }
                RenderState.STOP -> {
                    //【6】釋放全部資源
                    releaseEGL()
                    return
                }
                else -> {
                    holdOn()
                }
            }
            sleep(20)
        }
    }

//------------第4部分:EGL相關操做------------

    private fun initEGL() {
        mEGLSurface = EGLSurfaceHolder()
        mEGLSurface?.init(null, EGL_RECORDABLE_ANDROID)
    }

    private fun createEGLSurfaceFirst() {
        if (!mHaveBindEGLContext) {
            mHaveBindEGLContext = true
            createEGLSurface()
            if (mNeverCreateEglContext) {
                mNeverCreateEglContext = false
                generateTextureID()
            }
        }
    }

    private fun createEGLSurface() {
        mEGLSurface?.createEGLSurface(mSurfaceView?.get()?.holder?.surface)
        mEGLSurface?.makeCurrent()
    }

    private fun destroyEGLSurface() {
        mEGLSurface?.destroyEGLSurface()
        mHaveBindEGLContext = false
    }

    private fun releaseEGL() {
        mEGLSurface?.release()
    }
    
//------------第5部分:OpenGL ES相關操做-------------

    private fun generateTextureID() {
        val textureIds = OpenGLTools.createTextureIds(mDrawers.size)
        for ((idx, drawer) in mDrawers.withIndex()) {
            drawer.setTextureID(textureIds[idx])
        }
    }

    private fun configWordSize() {
        mDrawers.forEach { it.setWorldSize(mWidth, mHeight) }
    }

    private fun render() {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
        mDrawers.forEach { it.draw() }
        mEGLSurface?.swapBuffers()
    }
}
複製代碼

主要分爲5部分,1-2很簡單,相信你們都看得懂。至於4-5,都是run中調用的方法。

重點來看第3部分,也就是run方法。

【1】在進入while(true)以前,initEGL使用EGLSurfaceHolder來初始化EGL。

須要注意的是,initEGL只會調用一次,也就是說EGL只初始化一次,不管後面surface銷燬和重建多少次。

【2】有了可用的surface後,進入FRESH_SURFACE狀態,調用EGLSurfaceHolder的createEGLSurface和makeCurrent來綁定線程、上下文和窗口。

【3】根據surface窗口寬高,設置OpenGL窗口的寬高,而後自動進入RENDERING狀態。這部分對應GLSurfaceView.Renderer中回調onSurfaceChanged方法。

【4】進入循環渲染render,這裏每隔20ms渲染一次畫面。對應GLSurfaceView.Renderer中回調onDrawFrame方法。

爲方便對比,這裏貼一下以前文章定義的SimpleRender以下:

class SimpleRender: GLSurfaceView.Renderer {

    private val drawers = mutableListOf<IDrawer>()

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        GLES20.glClearColor(0f, 0f, 0f, 0f)
        //開啓混合,即半透明
        GLES20.glEnable(GLES20.GL_BLEND)
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)

        val textureIds = OpenGLTools.createTextureIds(drawers.size)
        for ((idx, drawer) in drawers.withIndex()) {
            drawer.setTextureID(textureIds[idx])
        }
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)
        for (drawer in drawers) {
            drawer.setWorldSize(width, height)
        }
    }

    override fun onDrawFrame(gl: GL10?) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
        drawers.forEach {
            it.draw()
        }
    }

    fun addDrawer(drawer: IDrawer) {
        drawers.add(drawer)
    }
}
複製代碼

【5】若是surface被銷燬(好比,進入後臺),調用EGLSurfaceHolder的destroyEGLSurface銷燬和解綁窗口。

注:當頁面從新回到前臺時,會從新建立surface,這時只要從新建立EGLSurface,並綁定上下文和EGLSurface,就能夠繼續渲染畫面,無需開啓新的渲染線程。

【6】SurfaceView被銷燬(好比,頁面finish),這時已經無需再渲染了,須要釋放全部的EGL資源,並退出線程。

4,使用渲染器

新建頁面EGLPlayerActivity

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto">
    <SurfaceView
            android:id="@+id/sfv"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
複製代碼
class EGLPlayerActivity: AppCompatActivity() {
    private val path = Environment.getExternalStorageDirectory().absolutePath + "/mvtest_2.mp4"
    private val path2 = Environment.getExternalStorageDirectory().absolutePath + "/mvtest.mp4"

    private val threadPool = Executors.newFixedThreadPool(10)

    private var mRenderer = CustomerGLRenderer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_egl_player)
        initFirstVideo()
        initSecondVideo()
        setRenderSurface()
    }

    private fun initFirstVideo() {
        val drawer = VideoDrawer()
        drawer.setVideoSize(1920, 1080)
        drawer.getSurfaceTexture {
            initPlayer(path, Surface(it), true)
        }
        mRenderer.addDrawer(drawer)
    }

    private fun initSecondVideo() {
        val drawer = VideoDrawer()
        drawer.setAlpha(0.5f)
        drawer.setVideoSize(1920, 1080)
        drawer.getSurfaceTexture {
            initPlayer(path2, Surface(it), false)
        }
        mRenderer.addDrawer(drawer)

        Handler().postDelayed({
            drawer.scale(0.5f, 0.5f)
        }, 1000)
    }

    private fun initPlayer(path: String, sf: Surface, withSound: Boolean) {
        val videoDecoder = VideoDecoder(path, null, sf)
        threadPool.execute(videoDecoder)
        videoDecoder.goOn()

        if (withSound) {
            val audioDecoder = AudioDecoder(path)
            threadPool.execute(audioDecoder)
            audioDecoder.goOn()
        }
    }

    private fun setRenderSurface() {
        mRenderer.setSurface(sfv)
    }
}
複製代碼

整個使用過程幾乎和上篇文章中,使用GLSurfaceView來渲染視頻畫面同樣。

惟一點不同的,就是須要把SurfaceView設置給CustomerRenderer。

至此,就能夠播放視頻了。EGL基礎知識、如何使用基本上就講完了。

可是,彷佛沒有發現EGL真正的用途在哪裏,該有的東西GLSurfaceView都有了,爲何還要學習EGL?

且聽我繼續吹吹水,哈哈哈。

3、EGL的用途

1,加深對OpenGL認識

若是你沒有認真學習過EGL,那麼你的OpenGL生涯將是不完整的,由於你始終沒法深入的認識到OpenGL渲染機制是怎樣的,那麼在處理一些的問題的時候,就會顯得很無力。

2,Android視頻硬編碼必需要使用EGL

若是你須要使用到Android Mediacodec的編碼能力,那麼EGL就是必不可少的東西,在後續的關於視頻編碼的文章中,你將會看到如何使用EGL來實現編碼。

3, FFmpeg編解碼都須要用到EGL相關的知識

在JNI層,Android並無實現一個相似GLSurfaceView的工具,來幫咱們隱藏EGL相關的內容。所以,若是你須要在C++層實現FFmpeg的編解碼,那麼就須要本身去實現整個OpenGL的渲染流程。

這纔是學習EGL的真正目的,若是隻是用於渲染視頻畫面,GLSurfaceView已經足夠咱們使用了。

因此,EGL,必學!

4、參考文章

OpenGL 之 EGL 使用實踐

從源碼角度剖析Android系統EGL及GL線程

相關文章
相關標籤/搜索