天天看點

利用 DirectShow 開發自己的 Filter

利用 DirectShow 開發自己的 Filter

首先定義導出函數:

  要導出這些函數有兩種方法,一是在定義函數時使用導出關鍵字_declspec(dllexport),另外一種方法是在建立dll檔案時使用子產品定義檔案.def。 使用導出函數關鍵字_declspec(dllexport)建立mydll.dll就是在 .h檔案中定義定義函數如下:

   為了用.def檔案建立dll,往該工程中加入一個文本檔案,命名為mydll.def,再在該檔案中加入如下代碼:

   其中library語句說明該def檔案是屬于相應dll的,exports語句下列出要導出的函數名稱。我們可以在.def檔案中的導出函數後加@n,如max@1,min@2, 表示要導出的函數順序号,在進行顯式連時可以用到它。該dll編譯成功後,打開工程中的debug目錄,同樣也會看到mydll.dll和mydll.lib檔案。

   然後要定義這些函數的實作了,其實這些工作dshow的基類裡都已經替我們做好了,我們所要做的就拿來用就是了,最重要的三個函數的實作一般如下  

其中dllentrypoint 是在c:/dx90sdk/samples/c++/directshow/baseclasses/dllentry.cpp定義的,如果感興趣我們可以去看看它的定義。 amoviedllregisterserver2函數是在下面 c:/dx90sdk/samples/c++/directshow/baseclasses/dllsetup.cpp這個檔案定義的,具體實作可以自己看看。

   到了這裡你恐怕要做點工作,還是要設定一下你的項目環境,否則恐怕你編譯是通不過的,因為你用到了基類的一些東西,是以你要将你的dshow基類的定義和庫檔案包含進來。 首先包含:

其次在project –setting菜單下配置自己的filter輸出的名字和連接配接的lib檔案

利用 DirectShow 開發自己的 Filter

圖5

其中library modules裡的包含的動态庫如下

c:/dx90sdk/samples/c++/directshow/baseclasses/debug/strmbasd.lib msvcrtd.lib quartz.lib vfw32.lib winmm.lib kernel32.lib advapi32.lib version.lib largeint.lib user32.lib gdi32.lib comctl32.lib ole32.lib olepro32.lib oleaut32.lib uuid.lib

   此時你編譯一下,好像還是通不過,它提示有一個全局的用于實作com接口的變量沒有定義,不着急,下面我們就開始實作filter的com接口。

三、如何實作filter 的類廠對象

   我們知道一個filter是一個com元件,是以它com特性的實作其實在其基類中實作的,比如iunknown接口,我們直接從基類派生出我們的filter後,它就支援com接口了,它就是一個com元件了。

  所有的com元件為了實作二進制的封裝,是以連建立的接口都封裝了,是以每個com對象都有個類對象(也叫類廠對象,本身也是com對象,用來建立com元件)來建立com元件。

下面溫習一下com元件的建立過程,其中涉及到幾個函數:

當用戶端要建立一個com元件時,它通過底層的com api函數 cogetclassobject()使用scm的服務,這個函數請scm把一個指針綁定到用戶端請求的com元件的類對象上, 其實在cogetclassobject()裡它裝載了該dll的庫,通過該dll的導出函數dllgetclassobject();dllgetclassobject根據用戶端提供的com元件classid,傳回該com元件類對象的指針。下面com元件的建立和scm無關了。

用戶端利用元件的類對象(類廠對象)的iclassfactory::createinstance方法建立com元件。

filter在這裡使用了一個類廠模闆類來當作filter的類廠對象。下面看看類廠在dshow是怎麼工作的。  

   類廠對象也是一個com元件。本來dllgetclassobject是應該由我們自己完成一個函數,在directshow基類裡已經完成了,我們不用管它了。它的功能就是來尋找這個dll中的類廠對象,看是否有符合用戶端請求的類廠對象。

  dll裡聲明了一個全局的類廠模闆數組,當dllgetclassobject請求類廠對象的時候,它就搜尋這個數組,看是否有和clsid比對的類廠對象。當它找到一個比對的clsid,它就建立一個類廠對象,然後講類廠指針傳回給cogetclassobject, 然後用戶端可以根據傳回去的類廠指針,調用 iclassfactory::createinstance方法建立元件,類廠就根據數組裡定義的方法建立com元件。

factory template包含下列變量:

其中的兩個函數指針m_lpfnnew and m_lpfninit使用下面的定義:

你可以參照如下的方式定義你的類廠對象:

你可以聲明自己的類廠數組如下:

如果在這個com元件中你要支援多個filter,你可以在這個數組中繼續添加就是了。

四、如何實作自己的 filter

   在這裡就要講如何建立自己的filter了,下面我們以寫一個ctransformfilter為例:

1、選擇一個基類,聲明自己的類。

   建立filter很簡單,你隻要根據自己的需要選擇不同的基類filter派生出自己的filter,它就已經支援com特性了。

  從邏輯上考慮,在寫filter之前,選擇一個合适的filter基類是至關重要的。為此,你必須對幾個filter的基類有相當的了解。 在實際應用中,filter的基類并不總是選擇cbasefilter的。相反,因為我們絕大部分寫的都是中間的傳輸filter(transform filter),是以基類選擇ctransformfilter和ctransinplacefilter的居多。如果我們寫的是源filter,我們可以選擇csource作為基類;如果是renderer filter,可以選擇cbaserenderer或cbasevideorenderer等。

  總之,選擇好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沒有pin接口,但在我的demo裡的filter,卻是有個out pin和一個input pin。我的filter類的定義如下:

注:因為基類是一個純虛的基類,是以在你的filter一定要派生一個其中的純虛函數,否則編譯器會提示你的派生類也是一個純虛類, 你在建立這個com元件對象的時候,純虛類是沒法建立對象的。

2、給自己的filter生成一個clsid

   你可以用guidgen or uuidgen給自己的filter生成一個128位的id号,然後利用define_guid宏在filter的頭檔案聲明該filter的clsid;

這個clsid_myfilter在類廠數組用到,在注冊filter時也要用到。

3、cmyfilter類的簡單實作

   這個類純粹為了示範用,是以特别簡單,你可以參考我的demo,那個filter寫的功能比較全。

這樣基本上就實作了一個filter,但是這個filter沒有與之相聯系的pin,但是實作filter的基本過程就時這樣了,至于邏輯上的東西,比如filter和pin如何連接配接,資料流是如何流動的,你都要去看看sdk了,按照上面的步驟你就可以寫一個filter的架構出來。

   下面我們總結一下寫一個filter至少需要那些東西。

1、filter的實作類

   在這裡就是cmyfilter類,在這個類裡你可以實作自己的邏輯上的功能,包括定義你的filter的特性,給你的filter配備pin接口等。

2 com元件的引出函數, 五個全局函數:

3、com元件的類廠對象

   類廠對象是用來生成filter對象的,用的模闆類定義了一個全局的模闆類對象數組,一般格式如下:

4、關于你自己定義的filter以及pin的資訊

   這些是一個全局的結構變量,用于描述你的filter和你定義的pin,在注冊filter的時候會用到,如下:

下面的代碼描述了一個filter帶有一個output pin:

最後如果你還是調試通不過,看看你是否包含了下面的頭檔案:

繼續閱讀