【Android 音视频开发打怪升级:OpenGL渲染视频画面篇】四、深入了解OpenGL之EGL

【声 明】

首先,这一系列文章均基于自己的理解和实践,可能有不对的地方,欢迎大家指正。
其次,这是一个入门系列,涉及的知识也仅限于够用,深入的知识网上也有许许多多的博文供大家学习了。
最后,写文章过程中,会借鉴参考其他人分享的文章,会在文章最后列出,感谢这些作者的分享。

码字不易,转载请注明出处!

教程代码:【Github传送门

目录

一、Android音视频硬解码篇:
二、使用OpenGL渲染视频画面篇
三、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。

一、EGL是什么

作为Android开发者,EGL仿佛是一个很陌生的东西,为什么?

都怪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的绘制流程。

这里我们就可以猜测,能够不断被调用的,有没有可能就是一个while循环的线程呢?

答案是:Yes。

如果你去看一下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的过程其实就是配置以上几个信息的过程。

二、如何使用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?

且听我继续吹吹水,哈哈哈。

三、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,必学!

四、参考文章

OpenGL 之 EGL 使用实践

从源码角度剖析Android系统EGL及GL线程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343