VLC媒体播放器系统结构


VLC是免费和 开源跨平台多媒体播放器,一个可以播放大部分多媒体格式如DVD,CD,VCD和各种媒体流协议的框架。从技术来讲,只是一个处理计算机和网络上的媒体数据的软件包。VLC提供一套直观的API和模块化框架,可以很容易地添加新的编解码器,容器格式和传输协议。


源代码由C语言方面的专家编写。有时非常难以理解。要完全解释VLC播放器工作原理的话,可能需要编写一本书。我将努力用最少的语言文字来说明我对VLC源代码的研究的一些结果。让我们从最顶层的系统结构开始吧。

VLC由一个运行核和很多功能模块组成(200-400不等),VLC不能没有功能模块,很多功能都有由模块提供。主要有下面几个模块:


  • libVLCcore是框架的中心模块。提供一个面向对象的层给c语言写成的模块,进行模块装载,对多媒体操作的功能抽象,如:输入,复用,解复,语音输出,视频输出。
  • modules实现livVLCcore具体的功能,模块按照不同的能力进行分类。如输入管理模块(文件,网络),解码模块(mp3,dirvx),用户接口模块(文本,网页,telnet,等)
  • Externallibraries外部依靠文件,通常为具体的功能模块服务。
  • vlc (主要程序)–播放器主要入口,初始化libVLC,建立和显示用户接口(播放器窗口)。


功能模块

没有功能模块,VLC实际上是没有任何用处的。功能模块可以根据其能够提供的功能分类。每个具体的功能可以认为是类似面向对象的接口。模块装载简单解释如下:当VLC需要一个特别功能的模块(如解码功能)的时候,将打开所有的模块,然后找到一个最合适的。打开的时候按照匹配的分数按照从大到小进行排列,调用模块的Open()函数,如果返回OK,VLC就使用该模块。(更多详情,可以参考”VLC如何装载模块”一文)。下图说明一些主要模块的功能。

VLC 提供不同风格的用户界面。每个界面也是一个模块。与VLC一起发布的界面有基于QT的用于rWindows/Linux的界面, 基于Cocoa/Objective-C用于OSX的界面,基于Ncurses用于DOS控制台的界面和用于Maemo设备的界面等。


下图说明功能模块代码在文件系统中存放结构,以modules/audio_output为例。

audio_output目录下实现了不同语音技术的输出:ALSA,PulseOSS,DirectX(Windows平台)


面向对象的层 – VLC对象


VLC定义了一些管理模块的层,这些层使用面向对象开发方法。VLC对象的定义如下:

/*****************************************************************************
 * The vlc_object_t type. Yes, it's that simple :-)
 *****************************************************************************/
/** The main vlc_object_t structure */
struct vlc_object_t
{
    VLC_COMMON_MEMBERS
};

VLC_COMMON_MEMBERS is a C“#define” coded as the following:

/* VLC_COMMON_MEMBERS : members common to all basic vlc objects */
#define VLC_COMMON_MEMBERS                                       \
/** \name VLC_COMMON_MEMBERS                                     \
 * these members are common for all vlc objects                  \
 */                                                              \
/**@{*/                                                          \
  const char *psz_object_type;                                   \
                                                                     \
  /* Messages header */                                          \
  char *psz_header;                                              \
  int  i_flags;                                                  \
                                                                     \
  /* Object properties */                                        \
  volatile bool b_die;                /**< set by the outside */ \
  bool b_force;   /**< set by the outside (eg. module_need()) */ \
                                                                     \
  /* Stuff related to the libvlc structure */                    \
  libvlc_int_t *p_libvlc;         /**< (root of all evil) - 1 */ \
                                                                     \
  vlc_object_t *  p_parent;                   /**< our parent */ \
                                                                     \
/**@}*/

src/misc/objects.c里面,定义了VLC对象的创建,销毁和查找方法:vlc_custom_create(..),vlc_object_destroy(...),vlc_object_find(...).例如,下面的例子创建一个心的VLC对象:

p_libvlc = vlc_custom_create( (vlc_object_t *)NULL, 
                        sizeof (*priv),VLC_OBJECT_GENERIC, "libvlc" );

LibVLC 实例

VLC库的根类是libvlc实例,该类定义如下:

struct libvlc_instance_t
{
  libvlc_int_t *p_libvlc_int;
  libvlc_vlm_t  libvlc_vlm;
  unsigned      ref_count;
  int           verbosity;
  vlc_mutex_t   instance_lock;
  struct libvlc_callback_entry_list_t *p_callback_list;
};

这里 libvlc_int_t为一个vlc对象。在初始化过程中,指向一个类型为libvlc_priv_t的对象。


这个libvlc_priv_t类型包含库主要的数据和结构。如playlist_t(src/playlist/)为一个主要的抽象结构,表示媒体播放清单。vlm_t是服务器运行核的根对象,允许多个媒体流同时运行。VLC可以有不同的用户界面,这些界面模块功过p_intf变量指向的对象进行访问。

/**
 * Private LibVLC instance data.
 */
typedef struct libvlc_priv_t
{
    . . . . . .
    . . . . . . 
    bool               playlist_active;

    /* Messages */
    msg_bank_t        *msg_bank;    ///< The message bank

    /* Singleton objects */
    module_t          *p_memcpy_module;  ///< Fast memcpy plugin used
    playlist_t        *p_playlist; //< the playlist singleton
    vlm_t             *p_vlm;  ///< the VLM singleton (or NULL)
    vlc_object_t      *p_dialog_provider; ///< dialog provider
    httpd_t           *p_httpd; ///< HTTP daemon (src/network/httpd.c)
    . . . . . . 
    . . . . . .

    /* Interfaces */
    struct intf_thread_t *p_intf; ///< Interfaces linked-list

} libvlc_priv_t;

模块抽象和通信

正如当前谈到的,VLC是有一套功能模块组成。在开发过程中,主要分两个方面:

  • 初始化。调用open()close()函数。
  • 实现功能。实现每个具体的功能需要的方法。

例如,语音输出类模块需要实现下面的方法:

..................
int aout_VolumeDown( vlc_object_t * p_object, int i_nb_steps,audio_volume_t * pi_volume )
int aout_VolumeUp( vlc_object_t * p_object, int i_nb_steps, audio_volume_t * pi_volume )
int aout_VolumeGet( vlc_object_t * p_object, audio_volume_t * pi_volume )
int aout_ToggleMute( vlc_object_t * p_object, audio_volume_t * pi_volume )
..................

视频输出类的模块需要实现下面的方法:

....................
picture_t *vout_CreatePicture( vout_thread_t *p_vout, bool b_progressive,bool b_top_field_first,
                               unsigned int i_nb_fields )
void vout_DisplayPicture( vout_thread_t *p_vout, picture_t *p_pic )
void DestroyPicture( vout_thread_t *p_vout, picture_t *p_picture )
void vout_DropPicture( vout_thread_t *p_vout, picture_t *p_pic  )
....................

另外一个VLC的基本概念是:按照“察看者/可察看“结构开发,这样可以降低模块之间的耦合度。这个结构通过VLC变量的这种概念实现。

所谓VLC变量是一个可以设置为任何VLC对象的值。令人感兴趣的是,这些变量可以激活回调函数。下面说明变量如何工作的:


  • 首先创建变量,使用 var_Create(p_libvlc, name, VLC_VAR_INTEGER )p_libvlc为指向vlc对象的指针,name为变量名称,VLC_VAR_INTEGER为类型。
  • 当需要设置变量的时候,调用var_SetInteger(p_libvlc, name, temp_value)之类的函数。temp_value是需要设置的值。对应地,可以使用var_getInteger将数据读取回来。
  • var_AddCallback(vlc_object_t *p_this, const char *psz_name,vlc_callback_tpf_callback, void *p_data )可以设置回调函数,psz_name为变量名称,pf_callback为函数指针,p_data为通用的函数指针,指向用户数据。
  • 当改变一个变量的值的时候,TriggerCallback(..)方法将会被调用,这样变量的察看者将会获得通知。

这个机制如何具体实现的呢?让在VLC的ncurses文本用户界面模块里面有一段代码:var_AddCallback(p_playlist, "playlist-item-append", PlaylistChanged, p_intf); 该代码的作用是:当 “playlist-item-append”变量发生变化时,PlaylistChanged()回调函数将会被调用。


这就是”察看者/可察看”结构的复杂执行的执行机制。