利用 DirectShow 開發自己的 Filter
學習directshow已經有幾天了,下面將自己的學習心得寫下來,希望對其他的人有協助。 Filter實質是個COM組件,所以學習開發Filter之前你應該對com的知識有點瞭解。Com組件的實質是一個實現了純虛指標介面的C++對象。關於com的東西,這裡不多講。
一、給vc配置DirectShow的開發環境
無論開發Filter還是開發Dshow的應用程式都要配置一下開發環境的,其實就是包含一下dshow用到的標頭檔和動態庫。 選擇Tools菜單下面的Options。在彈出的Option對話方塊配置如下:
圖1 添加標頭檔
選擇動態庫檔案添加到工程中
圖2 添加動態庫
二、建立工程以及Filter的入口函數
建立工程:
一般情況下,建立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
首先定義匯出函數:
要匯出這些函數有兩種方法,一是在定義函數時使用匯出關鍵字_declspec(dllexport),另外一種方法是在建立DLL檔案時使用模組定義檔案.Def。使用匯出函數關鍵字_declspec(dllexport)建立MyDll.dll就是在 .h檔案中定義定義函數如下:
extern "C" _declspec(dllexport)BOOL DllRegisterServer; 等等
為了用.def檔案建立DLL,往該工程中加入一個文字檔,命名為MyDll.def,再在該檔案中加入如下代碼:
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組件的建立過程,其中涉及到幾個函數:
當用戶端要建立一個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包含下列變數:
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);
你可以參照如下的方式定義你的類廠對象:
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
你可以用Guidgen or Uuidgen給自己的Filter產生一個128位的ID號,然後利用DEFINE_GUID宏在Filter的標頭檔聲明該Filter的CLSID;
[myFilter.h]
// {1915C5C7-02AA-415f-890F-76D94C85AAF1}
DEFINE_GUID(CLSID_MYFilter,
0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);
這個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.
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;
}
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對象的,用的模板類定義了一個全域的模板類對象數組,一般格式如下:
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]);
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 <streams.h>
#include <initguid.h>
#include <tchar.h>
#include <stdio.h>
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/kasdmn/archive/2009/04/06/4052364.aspx