天天看點

利用DirectShow開發自己的Filter

學習directshow已經有幾天了,下面将自己的學習心得寫下來,希望對其他的人有幫助。 filter實質是個com元件,是以學習開發filter之前你應該對com的知識有點了解。com元件的實質是一個實作了純虛指針接口的c++對象。關于com的東西,這裡不多講。

  <b>一 給vc配置directshow的開發環境</b>

  無論開發filter還是開發dshow的應用程式都要配置一下開發環境的,其實就是包含一下dshow用到的頭檔案和動态庫。 選擇tools菜單下面的options。在彈出的option對話框配置如下

利用DirectShow開發自己的Filter

圖1 添加頭檔案

  選擇動态庫檔案添加到工程中

利用DirectShow開發自己的Filter

圖2 添加動态庫

  <b>二 建立工程以及filter的入口函數</b>

  建立工程

  一般情況下,建立filter使用一個普通的win32 dll項目。而且,一般filter項目不使用mfc。這時,應用程式通過cocreateinstance函數filter執行個體; filter與應用程式在二進制級别的協作。另外一種方法,也可以在mfc的應用程式項目中建立filter。

  在vc裡建立一個工程,選擇win32動态庫,如下圖

利用DirectShow開發自己的Filter

圖3

利用DirectShow開發自己的Filter

                  圖4

       這樣生成了一個簡單的dll,隻有一個dllmain入口函數。

  下面我要給這個filter添加入口函數了。

  filter是個基于dll的com元件,是以一般的filter都要實作下面幾個入口函數

              dllmain

             dllgetclassobject 

             dllcanunloadnow

             dllregisterserver 

             dllunregisterserver

dllmain

dllgetclassobject

dllcanunloadnow

dllregisterserver

dllunregisterserver

  首先定義導出函數

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

extern "c" _declspec(dllexport)bool dllregisterserver;

       extern "c" _declspec(dllexport)bool dllregisterserver;

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

        rary myfilter.ax 

        exports

        dllmain private

        dllgetclassobject private

        dllcanunloadnow private

        dllregisterserver private

        dllunregisterserver private

library myfilter.ax

exports

dllmain private

dllgetclassobject private

dllcanunloadnow private

dllregisterserver private

dllunregisterserver private

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

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

 

stdapi dllregisterserver()

{

 return amoviedllregisterserver2(true);

}

stdapi dllunregisterserver()

 return amoviedllregisterserver2(false);

extern "c" bool winapi dllentrypoint(hinstance, ulong, lpvoid);

bool apientry dllmain(handle hmodule, dword dwreason, lpvoid lpreserved)

 return dllentrypoint((hinstance)(hmodule), dwreason, lpreserved);

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

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

#include streams.h

  其次在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元件的建立過程,其中涉及到幾個函數

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

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

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

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

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

  factory template包含下列變量:

const wchar * m_name; // name

const clsid * m_clsid; // clsid

lpfnnewcomobject m_lpfnnew; // function to create an instance of the component

lpfninitroutine m_lpfninit; // initialization function (optional)

const amoviesetup_filter * m_pamoviesetup_filter; // set-up information (for filters)

      const wchar * m_name; // name

      const clsid * m_clsid; // clsid

      lpfnnewcomobject m_lpfnnew; // function to create an instance of the component

      lpfninitroutine m_lpfninit; // initialization function (optional)

      const amoviesetup_filter * m_pamoviesetup_filter; // set-up information (for filters)

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

typedef cunknown *(callback *lpfnnewcomobject)(lpunknown punkouter, hresult *phr);

typedef void (callback *lpfninitroutine)(bool bloading, const clsid *rclsid);

        typedef cunknown *(callback *lpfnnewcomobject)(lpunknown punkouter, hresult *phr);

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

cunknown * winapi cmyfilter::createinstance(lpunknown punk, hresult *phr)

 cmyfilter *pfilter = new cmyfilter(name("my filter"), punk, phr);

 if (pfilter== null)

 {

  *phr = e_outofmemory;

 }

 return pfilter;

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

cfactorytemplate g_templates[1] =

  l"my filter", // name

  &amp;clsid_myfilter, // clsid

  cmyfilter::createinstance, // method to create an instance of mycomponent

  null, // initialization function

  &amp;sudinftee // set-up information (for filters)

};

int g_ctemplates = sizeof(g_templates) / sizeof(g_templates[0]);

  如果在這個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類的定義如下:

class cmyfilter : public ccritsec, public cbasefilter

 public:

  cmyfilter(tchar *pname,lpunknown punk,hresult *hr);

  virtual ~cmyfilter();

  static cunknown * winapi createinstance(lpunknown punk, hresult *phr);

  cbasepin *getpin(int n);

  int getpincount();

  注:因為基類是一個純虛的基類,是以在你的filter一定要派生一個其中的純虛函數,否則編譯器會提示你的派生類也是一個純虛類,

你在建立這個com元件對象的時候,純虛類是沒法建立對象的。

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

// {1915c5c7-02aa-415f-890f-76d94c85aaf1}

define_guid(clsid_myfilter,

0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);

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

[myfilter.h]

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

  3 cmyfilter類的簡單實作

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

cmyfilter::cmyfilter(tchar *pname,lpunknown punk,hresult *hr)

:cbasefilter(name("my filter"), punk, this, clsid_myfilter)

{ }

cmyfilter::~cmyfilter()

{}

// public method that returns a new instance.

cbasepin * cmyfilter::getpin(int n)

 return null;

int cmyfilter::getpincount()

 return 0;

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

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

  1、filter的實作類

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

  2、com元件的引出函數

  五個全局函數:

  dllmain //dll的入口函數

  dllgetclassobject //獲得com元件的類廠對象

  dllcanunloadnow //com元件是否可以解除安裝

  dllregisterserver //注冊com元件

  dllunregisterserver //解除安裝com元件

  其中dllgetclassobject 已經由基類完成你自己隻要完成三個函數即可dllmain,dllregisterserver,dllunregisterserver。

  3、com元件的類廠對象

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

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

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

  amoviesetup_filter 描述一個filter

  amoviesetup_pin 描述pin

  amoviesetup_mediatype 描述資料類型

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

static const wchar g_wszname[] = l"some filter";

amoviesetup_mediatype sudmediatypes[] = {

 { &amp;mediatype_video, &amp;mediasubtype_rgb24 },

 { &amp;mediatype_video, &amp;mediasubtype_rgb32 },

amoviesetup_pin sudoutputpin = {

 l"", // obsolete, not used.

 false, // is this pin rendered?

 true, // is it an output pin?

 false, // can the filter create zero instances?

 false, // does the filter create multiple instances?

 &amp;guid_null, // obsolete.

 null, // obsolete.

 2, // number of media types.

 sudmediatypes // pointer to media types.

amoviesetup_filter sudfilterreg = {

 &amp;clsid_somefilter, // filter clsid.

 g_wszname, // filter name.

 merit_normal, // merit.

 1, // number of pin types.

 &amp;sudoutputpin // pointer to pin information.

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

#include initguid.h

#include tchar.h

#include stdio.h