播放器开发基础知识

开发播放器并不是一件十分困难的事情。在展开这个话题之前,咱们先来看一下什么是播放器。
咱们常用的播放器有很多,比如:暴风影音,VLC,Mplayer,FlashPlayer等等 再如一些比较另类的如各种看图工具,PowerPoint,魔兽回看,VI等等 通常播放器是指把数据经过解析展现给用户的一种软件。

一般情况下,咱们常说的播放器特质视频播放器。 其实视频播放器所做的事情和看图软件等等是类似的,都可以归结为下边的一个流程: 但是视频播放器又有它的特殊性。
可以粗略计算一下 32位色 800 X 600 一张位图 数据量大约为2M,而为了能够保证画面流畅,一般每秒钟需要处理30帧。而目前清晰度较高的视频分辨率能够达到1280甚至更高,处理的数据量就更大了。 而处理一帧所需要的时间平均只有30毫秒,高于这个时间就会影响显示流畅度。
关于如何保证每一帧数据在经历了读取、解析到最终展示所用的总时间不超过30ms,一般广泛采用的方式是使用多线程并行完成。也就是说读取、解析、展示每个环节都是一个独立的线程。假设每一帧每个环节都需要20ms的时间,那么总时间就是60ms。当并行以后,整个过程的时序图就会像下面这样: 可以看到,每一帧所需要的总处理时间依然是60ms,但是两帧之间的间隔变成了只有20ms。

当然,如果其中某一步的时间本身就超过了30ms,还可以把这个线程分得更细,从而提高程序的并行程度,使其最终达到要求。分的更细还有一个好处,这点我们稍后会讨论。
在这之前需要科普一下的是视频文件的存储方式,这里有两个容易混淆的概念,一个是视频数据的编码格式,一个是视频文件格式。先说数据编码格式,前面提到了,视频数据本身数据量比较大,如果把每一帧都按图片的方式存储下来会占用很大的磁盘空间,而且也不利于数据的传输。因此需要依据视频数据的特点,对冗余的数据进行压缩,所使用的压缩算法就是视频数据的编码格式。而这些处理后的数据又通过特定的方式组织起来,比如把视频数据按照特定的时间间隔分割成小的分片,并且建立索引以便进行时移播放等等。这种组织数据的方式就是我们平常所说的文件格式。
所以对于同一种文件格式,视频信息的编码有可能是不一样的。反过来,不同格式的文件,有可能使用了同一种视频编码。
搞清楚这两个概念以后,我们再回到我们的流程划分的问题,把流程分得更细的另一个好处就是有利于我们代码模块的复用。对于不同格式但是编码相同的文件,处理流程可能是像下图的样子 上图中,对于编码A和编码B,分别经过两条不同的解码路径,最终到达了显示设备。而这个过程中,最后两个解析阶段使用的是相同的解析过程。
另外,除了支持多种编码,可能还需要支持不同的显示终端,而不同的显示终端需要不同的显示数据。所以对于不同的显示终端,可能需要选择不同的解码路径。整个路径图于是又变成了下面的样子
在需要在A设备上显示时,不同的编码会经过黄线标注的路径进行解析。而在B设备上进行显示时,不同的编码会经过红线标注的路径进行解析。这看起来有一些复杂,但是事实上还不只是这样。每一个视频流里会同时有视频和音频信息,有的格式还能附加字幕信息,音频信息又可能分为不同语言,或者不同声道。因此实际情况要比这更加复杂。播放器需要能够动态根据不同的编码以及媒体文件中的内容选择不同的解析路径。
现在考虑一种最简单的情况,假设最终我们构建出的解析过程如下图所示: 最终视频数据显示和音频数据播放分别会在不同的线程里进行。为了保证视频和音频播放的同步,需要对这两个线程的处理速度进行控制。媒体数据中每个数据单元都有一个时间戳,以供播放器校准播放速度。如果数据处理的速度高于播放所需要的速度,那么需要缓存已经处理好的数据,等到达指定的时间后再播放该时间对应的数据。如果播放时间已到,而数据还没有准备好,则需要进入等待状态。 正常情况下数据的处理速度是高于播放速度的,处理出来的数据需要保存在内存里以备后续的播放,为了控制播放器的内存占用,需要对缓存的数据大小进行限制。对于数据缓存问题,不同的播放器有不同的策略。
以上讨论的是是开发播放器需要处理的几个主要问题。关于更详细的内容将在后边几讲为大家介绍。