二、Filter 原理
2.1 Filter 概述
Filter(過濾器)是 DirectShow 中最基本的概念。DirectShow 是通過 Filter Graph 來管理 Filter 的。Filter Graph 是 Filter 的 "容器",而 Filter 是 Filter Graph 中的最小功能模塊。
Filter 一般由一個或多個 Pin 組成,Filter 通過 Pin 相互連接,構成一條順序的鏈路。Pin 是由 Filter 創建的 COM 對象。在上面曾經提到,Filter 根據實現功能的不同大致可分為 3 類:Source Filters、Transform Filters 和 Rendering Filters。另一種實用的判別方法是根據 Filter 包含的輸入 Pin 或者輸出 Pin 的數量來判斷。
如下圖所示,可以根據 Filter 所包含的輸入 Pin 和輸出 Pin 的熟練來判斷 Filter 的類型:
- 只有輸出 Pin,沒有輸入 pin,為 Source Filter。
- 既有輸出 Pin,又有輸入 pin,為 Transform Filter。
- 沒有輸出 Pin,只有輸入 pin,為 Rendering Filter。

Filter 是一種 COM 組件。為了實現在 Filter Graph 中的統一操作,每個 Filter 上都至少實現了 IBaseFilter 接口。實現 Filter 的文件一般是一個 DLL,擴展名可以是 .dll,但更多是 .ax。跟普通的 COM 組件一樣,Filter 的創建是通過 API 函數 CoCreateInstance 來完成的,代碼如下:
STDAPI CoCreateInstance(
REFCLSID rclsid, // class identifier (CLSID) of the object
LPUNKNOWN pUnkOuter, // Pointer to controlling IUnknown
DWORD dwClsContext, // Context for running executable code
REFIID riid, // Reference to the identifier of the interface
LPVOID * ppv // the interface pointer requested in riid
);
- 參數 rclsid 指定要創建的 Filter 的 CLSID;
- 因為絕大多數情況下創建的 Filter 不是被 “聚合”(Aggregation)的,所以 pUnkOuter 指定為 NULL;
- dwClsContext 可以指定為 CLSCTX_INPROC_SERVER,以創建進程內組件對象;
- riid 在創建 Filter 成功后獲得的接口的 ID,一般為 IID_IBaseFilter,也可以是其他特殊的接口;
- ppv 用於獲得接口對象的指針。
Filter 必須加入到 Filter Graph 且接入到工作鏈路中才能發揮作用。如果想繞過 Filter Graph 而直接使用 Filter 實現的模塊功能,微軟公司提供了另一種解決方案,就是將 Filter 功能移植成 DirectX 媒體對象(DMO)。
2.2 Filter 的注冊
如果需要編寫自己的 Filter,才需要了解Filter 的注冊。所以這里由於篇幅原因不詳說,如果有這方面需求的話可以去看《DirectShow開發指南》2.2 小節,以及DirectShow Filter的開發實踐
2.3 Filter 的媒體類型
媒體類型實際上是 DirectShow 定義的一個數據結構 AM_MEDIA_TYPE,代碼如下:
typedef struct _AMMediaType
{
GUID majortype;
GUID subtype;
BOOL bFixedSizeSamples;
BOOL bTemporalCompression;
ULONG lSampleSize;
GUID formattype;
IUnknown *pUnk;
ULONG cbFormat;
BYTE *pbFormat;
}AM_MEDIA_TYPE;
從代碼中可以看出,媒體類型主要用 3 部分來描述:majortype(主類型)、subtype(輔助說明類型)和 formattype(格式細節類型)。這 3 部分各自用一個 GUID 來標識。它們的作用分別是:
- majortype 定性地描述媒體類型,如制定這是一個視頻(MEDIATYPE_Video)、音頻(MEDIATYPE_Audio)或者字節流(MEDIATYPE_Stream)等;
- subtype 輔助說明 majortype,指明具體是哪種格式,例如,若 majortype 是視頻, subtype 可以進一步指明這是 UYVY(MEDIASUBTYPE_UYVY)、RGB24(MEDIASUBTYPE_RGB24)還是RGB32(MEDIASUBTYPE_RGB32)等,若 majortype 是音頻,subtype 可以進一步指明這是 PCM 格式(MEDIASUBTYPE_PCM)還是 AC3 格式(MEDIASUBTYPE_DOLBY_AC3)等;
- formattype 指定了一種進一步描述格式細節的數據結構類型,格式細節描述的內容主要包括視頻圖像的大小、幀率,或者音頻的采樣頻率、量化精度等參數,這個描述格式細節的數據塊指針保存在 pbFormat 成員中。
AM_MEDIA_TYPE 結構中的其他成員,如 bFixedSizeSamples、bTemporalCompression 和 lSampleSize,它們都是可選參數,也就是說,Filter 並不一定定義這些參數,在 Filter 中引用這些參數的值也並不總是 “可信” 的。這一點對於 Filter 開發人員來說尤為重要。
常見的媒體類型這里不再列出,有興趣了解可以在 DirectShow MSDN 查看,或者搜索相關博客,例如:媒體結構(AM_MEDIA_TYPE structure)摘自MSDN
當使用一個 AM_MEDIA_TYPE 數據結構描述媒體類型時,如果 majortype、subtype 和 formattype 都指定了一個特定的 GUID 值,那么稱這種媒體類型為 “完全指定的媒體類型”;這 3 個部分只要有一個指定為 GUID_NULL,則稱之為 “不完全指定的媒體類型”。GUID_NULL 具有 “通配符” 的作用。
為了方便使用,DirectShow 提供了另一個 C++ 類——CMediaType 來操作媒體類型。CMediaType 類是從 AM_MEDIA_TYPE 數據結構中 “公共”(public)派生而來的,這樣既保留了直接訪問數據成員的靈活性,又增加了一些有用的類成員接口,如媒體類型賦值、媒體類型比較、格式數據塊內存的自動管理等。
2.4 Filter的連接
Filter 的連接實際上也就是 Filter上Pin 的連接。連接的方向一般總是由上一級 Filter(Upstream Filter)的輸出 Pin 指向下一級 Filter(Downstream Filter)的輸入 Pin。
Pin 的連接實際上是連接雙方使用的媒體類型的一個 “協商” 過程。這個過程很重要,也有點復雜。具體怎么連接,這里由於篇幅原因不詳說,感興趣可以去看《DirectShow開發指南》2.4 小節,或者去 《DirectShow開發指南》學習筆記_4 了解。
2.5 智能連接
Filter 通過 Pin 這樣的連接,就能 “串聯” 起來,從而構建一個完整的 Filter Graph。Filter Graph 的構建方法大概有以下幾種:
IFilterGraph::AddFilter:該參數提供一個 Filter 對象,將其加入到 Filter Graph 中;IFilterGraph::ConnectDirect:該參數提供輸出 Pin、輸入 Pin 以及媒體類型,進行直接的連接;IGraphBuilder::AddSourceFilter:該參數提供源文件名,自動將一個 Source Filter 加入到 Filter Graph 中;IGraphBuilder::Connect:該參數提供輸出 pin 和輸入 pin 進行連接,如果連接失敗,自動嘗試在中間插入必要的格式轉換 Filter;IGraphBuilder::Render:該參數提供輸出 pin,自動加入必要的 Filter 完成剩下部分 Filter Graph 的構建(直到連接到 Rendering Filter);IGraphBuilder::RenderFile:該參數提供源文件名,自動加入必要的 Filter 完成這個文件的回放 Filter Graph 構建。
IFilterGraph::AddFilter用到的情況較多。后 4 種構建方法都有 “自動” 的功能。在 DirectShow 中,這種 “自動” 機制也稱為智能連接(Intelligent Connect)。智能連接的具體實現過程,這里由於篇幅原因不詳說,感興趣可以去看《DirectShow開發指南》2.5 小節,或者去 《DirectShow開發指南》學習筆記_5 了解。
