DirectShow系列講座之三——開發自己的Filter (轉)

worldblog發表於2008-01-06
DirectShow系列講座之三——開發自己的Filter (轉)[@more@]在上兩講中,筆者介紹了DirectShow的應用原理以及開發Filter之前的一些預備知識。這一講,筆者就要手把手教你如何寫自己的Filter啦。
首先,從VC++的專案開始(請確認你已經給VC++好了的開發環境)。寫自己的Filter,第一步是使用VC++建立一個Filter的專案。由於DirectX SDK提供了很多Filter的例子專案(位於DXSDKsamplesMultimediaDirectShow Filters目錄下),最簡單的方法就是複製一個,然後再在此基礎上修改。但如果你是Filter開發的初學者,筆者並不贊成這麼做。
自己新建一個Filter專案也很簡單。使用VC++的嚮導,建立一個空的” Dynamic-link Library”專案。注意,幾個是必須有的:.def檔案,定義四個匯出;定義Filter類的.cpp檔案和.h檔案,並在.cpp檔案中定義Filter的註冊資訊以及兩個Filter的註冊函式:DllRegisterServer和DllUnregisterServer。(注:Filter的註冊資訊是Filter在註冊時寫到登錄檔裡的內容,格式可以參考SDK的示例程式碼,Filter相關的GUID務必使用GuidGen.exe產生。)接下去進行專案的設定(Project->Settings…)。此時,你可以開啟一個SDK的例子專案進行對比,有些宏定義完全可以照抄,最後注意將輸出檔案的副檔名改為.ax。
上一講曾經提到過,在寫Filter之前,選擇一個合適的Filter基類是至關重要的。為此,你必須對幾個Filter的基類有相當的瞭解。在實際應用中,Filter的基類並不總是選擇CBaseFilter的。相反,因為我們絕大部分寫的都是中間的傳輸Filter(TranoFilter),所以基類選擇CTransformFilter和CTransInPlaceFilter的居多。如果我們寫的是源Filter,我們可以選擇C作為基類;如果是Renderer Filter,可以選擇CBaseRenderer或CBaseVoRenderer等。
總之,選擇好Filter的基類是很重要的。當然,選擇Filter的基類也是很靈活的,沒有絕對的標準。能夠透過CTransformFilter實現的Filter當然也能從CBaseFilter一步一步實現。下面,筆者就從本人的實際出發,對Filter基類的選擇提出幾點建議供大家參考。
首先,你必須明確這個Filter要完成什麼樣的功能,即要對Filter專案進行需求分析。請儘量保持Filter實現的功能的單一性。如果必要的話,你可以將需求分解,由兩個(或者更多的)功能單一的Filter去實現總的功能需求。
其次,你應該明確這個Filter大致在整個Filter Graph的位置,這個Filter的輸入是什麼資料,輸出是什麼資料,有幾個輸入Pin、幾個輸出Pin等等。你可以畫出這個Filter的草圖。弄清這一點十分重要,這將直接決定你使用哪種“模型”的Filter。比如,如果Filter僅有一個輸入Pin和一個輸出Pin,而且一進一處的型別相同,則一般採用CTransInPlaceFilter作為Filter的基類;如果媒體型別不一樣,則一般選擇CTransformFilter作為基類。
再者,考慮一些資料傳輸、處理的特殊性要求。比如Filter的輸入和輸出的Sample並不是一一對應的,這就一般要在輸入Pin上進行資料的快取,而在輸出Pin上使用專門的執行緒進行資料處理。這種情況下,Filter的基類選擇CSource為宜(雖然這個Filter並不是源Filter)。
當Filter的基類選定了之後,Pin的基類也就相應選定了。接下去,就是Filter和Pin上的程式碼實現了。有一點需要注意的是,從設計的角度上來說,應該將你的邏輯類程式碼同Filter的程式碼分開。下面,我們一起來看一下輸入Pin的實現。你需要實現基類所有的純虛擬函式,比如CheckMediaType等。在CheckMediaType內,你可以對媒體型別進行檢驗,看是否是你期望的那種。因為大部分Filter採用的是推傳輸資料,所以在輸入Pin上一般都實現了Receive方法。有的基類裡面已經實現了Receive,而在Filter類上留一個純虛擬函式供過載進行資料處理。這種情況下一般是無需過載Receive方法的,除非基類的實現不符合你的實際要求。而如果你過載了Receive方法,一般會同時過載以下三個函式EndOfStream、BeginFlush和EndFlush。我們再來看一下輸出Pin的實現。一般情況下,你要實現基類所有的純虛擬函式,除了CheckMediaType進行媒體型別檢查外,一般還有DecideBufferSize以決定Sample使用的大小,GetMediaType提供支援的媒體型別。最後,我們看一下Filter類的實現。首先當然也要實現基類的所有純虛擬函式。除此之外,Filter還要實現CreateInstance以提供COM的入口,實現NonDelegatingQueryInterface以暴露支援的介面。如果我們建立了自定義的輸入、輸出Pin,一般我們還要過載GetPinCount和GetPin兩個函式。
Filter的實現大致就是這樣。你或許還想知道怎樣在Filter上實現一個自定義的介面,以及怎麼實現Filter的屬性頁等等。限於篇幅,筆者就不展開闡述了。其實,這些問題都能在SDK的示例專案中找到答案。其他的,關於在實際中應該注意的一些問題,筆者整理了一下,供大家參考。
1. 鎖(Lock)問題
DirectShow應用至少包含有兩條執行緒:一條主執行緒和一條資料傳輸執行緒。既然是多執行緒,肯定會碰到執行緒同步的問題。Filter有兩種鎖:Filter鎖和資料流鎖。Filter物件鎖用於Filter級別的如Filter狀態轉換、BeginFlush、EndFlush等;資料流鎖用於資料處理執行緒內,比如Receive、EndOfStream等。如果這兩種鎖沒有搞清楚,很容易產生程式的死鎖,這一點特別需要提醒。
2. EndOfStream問題
當Filter接收到這個“訊息”,意味著上一級Filter的資料都已經傳送完畢。在這之後,如果Receive再有資料接收,也不應該去理睬它。如果Filter對輸入Pin上的資料進行了快取,在接收到EndOfStream後應確保所有快取的資料都已經處理過了才能返回。
3. Media Seeking問題
一般情況下,你只需要在Filter的輸出Pin上實現NonDelegatingQueryInterface方法,當使用者申請得到IID_ImediaPosition介面或IID_IMediaSeeking介面時將請求往上一級Filter的輸出Pin遞。當Filter Graph進行Mediaseeking的時候,一般會Filter上的BeginFlush、EndFlush和NewSegment。如果你的Filter對資料進行了快取,你就要過載它們,並做出相應的處理。如果你的Filter負責給傳送出去的Sample打時間戳,那麼,在Mediaseeking之後應該重新從零開始打起。
4. 關於使用專門的執行緒
如果你使用了專門的執行緒進行資料的處理和傳送,你需要特別小心,不要讓執行緒進行死迴圈,並且要讓執行緒處理函式能夠去時時檢查執行緒命令。應該確保在Filter結束工作的時候,執行緒也能正常地結束。有時候,你把GraphEdit程式關掉,但GraphEdit程式仍在記憶體中,往往就是因為資料執行緒沒有關閉這個原因。
5. 如何從媒體型別中獲取資訊
比如,你想在輸入Pin連線的媒體型別中,獲取影片影像的寬、高等資訊,你應該在輸入Pin的CompleteConnect方法中實現,而不要在SetMediaType中。

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

相關文章