利用Directshow開發傳輸filter

摘要:本篇文檔主要講述了利用Directshow開發傳輸filter 時應該注意的一些事情。


       在開發自己的filter之前,看看DMO(DirectX Media Object)是否滿足你的要求,因爲DMO可以做許多和filter相同的工作,但是開發DMO比開發filter要簡單多了。開發transform filter主要有下面的幾個步驟,努力的遵循吧

第一步選擇一個基類

下面的基類適合開發transform filter。
CTransformFilter就是爲了transform filter而設計的基類,這個類中有分開的輸入和輸出buffers,這種類型的filter有時也稱作copy-transform filter,當一個copy-transform filter接收到一個輸入samples的時候,它就將sample寫入到一塊新的輸出buffer中,然後將這個新的buffer傳遞給下一個filter。

       CTransInPlaceFilter,這個類型的filter在原來的buffer裏修改data,也叫trans-in-place filters。當這種類型的filter接收到一個sample,它改變這個sample中的數據,然後將sample傳遞下去,這種類型的輸入pin和輸出pin總是按照某個媒體類型連接起來。
CVideoTransformFilter這個類型的filter僅僅是爲了視頻解碼器設計的。從CTransFormFilter派生而來,但是這個filter可以根據下游的render自動的丟棄data。CBaseFilter是個總基類,所有的filter都是從這個類派生出去的。如果上面的filter都不適合你,那麼你只有自己從這個基類中派生了。


第二步   聲明自己的Filter 類


首先聲明一個從基類派生的c++類
class CRleFilter : public CTransformFilter
{
/* Declarations will go here. */
};

每個filter類都需要連接的pin類。根據你的需要,你要派生和你的filter連接的pin類。
你還要給你的filter設置一個不能重複的CLSID,你可以利用Guidgen or Uuidgen來產生一個128位CLSID,切忌不要拷貝其它的filter的。有很多種方法來聲明CLSID,下面的例子使用了DEFINE_GUID宏。
[RleFilt.h]
// {1915C5C7-02AA-415f-890F-76D94C85AAF1}
DEFINE_GUID(CLSID_RLEFilter,
0x1915c5c7, 0x2aa, 0x415f, 0x89, 0xf, 0x76, 0xd9, 0x4c, 0x85, 0xaa, 0xf1);

[RleFilt.cpp]
#include <initguid.h>
#include "RleFilt.h"
然後,給你的filter寫一個構造函數
CRleFilter::CRleFilter()
: CTransformFilter(NAME("My RLE Encoder"), 0, CLSID_RLEFilter)
{
/* Initialize any private variables here. */
}
注意,構造函數中有個參數就是我前面定義的CLSID。


第三步 支持媒體類型協議

       當兩個pin連接的時候,他們必須就某種媒體類型達成一致協議,否則連接失敗,數據媒體類型描述了數據的格式,如果沒有媒體類型,一個filter可能傳遞一種類型的數據,然後其它的filte卻不能識別這種數據。

Pin連接的時候達成協議的機制主要通過IPin::ReceiveConnection方法來實現的。輸出pin用某種媒體類型作參數調用輸入pin上的這個方法,輸入pin要麼接受,要麼拒絕。如果輸入pin拒絕連接,那麼輸出pin更改一下媒體類型繼續連接,直至所有的媒體類型都連接一遍,如果沒有找到合適的媒體的類型,那麼連接失敗。在輸入pin也可以通過IPin::EnumMediaTypes方法來任意的枚舉它所支持的媒體類型list。輸出pin可以通過這個list也可以檢查是否支持某種媒體類型。

CTransformFilter實現一個通用的框架。如下:

1 輸入pin沒有首選的媒體類型,這個主要看上游的filter提議的媒體類型。對於視頻數據,媒體類型包括圖片的大小,和楨率,這個信息必須由上游的源filter或者parser filter提供。對於音頻數據,設置的數據格式就小了許多,因此,要重載輸入pin的CBasePin::GetMediaType

2 當上遊的filter提議一個媒體類型進行連接的時候,輸入pin就調用
CTransformFilter::CheckInputType方法,這個方法拒絕和接受媒體類型。

3 只有輸入pin連接以後,輸出pin才能夠連接,這個是屬於transform filter的一個特性。大多數情況下,filter在設置輸出pin的type之前一定要設置好輸入pin的類型

4當輸出pin沒有連接的時候,它向下遊filter連接的時候,要枚舉本filter支持的媒體類型,形成一個list,他通過調用CTransformFilter::GetMediaType方法來產生這個list,輸出pin會就下游filter所支持的所有的媒體類型進行連接

5 爲了檢測輸入pin是否支持某個特定的輸出媒體類型,輸出pin通過調用CTransformFilter::CheckTransform方法。
上面列出的三個CTransformFilter方法都是純虛函數,因此你的filter必須實現這三個函數。當上遊的filter連接的時候提議一個媒體類型,那麼輸入pin就會調用函數virtual HRESULT CheckInputType(const CMediaType* mtIn) pure;  這個函數包含了一個CMediaType類型的對象指針,這個類型封裝了一個AM_MEDIA_TYPE結構。在這個函數中,你要檢查AM_MEDIA_TYPE結構的中相關的field,如果該結構中有任何fied不合法,就返回VFW_E_TYPE_NOT_ACCEPTED,如果所有的媒體類型都是正確的,返還S_OK

例如,在RLE編碼filter,輸入類型必須是8位或者4位的沒有壓縮的RGB視頻。沒有必要支持其它的輸入格式,例如16,24位,因爲那樣,filter還得進行轉換。下面的例子假定filter只支持8位的視頻,不支持4位的視頻

HRESULT CRleFilter::CheckInputType(const CMediaType *mtIn)
{
if ((mtIn->majortype != MEDIATYPE_Video) ||
(mtIn->subtype != MEDIASUBTYPE_RGB8) ||
(mtIn->formattype != FORMAT_VideoInfo) ||
(mtIn->cbFormat < sizeof(VIDEOINFOHEADER)))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(mtIn->pbFormat);
if ((pVih->bmiHeader.biBitCount != 8) ||
(pVih->bmiHeader.biCompression != BI_RGB))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Check the palette table.
if (pVih->bmiHeader.biClrUsed > PALETTE_ENTRIES(pVih))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
DWORD cbPalette = pVih->bmiHeader.biClrUsed * sizeof(RGBQUAD);
if (mtIn->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Everything is good.
return S_OK;
}

在這個例子中,函數首先檢查major type and subtype,然後檢查格式類型,爲了確保block格式是一個VIDEOINFOHEADER結構,這個filter也要支持VIDEOINFOHEADER2,

如果格式類型是正確的,這個sample還得檢查VIDEOINFOHEADER結構的biBitCount and biCompression members,

2 virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE;
CTransformFilter::GetMediaType根據序號iPositiong返回一個fiter支持的輸出類型。只有輸入pin被連接上以後,這個方法纔會被調用。因此,你可以利用上游filter支持的媒體類型來決定下游輸出的媒體類型

下面的例子返回一個輸出媒體類型,這個輸出是根據輸入類型修改的

HRESULT CRleFilter::GetMediaType(int iPosition, CMediaType *pMediaType)
{
ASSERT(m_pInput->IsConnected());
if (iPosition < 0)
{
return E_INVALIDARG;
}
if (iPosition == 0)
{
HRESULT hr = m_pInput->ConnectionMediaType(pMediaType);
if (FAILED(hr))
{
return hr;
}

FOURCCMap fccMap = FCC(''''MRLE'''');
pMediaType->subtype = static_cast<GUID>(fccMap);
pMediaType->SetVariableSize();
pMediaType->SetTemporalCompression(FALSE);

ASSERT(pMediaType->formattype == FORMAT_VideoInfo);
VIDEOINFOHEADER *pVih =
reinterpret_cast<VIDEOINFOHEADER*>(pMediaType->pbFormat);
pVih->bmiHeader.biCompression = BI_RLE8;
pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
return S_OK;
}
// else
return VFW_S_NO_MORE_ITEMS;
}

這個例子函數中,調用了IPin::ConnectionMediaType從輸入pin上得到輸入的媒體類型。然後改變了媒體類型結構的幾個filed,表示是壓縮格式

1 It assigns a new subtype GUID, which is constructed from the FOURCC code ''''MRLE'''', using the FOURCCMap class.
2 It calls the CMediaType::SetVariableSize method, which sets the bFixedSizeSamples flag to FALSE and the lSampleSize member to zero, indicating variable-sized samples.
3 It calls the CMediaType::SetTemporalCompression method with the value FALSE, indicating that every frame is a key frame. (This field is informational only, so you could safely ignore it.)
4 It sets the biCompression field to BI_RLE8.
5 It sets the biSizeImage field to the image size.


3 virtual HRESULT CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut) PURE;

CTransformFilter::CheckTransform檢查輸出的媒體類型和輸入的媒體類型是否匹配。當輸入pin在輸出pin連接之後纔開始連接的時候,輸出pin會調用這個函數來檢查輸出媒體類型是否和輸入媒體類型是否匹配。
下面的例子演示了查詢數據的格式是否爲RLE8視頻,圖像的大小是否和輸入的匹配,調色板的入口是否一致,如果圖像大小不一致就要拒絕

HRESULT CRleFilter::CheckTransform(
const CMediaType *mtIn, const CMediaType *mtOut)
{
// Check the major type.
if (mtOut->majortype != MEDIATYPE_Video)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Check the subtype and format type.
FOURCCMap fccMap = FCC(''''MRLE'''');
if (mtOut->subtype != static_cast<GUID>(fccMap))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
if ((mtOut->formattype != FORMAT_VideoInfo) ||
(mtOut->cbFormat < sizeof(VIDEOINFOHEADER)))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Compare the bitmap information against the input type.
ASSERT(mtIn->formattype == FORMAT_VideoInfo);
BITMAPINFOHEADER *pBmiOut = HEADER(mtOut->pbFormat);
BITMAPINFOHEADER *pBmiIn = HEADER(mtIn->pbFormat);
if ((pBmiOut->biPlanes != 1) ||
(pBmiOut->biBitCount != 8) ||
(pBmiOut->biCompression != BI_RLE8) ||
(pBmiOut->biWidth != pBmiIn->biWidth) ||
(pBmiOut->biHeight != pBmiIn->biHeight))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Compare source and target rectangles.
RECT rcImg;
SetRect(&rcImg, 0, 0, pBmiIn->biWidth, pBmiIn->biHeight);
RECT *prcSrc = &((VIDEOINFOHEADER*)(mtIn->pbFormat))->rcSource;
RECT *prcTarget = &((VIDEOINFOHEADER*)(mtOut->pbFormat))->rcTarget;
if (!IsRectEmpty(prcSrc) && !EqualRect(prcSrc, &rcImg))
{
return VFW_E_INVALIDMEDIATYPE;
}
if (!IsRectEmpty(prcTarget) && !EqualRect(prcTarget, &rcImg))
{
return VFW_E_INVALIDMEDIATYPE;
}

// Check the palette table.
if (pBmiOut->biClrUsed != pBmiIn->biClrUsed)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
DWORD cbPalette = pBmiOut->biClrUsed * sizeof(RGBQUAD);
if (mtOut->cbFormat < sizeof(VIDEOINFOHEADER) + cbPalette)
{
return VFW_E_TYPE_NOT_ACCEPTED;
}
if (0 != memcmp(pBmiOut + 1, pBmiIn + 1, cbPalette))
{
return VFW_E_TYPE_NOT_ACCEPTED;
}

// Everything is good.
return S_OK;
}


第四步 設置Allocator屬性

         當連個pin就某個媒體類型達成一致協議的時候,他們就選擇一個allocator,就allocator的屬性進行設置,比如buffer大小,buffer的數量。

在CTransformFilter 類中,有兩個allocator,一個用於上游的pin的連接,一個用於下游的pin的連接,上游的filter選擇upstream allocator設置屬性,無論上游的filter怎麼設置這個upstream allocator,輸入pin都會接受,如果你想改變這個中狀況,你可以繼承CBaseInputPin::NotifyAllocator函數

Transform filter的輸出pin選擇下游的allocator,步驟如下

1 如果下游的filter可以提供一個allocator,那麼輸出pin就使用這個allocator,否則,輸出pin就創建一個新的allocator。

2 輸出pin通過下游filter的輸入pin上的IMemInputPin::GetAllocatorRequirements.方法來確定下游filter的allocator的要求。

3 輸出pin調用transform filter上的CTransformFilter::DecideBufferSize函數,這個函數也是一個純虛的函數,

virtual HRESULT DecideBufferSize( IMemAllocator * pAllocator,ALLOCATOR_PROPERTIES *pprop) PURE;
這個函數有一個指向allocator的指針,和一個指向ALLOCATOR_PROPERTIES結構的指針,這個指針包含了對allocator的屬性的設置,如果下游的filter對allocator沒有設置屬性,那麼這個結構就是NULL。

4在DecideBufferSize方法中,派生類的函數通過調用IMemAllocator::SetProperties.函數來設置allocator的屬性。
通常,派生類會根據輸出的格式,下游filter得要求,自身得要求來設置allocator的屬性,allocator屬性的設置要符合下游filter的要求,否則的話,連接就可能被拒絕。

下面的例子中,

HRESULT CRleFilter::DecideBufferSize(
IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp)
{
AM_MEDIA_TYPE mt;
HRESULT hr = m_pOutput->ConnectionMediaType(&mt);
if (FAILED(hr))
{
return hr;
}

ASSERT(mt.formattype == FORMAT_VideoInfo);
BITMAPINFOHEADER *pbmi = HEADER(mt.pbFormat);
pProp->cbBuffer = DIBSIZE(*pbmi) * 2;
if (pProp->cbAlign == 0)
{
pProp->cbAlign = 1;
}
if (pProp->cBuffers == 0)
{
pProp->cBuffers = 1;
}
// Release the format block.
FreeMediaType(mt);

// Set allocator properties.
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProp, &Actual);
if (FAILED(hr))
{
return hr;
}
// Even when it succeeds, check the actual result.
if (pProp->cbBuffer > Actual.cbBuffer)
{
return E_FAIL;
}
return S_OK;
}

即使SetProperties函數成功,你也要檢查結果,以確保滿足你的需要。缺省的情況下,所有的filter都採用CMemAllocator類類分配內存,這個類從客戶進程的虛擬地址中分配內存,如果你的filter需要其它的內存,比如,DirectDraw表面,你可以派生一個通用的allocator,你可以從CBaseAllocator類派生一個新的類,根據不同的pin使用你的派生的新的allocator類,你需要繼承不同的函數,

Input pin: CBaseInputPin::GetAllocator and CBaseInputPin::NotifyAllocator.

Output pin: CBaseOutputPin::DecideAllocator.

如果其它的filter拒絕使用你的custom allocator,你的filter和其它filter連接的時候就會失敗。


第五部傳輸媒體數據

      上游filter通過調用filter上輸入pin上的IMemInputPin::Receive方法,將sample傳遞到filter,filter調用CTransformFilter::Transform方法來處理數據,注意,這個方法也是一個純虛的函數,你要是想用,你必須提供函數實現。
CTransformFilter::Transform有兩個指針,一個指向輸入sample,一直只想輸出smaple,再調用這個方法之前,要將sample從輸入sample拷貝到輸出sample。

如果transform返回S_ok,filter就將sample傳遞到下游的filter。下面的代碼演示了RLE encoder如何實現這個函數的,你可以參考一下,當然你的函數和這個是不一樣的。要注意

HRESULT CRleFilter::Transform(IMediaSample *pSource, IMediaSample *pDest)
{
// Get pointers to the underlying buffers.
BYTE *pBufferIn, *pBufferOut;
hr = pSource->GetPointer(&pBufferIn);
if (FAILED(hr))
{
return hr;
}
hr = pDest->GetPointer(&pBufferOut);
if (FAILED(hr))
{
return hr;
}
// Process the data.
DWORD cbDest = EncodeFrame(pBufferIn, pBufferOut);
KASSERT((long)cbDest <= pDest->GetSize());

pDest->SetActualDataLength(cbDest);
pDest->SetSyncPoint(TRUE);
return S_OK;
}


需要注意的幾個問題

1 時間戳,CTransformFilter在調用Transform方法之前就給輸出sample打上了時間戳,它僅僅是從輸入的stample上講時間戳拷貝過來,不做任何的改動,如果你的filter需要改動時間戳,你可以調用輸出sample上的IMediaSample::SetTime方法

2 數據格式的改變

上游的filter可以 動態的改變數據的格式,在改動數據的格式之前,它要調用你的輸入pin上的IPin::QueryAccept方法,在filter上,這個方法的調用會引起CheckInputType,和CheckTransform的方法的調用。下游的filter也可以改變數據格式,機理和這個一樣。

在你的filter中,需要做兩件事情

1 )要確保QueryAccept返回正確
2 )如果你的filter不接受數據格式的改變,那麼就在你的filter的Transform方法中調用IMediaSample::GetMediaType.方法,如果這個方法返回s_ok,那麼你的filter就要適用數據的改變。

3線程

在CTransformFilter中,filter在Receive方法同步的發送輸出sample。Filter沒有創建任何的線程來處理數據。

第六步 支持COM特性

最後一步是支持com屬性。添加com支持的步驟和前面一樣,並且在前面也講述的很清楚了,下面列出必須的幾個要素

1 引用計數Reference Counting,接口查詢QueryInterface 很簡單,從基類派生即可

CMyFilter : public CBaseFilter, public IMyCustomInterface
{
public:
DECLARE_IUNKNOWN
STDMETHODIMP NonDelegatingQueryInterface(REFIID iid, void **ppv);
};

STDMETHODIMP CMyFilter::NonDelegatingQueryInterface(REFIID iid, void **ppv)
{
if (riid == IID_IMyCustomInterface) {
return GetInterface(static_cast<IMyCustomInterface*>(this), ppv);
}
return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
}

2對象的創建Object Creation

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

模板數組

static WCHAR g_wszName[] = L"My RLE Encoder";
CFactoryTemplate g_Templates[] =
{
{
g_wszName,
&CLSID_RLEFilter,
CRleFilter::CreateInstance,
NULL,
NULL
}
};

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

3組件的註冊

// Declare media type information.
FOURCCMap fccMap = FCC(''''MRLE'''');
REGPINTYPES sudInputTypes = { &MEDIATYPE_Video, &GUID_NULL };
REGPINTYPES sudOutputTypes = { &MEDIATYPE_Video, (GUID*)&fccMap };

// Declare pin information.
REGFILTERPINS sudPinReg[] = {
// Input pin.
{ 0, FALSE, // Rendered?
FALSE, // Output?
FALSE, // Zero?
FALSE, // Many?
0, 0,
1, &sudInputTypes // Media types.
},
// Output pin.
{ 0, FALSE, // Rendered?
TRUE, // Output?
FALSE, // Zero?
FALSE, // Many?
0, 0,
1, &sudOutputTypes // Media types.
}
};

// Declare filter information.
REGFILTER2 rf2FilterReg = {
1, // Version number.
MERIT_DO_NOT_USE, // Merit.
2, // Number of pins.
sudPinReg // Pointer to pin information.
};

STDAPI DllRegisterServer(void)
{
HRESULT hr = AMovieDllRegisterServer2(TRUE);
if (FAILED(hr))
{
return hr;
}
IFilterMapper2 *pFM2 = NULL;
hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
IID_IFilterMapper2, (void **)&pFM2);
if (SUCCEEDED(hr))
{
hr = pFM2->RegisterFilter(
CLSID_RLEFilter, // Filter CLSID.
g_wszName, // Filter name.
NULL, // Device moniker.
&CLSID_VideoCompressorCategory, // Video compressor category.
g_wszName, // Instance data.
&rf2FilterReg // Filter information.
);
pFM2->Release();
}
return hr;
}

STDAPI DllUnregisterServer()
{
HRESULT hr = AMovieDllRegisterServer2(FALSE);
if (FAILED(hr))
{
return hr;
}
IFilterMapper2 *pFM2 = NULL;
hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
IID_IFilterMapper2, (void **)&pFM2);
if (SUCCEEDED(hr))
{
hr = pFM2->UnregisterFilter(&CLSID_VideoCompressorCategory,
g_wszName, CLSID_RLEFilter);
pFM2->Release();
}
return hr;
}

有時候,你的filter並不總是通過DLL提供的,有時,你可能給一個特定的程序寫了一個特定的filter,那麼你就可以直接用你的filter,你可以直接用new方法,如下

#include "MyFilter.h" // Header file that declares the filter class.
// Compile and link MyFilter.cpp.
int main()
{
IBaseFilter *pFilter = 0;
{
// Scope to hide pF.
CMyFilter* pF = new MyFilter();
if (!pF)
{
printf("Could not create MyFilter.\n");
return 1;
}
pF->QueryInterface(IID_IBaseFilter,
reinterpret_cast<void**>(&pFilter));
}

/* Now use pFilter as normal. */

pFilter->Release(); // Deletes the filter.
return 0;
}



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