大家好,今天小编来为大家解答以下的问题,关于Android音视频开发进阶:OpenGL视频画面渲染详解,这个很多人还不知道,现在让我们一起来看看吧!
教程代码:【Github传送门】
目录
一、Android音视频硬解码篇:
1.音视频基础知识2.音视频硬解码流程:封装基本解码框架3.音视频播放:音视频同步4.音视频解封装与封装:生成MP4
二、使用OpenGL渲染视频画面篇
1、初步了解OpenGL ES2、使用OpenGL渲染视频图像3、OpenGL渲染多个视频、实现画中画4、深入了解OpenGL的EGL5、OpenGL FBO数据缓冲区6、Android音视频硬编码:生成MP4
三、Android FFmpeg音视频解码篇
1、FFmpeg so库编译2、Android介绍FFmpeg3、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时,系统会回调以下方法:
覆盖乐趣onSurfaceCreated(gl: GL10? config: EGLConfig?) {
}
覆盖乐趣onSurfaceChanged(gl: GL10? width: Int, height: Int) {
}
覆盖乐趣onDrawFrame(gl: GL10?) {
}并且onDrawerFrame方法会被不断调用。这就是我们实现OpenGL绘图过程的地方。
这里我们可以猜测,有没有可能能连续调用的线程是一个while循环线程呢?
答案是:是的。
如果你看GLSurfaceView的源码,你会发现有一个叫做GLThread的线程,在这个线程中初始化了EGL相关的内容。并且在适当的时候,调用了Renderer中的三个方法。
那么,EGL到底是什么?
2,EGL是个啥?
我们知道OpenGL是一组可以操作GPU的API。但它只能操作GPU,无法将图像渲染到设备的显示窗口。那么就需要一个中间层来连接OpenGL和设备窗口,而且最好是跨平台的。
于是EGL就出现了,这是Khronos Group提供的一组独立于平台的API。
3,EGL的一些基础知识
EGLDisplayEGL 定义的抽象系统显示类,用于操作设备窗口。
EGLConfigEGL配置,例如rgba数字
EGLSurface渲染缓存,一块内存空间,所有要渲染到屏幕上的图像数据必须先缓存在EGLSurface上。
EGLContextOpenGL上下文,用于存储OpenGL绘图状态信息和数据。
初始化EGL的过程实际上就是配置上述信息的过程。
二、如何使用EGL
光看上面的介绍,其实很难理解EGL到底是做什么的,或者如何使用EGL。
请大家先思考一个问题如果两个GLSurfaceView同时渲染视频图像,为什么OpenGL能够正确地将图像绘制到两个GLSurfaceView中?
仔细思考OpenGL ES的每一个API。有没有API指定当前图片渲染到哪个GLSurfaceView?
不!
请带着这个问题来阅读下面的内容。
1,封装EGL核心API
首先封装EGL初始化的核心内容(第一节介绍的4个),命名为EGLCoreconst val FLAG_RECORDABLE=0x01
私有常量值EGL_RECORDABLE_ANDROID=0x3142
类EGLCore {
私有val TAG="EGLCore"
//EGL相关变量
私有变量mEGLDisplay: EGLDisplay=EGL14.EGL_NO_DISPLAY
私有变量mEGLContext=EGL14.EGL_NO_CONTEXT
私有变量mEGLConfig: EGLConfig?=空
/**
* 初始化EGL显示
* @param eglContext 共享上下文
*/
有趣的初始化(eglContext: EGLContext?flags: Int){
if (mEGLDisplay !==EGL14.EGL_NO_DISPLAY) {
throw RuntimeException("EGL 已设置")
}
val共享上下文=eglContext? EGL14.EGL_NO_CONTEXT
//1. 创建EGLDisplay
mEGLDisplay=EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
如果(mEGLDisplay===EGL14.EGL_NO_DISPLAY){
throw RuntimeException("无法获取EGL14显示")
}
//2、初始化EGLDisplay
val 版本=IntArray(2)
if (!EGL14.eglInitialize(mEGLDisplay, 版本, 0, 版本, 1)) {
mEGLDisplay=EGL14.EGL_NO_DISPLAY
抛出RuntimeException("无法初始化EGL14")
}
//3.初始化EGLConfig、EGLContext上下文
如果(mEGLContext===EGL14.EGL_NO_CONTEXT){
val config=getConfig(flags, 2) ? throw RuntimeException("无法找到合适的EGLConfig")
val attr2List=intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
val 上下文=EGL14.eglCreateContext(
mEGLDisplay、配置、sharedContext、
attr2列表, 0
)
mEGLConfig=配置
mEGLContext=上下文
}
}
/**
* 获取EGL配置信息
* @param flags 初始化标志
* @param version EGL版本
*/
私人乐趣getConfig(flags: Int, version: Int): EGLConfig? {
var renderableType=EGL14.EGL_OPENGL_ES2_BIT
如果(版本=3){
//配置EGL 3
renderableType=renderableType 或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,可渲染类型,
EGL14.EGL_NONE, 0, //可记录的占位符[@-3]
EGL14.EGL_NONE
)
//配置Android指定标签
if (标志和FLAG_RECORDABLE !=0) {
attrList[attrList.size - 3]=EGL_RECORDABLE_ANDROID
attrList[attrList.size - 2]=1
}
valconfigs=arrayOfNulls(1)
val numConfigs=IntArray(1)
//获取可用的EGL配置列表
if (!EGL14.eglChooseConfig(mEGLDisplay, attrList, 0,
配置,0,配置大小,
numConfigs, 0)) {
Log.w(TAG, "无法找到RGB8888/$版本EGLConfig")
返回空值
}
//使用系统推荐的第一个配置
返回配置[0]
}
/**
* 创建可显示的渲染缓存
* @param surface 渲染窗口的表面
*/
有趣的createWindowSurface(surface:任何): EGLSurface {
if (表面!是表面表面!是表面纹理) {
抛出RuntimeException("无效的surface: $surface")
}
val surfaceAttr=intArrayOf(EGL14.EGL_NONE)
val eglSurface=EGL14.eglCreateWindowSurface(
mEGLDisplay、mEGLConfig、表面、
表面属性, 0)
如果(eglSurface==null){
抛出RuntimeException("表面为空")
}
返回eglSurface
}
/**
* 创建离屏渲染缓存
* @param width 缓存窗口宽度
* @param height 缓存窗口高度
*/
有趣的createOffscreenSurface(width: Int, height: Int): EGLSurface {
val surfaceAttr=intArrayOf(EGL14.EGL_WIDTH, 宽度,
EGL14.EGL_HEIGHT,高度,
EGL14.EGL_NONE)
val eglSurface=EGL14.eglCreatePbufferSurface(
mEGLDisplay、mEGLConfig、
表面属性, 0)
如果(eglSurface==null){
抛出RuntimeException("表面为空")
}
返回eglSurface
}
/**
* 将当前线程绑定到上下文
*/
有趣的makeCurrent(eglSurface: EGLSurface) {
如果(mEGLDisplay===EGL14.EGL_NO_DISPLAY){
throw RuntimeException("EGLDisplay 为null,请先调用init")
}
如果(!EGL14.eglMakeCurrent(mEGLDisplay,eglSurface,eglSurface,mEGLContext)){
抛出RuntimeException("makeCurrent(eglSurface) 失败")
}
}
/**
* 将当前线程绑定到上下文
*/
有趣的makeCurrent(drawSurface: EGLSurface,readSurface: EGLSurface){
如果(mEGLDisplay===EGL14.EGL_NO_DISPLAY){
throw RuntimeException("EGLDisplay 为null,请先调用init")
}
如果(!EGL14.eglMakeCurrent(mEGLDisplay,drawSurface,readSurface,mEGLContext)){
抛出RuntimeException("eglMakeCurrent(draw,read) 失败")
}
}
/**
* 将缓存的图像数据发送到设备进行显示
*/
有趣的swapBuffers(eglSurface: EGLSurface): 布尔值{
返回EGL14.eglSwapBuffers(mEGLDisplay,eglSurface)
}
/**
* 设置当前帧的时间,单位:纳秒
*/
有趣的setPresentationTime(eglSurface: EGLSurface,nsecs:长){
EGLExt.eglPresentationTimeANDROID(mEGLDisplay,eglSurface,纳秒)
}
/**
* 销毁EGLSurface并解绑上下文
*/
有趣的destroySurface(elg_surface: EGLSurface) {
EGL14.eglMakeCurrent(
mEGLDisplay,EGL14.EGL_NO_SURFACE,EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT
)
EGL14.eglDestroySurface(mEGLDisplay, elg_surface);
}
/**
* 释放资源
*/
有趣的释放(){
if (mEGLDisplay !==EGL14.EGL_NO_DISPLAY) {
//Android 的不同寻常之处在于它使用引用计数的EGLDisplay。所以对于
//每个eglInitialize()我们都需要一个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=空
}
}以上就是最基本、最简洁的EGL初始化封装。基本上每种方法都是必要的。
让我们仔细看看:
初始化init分为3步:
EGLDisplay是通过eglGetDisplay创建的。 EGLDisplay通过eglInitialize进行初始化。 EGLDisplay是通过eglCreateContext初始化的。 EGLContext 已初始化。初始化EGLContext时,会调用getConfig方法。
配置上下文getConfig:
根据选择的EGL版本,配置版本标志,初始化配置列表,配置渲染的rgba位数和深度位数,两者是一对,第一个是类型,最后一个是值,必须以EGL14.EGL_NONE 结尾。配置Android 特定属性EGL_RECORDABLE_ANDROID。根据以上配置信息,通过eglChooseConfig,系统会返回匹配的配置信息列表。一般情况下,返回第一配置信息。Android 指定的标志EGL_RECORDABLE_ANDROID告诉EGL 它创建的表面必须与视频编解码器兼容。
如果没有此标志,EGL 可能会使用MediaCodec 无法理解的缓冲区。
这个变量是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。 自定义一个渲染器CustomerRenderclass CustomerGLRenderer : SurfaceHolder.Callback { //OpenGL渲染线程 private val mThread = RenderThread() //页面上的SurfaceView弱引用 private var mSurfaceView: WeakReference? = null //所有的绘制器 private val mDrawers = mutableListOf() 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() } }主要如下: 一个自定义的渲染线程RenderThread一个SurfaceView的弱引用一个绘制器列表初始化时,启动渲染线程。然后就是将SurfaceView生命周期转发给渲染线程,没有其他了。 定义渲染状态/** * 渲染状态 */ enum class RenderState { NO_SURFACE, //没有有效的surface FRESH_SURFACE, //持有一个未初始化的新的surface SURFACE_CHANGE, // surface尺寸变化 RENDERING, //初始化完毕,可以开始渲染 SURFACE_DESTROY, //surface销毁 STOP //停止绘制 }根据这几个状态,在RenderThread中,切换线程的执行状态。 渲染状态切换流程图说明如下: 线程start,进入while(true)循环时,状态为NO_SURFACE,线程进入等待(hold on);Surface create后,状态变为 FRESH_SURFACE ;Surface change后,进入 SURFACE_CHANGE 状态;执行完 SURFACE_CHANGE 后,自动进入 RENDERING 状态;在没有其他中断的情况下,每隔20ms执行一遍Render渲染画面;如果Surface 销毁,重新进入 NO_SURFACE 状态;如有新surface,重新执行2-5;如果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() 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 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,必学!【Android音视频开发进阶:OpenGL视频画面渲染详解】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于要开始学EGL了!之前一直对这个头很大...
有8位网友表示赞同!
这可是Android音视编真核心的部分啊,希望能深入理解...
有8位网友表示赞同!
OpenGL和EGL这对组合实在太强大了!看下我能不能把视频渲染搞懂...
有14位网友表示赞同!
原来还有这么多关于OpenGL的知识点没了解呢,好兴奋...
有20位网友表示赞同!
最近在研究Android开发,正好碰到这篇博客,感觉可以帮到我大忙!
有13位网友表示赞同!
希望能学会用EGL来控制视频播放画面,这太棒了!
有9位网友表示赞同!
看标题感觉很厉害的样子,期待学习新的技术!
有16位网友表示赞同!
对OpenGL渲染视频有了一定了解,但EGL还是挺陌生的...
有9位网友表示赞同!
Android音视频开发是我的目标之一,这次一定要好好学习下
有14位网友表示赞同!
希望文章能详细讲解EGL的使用方法,方便我学习。
有17位网友表示赞同!
看到这篇文章,感觉自己的Android开发之路又要往前迈一大步了
有17位网友表示赞同!
想要在Android上实现更流畅的视频播放体验,肯定得掌握EGL技术的!
有20位网友表示赞同!
每次看到OpenGL和EGL,就觉得科技真是太神奇了!
有10位网友表示赞同!
我需要学习一些新的技术才能完成我的开发项目,这篇文章正好合适!
有11位网友表示赞同!
感觉Android音视频开发越来越有趣了,我要继续探索下去!
有20位网友表示赞同!
学习新的知识总让我充满动力!期待这篇博客的分享。
有16位网友表示赞同!
现在学习EGL比当初自己摸索要方便多了,谢谢作者!
有12位网友表示赞同!
文章内容看起来很专业,应该能帮助到我理解EGL的复杂之处
有16位网友表示赞同!
学习安卓开发需要不断学习新的知识,这篇文章很有帮助!
有11位网友表示赞同!