學習directshow已經有幾天了,下面将自己的學習心得寫下來,希望對其他的人有幫助。 filter實質是個com元件,是以學習開發filter之前你應該對com的知識有點了解。com元件的實質是一個實作了純虛指針接口的c++對象。關于com的東西,這裡不多講。
<b>一 給vc配置directshow的開發環境</b>
無論開發filter還是開發dshow的應用程式都要配置一下開發環境的,其實就是包含一下dshow用到的頭檔案和動态庫。 選擇tools菜單下面的options。在彈出的option對話框配置如下

圖1 添加頭檔案
選擇動态庫檔案添加到工程中
圖2 添加動态庫
<b>二 建立工程以及filter的入口函數</b>
建立工程
一般情況下,建立filter使用一個普通的win32 dll項目。而且,一般filter項目不使用mfc。這時,應用程式通過cocreateinstance函數filter執行個體; filter與應用程式在二進制級别的協作。另外一種方法,也可以在mfc的應用程式項目中建立filter。
在vc裡建立一個工程,選擇win32動态庫,如下圖
圖3
圖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檔案
圖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
&clsid_myfilter, // clsid
cmyfilter::createinstance, // method to create an instance of mycomponent
null, // initialization function
&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[] = {
{ &mediatype_video, &mediasubtype_rgb24 },
{ &mediatype_video, &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?
&guid_null, // obsolete.
null, // obsolete.
2, // number of media types.
sudmediatypes // pointer to media types.
amoviesetup_filter sudfilterreg = {
&clsid_somefilter, // filter clsid.
g_wszname, // filter name.
merit_normal, // merit.
1, // number of pin types.
&sudoutputpin // pointer to pin information.
最後如果你還是調試通不過,看看你是否包含了下面的頭檔案
#include initguid.h
#include tchar.h
#include stdio.h