VLC源码分析总结
From: http://blog.csdn.net/lvmaker/article/details/8785936
?
1. 概述
VLC屬于Video LAN開源項目組織中的一款全開源的流媒體服務器和多媒體播放器。作為流媒體服務器,VLC跨平臺,支持多操作系統(tǒng)和計算機體系結構;作為多媒體播放器,VLC可以播放多種格式的媒體文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多種常見媒體格式。
VLC采用全模塊化結構,在系統(tǒng)內部,通過動態(tài)的載入所需的模塊,放入一個module_bank的結構體中統(tǒng)一管理,連VLC的Main模塊也是通過插件的方式動態(tài)載入的(通過module_InitBank函數(shù)在初始化建立module_bank時)。對于不支持動態(tài)載入插件的系統(tǒng)環(huán)境中,VLC也可以采用builtin的方式,在VLC啟動的時候靜態(tài)載入所需要的插件,并放入module_bank統(tǒng)一管理。
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 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. 摘于官網(wǎng)說明)它實質處理的是ES、PES、PS、TS等流間的轉換、傳輸與顯示。對于流媒體服務器,如果從文件作為輸入即:PS->DEMUX->ES->MUX->TS;對于多媒體播放器如果采用UDP方式傳輸即:TS->DEMUX->ES。
2. 插件管理框架
在VLC中每種類型的模塊中都有一個抽象層/結構體,在抽象層或結構體中定義了若干操作的函數(shù)指針,通過這些函數(shù)指針就能實現(xiàn)模塊的動態(tài)載入,賦值相關的函數(shù)指針的函數(shù)地址,最后通過調用函數(shù)指針能調用實際模塊的操作。
對于VLC所有的模塊中,有且僅有一個導出函數(shù):vlc_entry__(MODULE_NAME)。(其中MODULE_NAME為宏定義,對于main模塊,在\include\modules_inner.h中定義為main)動態(tài)載入模塊的過程是:使用module_Need函數(shù),在module_bank中根據(jù)各個插件的capability等相關屬性,尋找第一個能滿足要求并激活的模塊。所謂激活是指,調用插件的初始化函數(shù)成功。對于各個插件的初始化函數(shù)和析構函數(shù)均在vlc_entry__(MODULE_NAME)函數(shù)中指定了相關函數(shù)地址。因此載入各個插件(動態(tài)庫)的過程,就成為了解析動態(tài)庫文件,并找到其中vlc_entry__函數(shù)的地址,然后運行。這樣各個模塊的激活函數(shù)就會賦值各個操作的函數(shù)地址,以待后面函數(shù)動態(tài)調用。
具體函數(shù)調用過程如下:
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模塊調用的實際函數(shù)為導出函數(shù)vlc_entry__main,其它模塊導出的均為vlc_entry__0_8_6)
l? Module_Need函數(shù)實現(xiàn)載入任意模塊的過程:
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(將所有已經(jīng)載入的模塊查詢出來)->然后循環(huán),根據(jù)capability查找第一個最合適的module->AllocatePlugin(動態(tài)載入所需要的插件,該函數(shù)會在動態(tài)庫所在目錄,遍歷所有動態(tài)庫文件,)->p_module->pf_activate(調用激活函數(shù))
l? VLC_Init函數(shù)流程:
module_InitBank->module_LoadBuiltins(載入靜態(tài)插件)->module_LoadPlugins(載入動態(tài)插件->VLC_AddIntf(添加interface插件,VLC會靜態(tài)載入hotkeys模塊)
在VLC中根據(jù)處理任務不同,會靜態(tài)載入不同的模塊,main、memcpy、hotkeys等;動態(tài)載入的模塊根據(jù)處理任務不同,差異很大。
3. VLC流媒體服務器體系結構
以下主要討論VLC作為流媒體服務器時的體系結構。針對一個節(jié)目單文件,調試其運行過程,并最后給出總結。
該實例的播放節(jié)目單為如下:
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是自己添加的)可以完成對廣播節(jié)目br的播放。
下面讓我們仔細看看通過這幾個接口,VLC內部到底是怎么工作完成了流媒體發(fā)布的。
1.? 首先程序調用libvlc_new(\src\control\core.c)接口,實現(xiàn)創(chuàng)建一個VLC運行實例libvlc_instance_t,該實例在程序運行過程中唯一。
2.? 在libvlc_new接口中,調用了VLC_Init函數(shù)實現(xiàn)具體的初始化工作。
3.? VLC_Init(\src\libvlc.c)函數(shù)中,首先通過system_Init函數(shù)完成傳入?yún)?shù)對系統(tǒng)的相關初始化,接著通過module_InitBank(\src\misc\modules.c)函數(shù)初始化module_bank結構體,并創(chuàng)建了main模塊,然后通過module_LoadBuiltins載入靜態(tài)模塊,通過module_LoadPlugins(\src\misc\modules.c)函數(shù)載入動態(tài)模塊,通過module_Need(\src\misc\modules.c)函數(shù)載入并激活memcpy模塊,通過playlist_Create(\src\playlist\playlist.c)函數(shù),創(chuàng)建了一個playlist播放管理的線程,其線程處理函數(shù)為RunThread(\src\playlist\playlist.c),通過VLC_AddIntf(\src\libvlc.c)函數(shù)添加并激活hotkeys模塊,最后根據(jù)系統(tǒng)設置定義了宏HAVE_X11_XLIB_H,因此還需要添加screensaver模塊。
4.? 總結:此時加載的模塊有main,hotkeys,screensaver,memcpy;多創(chuàng)建了一個線程,用于管理playlist,該線程無限循環(huán),直到p_playlist->b_die狀態(tài)為止。
5.? 其次程序中調用libvlc_vlm_new接口,創(chuàng)建VLM對象(該接口為自己添加的)。
6.? 該接口調用的是vlm_New(\src\misc\vlm.c)函數(shù),實現(xiàn)VLM對象的創(chuàng)建,函數(shù)返回值是指向vlm_t的指針。
7.? Vlm_new函數(shù)中,創(chuàng)建了一個vlm管理線程,線程處理函數(shù)為Manage(\src\misc\vlm.c)。該函數(shù)循環(huán)處理當前各種媒體(vod、broadcast、schedule)的播放實例,控制其每個播放細節(jié)(如:從一個input切換到下一個input;schedule周期循環(huán)調度等)。與playlist線程不同的是,Manage主要針對播放實例的操作,而RunThread主要針對播放列表的管理,也就是說VLC管理是分級的,播放列表級和播放列表中媒體播放實例級。
8.? 其次程序調用libvlc_vlm_load_file接口,載入播放節(jié)目單(該接口也為自己添加,播放節(jié)目單如上所述)。
9.? 該接口調用的是vlm_Load(\src\misc\vlm.c)函數(shù),在該函數(shù)中,依次調用如下函數(shù):stream_UrlNew、stream_Seek、stream_Read、Load,以下詳細介紹各個函數(shù)作用。
a)?? 首先是stream_UrlNew(\src\input\stream.c)函數(shù)。先調MRLSplit(\src\input\input.c)函數(shù)完成對access、demux和path的解析。具體對于本例解析的結果為:access="",demux="",path="aa"。然后調用access2_New(\src\input\access.c)函數(shù)創(chuàng)建一個access_t結構體并初始化。具體運行時載入模塊的相關參數(shù)是:capability="access2",name="access_file",psz_filename=access/libaccess_file_plugin.so。最后調用stream_AccessNew(\src\input\stream.c)函數(shù),創(chuàng)建stream_t結構體對象,并初始化對象中所有函數(shù)指針;
b)?? 再調用stream_Seek(\include\vlc_stream.h)內聯(lián)函數(shù),設置起始位置;
c)?? 再調用stream_Size(\include\vlc_stream.h)獲得大小;
d)?? 再調用stream_Read(\include\vlc_stream.h),讀取到緩沖區(qū);
e)?? 最后調用Load(\src\misc\vlm.c),完成實際的載入節(jié)目單。對于節(jié)目單文件,是一行行解析,并調用ExecuteCommand(\src\misc\vlm.c)完成解析的。Load函數(shù)的調用僅僅是設置了相關參數(shù),如:設置input字符串值,設置output字符串值,設置mux的值及與播放相關的enabled、loop等參數(shù)。Load工作僅僅是為了下一步發(fā)布流做準備的。
10. 程序中調用libvlc_vlm_play_media接口,將節(jié)目流發(fā)布出去。(自己添加接口)
11. 在libvlc_vlm_play_media接口中,實質是創(chuàng)建了命令“control br play”再調用vlm_ExecuteCommand(\src\misc\vlm.c),完成對命令的執(zhí)行,根據(jù)命令類型,由vlm_MediaControl(\src\misc\vlm.c)函數(shù)處理。
12. 在vlm_MediaControl函數(shù)中,會調用vlc_input_item_Init(\include\vlc_input.h)函數(shù)完成播放實例的初始化,并調用input_CreateThread2(\src\input\input.c)函數(shù)完成播放線程的創(chuàng)建。該線程的處理函數(shù)為Run(\src\input\input.c)。
13. Run線程是整個VLC作為流媒體服務器的核心。其主要分為如下幾個步驟:Init、MainLoop和End。其中MainLoop是一個無限循環(huán),是完成流媒體的整個發(fā)布過程。
a)?? 首先調用Init(\src\input\input.c)函數(shù),初始化相關統(tǒng)計參數(shù);
b)?? 其次再調用input_EsOutNew(\src\input\es_out.c)函數(shù),初始化es_out_t結構體對象和es_out_sys_t結構體對象,并設置相關函數(shù)指針;
c)?? 再調用InputSourceInit(\src\input\input.c)函數(shù),初始化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)函數(shù),完成讀取、解復用、解碼、復用和傳輸;
f)?? MainLoop函數(shù)為無限循環(huán),直到input_thread_t對象存在b_die、b_error、b_eof時為止。在該函數(shù)中,存在如下行代碼:
i_ret=p_input->input.p_demux->pf_demux(p_input->input.p_demux);
??????? 它就是流媒體服務器運行的起點,所有的后續(xù)操作都會在該函數(shù)中繼續(xù)衍生。
g)?? Pf_demux調用的是(\modules\demux\ps.c)中的Demux函數(shù),在該函數(shù)中主要完成如下操作:
1)?? 先調用ps_pkt_resynch(\modules\demux\ps.c)函數(shù),完成PS流中數(shù)據(jù)包重新同步(這里應該涉及到多媒體相關知識,需要補補);
2)?? 再調用ps_pkt_read(\modules\demux\ps.c)函數(shù),最終調用stream_Block函數(shù),這個函數(shù)內部會根據(jù)實際情況,調用stream_t模塊中的pf_read或pf_block函數(shù),函數(shù)結果會返回一個讀取的buffer;
3)?? 根據(jù)數(shù)據(jù)包的i_code的值,做不同的處理,對于音視頻數(shù)據(jù)流,調用es_out_Send(\include\vlc_es_out.h)函數(shù)處理;
4)?? es_out_Send一個抽象層函數(shù),其通過函數(shù)指針,實際調用的是EsOutSend(\src\input\es_out.c)函數(shù);
5)?? EsOutSend函數(shù)最終會調用input_DecoderDecode(\src\input\decoder.c)函數(shù);
6)?? input_DecoderDecode函數(shù)會調用DecoderDecode(\src\input\decoder.c)函數(shù)完成解碼;
7)?? DecoderDecode函數(shù)會調用pf_packetize(\modules\packetizer\mpegvideo.c)函數(shù)實現(xiàn)PES的打包;
8)?? DecoderDecode函數(shù)會調用sout_InputSendBuffer(\src\stream_output\stream_output.c)函數(shù),實現(xiàn)發(fā)送;
9)?? sout_InputSendBuffer函數(shù)中的pf_send指針,指向的是(\modules\stream_out\standard.c)Send函數(shù);
10)? Send函數(shù)調用的是流化輸出(stream_output)的抽象層(\src\stream_output\stream_output.c)中的sout_MuxSendBuffer函數(shù),首先將要發(fā)送的數(shù)據(jù)放入fifo隊列中,然后調用pf_mux函數(shù)指針,完成多路復用;
11)? Pf_mux函數(shù)指針指向的是(\modules\mux\mpeg\ts.c)的Mux函數(shù),完成多路復用后,最終調用(\modules\mux\mpeg\ts.c)TSSchedule函數(shù),準備調度發(fā)送了;
12)? TSSchedule函數(shù)中調用了TSDate(\modules\mux\mpeg\ts.c)函數(shù);
13)? TSDate函數(shù)中調用了流化輸出(stream_output)的抽象層(\src\stream_output\stream_output.c)中的sout_AccessOutWrite函數(shù),最終調用pf_write函數(shù)完成數(shù)據(jù)輸出;
14)? pf_write函數(shù)指向的是(\modules\access_output\udp.c)中的Write函數(shù),完成數(shù)據(jù)UDP發(fā)送,這樣數(shù)據(jù)就轉換稱TS流輸出了;
15)? 總結:pf_demux函數(shù)為流媒體所有操作的起點,通過該處衍生了很多其他模塊的處理,從上面的分析可以看出,系統(tǒng)實質就是PS、ES、PES和TS幾種流間的轉換,針對應用場合(主要指做服務器或客戶端)的不同,轉換的方式不同。 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
- 上一篇: 思科加强生成树性能的属性(Portfas
- 下一篇: 明翰游戏学笔记V0.2(持续更新)