1. 程式人生 > >VLC原始碼分析總結

VLC原始碼分析總結

1. 概述

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、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. 外掛管理框架

在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__函式的地址,然後執行。這樣各個模組的啟用函式就會賦值各個操作的函式地址,以待後面函式動態呼叫。

具體函式呼叫過程如下:

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