利用 DirectShow 開發自己的 Filter

來源:互聯網
上載者:User

  利用 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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.