嵌入式 vlc源码分析锦集一

原创 2013年12月02日 14:42:47

VLC源码分析

目录

1 VLC源码结构

vlc核心的是libvlc,它提供界面,应用处理功能,所有的libvlc的源代码都放在src目录及其子目录

1.1 ./config/

从命令行和配置文件中加载配置

1.2 ./control/

提供动作控制功能,如播放等操作

1.3  ./extras/

大多是平台的特殊代码

1.4  ./modules/

模块管理

1.5  ./network/

提供网络接口(socket管理,网络接口)

1.6  ./osd/

显示屏幕上的操作

1.7  ./test/

libvlc测试模块

1.8  ./text/

字符集

1.9  ./interface/

提供代码中可以调用的接口,如按键后的硬件作出反应

1.10  ./playlist/

管理播放功能

1.11  ./input/

建立并读取一个输入流,并且分离其中的音频和视频,然后把分离好的音频和视频流发给解码器

1.12  ./audio_output/

初始化音频混合器,即设置正确的同步频率,并对从解码器传来的音频流重新取样

 

1.13  ./video_output/

初始化视频播放器,把从解码器得到视频画面转化格式从yuv到rgb,然后播放

1.14  ./stream_output/ 

输出音频流和视频流到网络

1.15  ./misc/

libvlc使用的其他部分功能,如线程系统,消息队列等.

2 configure详解

 

概述

VLC属于Video LAN开源项目组织中的一款全开源的流媒体服务器和多媒体播放器。作为流媒体服务器,VLC跨平台,支持多操作系统和计算机体系结构;作为多媒体播放器,VLC可以播放多种格式的媒体文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多种常见媒体格式。

VLC采用全模块化结构,在系统内部,通过动态的载入所需的模块,放入一个module_bank的结构体中统一管理,连VLC的Main模块也是通过插件的方式动态载入的(通过module_InitBank函数在初始化建立module_bank时)。对于不支持动态载入插件的系统环境中,VLC也可以采用builtin的方式,在VLC启动的时候静态载入所需要的插件,并放入module_bank统一管理。

VLC 的模块分成很多类别主要有:access、access_filter、access_output、audio_filter、audio_mixer、audio_output、codec、control、demux、gui、misc、mux、packetizer、stream_output、video_filter、video_output、interface、input、playlist等(其中黑体为核心模块)。VLC无论是作为流媒体服务器还是多媒体播放器,它的实质思路就是一个“播放器”,之所以这么形象描述,是因为(The core gives framework to do the media processing, from input (files, network streams) to output (audio or video, on ascreen or network), going through various muxers, demuxers, decoders and filters. Even the interfaces are plugins for LibVLC. It is up to the developer to choose which module will be loaded. 摘于官网说明) 它实质处理的是ES、PES、PS、TS等流间的转换、传输与显示。对于流媒体服务器,如果从文件作为输入即:PS->DEMUX->ES->MUX->TS;对于多媒体播放器如果采用UDP方式传输即:TS->DEMUX->ES。

1. 插件管理框架

在VLC中每种类型的模块中都有一个抽象层/结构体,在抽象层或结构体中定义了若干操作的函数指针,通过这些函数指针就能实现模块的动态载入,赋值相关的函数指针的函数地址,最后通过调用函数指针能调用实际模块的操作。

对于VLC所有的模块中,有且仅有一个导出函数:vlc_entry__(MODULE_NAME)。(其中MODULE_NAME为宏定义,对于main模块,在\include\modules_inner.h中定义为main)动态载入模块的过程是:使用module_Need函数,在module_bank中根据各个插件的capability等相关属性,寻找第一个能满足要求并激活的模块。所谓激活是指,调用插件的初始化函数成功。对于各个插件的初始化函数和析构函数均在vlc_entry__(MODULE_NAME)函数中指定了相关函数地址。因此载入各个插件(动态库)的过程,就成为了解析动态库文件,并找到其中vlc_entry__函数的地址,然后运行。这样各个模块的激活函数就会赋值各个操作的函数地址,以待后面函数动态调用。

具体函数调用过程如下:

Main模块的载入过程:

int main( int i_argc, char *ppsz_argv[] )(src\vlc.c)->i_ret VLC_Init( 0, i_argc, ppsz_argv )->module_InitBank( p_vlc )(src\libvlc.c void __module_InitBank( vlc_object_t *p_this ))-> module_LoadMain( p_this )(src\misc\modules.c)->AllocateBuiltinModule( p_this, vlc_entry__main )->pf_entry( p_module )(激活了main模块,以上为main模块的载入过程,对于main模块调用的实际函数为导出函数vlc_entry__main,其它模块导出的均为vlc_entry__0_8_6)

Module_Need函数实现载入任意模块的过程:

module_t __module_Need( vlc_object_t *p_this, const char *psz_capability,

                          const char *psz_name, vlc_bool_t b_strict )(src\misc\modules.c)-> vlc_list_find(将所有已经载入的模块查询出来)->然后循环,根据capability查找第一个最合适的module->AllocatePlugin(动态载入所需要的插件,该函数会在动态库所在目录,遍历所有动态库文件,)->p_module->pf_activate(调用激活函数)

VLC_Init函数流程:

module_InitBank->module_LoadBuiltins(载入静态插件)->module_LoadPlugins(载入动态插件->VLC_AddIntf(添加interface插件,VLC会静态载入hotkeys模块)

在VLC中根据处理任务不同,会静态载入不同的模块,main、memcpy、hotkeys等;动态载入的模块根据处理任务不同,差异很大。

2. VLC流媒体服务器体系结构

以下主要讨论VLC作为流媒体服务器时的体系结构。针对一个节目单文件,调试其运行过程,并最后给出总结。

该实例的播放节目单为如下:

New br broadcast enabled

Setup br input /mnt/hgfs/movie/caiyan.mpg

Setup br output #standard{mux=ts,access=udp,url=234.0.1.4,sap,name=ch1}

在例子中,通过VLC提供API:libvlc_new,libvlc_vlm_new,libvlc_vlm_play_media,libvlc_vlm_load_file等(有些API是自己添加的)可以完成对广播节目br的播放。

下面让我们仔细看看通过这几个接口,VLC内部到底是怎么工作完成了流媒体发布的。

1. 首先程序调用libvlc_new(\src\control\core.c)接口,实现创建一个VLC运行实例libvlc_instance_t,该实例在程序运行过程中唯一。

2. 在libvlc_new接口中,调用了VLC_Init函数实现具体的初始化工作。

3. VLC_Init(\src\libvlc.c)函数中,首先通过system_Init函数完成传入参数对系统的相关初始化,接着通过module_InitBank(\src\misc\modules.c)函数初始化module_bank结构体,并创建了main模块,然后通过module_LoadBuiltins载入静态模块,通过module_LoadPlugins(\src\misc\modules.c)函数载入动态模块,通过module_Need(\src\misc\modules.c)函数载入并激活memcpy模块,通过playlist_Create(\src\playlist\playlist.c)函数,创建了一个playlist播放管理的线程,其线程处理函数为RunThread(\src\playlist\playlist.c),通过VLC_AddIntf(\src\libvlc.c)函数添加并激活hotkeys模块,最后根据系统设置定义了宏HAVE_X11_XLIB_H,因此还需要添加screensaver模块。

4. 总结:此时加载的模块有main,hotkeys,screensaver,memcpy;多创建了一个线程,用于管理playlist,该线程无限循环,直到p_playlist->b_die状态为止。

5. 其次程序中调用libvlc_vlm_new接口,创建VLM对象(该接口为自己添加的)。

6. 该接口调用的是vlm_New(\src\misc\vlm.c)函数,实现VLM对象的创建,函数返回值是指向vlm_t的指针。

7. Vlm_new函数中,创建了一个vlm管理线程,线程处理函数为Manage(\src\misc\vlm.c)。该函数循环处理当前各种媒体(vod、broadcast、schedule)的播放实例,控制其每个播放细节(如:从一个input切换到下一个input;schedule周期循环调度等)。与playlist线程不同的是,Manage主要针对播放实例的操作,而RunThread主要针对播放列表的管理,也就是说VLC管理是分级的,播放列表级和播放列表中媒体播放实例级。

8. 其次程序调用libvlc_vlm_load_file接口,载入播放节目单(该接口也为自己添加,播放节目单如上所述)。

9. 该接口调用的是vlm_Load(\src\misc\vlm.c)函数,在该函数中,依次调用如下函数:stream_UrlNew、stream_Seek、stream_Read、Load,以下详细介绍各个函数作用。

a) 首先是stream_UrlNew(\src\input\stream.c)函数。先调MRLSplit(\src\input\input.c)函数完成对access、demux和path的解析。具体对于本例解析的结果为:access= ",demux=" ",path=" aa"。然后调用access2_New(\src\input\access.c)函数创建一个access_t结构体并初始化。具体运行时载入模块的相关参数是:capability="access2",name="access_file",psz_filename=access/libaccess_file_plugin.so。最后调用stream_AccessNew(\src\input\stream.c)函数,创建stream_t结构体对象,并初始化对象中所有函数指针;

b) 再调用stream_Seek(\include\vlc_stream.h)内联函数,设置起始位置;

c) 再调用stream_Size(\include\vlc_stream.h)获得大小;

d) 再调用stream_Read(\include\vlc_stream.h),读取到缓冲区;

e) 最后调用Load(\src\misc\vlm.c),完成实际的载入节目单。对于节目单文件,是一行行解析,并调用ExecuteCommand(\src\misc\vlm.c)完成解析的。Load函数的调用仅仅是设置了相关参数,如:设置input字符串值,设置output字符串值,设置mux的值及与播放相关的enabled、loop等参数。Load工作仅仅是为了下一步发布流做准备的。

10. 程序中调用libvlc_vlm_play_media接口,将节目流发布出去。(自己添加接口)

11. 在libvlc_vlm_play_media接口中,实质是创建了命令“control br play”再调用vlm_ExecuteCommand(\src\misc\vlm.c),完成对命令的执行,根据命令类型,由vlm_MediaControl(\src\misc\vlm.c)函数处理。

12. 在vlm_MediaControl函数中,会调用vlc_input_item_Init(\include\vlc_input.h)函数完成播放实例的初始化,并调用input_CreateThread2(\src\input\input.c)函数完成播放线程的创建。该线程的处理函数为Run(\src\input\input.c)。

13. Run线程是整个VLC作为流媒体服务器的核心。其主要分为如下几个步骤:Init、MainLoop和End。其中MainLoop是一个无限循环,是完成流媒体的整个发布过程。

a) 首先调用Init(\src\input\input.c)函数,初始化相关统计参数;

b) 其次再调用input_EsOutNew(\src\input\es_out.c)函数,初始化es_out_t结构体对象和es_out_sys_t结构体对象,并设置相关函数指针;

c) 再调用InputSourceInit(\src\input\input.c)函数,初始化input_thread_t对象中的input_source_t对象,主要有access_t、stream_t、demux_t三个结构体对象;

d) 总结此时各个模块实际载入的情况:

1) (access_t)type="access",name="access_filter",capability="access2",psz_filename="access/libaccess_file_plugin.so";

2) (stream_t)type="stream",pf_read="AStreamReadStream",pf_seek="AStreamPeekStream",pf_control="AStreamControl",pf_destory="AStreamDestory";

3) (demux_t)type="demux",capability="demux2",shortcuts="ps";

4) (sout_instance_t)type="stream out",psz_capability="sout stream",shortcut="stream_out_standard",psz_filename="/stream_out/libstream_out_standard_plugin.so";

5) (es_out_t)pf_add="ESOutAdd",pf_send="ESOutSend",pf_del="ESOutDel",pf_control="ESOutControl";

e) 再调用MainLoop(\src\input\input.c)函数,完成读取、解复用、解码、复用和传输;

f) MainLoop函数为无限循环,直到input_thread_t对象存在b_die、b_error、b_eof时为止。在该函数中,存在如下行代码:

i_ret=p_input->input.p_demux->pf_demux(p_input->input.p_demux);

它就是流媒体服务器运行的起点,所有的后续操作都会在该函数中继续衍生。

g) Pf_demux调用的是(\modules\demux\ps.c)中的Demux函数,在该函数中主要完成如下操作:

1) 先调用ps_pkt_resynch(\modules\demux\ps.c)函数,完成PS流中数据包重新同步(这里应该涉及到多媒体相关知识,需要补补);

2) 再调用ps_pkt_read(\modules\demux\ps.c)函数,最终调用stream_Block函数,这个函数内部会根据实际情况,调用stream_t模块中的pf_read或pf_block函数,函数结果会返回一个读取的buffer;

3) 根据数据包的i_code的值,做不同的处理,对于音视频数据流,调用es_out_Send(\include\vlc_es_out.h)函数处理;

4) es_out_Send一个抽象层函数,其通过函数指针,实际调用的是EsOutSend(\src\input\es_out.c)函数;

5) EsOutSend函数最终会调用input_DecoderDecode(\src\input\decoder.c)函数;

6) input_DecoderDecode函数会调用DecoderDecode(\src\input\decoder.c)函数完成解码;

7) DecoderDecode函数会调用pf_packetize(\modules\packetizer\mpegvideo.c)函数实现PES的打包;

8) DecoderDecode函数会调用sout_InputSendBuffer(\src\stream_output\stream_output.c)函数,实现发送;

9) sout_InputSendBuffer函数中的pf_send指针,指向的是(\modules\stream_out\standard.c)Send函数;

10) Send函数调用的是流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的sout_MuxSendBuffer函数,首先将要发送的数据放入fifo队列中,然后调用pf_mux函数指针,完成多路复用;

11) Pf_mux函数指针指向的是(\modules\mux\mpeg\ts.c)的Mux函数,完成多路复用后,最终调用(\modules\mux\mpeg\ts.c)TSSchedule函数,准备调度发送了;

12) TSSchedule函数中调用了TSDate(\modules\mux\mpeg\ts.c)函数;

13) TSDate函数中调用了流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的sout_AccessOutWrite函数,最终调用pf_write函数完成数据输出;

14) pf_write函数指向的是(\modules\access_output\udp.c)中的Write函数,完成数据UDP发送,这样数据就转换称TS流输出了;

15) 总结:pf_demux函数为流媒体所有操作的起点,通过该处衍生了很多其他模块的处理,从上面的分析可以看出,系统实质就是PS、ES、PES和TS几种流间的转换,针对应用场合(主要指做服务器或客户端)的不同,转换的方式不同

相关文章推荐

嵌入式 vlc源码分析锦集

http://blog.sina.com.cn/s/blog_8795b0970101eeku.html 嵌入式 vlc源码分析锦集 VLC源码分析 目录 1 VLC源码结构 vlc核心的是...
  • yuyin86
  • yuyin86
  • 2014年03月19日 00:30
  • 1362

嵌入式 vlc从接收到数据流到播放视频的过程分析

Vlc流播放流程  vlc源码目录树: 目录名称 说明 bindings Java, CIL 和Python绑定 doc ...
  • cds9527
  • cds9527
  • 2016年12月07日 11:33
  • 1485

嵌入式 vlc中vlm介绍

概述 代码从两大部分入手,一个telnet 的deamon。还有就是rtsp的实现部分 。结果发现,他们通过了一个桥梁vlm的media进行沟通。 当受到new MEDIANAME vod ena...
  • skdkjxy
  • skdkjxy
  • 2013年12月02日 14:43
  • 685

Linux内核源码分析—Linux内核中的嵌入式汇编

内核中分配文件描述符时找第一个0的位置的一个底层函数,参考《LINUX内核源代码情景分析(上)》中的1.5节《1.5Linux内核源代码中的汇编语言代码》   函数代码: /**  *find_fir...

【嵌入式】探究bootloader,分析u-boot源码

Preface    之前也发表过关于《Bootloader启动过程分析》的文章,但是内容表达得比较抽象,大多是文字叙述,所以这里从系统和代码的角度来深入分析bootloader的启动过程。    工...

GoAhead 2.1.8嵌入式webserver源码分析学习(一)---开篇

一、GoAhead嵌入式webserver简介 以下是摘自开源中国对于GoAhead的介绍: GoAhead Web 服务器是一款主要面向嵌入式系统的WEB服务器,它的目标也许不在于目前的WEB...
  • Mr__G
  • Mr__G
  • 2016年03月28日 14:30
  • 1102

几种源码开放的嵌入式文件系统分析与比较

博客分类: 嵌入式综合   http://blog.csdn.net/xlongfeng/article/details/4600555   几种源码开放的嵌入式文件系统...

lwip源码分析3----嵌入式LwIP协议栈的内存管理

http://blog.chinaunix.net/uid-9863638-id-1996358.html 摘要:在内存需求分析的基础上,阐述了LwIP TCP/IP协议栈中pbuf结构的基...

vlc源码分析

  • 2016年11月01日 17:41
  • 48KB
  • 下载

GoAhead 2.1.8嵌入式webserver源码分析学习(二)---源码文件结构分析

一、源码文件结构 源码文件结构并不是很复杂,打开压缩包之后,你会发现有很多文件夹,然后文件夹之外会有很多源码文件,文件夹之内除一个web的文件夹含有大量的文件外,其他的文件夹里面都只有两个文件。 ...
  • Mr__G
  • Mr__G
  • 2016年03月30日 11:15
  • 742
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:嵌入式 vlc源码分析锦集一
举报原因:
原因补充:

(最多只允许输入30个字)