首先,這一系列文章均基於本身的理解和實踐,可能有不對的地方,歡迎你們指正。
其次,這是一個入門系列,涉及的知識也僅限於夠用,深刻的知識網上也有許許多多的博文供你們學習了。
最後,寫文章過程當中,會借鑑參考其餘人分享的文章,會在文章最後列出,感謝這些做者的分享。android
碼字不易,轉載請註明出處!git
教程代碼:【Github傳送門】 |
---|
EGL做爲OpenGL與本地窗口渲染的中間橋樑,不少時候是不會被剛入門OpenGL的開發者關注的,甚至有點忽略了。隨着學習的深刻,EGL將是不得不面對的東西。本文將介紹EGL是什麼,有什麼用,以及如何使用EGL。github
做爲Android開發者,EGL彷彿是一個很陌生的東西,爲何?api
都怪Android的GLSurfaceView封裝的太好了。哈哈哈~數組
前面的文章就介紹過,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到底是個什麼東西呢?
咱們知道OpenGL是一組能夠操做GPU的API,然而僅僅可以操做GPU,並不可以將圖像渲染到設備的顯示窗口上。那麼,就須要一箇中間層,鏈接OpenGL與設備窗口,而且最好是跨平臺的。
因而EGL出現了,由Khronos Group提供的一組平臺無關的API。
EGL定義的一個抽象的系統顯示類,用於操做設備窗口。
EGL配置,如rgba位數
渲染緩存,一塊內存空間,全部要渲染到屏幕上的圖像數據,都要先緩存在EGLSurface上。
OpenGL上下文,用於存儲OpenGL的繪製狀態信息、數據。
初始化EGL的過程其實就是配置以上幾個信息的過程。
單單看上面的介紹,其實仍是比較難理解EGL究竟有什麼做用,或者應該怎麼樣去使用EGL。
請你們先思考一個問題
若是同時有兩個GLSurfaceView在渲染視頻畫面,OpenGL爲何可以正確的把畫面分別繪製到兩個GLSurfaceView中?
仔細回想一下OpenGL ES的每一個API,有沒有哪一個API是指定當前畫面是渲染到哪一個GLSurfaceView的?
沒有!
請帶着這個疑問,閱讀下面的內容。
首先,對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個步驟:
其中,在初始化EGLCongtext的時候,調用了getConfig方法。
配置上下文getConfig:
Android 指定的標誌EGL_RECORDABLE_ANDROID
告訴EGL它建立的surface必須和視頻編解碼器兼容。
沒有這個標誌,EGL可能會使用一個MediaCodec不能理解的Buffer。
這個變量在api26之後系統才自帶有,爲了兼容,咱們本身寫好這個值0x3142。
建立EGLSurface,分爲兩種模式:
第一種是最經常使用的,一般將頁面上的SurfaceView持有的Surface,或SurfaceTexture傳遞進去進行綁定。這樣OpenGL處理的圖像數據就能夠顯示在屏幕上。
第二種用於離屏渲染,也就是將OpenGL處理的圖像數據保存在緩存中,不會顯示到屏幕上,可是整個渲染流程和普通模式同樣,這樣能夠很好的處理一些用戶不須要看見的圖像數據。
綁定OpenGL渲染線程與繪製上下文:makeCurrent
到這裏,使用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
解綁數據緩存表面,以及釋放資源
上面的僅僅作了核心API的封裝,接下來要新建一個類來調用它。
這裏,新建一個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操做方法給外部進行調用。
爲了更好的認識EGL,這裏經過模擬GLSurfaceView來了解如何使用EGL。
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()
}
}
複製代碼
主要以下:
初始化時,啓動渲染線程。而後就是將SurfaceView生命週期轉發給渲染線程,沒有其餘了。
/** * 渲染狀態 */
enum class RenderState {
NO_SURFACE, //沒有有效的surface
FRESH_SURFACE, //持有一個未初始化的新的surface
SURFACE_CHANGE, // surface尺寸變化
RENDERING, //初始化完畢,能夠開始渲染
SURFACE_DESTROY, //surface銷燬
STOP //中止繪製
}
複製代碼
根據這幾個狀態,在RenderThread中,切換線程的執行狀態。
說明以下:
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資源,並退出線程。
新建頁面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,那麼你的OpenGL生涯將是不完整的,由於你始終沒法深入的認識到OpenGL渲染機制是怎樣的,那麼在處理一些的問題的時候,就會顯得很無力。
若是你須要使用到Android Mediacodec的編碼能力,那麼EGL就是必不可少的東西,在後續的關於視頻編碼的文章中,你將會看到如何使用EGL來實現編碼。
在JNI層,Android並無實現一個相似GLSurfaceView的工具,來幫咱們隱藏EGL相關的內容。所以,若是你須要在C++層實現FFmpeg的編解碼,那麼就須要本身去實現整個OpenGL的渲染流程。
這纔是學習EGL的真正目的,若是隻是用於渲染視頻畫面,GLSurfaceView已經足夠咱們使用了。
因此,EGL,必學!