VLC学习

来源:互联网 发布:anaconda linux 下载 编辑:程序博客网 时间:2024/06/03 09:27

dvbpsi

psi.c: psi section structure
dvbpsi.c: 抽象成DVB/PSI decoders,封装出接口,供应用层调用。
descriptor.c: 各种描述符数据的抽象
demux.c: 解复用器
descriptor/: 各种描述符的解析
tables/: 各种psi子表解析的具体实现
每个解码器被划分成两个实体:即the PSI decoder和the specific decoder。之所以如此划分的原因是,每个psi表的section都有相同的格式。
     PSI解码器:主要任务就是获取应用层提供的ts流数据包( STB则是根据底层的解码器芯片获取ts流),并将完整的psi section(段)发送给专用的解码器解析。对于不连续的ts流,PSI解码器也必须稳定可靠的工作,并将ts流交给专用的解码器处理。
    专用解码器(specific decoder):主要任务就是根据psi解码器提供的psi sections,重建完整的表(PSI/SI)并将 他们返回给应用层处理(STB通常是存入相应的database),同时还要根据psi decoder的指示去检查ts的完整性(作CRC校验)。如果不完整,则返回错误。
    PSI decoder可理解为对每个具体专用解码器相同特征或行为的抽象,也就是抽象出一个类:decoder,而每个具体的decoder则是具体的类的对象或实例,所以要具体实现。用C语言的解释就是抽象出decoder的接口(Interface),要使用哪个解码器则传入不同的回调函数/函数指针(callback)。

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探测,对象查询系统,或者特定平台代码。
组件
  组件位于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的组件叫做内置组件。

视频播放的基本原理

  当初看VLC代码花了不少时间,其中很大的原因是不太了解视频播放的基本原理。现在看来,几乎所有的视频播放器,如VLC、MPlayer、Xine,包 括DirectShow,在播放视频的原理和架构

上都是非常相似的,理解这个对理解VLC的源码会有事半功倍的效果。
    大致的来说,播放一个视频分为4个步骤:
    1.  acess 访问,或者理解为接收、获取、得到
    2. demux 解复用,就是把通常合在一起的音频和视频分离(还有可能的字幕)  
    3. decode 解码,包括音频和视频的解码
    4. output 输出,也分为音频和视频的输出(aout和vout)

    拿播放一个UDP组播的MPEG TS流来说吧,access部分负责从网络接收组播流,放到VLC的内存缓冲区中,access模块关注IP协议,如是否IPv6、组播地址、组播协议、 端口等信息;如果检测出来是RTP协议(RTP协议在UDP头部简单得加上了固定12个字节的信息),还要分析RTP头部信息。这部分可以参看VLC源码 /modules/access/udp.c 。在同目录下还可以看到大量的access模块,如file、http、dvd、ftp、smb、tcp、dshow、mms、v4l…等等。
    而demux部分首先要解析TS流的信息。TS格式是MPEG2协议的一部分,概括地说,TS通常是固定188字节的一个packet,一个TS流可以包 含多个program(节目),一个program又可以包含多个视频、音频、和文字信息的ES流;每个ES流会有不同的PID标示。而又为了可以分析这 些ES流,TS有一些固定的PID用来间隔发送program和es流信息的表格:PAT和PMT表。关于TS格式的详细信息可以去google一下。VLC专门做了一个独立的库libdvbpsi来解析和编码TS流,而调用它的代码可以参见VLC源码 /modules/demux/ts.c。
    其实之所以需要demux,是因为音视频在制作的时候实际上都是独立编码的,得到的是分开的数据,为了传输方便必须要用某种方式合起来,这就有了各种封装 格式也就有了demux。
    demux分解出来的音频和视频流分别送往音频解码器和视频解码器。因为原始的音视频都是占用大量空间,而且冗余度较高的数据,通常在制作的时候就会进行 某种压缩。这就是我们熟知的音视频编码格式,包括MPEG1(VCD)、MPEG2(DVD)、MPEG4、H.264、rmvb等等。音视频解码器的作 用就是把这些压缩了的数据还原成原始的音视频数据。VLC解码MPEG2使用了一个独立的库libmpeg2,调用它的源文件是 /modules/codec/libmpeg2.c。VLC关于编解码的模块都放在/modules/codec目录下,其中包括著名的庞大的 ffmpeg。
    解码器,例如视频解码器输出的是一张一张的类似位图格式的图像,但是要让人从屏幕看得到,还需要一个视频输出的模块。当然可以像一个Win32窗口程序那 样直接把图像画到窗口DC上——VLC的一个输出模块WinGDI就是这么干的,但是通常这太慢了,而且消耗大量的CPU。在Windows下比较好的办法是用DirectX的接口,会自动调用显卡的加速功能。
    这样的功能分解使得模块化更容易一点,每个模块住需要专注于自己的事;从整体来说功能强大而且灵活。
    但是事情总是不会那么简单。就拿access来说,媒体的访问是分层的,如RTSP就涉及到IPv4、TCP、UDP、RTCP、RTSP等多个层次的协 议。有些视频格式包括了传输、封装格式和编辑码格式如MPEG系列,有些封装格式是独立的容器,但是很多人会误解它是编解码格式,如mkv、avi这些。
    音频和视频在demux之后就是独立的,但是需要有一套机制把它们同步起来。同时我们需要有一套机制来控制速度、暂停、停止、跳进,获取各种媒体信息,这 些都是很复杂而又很重要的事情。
    另外也许需要在某个地方插入一些修改,来实现某种效果。如音频的EQ,视频的亮度调整之类的,VLC专门设计了access_filter、 audio_filter和video_filter类型的模块来做这一类事情。
    VLC比较独特的地方是集成了原来的VLS的功能,这依赖于VLC中stream_output类型的模块,它们可以把正在播放的视频以某种方式重新转码 和发送出去,如http、UDP、文件等等。
    MPlayer的结构与此是类似的,如/stream目录对应的是access的功能,/mpdemux对应的demux功能,/libmpcodecs 是解码器,/libvo和/libao2分别是视频和音频的输出。
    DirectShow也是类似的,不过分类更多一些更复杂一点。DirectShow里面的模块叫做“filter”,filter之间通过”pin”来 连接。access的模块对应于DirectShow中的Source FIlter,这一类Filter只有输出pin没有输入pin。demux模块对应于splitter filter,这种filter有一个输入pin,多个输出pin。解码模块是一类transform filter,有一个输入pin、一个输出pin,输出模块对应于readering filter,有一个输入pin,没有输出pin。当然transform filter不一定是解码器,也可能是某种其他的处理。


线程分析
(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中,通常的解码时间被记录,保证图像被即时解码。
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使用的问题

VLC播放RTSP视频延迟
解决方法:
1>PC平台,找到工具->首选项,然后参数设置左下角选择“全部”,左边选择 “输入编解码”->“网络缓存”选项,可以根据具体需要加以修改,不过这个值不要太小,否则缓存太小,播放视频的过程中会很卡。如果网络环境较好,300ms以内效果很好,实时性较好。
2>LINUX平台,linux平台上的vlc可以以命令行的方式来运行,而命令行是可以带参数的,我们只需要在参数里面指定这个延迟时间就可以了,下面是具体的命令行形式,大家可以根据各自需要加以调整 vlc rtsp://192.168.1.1:8556/test.avi  --newwork-caching=300。

原创粉丝点击