利用DirectShow開發自己的Filter(最簡單,最基礎,不帶Pin的Filter)

學習directshow已經有幾天了,下面將自己的學習心得寫下來,希望對其他的人有幫助。

Filter實質是個COM組件,所以學習開發Filter之前你應該對com的知識有點了解。Com組件的實質是一個實現了純虛指針接口的C++對象。關於com的東西,這裏不多講。

1 給VC配置DShow的開發環境

  無論開發Filter還是開發Dshow的應用程序都要配置一下開發環境的,其實就是包含一下dshow用到的頭文件和動態庫。 選擇Tools菜單下面的Options。在彈出的Option對話框配置如下;

 添加頭文件

ASPectratio="t" v:ext="edit">

選擇動態庫文件添加到工程中

2 創建工程以及Filter的入口函數

創建工程

一般情況下,創建Filter使用一個普通的Win32 DLL項目。而且,一般Filter項目不使用MFC。這時,應用程序通過CoCreateInstance函數Filter實例;Filter與應用程序在二進制級別的協作。另外一種方法,也可以在MFC的應用程序項目中創建Filter

vc裏新建一個工程,選擇win32動態庫,如下圖;

 

這樣生成了一個簡單的DLL,只有一個Dllmain入口函數。

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

Filter是個基於DLLcom組件,所以一般的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@1Min@2,表示要導出的函數順序號,在進行顯式連時可以用到它。該DLL編譯成功後,打開工程中的Debug目錄,同樣也會看到MyDll.dllMyDll.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文件

其中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接口的變量沒有定義,不着急,下面我們就開始實現Filtercom接口。

3 如何實現Filter 的類廠對象

本節內容要講一下Filter是如何實現com接口的,它和其他的com實現方式的不同。

我們知道一個Filter是一個com組件,所以它com特性的實現其實在其基類中實現的,比如Perlink>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包含下列變量:

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

 

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

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]);    

4 如何實現自己的Filter

在這裏就要講如何創建自己的Filter了,下面我們以寫一個CTransformFilter爲例

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

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

從邏輯上考慮,在寫Filter之前,選擇一個合適的Filter基類是至關重要的。爲此,你必須對幾個Filter的基類有相當的瞭解。在實際應用中,Filter的基類並不總是選擇CBaseFilter的。相反,因爲我們絕大部分寫的都是中間的傳輸FilterTransform Filter),所以基類選擇CTransformFilterCTransInPlaceFilter的居多。如果我們寫的是源Filter,我們可以選擇CSource作爲基類;如果是Renderer Filter,可以選擇CBaseRendererCBaseVideoRenderer等。
  
總之,選擇好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的基類也就相應選定了。接下去,就是FilterPin上的代碼實現了。有一點需要注意的是,從軟件設計的角度上來說,應該將你的邏輯類代碼同Filter的代碼分開。下面,我們一起來看一下輸入Pin的實現。你需要實現基類所有的純虛函數,比如CheckMediaType等。在CheckMediaType內,你可以對媒體類型進行檢驗,看是否是你期望的那種。因爲大部分Filter採用的是推模式傳輸數據,所以在輸入Pin上一般都實現了Receive方法。有的基類裏面已經實現了Receive,而在Filter類上留一個純虛函數供用戶重載進行數據處理。這種情況下一般是無需重載Receive方法的,除非基類的實現不符合你的實際要求。而如果你重載了Receive方法,一般會同時重載以下三個函數EndOfStreamBeginFlushEndFlush。我們再來看一下輸出Pin的實現。一般情況下,你要實現基類所有的純虛函數,除了CheckMediaType進行媒體類型檢查外,一般還有DecideBufferSize以決定Sample使用內存的大小,GetMediaType提供支持的媒體類型。

最後,我們看一下Filter類的實現。首先當然也要實現基類的所有純虛函數。除此之外,Filter還要實現CreateInstance以提供COM的入口,實現NonDelegatingQueryInterface以暴露支持的接口。如果我們創建了自定義的輸入、輸出Pin,一般我們還要重載GetPinCountGetPin兩個函數。

  這裏我主要爲了舉例,所以簡單寫的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的頭文件聲明該FilterCLSID;

// {1915C5C7-02AA-415f-890F-76D94C85AAF1}

DEFINE_GUID(CLSID_MYFilter,

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

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的基本過程就時這樣了,至於邏輯上的東西,比如Filterpin如何連接,數據流是如何流動的,你都要去看看sdk了,按照上面的步驟你就可以寫一個Filter的框架出來。

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

1 Filter的實現類

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

2 com組件的引出函數

五個全局函數

   DllMain                //dll的入口函數

   DllGetClassObject        //獲得com組件的類廠對象

   DllCanUnloadNow       //com組件是否可以卸載  

   DllRegisterServer        //註冊com組件   

   DllUnregisterServer      //卸載com組件

  其中DllGetClassObject 已經由基類完成你自己只要完成三個函數即可DllMainDllRegisterServerDllUnregisterServer

3 com組件的類廠對象

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

CFactoryTemplate g_Templates[1] =

{

    {

      L"my filter",                // Name

      &CLSID_MYFilter,             // FilterCLSID

      CMyFilter::CreateInstance,      // 創建Filter的方法

      NULL,                        // Initialization function

      &sudInfTee                   //Filter的信息(一個結構)

    }

};

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.

};

最後sudFilterReg會在類廠對象數組中用到。

CFactoryTemplate g_Templates[1] =

{

    {

      L"my filter",                // Name

      &CLSID_MYFilter,             // CLSID

      CMyFilter::CreateInstance,   // Method to create an instance of MyComponent

      NULL,                           // Initialization function

      &sudFilterReg                          // Set-up information (for filters)

    }

};

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

#include <streams.h>   #include <initguid.h>

#include <tchar.h>     #include <stdio.h>

以下提供Filter的.h和.CPP文件

/*FilterTest.h*/


#if !defined(AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_)
#define AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class FilterTest : public CCritSec, public CBaseFilter
{
public:
    int GetPinCount();
    CBasePin * GetPin(int n);
    static CUnknown * WINAPI CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr);
    FilterTest(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr);
    virtual ~FilterTest();
};

#endif // !defined(AFX_FILTERTEST1_H__7401629D_FA1F_4728_9A3A_7CB605777860__INCLUDED_)


 


 

/*FilterTest.CPP*/

// FilterTest1.cpp: implementation of the FilterTest class.
//
//////////////////////////////////////////////////////////////////////
#include <streams.h>  
#include <initguid.h>
#include <tchar.h>    
#include <stdio.h>

#include "FilterTest.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

// {7FBB207C-1861-4191-89F2-7A07F25F32DD}
DEFINE_GUID(CLSID_FilterTest,
0x7fbb207c, 0x1861, 0x4191, 0x89, 0xf2, 0x7a, 0x7, 0xf2, 0x5f, 0x32, 0xdd);


FilterTest::FilterTest(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr)
:CBaseFilter(NAME("FilterTest"), punk, this, CLSID_FilterTest)
{

}

FilterTest::~FilterTest()
{

}

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_FilterTest,      // Filter CLSID.
    L"FilterTest",              // Filter name.
    MERIT_NORMAL,           // Merit.
    1,                      // Number of pin types.
    &sudOutputPin           // Pointer to pin information.
};

CFactoryTemplate g_Templates[1] =

{   
    {   
        L"FilterTest",                    // Name
        &CLSID_FilterTest,                // CLSID
        FilterTest::CreateInstance,        // Method to create an instance of MyComponent
        NULL,                           // Initialization function
        &sudFilterReg                   // Set-up information (for filters)   
    }
};

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

CUnknown * WINAPI FilterTest::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)
{
    FilterTest *pFilter = new FilterTest(NAME("FilterTest"), pUnk, pHr);
   
    if (pFilter== NULL)
    {
        *pHr = E_OUTOFMEMORY;
    }
   
    return pFilter;   
}

CBasePin * FilterTest::GetPin(int n)
{
    return NULL;
}

int FilterTest::GetPinCount()
{
    return 0;
}

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);
}

#pragma warning( disable:4514)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章