今天来聊聊用GLSurfaceView播视频的事儿。平时咱们用安卓播视频,要么直接扔个VideoView控件上去,要么就是MediaPlayer配个SurfaceView或者TextureView,简单方便。但有时候,就想搞点特殊的,比如想在视频上加些OpenGL的特效啥的,这时候SurfaceView或者TextureView就不太够用,得请出GLSurfaceView这大神。
为啥要折腾GLSurfaceView
主要还是为控制。普通的播放控件,位置、大小甚至画面内容都给你框死,想自己画点东西上去,或者做些滤镜、变形之类的效果,基本没戏。GLSurfaceView就不一样,它本质上就是一块能用OpenGL ES来画图的画布。视频的每一帧,咱可以把它当成一张纹理(Texture),用OpenGL画到这块画布上。这样一来,画面怎么画、画之前加点啥料,就都是咱说算。

开始动手
第一步:准备好画布和画笔
先得有个GLSurfaceView。布局文件里放一个,或者代码里new一个都行。然后,关键是得给它配个Renderer。这Renderer是个接口,里面有三个方法要我们实现:onSurfaceCreated、onSurfaceChanged和onDrawFrame。简单说,就是画布创建好时、画布大小变时、以及每一帧要画图时,系统会分别喊我们去干活。
第二步:准备好视频源
视频播放还是得靠MediaPlayer老哥。创建一个MediaPlayer实例。然后告诉它视频文件在哪,比如SD卡里的某个路径,或者网络上的地址也行。就是干这个的。设置好之后,别忘调用或者prepareAsync()让它准备一下,解码器啥的都得就位。
第三步:搭桥,把视频接到OpenGL

这是最关键的一步。普通的SurfaceView或者TextureView,可以直接通过*(surfaceHolder)或者*(surface)把画面输出过去。但GLSurfaceView不一样,它自己管理OpenGL的渲染,不能直接这么塞。咋办?
这里就得用到一个中间人:SurfaceTexture。这玩意儿挺神的,它可以接收来自MediaPlayer的图像流,并且把这些图像流转成OpenGL能用的纹理。
具体操作大概是这样:
- 在
Renderer的onSurfaceCreated方法里,创建一个OpenGL纹理ID。 - 用这个纹理ID创建一个
SurfaceTexture对象。surfaceTexture = new SurfaceTexture(textureId)。 - 这个
SurfaceTexture内部有个Surface,通过new Surface(surfaceTexture)可以把它拿出来。 - 把这个
Surface设置给MediaPlayer:*(surface)。

这样,MediaPlayer解码出来的视频帧就会源源不断地流向SurfaceTexture。
第四步:在OpenGL里画出来
桥搭好,数据也流过来,一步就是在Renderer的onDrawFrame方法里把视频帧画到GLSurfaceView上。

每次onDrawFrame被调用时:
- 先调用一下。这一步是让
SurfaceTexture去抓取最新的视频帧,更新到它关联的那个OpenGL纹理上。 - 然后就是标准的OpenGL绘制流程:设置视口、清屏、使用我们准备好的shader程序、绑定那个包含视频帧的纹理、设置顶点坐标和纹理坐标、调用
glDrawArrays或者glDrawElements把它画出来。
这里可能还需要处理一下纹理坐标的变换,因为SurfaceTexture输出的纹理可能需要翻转或者旋转才能正确显示,可以用*(mtx)拿到变换矩阵,传给顶点着色器去处理。

收尾
基本上,上面几步搞定,GLSurfaceView就能播放视频。实际做起来还有不少细节要注意,比如线程问题(MediaPlayer的操作最好别放UI线程,SurfaceTexture的操作要在OpenGL线程),资源释放(播放完或者Activity销毁,MediaPlayer、SurfaceTexture、OpenGL资源都要记得释放干净),还有OpenGL的着色器代码也得自己写。
总结一下,用GLSurfaceView播视频,比直接用TextureView要麻烦不少,得多写不少OpenGL相关的代码。但好处就是自由度高,能在视频上玩出花来。如果只是简单播个视频,那还是用老老实实用TextureView或者SurfaceView,省心。要是想搞点特效,那这番折腾就是值得的。
好,这回的实践记录就到这儿,希望能给同样想折腾的朋友一点参考。

