DirectShow系列講座之二——Filter原理 (轉)

worldblog發表於2008-01-06
DirectShow系列講座之二——Filter原理 (轉)[@more@]在上一講中,筆者介紹了DirectShow的總體。從這一講開始,我們要從員的角度,進一步深入探討一下DirectShow的應用以及Filter的開發。
在這之前,筆者首先要特別提一下提供的一個Filter測試工具——GraphEdit,它的路徑在DXSDKbinDXUtilsGraphEdit.exe。(如果您還沒有 SDK,請到微軟的網站上去。)透過這個工具,我們可以很直觀地看到Filter Graph的執行及處理流程,方便我們進行程式。(如果您手邊就有,還等什麼,馬上體驗一下吧:執行GraphEdit,File->Render Media File…選擇一個;當Filter Graph構建成功後,按下工具欄的執行按鈕;您就能看到剛才選擇的媒體檔案被回放出來了!看到了吧,寫一個媒體器也就這麼回事!)
接下去,我們開講Filter的開發。
學習DirectShow Filter的開發,不外乎以下幾種方法:看幫助文件、看示例程式碼和看SDK基類。看幫助文件,應著重於總體概念上的理解;看示例程式碼應與基類原始碼的研究同步進行,因為自己寫Filter,關鍵的第一步是選擇一個合適的Filter基類和Pin的基類。對於Filter的把握,一般認為要掌握以下三方面的內容:Filter之間Pin的連線、Filter之間的資料傳輸以及流媒體的隨機訪問(或者說流的定位)。下面就開始分別進行闡述。
所謂的Filter Pin之間的連線,實際上是Pin之間Media Type(媒體型別)的一個協商過程。連線總是從輸出Pin指向輸入Pin的。要想深入瞭解具體的連線過程,就必須認真研讀SDK的基類原始碼(位於DXSDKsamplesMultimediaDirectShowBaseClassesamfilter.cpp,類CBasePin的Connect方法)。連線的大致過程為,列舉欲連線的輸入Pin上所有的媒體型別,逐一用這些媒體型別與輸出Pin進行連線,如果輸出Pin也接受這種媒體型別,則Pin之間的連線宣告成功;如果所有輸入Pin上列舉的媒體型別輸出Pin都不支援,則列舉輸出Pin上的所有媒體型別,並逐一用這些媒體型別與輸入Pin進行連線。如果輸入Pin接受其中的一種媒體型別,則Pin之間的連線到此也宣告成功;如果輸出Pin上的所有媒體型別,輸入Pin都不支援,則這兩個Pin之間的連線過程宣告失敗。
有一點需要注意的是,上述的輸入Pin與輸出Pin一般不屬於同一個Filter,典型的是上一級Filter(也叫Upstream Filter)的輸出Pin連向下一級Filter(也叫Downstream Filter)的輸入Pin。如下圖所示:

當Filter的Pin之間連線完成,也就是說,連線雙方透過協商取得了一種大家都支援的媒體型別之後,即開始為資料傳輸做準備。這些準備工作中,最重要的是Pin上的分配器的協商,一般也是由輸出Pin發起。在DirectShow Filter之間,資料是透過一個一個資料包傳送的,這個資料包叫做Sample。Sample本身是一個COM,擁有一段記憶體用以裝載資料,Sample就由記憶體分配器(Allocator)來統一管理。已成功連線的一對輸出、輸入Pin使用同一個記憶體分配器,所以資料從輸出Pin傳送到輸入Pin上是無需記憶體複製的。而典型的資料複製,一般發生在Filter內部,從Filter的輸入Pin上讀取資料後,進行一定意圖的處理,然後在Filter的輸出Pin上填充資料,然後繼續往下傳輸。下面,我們就具體闡述一下Filter之間的資料傳送。
首先,大家要區分一下Filter的兩種主要的資料傳輸:推模式(Push Model)和拉模式(Pull Model)。參考圖如下:
 
所謂推模式,即源Filter( Filter)自己能夠產生資料,並且一般在它的輸出Pin上有獨立的子執行緒負責將資料傳送出去,常見的情況如代表WDM模型的採集卡的Live Source Filter;而所謂拉模式,即源Filter不具有把自己的資料送出去的能力,這種情況下,一般源Filter後緊跟著接一個Parser Filter或Splitter Filter,這種Filter一般在輸入Pin上有個獨立的子執行緒,負責不斷地從源Filter索取資料,然後經過處理後將資料傳送下去,常見的情況如檔案源。推模式下,源Filter是主動的;拉模式下,源Filter是被動的。而事實上,如果將上圖拉模式中的源Filter和Splitter Filter看成另一個虛擬的源Filter,則後面的Filter之間的資料傳輸也與推模式完全相同。
那麼,資料到底是怎麼透過連線著的Pin傳輸的呢?首先來看推模式。在源Filter後面的Filter輸入Pin上,一定實現了一個IMemInputPin介面,資料正是透過上一級Filter這個介面的Receive方法進行傳輸的。值得注意的是(上面已經提到過),資料從輸出Pin透過Receive方法呼叫傳輸到輸入Pin上,並沒有進行記憶體複製,它只是一個相當於資料到達的“通知”。再看一下拉模式。拉模式下的源Filter的輸出Pin上,一定實現了一個IAsyncReader介面;其後面的Splitter Filter,就是透過呼叫這個介面的Request方法或者SyncRead方法來獲得資料。Splitter Filter然後像推模式一樣,呼叫下一級Filter輸入Pin上的IMemInputPin介面Receive方法實現資料的往下傳送。深入瞭解這部分內容,請認真研讀SDK的基類原始碼(位於DXSDKsamplesMultimediaDirectShowBaseClassessource.cpp和pullpin.cpp)。
下面,我們來講一下流的定位(Media Seeking)。在GraphEdit中,當我們成功構建了一個Filter Graph之後,我們就可以播放它。在播放中,我們可以看到進度條也在相應地前進。當然,我們也可以透過拖動進度條,實現隨機訪問。要做到這一點,在應用程式級別應該可以知道Filter Graph總共要播放多長時間,當前播放到什麼位置等等。那麼,在Filter級別,這一點是怎麼實現的呢?
我們知道,若干個Filter透過Pin的相互連線組成了Filter Graph。而這個Filter Graph是由另一個COM物件Filter Graph Manager來管理的。透過Filter Graph Manager,我們就可以得到一個IMediaSeeking的介面來實現對流媒體的定位。在Filter級別,我們可以看到,Filter Graph Manager首先從最後一個Filter(Renderer Filter)開始,詢問上一級Filter的輸出Pin是否支援IMediaSeeking介面。如果支援,則返回這個介面;如果不支援,則繼續往上一級Filter詢問,直到源Filter。一般在源Filter的輸出Pin上實現IMediaSeeking介面,它告訴呼叫者總共有多長時間的媒體內容,當前播放位置等資訊。(如果是檔案源,一般在Parser Filter或Splitter Filter實現這個介面。)對於Filter開發者來說,如果我們寫的是源Filter,我們就要在Filter的輸出Pin上實現IMediaSeeking這個介面;如果寫的是中間的傳輸Filter,只需要在輸出Pin上將的獲得介面請求往遞給上一級Filter的輸出Pin;如果寫的是Renderer Filter,需要在Filter上將使用者的獲得介面請求往上傳遞給上一級Filter的輸出Pin。進一步的瞭解,請認真研讀SDK的基類原始碼(位於DXSDKsamplesMultimediaDirectShowBaseClassestranrm.cpp的類方法CTransformOutputPin::NonDelegatingQueryInterface實現和ctlutil.cpp中類CPosPassThru的實現)。
以上我們介紹了一下如何學習DirectShow Filter開發,以及一些開始寫自己的Filter之前的預備知識。下一講,筆者將根據自己開發Filter的,手把手教你如何寫自己的Filter。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-996732/,如需轉載,請註明出處,否則將追究法律責任。

相關文章