今天来聊聊用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
,省心。要是想搞点特效,那这番折腾就是值得的。
好,这回的实践记录就到这儿,希望能给同样想折腾的朋友一点参考。