閱讀669 返回首頁    go 技術社區[雲棲]


利用 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組件的創建過程,其中涉及到幾個函數:

  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)

其中的兩個函數指針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>

最後更新:2017-04-03 14:54:18

  上一篇:go directdraw的多畫麵顯示rgb
  下一篇:go Java IO--合並流SequenceInputStream