VLC源碼分析總結 ——入門綱領

http://blog.chinaunix.net/uid-24951403-id-3022939.htmlhtml

 

VLC源碼分析總結服務器

1. 概述框架

VLC屬於Video LAN開源項目組織中的一款全開源的流媒體服務器和多媒體播放器。做爲流媒體服務器,VLC跨平臺,支持多操做系統和計算機體系結構;做爲多媒體播放器,VLC能夠播放多種格式的媒體文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多種常見媒體格式。ide

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、muxpacketizerstream_output、video_filter、video_output、interfaceinputplaylist等(其中黑體爲核心模塊)。VLC不管是做爲流媒體服務器仍是多媒體播放器,它的實質思路就是一個「播放器」,之因此這麼形象描述,是由於(The core gives a framework to do the media processing, from input (files, network streams) to output (audio or video, on ascreen or a 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。函數

2. 插件管理框架oop

在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__函數的地址,而後運行。這樣各個模塊的激活函數就會賦值各個操做的函數地址,以待後面函數動態調用。ui

具體函數調用過程以下:this

l  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)

l  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(調用激活函數)

l  VLC_Init函數流程:

module_InitBank->module_LoadBuiltins(載入靜態插件)->module_LoadPlugins(載入動態插件->VLC_AddIntf(添加interface插件,VLC會靜態載入hotkeys模塊)

在VLC中根據處理任務不一樣,會靜態載入不一樣的模塊,main、memcpy、hotkeys等;動態載入的模塊根據處理任務不一樣,差別很大。

3. 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幾種流間的轉換,針對應用場合(主要指作服務器或客戶端)的不一樣,轉換的方式不一樣。

相關文章
相關標籤/搜索