写点什么

Android 进阶:自定义视频播放器开发(上)

发布于: 2021 年 11 月 07 日

视频播放内核

我们知道自己开发视频播放器


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


内核肯定是不现实的,这需要一定的技术成本,单个人很难达到,所以我们就选择一个最受欢迎的开源的内核即可:bilibili 开源的视频播放器:ijkplayer

视频播放器

视频播放这块需要给大家普及两个知识点:

SurfaceView

先来介绍一下大部分软件如何解析一段视频流。首先它需要先确定视频的格式,这个和解码相关,不同的格式视频编码不同,不是这里的重点。知道了视频的编码格式后,再通过编码格式进行解码,最后得到一帧一帧的图像,并把这些图像快速的显示在界面上,即为播放一段视频。SurfaceView 在 Android 中就是完成这个功能的。


既然 SurfaceView 是配合 MediaPlayer 使用的,MediaPlayer 也提供了相应的方法设置 SurfaceView 显示图片,只需要为 MediaPlayer 指定 SurfaceView 显示图像即可。它的完整 API 如下:


void setDisplay(SurfaceHolder sh);


它需要传递一个 SurfaceHolder 对象,SurfaceHolder 可以理解为 SurfaceView 装载需要显示的一帧帧图像的容器,它可以通过 SurfaceHolder.getHolder()方法获得。


使用 MediaPlayer 配合 SurfaceView 播放视频的步骤与播放使用 MediaPlayer 播放 MP3 大体一致,只需要额外设置显示的 SurfaceView 即可。

SurfaceView 双缓冲

上面有提到,SurfaceView 和大部分视频应用一样,把视频流解析成一帧帧的图像进行显示,但是如果把这个解析的过程放到一个线程中完成,可能在上一帧图像已经显示过后,下一帧图像还没有来得及解析,这样会导致画面的不流畅或者声音和视频不同步的问题。所以 SurfaceView 和大部分视频应用一样,通过双缓冲的机制来显示帧图像。那么什么是双缓冲呢?双缓冲可以理解为有两个线程轮番去解析视频流的帧图像,当一个线程解析完帧图像后,把图像渲染到界面中,同时另一线程开始解析下一帧图像,使得两个线程轮番配合去解析视频流,以达到流畅播放的效果。


下图为演示了双缓冲的过程,线程 A 和线程 B 配合解析渲染视频流的帧图像:


SurfaceHolder

SurfaceView 内部实现了双缓冲的机制,但是实现这个功能是非常消耗系统内存的。因为移动设备的局限性,Android 在设计的时候规定,SurfaceView 如果为用户可见的时候,创建 SurfaceView 的 SurfaceHolder 用于显示视频流解析的帧图片,如果发现 SurfaceView 变为用户不可见的时候,则立即销毁 SurfaceView 的 SurfaceHolder,以达到节约系统资源的目的。


如果开发人员不对 SurfaceHolder 进行维护,会出现最小化程序后,再打开应用的时候,视频的声音在继续播放,但是不显示画面了的情况,这就是因为当 SurfaceView 不被用户可见的时候,之前的 SurfaceHolder 已经被销毁了,再次进入的时候,界面上的 SurfaceHolder 已经是新的 SurfaceHolder 了。所以 SurfaceHolder 需要我们开发人员去编码维护,维护 SurfaceHolder 需要用到它的一个回调,SurfaceHolder.Callback(),它需要实现三个如下三个方法:


  • void surfaceDestroyed(SurfaceHolder holder):当 SurfaceHolder 被销毁的时候回调。

  • void surfaceCreated(SurfaceHolder holder):当 SurfaceHolder 被创建的时候回调。

  • void surfaceChange(SurfaceHolder holder):当 SurfaceHolder 的尺寸发生变化的时候被回调。


在应用中分别为 SurfaceHolder 实现了这三个方法,先进入应用,SurfaceHolder 被创建,创建好之后会改变 SurfaceHolder 的大小,然后按 Home 键回退到桌面销毁 SurfaceHolder,最后再进入应用,重新创建 SurfaceHolder 并改变其大小。

SurfaceView 的优点:

如上面所说,SurfaceView 可以在一个独立的线程中进行绘制,不会影响主线程,并且使用双缓冲机制,播放视频时画面更流畅。

SurfaceView 的缺陷:

因为这个 Surface 不在 View hierachy 中,它的显示也不受 View 的属性控制,所以不能进行平移,缩放等变换,也不能放在其它 ViewGroup 中,一些 View 中的特性也无法使用。

TextureView

与 SurfaceView 一样继承 View,它可以将内容流直接投影到 View 中,可以用于实现 Live preview 等功能。


和 SurfaceView 不同的是,它不会在 WMS 中单独创建窗口,而是作为 View hierachy 中的一个普通 View,因此可以和其它普通 View 一样进行移动,旋转,缩放,动画等变化。


值得注意的是 TextureView 必须在硬件加速的窗口中。它显示的内容流数据可以来自 App 进程或是远端进程。从类图中可以看到,TextureView 继承自 View,它与其它的 View 一样在 View hierachy 中管理与绘制。

SurfaceTexture

TextureView 重载了 draw()方法,其中主要 SurfaceTexture 中收到的图像数据作为纹理更新到对应的 HardwareLayer 中。SurfaceTexture.OnFrameAvailableListener 用于通知 TextureView 内容流有新图像到来。SurfaceTextureListener 接口用于让 TextureView 的使用者知道 SurfaceTexture 已准备好,这样就可以把 SurfaceTexture 交给相应的内容源。

Surface

Surface 为 BufferQueue 的 Producer 接口实现类,使生产者可以通过它的软件或硬件渲染接口为 SurfaceTexture 内部的 BufferQueue 提供 graphic buffer。

评论

发布
暂无评论
Android进阶:自定义视频播放器开发(上)