vlc架构剖析
1. VideoLan简介
1.1 videolan组成
Videolan有以下两部分组成:
VLC:一个最主要的部分,它可以播放各种类型的媒体文件和流媒体文件,并且可以创造媒体
流并保存成各种格式的媒体文件,这些文件的质量要比没保存前的件好。videolan作为客户
端可以播放本地文件,httP://,rtsp://。
VLS:是一种流服务器,专门用来解决流的各种问题,它也具有一些VLC的特征。videolan作
为服务器可以输出httP,rtP,rtsp的流。
1.2 VLC优点
VLC是一种跨平台的媒体播放器和流媒体服务器,最初为videolan的客户端,它是一种
非常简便的多媒体播放器,它可以用来播放各种各样的音视频的格式文件(MPEG-1、MPEG-
2、MPEG- 4、DivX、WMV、mp3、OGG、Vorbis、AC3、AAC等等)流媒体协议,最具特色的
功能是可以边下载边观看Divx媒体文件,并可以播放不完全的AVI文件。并且支持界面的
更改。VLC支持多种的操作系统,linux(rh9,Debian,Mandrake,Gentoo),BSD,windows,
Mac OS X,Be OS,Solaris等等。支持带菜单的VCD,SVCD,和DVD,数字卫星频道、数字
地球电视频道(digital terrestrial television channels),在这些操作系统下通过宽带IPv4、IPv6
网络播放线上影片。此软件开发项目是由法国学生所发起的,参与者来自于世界各地,设计
了多平台的支持,可以用于播放网络流媒体及本机多媒体文件,特别是它能直接播放未下载
完整的多媒体文件。
下图表示出了VideoLan的解决方案:
VideoLan Client是VideoLan项目(一个完整的MPEG-2客户/服务器解决方案)的一个组成
部分。不过VideoLan Client也可以作为一个独立的程序来播放来自硬盘或者DVDROM的
MPEG数据流。它目前支持GTK+、GNOME、KDE和QT,并且可以使用X11、Xvideo、SDL
或者DirectX作为视频输出。对于声音,VideoLan Client支持OSS、ALSA和ESD。要访问DVD,
VideoLan Client使用的是Libdvdcss库。它是一个简单的专为DVD访问设计的库。它可以像
访问块设备一样访问DVD,而不用考虑解密问题。
2. VLC整体架构分析
2.1 LibVLC
LibVLC是VLC的核心部分。它是一个提供接口的库,比如给VLC提供些功能接口:流的
接入,音频和视频输出,插件管理,线程系统。所有的LibVLC源码位于src\及其子目录:
Interface/:包含与用户交互的代码如按键和设备弹出。
Playlist/:管理播放列表的交互,如停止,播放,下一个,或者随机播放。
Input/:打开一个输入组件,读包,解析它们并且将被还原的基本流传递给解器。
Video_output/:初始化video显示器,从解码器得到所有的图片和子图片(如subtitles)。随意
将它们转换为其它格式(如:YUV到RGB)并且播放。
Audio_output/:初始化音频mixer(混合器)。如:发现正确的播放频率,然后重新制作从解码器
接收过来的音频帧。
Stream_output/:类似Audio_output。
Misc/:被libvlc其它部分使用的杂项,如线程系统,消息队列,CPU探测,对象查询系统,
或者特定平台代码。
2.2 VLC
VLC是一个纯粹围绕着LibVLC写成的程序。它是非常小的,但是功能很齐全的媒体播放
器,归功于LibVLC的动态组件支持。
2.3 组件
组件位于modules\子目录,在运行时被加载。每一个组件提供不同的特征适应特定的
文件的环境。另外,大量的不断编写的可移植功能位于audio_output\,vidco_output\和
interface\组件,以支持新的平台(如:BeoS Mae OS X)。
组件中的插件被位于src\misc\modules.c和include\modules*.h中的函数动态加载和卸
载。写组件的API描述如下,共3种:
(l)组件描述宏:声明组件具有哪种优先级的能力(接口,demux2等等),还有GUI组件的
实现参数,特定组件的配置变量,快捷方式,子组件等等;
(2)Open(vlc_objeet_t*p_object):被VLC调用初始化这个组件,它被组件描述宏赋值给了
结构体module_t中的pf_activate函数指针,被Module_Need调用;
(3)Close(vlc_objeet_t*p_object):被VLC调用负初始化这个组件,保证消耗Open分配的所
有资源。它被组件描述宏赋值给了结构体module_t中的pf_deactivate函数指针,被
Module_Unneed调用。
用LibVLC写的组件能够直接被编译进VLC,因为有的OS不支持动态加载代码。被静态
编译进VLC的组件叫做内置组件。
2.4 线程分析
(l)线程管理:
VLC是一个密集的多线程应用。由于解码器必须预先清空和播放工序必须预先做好流程
(比如说解码器和输出必须被分开使用,否则无法保证在要求的时间里播放文件),因此VLC
不采用单线程方法。目前不支持单线程的客户端,多线程的解码器通常就意味着更多的开销
(各线程共享内存的问题等),进程间的通信也会比较复杂。
VLC的线程结构基于pthreads线程模型。为了可移植的目的,没有直接使用pthreads
函数,而是做了一系列类似的包裹函数:vlc_thread_create,vlc_thread_exit,vlc_thread_join,
vlc_mutex_init,vlc_mutex_lock,vlc_mutex_unlock,vlc_mutex_destroy,vlc_cond_init,
vlc_cond_signal,vlc_cond_broadcast,vlc_cond_wait,vlc_cond_destroy和类似结
构:vlc_thread_t,vlc_mutex_t,and vlc_cond_t。
(2)线程同步:
VLC的另一个关键特征就是解码和播放是异步的:解码由一个解码器线程工作,播放由音
频输出线程或者视频输出线程工作。这个设计的主要目的是不会阻塞任何解码器线程,能够
及时播放正确的音频帧或者视频帧。这样实现也导致产生了在接口,输入,解码器和输出之
间的一个复杂的通讯结构。
虽然当前接口并不允许,但是让若干个输入和视频输出线程在同一时刻读取多个文件是
可行的(这是VLC未来改进的主要方向)。现在的客户端就是用这种思想实现的,这就意味着
如果没有用到全局锁的话那么一个不能重入的库是不能被使用的(尤其是liba52库)。
VLC输出的流里包含时间戳,被传递给解码器,所有有时间戳标记的流也均被记录,这
样输出层可以正确及时的播放这些流。时间mtime_t是一个有符号的64-bit整形变量,单位
是百万分之一秒,是从1970年7月1日以来的绝对时间。
当前时间能够被mdate()函数恢复。一个线程可以被阻塞到mwait(mtime_t date)等到一
个确定的时间才被执行。也可以用msleep(mtime_t delay)休眠一段时间。如果有重要的事情
要处理的话,那么应该在正常时间到来之前被唤醒(如色度变换)。例如在
modules\codec\mpeg_vldeo\synchro.c中,通常的解码时间被记录,保证图像被即时解码。
3. VLC接口技术分析
3.1 VLC运行过程
通过对相关资料和自己的分析,VLC的运行过程如下:
ELF(Linux下可执行文件的格式)先被动态加载,然后主线程就变成了接口线程并且在
src/interface/interface.c中开始。它执行下列步骤:
1.cpu探测:什么型号?所有能力(MMX,MMXEXT,3DNow,AltiVec等等)
2.消息接口初始化;
3.命令行选项解析组件
4.创建播放列表
5.仓库初始化
6.加载所有内置和动态组件
7.打开接口
8.安装信号处理器:SIGHUP,SIGINT和SIGQUIT(捕获一个,忽略后来的并退出)。
9.派生音频输出线程;
10.派生视频输出线程;
11.主循环:事件管理;
下图表示了这些步骤的执行过程:
VLC的运行过程图
地址 http://www.yanfaw.com/technology/201105/10/197.html
3.2 消息接口
由于printf()函数不是线程安全的,因此在调用printf()函数时一个线程的执行将会受到
干扰,当这个线程被另一个函数所调用时就会其状态被破坏而退出程序。所以VLC构造了自
己的线程安全的消息接口。
VLC的线程安全的消息接口有两种实现方式:如果在config.h里定义了INTF_MSG_QUEUE
的话,每一个类似printf()的函数将会把排队的消息放到链表里,这个链表将会在事件循环
中被线程接口用红色标记的方式打印出来。如果INTF_MSG_QUEUE没被定义的话,调用线
程将会获得一个print lock(用来防止在同一时刻有两个printf操作被执行)同时直接打印出消
息(默认操作)。
以下为VLC线程安全消息的API:
QueueMsg:添加一条消息到消息队列,如果消息队列满了,先打印所有的消息;
FlushMsg:打印所有在消息队列里的消息,特别的,消息队列必须被提前加锁,因为该
函数不检查锁。
PrintMsg:打印一条消息到stderr,可以打印彩色消息。
3.3 命令行选项
VLC用GNU的getopt解析命令行选项。Getopt结构定义在src\extras\getopt.h里。所有
的配置也可以用环境变量改变:调用函数main_Put*Variable和main_Get*Variable。所
以,.\vlc--height=240和 .\vic_height=240./vlc(这种方式用于所有地方,包括插件)是一样的。
但是为了线程安全的考虑,当第二个线程派生了,main_Put*Variable便不能被使用了。