• 沒有找到結果。

第三章 VLC 播放程式流程架構

3.2 VLC 重要函數說明

3.2.3 module_Need()函數

VLC 中模組的定義: 在上面幾個小節中我們知道在 VLC 中要播放一個多媒體影音 資料時,會尋找一些模組來完成特定的程序,例如:負責 MPEG-2 PS 格式解多工的 ps

模組和解碼的libmpeg2 模組,然而這些模組都會利用巨集的方式表示其模組的特性,也

由於每個模組都有各自的特性,所以 module_Need()函數才能根據每個模組所定義的特 性來找到最適合的模組。而且每個模組都會將自己封裝成 DLL 格式;讓 VLC 可以用外

掛的方式來呼叫,其詳細的DLL 原理會在第五章節中詳述。

圖 3.16 module_Need() 簡易流程 Step1

Step2

Step3

21

我們介紹完 3.2.2 章後,我們可以知道 VLC 在需要執行某些處理多媒體程序時,會 利用 module_Need()函數來尋找適合的模組來完成某些程序。譬如開啟檔案的程序需要 資源存取模組,要將影音資料分離成audio bitstream 和 video bitstream 的程序需要解多 工器模組等等,接下來我們就會去分析VLC 尋找模組的函式 module_Need()函數。

上圖3.16 表示整個 module_Need()函數的流程,我們將整個流程分三大步驟來討論, 當然整個過程和其運作機制並沒那麼簡單,每個步驟裡面都有很多的條件判斷和變數設 定。其中步驟1 的 Deal with variables 程序只是會因為需要尋找何種功能的模組不同而 會不同。下面只會做個簡單的說明,我們將會把重點放在其他三個步驟來加以說明整個 module_Need()函數的重點所在。

Step1:

module_Need()函數是一個尋找模組的重要函數,我們先來看看傳入的參數所代表的 意義。參數p_this 代表尋找模組時所需的資訊;如模組在記憶體中的位置。參數 const char

*psz_name 會依所要尋找模組的功能不同而不同,參數 psz_capability 則是尋找的模組功 能的重要參數,譬如若是要找一個解多工器模組,所傳入的字串就是〝demux2〞;若是 要尋找解碼器模組,則會傳入字串為〝decoder〞,參數 vlc_bool_t b_strict 則是一個旗標 變數。

Step2:

在第二步驟中我們可以了解到 module_Need()函數是如何可以找到所有的模組,並 又是如何選出符合能力的模組。其中宣告的module_list_t 結構成員中的*p_module 指標 變數就是用來存放符合傳入參數 psz_capability 能力的模組其中包含了模組的所有特 性,其成員i_score 則是紀錄能力值的變數,然後 module_list_t *p_next 則是用來對符合 能力的模組做linked list 而宣告的,如圖 3.17 所示,module_list_t 的結構就像是一個模 組的標籤,所以藉著模組的標籤 module_Need()函數能將符合同能力條件的模組(符合 psz_capability 字串)做 linked list 的動作。

22 p_all->i_count 就是來儲存模組的總數。下面我們就來討論vlc_list_find()函數的功用。

p_all = vlc_list_find( p_this,

VLC_OBJECT_MODULE, FIND_ANYWHERE );

p_list = malloc( p_all->i_count * sizeof( module_list_t ) ); 數module_Need()中 i_type 的值 VLC _ OBJECT _MODULE,並用變數 i_count 來計算總

共有多少個模組。

Linked list 用

23

for( ; pp_current < pp_end ; pp_current++ ) {

if( (*pp_current)->b_attached

&& (*pp_current)->i_object_type == i_type ) {

i_count+

圖3.19 判斷模組條件

計算完模組總數之後,就會將 i_count 變數傳入 NewList()函數中做記憶體分配的工 作,如圖3.20 所示,然後再由一個 for()迴圈內執行 ListReplace()函數來儲存我們的模組 特性。要注意的是我們必須先經NewList()函數做記憶體分配後, p_list->p_values 才能 以結構陣列的方式表達。

for( ; pp_current < pp_end ; pp_current++ ) { if( (*pp_current)->b_attached

&& (*pp_current)->i_object_type == i_type ) {

ListReplace( p_list, *pp_current, i_index );

if( i_index < i_count ) i_index++;

} }

p_list->p_values[i_index].p_object = p_object;

圖3.20 用陣列形式儲存所有模組 以結構陣列形式儲存

24

圖 3.21 vlc_list_find()函數的流程圖

由於在VLC 中有許多的內建物件,其中當然也包括了 200 多個模組,所以 VLC 就 會利用vlc_list_find()函數來進行所有模組的額外的儲存,主要的原因是防止別的程式不 小心去修改到記憶體中模組的特性。然後 module_Need()函數會將全部的模組一個個拿

來做能力的比對,在下面我們就會說明如何將這些模組來做能力的分類。在這裡

module_Need()函數裡會找出全部的模組並另外儲存。首先每個模組的特性在編譯時會

利用的巨集函數來做一個模組基本的設定。

在儲存好所有的模組之後,便會開始利用一個 for()迴圈來尋找我們所需的模組。在 迴圈內會先利用指標變數 p_module 指向儲存模組的 p_all->p_values[].p_object,然後做

能力字串的比較,若能力字串不一樣就會執行continue 指令回到迴圈的起點並判斷下一

25

個模組,藉此來找出我們符合能力的模組。

p_module = (module_t *)p_all->p_values[i_which_module].p_object; /* Test that this module can do what we need

p_list[ i_index ].i_score = p_module->i_score + i_shortcut_bonus;

p_list[ i_index ].b_force = i_shortcut_bonus && b_strict;

圖3.23 儲存要測試的模組

26

如圖 3.25 所示,再次進入迴圈且又為符合能力的模組情形下,就會用*p_newlist 指 標變數存放*p_first 指標變數指向的位址,並判斷此時的候選的模組能力值是否大於第 一個候選(目前能力值最高)模組,若大於最高能力值的候選模組,就會將其被判斷的候 選模組(假設為 module X)的*p_next 指向*p_first 指標所指向的位址,並將*p_first 指標指 向其候選模組,如圖3.26 所示。

27

while( p_newlist->p_next != NULL &&

p_newlist->p_next->i_score >= p_list[ i_index ].i_score ) {

p_newlist = p_newlist->p_next;//指向下一個 }

p_list[ i_index ].p_next = p_newlist->p_next;

p_newlist->p_next = &p_list[ i_index ];

圖 3.27 被判斷的候選模組不大於分數最高的候選模組程式

p-newlist->next

p_first p-newlist module X p-newlist->next

module 1 … module n-1 module n

圖3.28 被判斷的候選模組不大於分數最高的候選模組時情形

我們可以很清楚地看到*p_newlist 指標扮演著一個偵查和分配的角色,負責在上述

各種不同的情況下,將被判斷的候選模組分配到已經連結好的候選模組中。程式中的

if( i_shortcuts > 0 )來判斷先在這個候選模組有沒有被設定符合捷近的條件,若有符合捷 近的條件的話就會讓這個候選模組的能力值大大的增加,最後必定是此候選模組排在第 一順位,不過此設定只有在*psz_name=$memcpy 的條件下或選擇特定串流模組下才會執 行。如圖3.29 是 Step2 的流程圖。

已經照分數高低排序

28

圖3.29 module_Need()函數 Step2 流程圖

Step3:

在Step2 結束後,符合能力的候選模組就會依照分數的高低作好 linked list。然後

就會利用*p_tmp 指標指向第一個候選模組並鎖定 linked list 內的所有候選模組,因為我

們已經儲存所有符合能力條件的模組,所以會將存放所有模組的 p_all 給釋放掉。然後

進入一個while()迴圈〝p_tmp->p_module->pf_activate〞會指向一個候選模組並執行由一 個模組在經由編譯器編譯時由巨集所設定好的開啟測試函數的callback function 進行候

選模組開啟條件的測試,如圖3.30 所示,此時的測試函數會因不同能力的模組而有不同

的測試方式,譬如解多工器模組就是利用看資料header 的方式來進行測試,而解碼器模

組則是利用四字元比較的方式進行測試。若開啟條件不符合的情形下就會指向下一個候

選模組,然後執行下一個模組的測試函數,並釋放不適合的候選模組,若成功開啟就會 設定此模組所需的callback functions 並跳出尋找模組的迴圏且不在尋找其他的模組。在

29

成功開啟模組後,會先儲存其候選模組的資訊,並將其他的候選模組都釋放掉。最後會

回傳其候選模組的位址,圖3.31 為 step3 的流程。

if( p_tmp->p_module->pf_activate

&& p_tmp->p_module->pf_activate( p_this ) == VLC_SUCCESS ) {

break;

}

圖3.30 判斷是否為適合模組條件程式

圖3.31 module_Need()函數 Step3 流程圖

30

整個 module_Need()函數尋找模組的機制就是由上述的三大步驟所構成,雖然在尋 找不同能力的模組時module_Need()函數會有些許的不同變數處理程序,但是大致上尋 找的程序是一樣的,都是會透過linked list 的方式將適合的模組串在一起。但為什麼一 定需要使用linked list 呢? 因為 linked list 可以動態的連結我們所需的模組,不像宣告陣 列的方式必須先將陣列的大小事先設定好,而且在宣告陣列所配置記憶體的大小後就不 能更改,若配置太大則會浪費寶貴的記憶體空間,配置太小又會造成記憶體不足的問 題,造成程式執行的錯誤。相對之下linked list 的方式就彈性了許多,除非是在系統 上有記憶體不足的原因,無法滿足動態記憶體的分配需要時,linked list 才會塞滿。另 外最重要的就是因為我們沒法無法事先得知在比較各種不同的條件後各有多少適合的

模組。而且不論在程式設計者額外增加或減少模組都不用特別去修改程式本身,在程式

維護的方面來看linked list 的方式確實比宣告陣列的方式來的方便多了。