• 沒有找到結果。

第二章 模組間內部機制介紹

2.2 模組間工作原理

2.2.1 模組介紹

Filter 通常由一個或多個接腳(Pin)組成,每個 Filter 透過接腳(Pin)有規 則的連接。在 2.1 節有提過要構成 DirectShow 系統有三大 Filter: Source Filter、Transform Filter、Render Filter,此三種 Filter 依操作功能不同來 區分或者也可以用輸入接腳(Input Pin)及輸出接腳(Output Pin)的個數來區 分,例如 Source Filter 有一支輸出接腳(OutPut Pin)無輸入接腳(Input Pin),

Transform Filter 至少各有一支輸出接腳(OutPut Pin)及輸入接腳(Input Pin),而 Render Filter 有一支輸入接腳(Input Pin)無輸出接腳(OutPut Pin),

簡圖如圖 2-2 所示。

Source Filter

輸出

Transform Filter Render Filter 輸入

Pin

圖 2-2 各式 Filter 圖示 2.2.2 模組間協商機制

本節開始探討第一個問題 Filter 之間如何做連接,在 DirectShow 中它提供 了一套協商機制來解決這個連接的問題,所謂 Filter 的連接其實就是接腳(Pin) 與接腳(Pin)之間的相互認可,欲連接的兩支接腳去協商一個雙方都認可的傳輸 格式,若無法取得共同認可的格式將導致連接失敗,接下來我們將說明這個協商 機制如何運作。

我們可以透過 Filter Graph Manager 對 Filter 下達 Connect 指令來做連 接的動作。而 DirectShow 規定兩個試圖想要做連接的 Filter 必須在同一個 Filter Graph Manager 裡面,所以連接的前置作業就是要把連接的 Filter 加到 同一個 Filter Graph Manager 裡面,加入的動作可以透過函式 AddFilter 來幫 我們實現。而下圖 2-3 就是 Filter 連接的流程圖,我們將透過這張圖來講解整

5

個 Filter 連接的細部過程。

應用程式透過 Filter Graph Mnanger 下達 Connect 指令之後,便啟動 Connect 函式開始執行,首先會檢查這個輸出 Pin 是否已經被連接,若已經 被連接則導致失敗,若沒有被別的 Filter 連接則表示可以使用,接著檢查 Filter 是否是停止的狀態,因為要連接 Pin,Filter 必須是在停止的狀態 才有辦法做連接的動作,否則一樣會導致連接失敗。其 Connect 函式 Virtual Code 如圖 2-4 所示。

6

printf(“This filter is not stopped”);

return VFW_E_NOT_STOPPED;

}

//開始輪詢支援的媒體類型 AgreeMediaType();

}

{

printf(“This Pin is already connection”);

return VFW_E_ALREADY_CONNECT;

} 叫 AgreeMediaType 函式開始執行,而此函式便會啟動 TryMediaTypes 函式執行,

所做的動作就是透過輸出 Pin 所提供的媒體格式(Media Type),來跟輸入 Pin 上所支援的所有媒體格式做一個比對的動作。若都沒有兩者相同的媒體類型,則 此連接是不成立的,若找到一個媒體類型是雙方都支援的則兩個 Filter 就會以 這個媒體格式進行連接,至此模組間的協商連接便成功。

‹ Step 3: 協商成功

媒 體 類 型 以 及 Pin 的 連 接 都 協 商 成 功 後 , 接 著 輸 入 Pin 會 調 用 ReceiveConnection 函式,進行一系列的內部檢查,其檢查次序內容如同輸出 Pin 檢查輸入 Pin 的動作一樣,如果檢查成功輸入 Pin 便會設置這個協商成功的媒體 格 式 為 目 前 所 支 援 的 媒 體 格 式 , 最 後 輸 出 Pin 及 輸 入 Pin 都 會 調 用 CompleteConnect 函式來代表雙方連接協商成功,CompleteConnect 函式的

7

Virtual code 如圖 2-5 所示。

CompleteConnect(…) {

return DecideAllocator(…);

}

圖 2-5 CompleteConnect 函式

而當連接成功後代表雙方支援的媒體格式已經確定,也就是說雙方之間所要傳輸 的數據格式已經確定,因此必須再協商一個 Allocator 來管理建立這個固定的數 據格式之記憶體,以下我們先對 Allocator 做一個介紹

¾ Sample

在介紹 Allocator 之前先來介紹一個名詞 Sample,它是 DirectShow 中 的傳輸單元,不管是 Video、Audio 或 File 只要是在 Filter 間傳輸的 我們都通稱為 Sample,而 Sample 所代表的不只是實質的數據而已,它 還包含數據的一些資料(大小、媒體格式等),因此一個 Sample 除了有 一塊真正存放數據的記憶體外,它還有存放數據的資訊,而這些都由 IMediaSample 這 個 類 別 所 管 理 , 它 提 供 一 些 函 式 來 讓 我 們 控 制 Sample,例如 GetPoint 取得存放數據的記憶體位址,GetSize 取得數 據大小等。

¾ Allocator

Allocator 就是負責管理建立這些 Sample,它的功能有點類似 C++中 new memory 的指令一樣,不同的是他每次所 new 出來的是一個 Sample 的類 別,而 Allocator 是由 IMemAllocator 類別所控制,它一樣提供一些函 式,例如 GetBuffer 建立 Sample 類別,SetProperties 設定 Allocato 特性等。

‹ Step 4: 協商配置 Allocator

了解 Allocator 功能後,就可以介紹 Allocator 是如何協商配置出來,這個

8

協 商 動 作 是 由 輸 出 Pin 上 CompleteConnect 函 式 中 所 衍 伸 的 DecideAllocator 來實現,DecideAllocator 函式 Virtual code 如圖 2-6 所示。

DecideBufferSize(…) //設定 Allocator 的特性 }

因此在 DecideAllocator 函式中首先會先去詢問輸入 Pin 是否提供 Allocator,

如果提供便呼叫 DecideBufferSize 函式來設定 Allocator 的特性,如果不提供 則必須由輸出 Pin 來建立 Allocator 並設定特性,當 Allocator 配置完成整個模 組間的連接才算真正的完成。

2.2.3 模組間數據傳輸機制

前面已經提到 Filter 之間如何成功的連接,接著本節即將繼續探討第二個 問題 Filter 之間如何傳輸數據。在 DirectShow 中有一條專門負責傳輸的線程 (Thread)稱為傳輸執行緒,因此我們以下會介紹這條執行緒如何建立以及建立後 如何傳輸,而圖 2-7 是一個傳輸機制的整體流程圖,我們將藉由這個圖來說明 DirectShow 透過怎樣的機制來解決數據傳輸的問題。

9

圖 2-7 傳輸機制流程圖

10

1. 當使用者按下 Run 後,便將 Run 命令傳送給 Filter Graph Manager。

2. Filter Graph Manager 便啟動 Source Filter 的 Active()函式開始執行,

Active 函式程式碼如圖 2-8 所示。

圖 2-8 Active 函式程式碼

我們可以看到 Activec 函式會先把目前狀態鎖住,接著檢查 Source Filter 是否為 Active 狀態,以及檢查 Source Filter 是否連接,必須是在 Active 狀態 以及 Filter 是連接的狀態才可以繼續下面的動作。

3. 通過以上檢查後,Active 函式就會透過 Init()函式發出 CMD_INIT 的命令,

Filter Graph Manager 收到 CMD_INIT 命令後就會啟動 ThreadProc()函式開 始執行,ThreadProc 函式程式碼如圖 2-9 所示。

11

圖 2-9 ThreadProc 函式程式碼

4. ThreadProc() 函 式 先 確 定 目 前 確 實 是 CMD_INIT 命 令 後 , 才 會 透 過 OnThreadCreate()函式建立一條傳輸執行緒,當傳輸執行緒建立起來後,接 著便進入一個重覆檢查狀態的 while 迴圈,迴圈中將檢查四種狀態,分別對 應的動作如下

z Pause :啟動 DoBufferProcessingLoop 函式開始執行

z Run :觸發 Pause 狀態(因為 DirectShow 規定,要轉換到 Run 或 Stop 都 必須先經過 Pause 的狀態)

z Stop :觸發 Pause 狀態

z Exit :啟動 OnThreadDestory()函式執行銷毀釋放傳輸執行緒的動作 5. 因此除了 Exit 狀態外,其他狀態其實都會啟動 DoBufferProcessingLoop 函

式 , 其 函 式 程 式 碼 如 圖 2-10 所 示 , 而 這 函 式 一 開 始 會 先 透 過 OnthreadStartPlay 函式啟動傳輸執行緒準備做傳輸的動作,接著會檢查是 否有收到狀態的命令,有接收到才繼續檢查目前狀態是否為 Stop 狀態,若是 Stop 就 發 出 Exit 指 令 銷 毀 釋 放 執 行 緒 , 若 不 是 Stop 就 透 過 GetDeliverBuffer()函式向 Allocator 要求一個空的 Sample,並傳給 Source Filter 的 Fillbuffer()函式做一個填充資料的動作,接著 FillBuffer()函 式將回傳填充的狀況,當狀況為填充失敗或其他時將把要來的空 Sample 釋放

12

掉,並回到 ThreadProc 函式中的重覆檢查狀態的樣子,當狀況為填充成功時 就會透過 Deliver()函式來做傳送的動作,如果傳送成功 Transform Filter 的 Receiver()函式就會接收到 Sample,反之則一樣回到 ThreadProc 函式中 的重覆檢查狀態的樣子。

圖 2-10 DoBufferProcessingLoop 函式程式碼

6. 當 Transform Filter 透過 Receiver()函式接收到 Sample 後就會先呼叫 InitalizeOutputSample()函式向 Allocator 要求一個空的 Sample 以便作為 傳送到下一級的準備,接著便把接收到的 Sample 以及要求的空的 Sample 傳 給本身的 Transform()函式做影像處理等動作。

7. Transform()函式做完處理後會把處理完的資料直接填充到準備好的那個空 的 Sample 中,填 充成功便回傳 S_OK ,反之則回傳 S_FALSE 並且回到 DoBufferProcessingLoop()函式。

8. 若收到 S_OK 代表填充成功便會呼叫 Render Filter 的 Receiver()函式來做 一個接收的動作,當 Render Filter 接收到 Sample 就呼叫 Render()函式做 播放的動作,撥放完就釋放 Sample,接著就又回到 DoBufferProcessingLoop() 函式做下一回合的傳輸。

13

2.2.4 模組間狀態轉換機制

當討論完連接與傳輸的問題之後,接下來本節將繼續探討第三個問題就是 Filter 的狀態如何做轉換才不會有傳輸執行緒(Thread)鎖死的問題產生。所以 我們以下將解說狀態轉換機制。

狀態轉換機制

上一節我們已經講解傳輸執行緒如何被建立起來,接下來我們再來探討當 Filter 狀態轉換的時候,它是透過怎樣的機制,才比較不會使傳輸執行緒發生 錯亂死結的情形產生。首先我們來看 Filter 三種狀態停止、暫停、開啟對應傳 輸執行緒的動作為何

— 停止(Stopped):讓傳輸執行緒停止,並清空執行緒內的所有殘留 Sample。

— 暫停(Pause):讓傳輸執行緒暫時鎖住(Lock),讓 Sample 無法傳送。

— 開啟(Running):打開鎖住的傳輸執行緒(UnLock),讓 Sample 繼續傳送。

其中暫停是一個轉換狀態的起始點,不管要開啟(Running)或停止(Stopped) 都會先做暫停的動作,目的是要讓執行緒先暫時鎖住不要再有 Sample 傳送,不 然轉換狀態的時候如果執行緒內還有 Sample 在傳送的話可能會發生 Sample 丟失 的情形。而且 DirectShow 發展了一套由後往前的回朔機制來解決傳輸執行緒容 易錯亂鎖死的問題。回朔機制就是當狀態要轉換時都先由最後一級的 Render Filter 開始轉換起,確保最後一級 Filter 已經轉換完成才叫上一級 Filter 作 轉換,這樣做的目的可以避免前面都已經轉換成停止了後面卻還沒轉換還在要數 據導致要不到數據而當掉的情形產生。

接著我們透過打開應用程式後要轉換到開啟(Running)的例子來了解各 Filter 之間的狀態如何變化,首先要轉換到 Run 狀態必須先通過 Pause 狀態,

因此 Source Filter 將第一個 Sample 推給下一級的 Transform Filter 此時 Source Filter 是暫停狀態,而 Transform Filter 接到第一個 Sample 後對它做 完處理,再送給下一級的 Render Filter 此時 Transform Filter 也是暫停狀態,

14

而 Render Filter 收到第一個 Sample 把它顯示出來,此時 Render Filter 也是 暫 停 狀 態 , 而 當 使 用 者 下 達 Play 的 指 令 後 , 即 表 示 狀 態 要 轉 換 成 開 啟 (Running),需先將 Render Filter 轉換到開啟狀態,把鎖住的傳輸執行緒打開 (Unlock)接著再通知上一級轉換成開啟,以此類推一級一級往上通報,等到全部 Filter 都轉換成開啟時就完成整個動作。

這裡我們再看一個 Real time Source 的應用程式打開後要轉換到開啟狀態 的情形,相較於上一個例子有些許的不同,因為 Real time Source 的 Sample 是即時的過去就沒了,所以如果還是保留第一個 Sample 在畫面上,這樣難免有 些奇怪,所以若是 Real time Source 的 Filter chain 它的 Pause 狀態是不要求 先處理第一個 Sample 的,只需將傳輸執行緒鎖住(Lock)即可,當要轉換成開啟

這裡我們再看一個 Real time Source 的應用程式打開後要轉換到開啟狀態 的情形,相較於上一個例子有些許的不同,因為 Real time Source 的 Sample 是即時的過去就沒了,所以如果還是保留第一個 Sample 在畫面上,這樣難免有 些奇怪,所以若是 Real time Source 的 Filter chain 它的 Pause 狀態是不要求 先處理第一個 Sample 的,只需將傳輸執行緒鎖住(Lock)即可,當要轉換成開啟