創建一個filter實例

1、選擇所要創建的filter的用途,據此來選擇基類。基類可以從CTransformFilter、CTransInPlaceFilter、CVideoTransformFilter和CBaseFilter中來選取。

(1) CTransInPlaceFilter提供了本地處理Sample的機制(Sample可以認爲是存儲一個視頻幀的結構),當一個trans-in-place filter收到一個sample時,你可以通過重載它的Transform()函數來修改其中的數據,trans-in-place filter會在Transform()函數執行完後直接把這個sample傳遞給下一個filter。

(2) CTransformFilter完成的功能與CTransInPlaceFilter一樣,它們的區別就是CTransformFilter總是把上游filter傳遞過來的sample複製一份,並把複製後的sample傳遞給下一個filter。當然,你可以通過重載Transform()函數來控制這個過程,包括修改其中的數據(這也是自己寫filter的原因)。

(3)CVideoTransformFilter與CTransformFilter一樣,只是多加了質量控制功能。

(4)以上三個filter都繼承於CBaseFilter,所以如果想對filter進行更多的控制,就要直接從CBaseFilter來繼承,但是所要做的工作也最多。

在這個例子中,我選擇CTransformFilter,因爲CTransInPlaceFilter太簡單了,dx9sdk中的例子NullNull就是一個完整的CTransInPlaceFilter的框架,並且只是一個框架,什麼工作也沒有做,如果要用的話直接修改就可以用了。

2、在vc中選擇win32 dll,創建一個dll,名字隨便取,這裏我取SplitFilter,選擇空dll。

3、

然後,創建一個類CSplitFilter,繼承自

CTransformFilter,當然要選擇public方式。

4、

爲filter生成一個

CLSID,可以使用Guidgen,它的用法是在命令行中打Guidgen,然後回車,Guidgen就執行了,單擊New GUID就會生成一個新的GUID;單擊Copy就可以把新生成的GUID複製到剪貼板上。然後在SplitFilter.h文件上粘貼進來,最後是這個樣子:

//////////////////////////////////////////////////////////////////////

// GUID

//////////////////////////////////////////////////////////////////////

// {3DCD790F-B7A0-429a-B9E1-3CE3255D8D1C}

DEFINE_GUID(<<name>>,

0x3dcd790f, 0xb7a0, 0x429a, 0xb9, 0xe1, 0x3c, 0xe3, 0x25, 0x5d, 0x8d, 0x1c);

然後把其中的<<name>>換成自己設置的名字,如下:

// {3DCD790F-B7A0-429a-B9E1-3CE3255D8D1C}

DEFINE_GUID(CLSID_SplitFilter,

0x3dcd790f, 0xb7a0, 0x429a, 0xb9, 0xe1, 0x3c, 0xe3, 0x25, 0x5d, 0x8d, 0x1c);

然後在SplitFilter.cpp中加入

#include <initguid.h>

再來修改構造函數,形式如下:

CSplitFilter::CSplitFilter() : CTransformFilter(NAME("SplitFilter"), 0, CLSID_SplitFilter)

{

}

5、處理媒體類型

     首先要明白一點,兩個filter連接,也就是兩個filter的輸出pin和輸入pin在進行連接,這個工作是由輸出pin發起的,由輸入pin來檢查媒體類型是否匹配,並決定是否接受這個連接。在CTransformFilter中協商媒體類型的工作是由CTransformFilter來完成的,這個工作本來是應該由pin來完成的,但是CTransformFilter中的pin只是簡單的調用CTransformFilter中相應的函數而已,所以我們所有的工作只是重載CTransformFilter中的三個虛函數而已:

(1)實現

CheckInputType(const CMediaType *mtIn)(不要問我如何添加這個函數,如果這個都不會的話趁早別看dshow了,趕緊去看vc的書去)。這個函數是由輸入pin來調用,當上遊輸出pin要來進行連接的時候,我們的filter的輸入pin就會調用這個函數來檢查是否支持上游輸出pin的媒體類型。其中的CMediaType類是對AM_MEDIA_TYPE結構的封裝,AM_MEDIA_TYPE包含了有關的媒體類型,具體可查閱dxsdk文檔。因爲我這裏只是寫一個框架來爲大家演示,功能只是傳遞sample,並不對數據有任何的修改,所以任何的格式都可以,所以不管什麼格式我們都應該返回ok,實際函數如下:

HRESULT CSplitFilter::CheckInputType(const CMediaType *mtIn)

{

// Everything is good.

    return S_OK;

}

(2)實現GetMediaType(int iPosition, CMediaType *pMediaType)

前面我們已經講了輸入pin接受連接的時候用的函數,那麼這個函數呢就是輸入pin進行連接的時候用的,輸出pin進行連接的時候首先要有一個支持的媒體類型列表,這個函數就是來生成這個列表的。實際上我們也可以直接返回一個ok了事,但是這樣做有點太不負責任,我們雖然什麼工作都不做,但是還是要把例行的檢查做完了,這樣老闆(filter graph)看到了也會很滿意,你們是不是也很贊同?

首先,我們要確定輸入pin是否已經連接了,如果輸入pin沒有連接,那我們和下面的filter連接了也沒有意義,因爲上游沒有數據傳過來,我們拿什麼給後面的filter?畫餅是不能充飢的:)

ASSERT(m_pInput->IsConnected());

然後,看看pin的位置是否正確

if (iPosition < 0)

    {

        return E_INVALIDARG;

}

    這個函數最後的結果是這樣的:

HRESULT CSplitFilter::GetMediaType(int iPosition, CMediaType *pMediaType)

{

    // Is the input pin connected

 

    if(m_pInput->IsConnected() == FALSE)

    {

        return E_UNEXPECTED;

    }

 

    // This should never happen

 

   if(iPosition < 0)

    {

        return E_INVALIDARG;

    }

 

    // Do we have more items to offer

 

    if(iPosition > 0)

    {

        return VFW_S_NO_MORE_ITEMS;

    }

 

    CheckPointer(pMediaType,E_POINTER);

 

    *pMediaType = m_pInput->CurrentMediaType();

    return NOERROR;

}

(3)實現CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut)

雖然輸入pin和輸出pin的媒體類型都已經協商過了,但是我們的filter是否能夠完成兩中媒體類型的轉換還是個未知數,那麼就要調用CheckTransform來判斷我們的filter是否能夠支持這兩個媒體類型的轉換。我在這裏的選擇是直接返回ok。呵呵:)

6、

設置分配器

雖然我們已經完成了媒體類型的匹配,但filter的連接還沒有完成,我們的pin還必須爲連接選擇

allocator,設置allocator的屬性,比如緩衝區的大小和數量等。輸入pin我們可以不用管它,因爲默認的情況下它只需要同意輸出pin提供的allocator就可以了,但是輸出pin的allocator就要我們自己來搞定了。

如果後面的filter提供了一個allocator,輸出pin可以使用這個allocator,否則就要建立一個新的allocator。這就要用到CTransformFilter的DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp),pAlloc是一個allocator的指針,pProp是一個ALLOCATOR_PROPERTIES結構體,代表後面的filter所需要的allocator的屬性。我們可以在這個函數中根據我們自己的filter的需要和後面的filter的需要來設置allocator的各項屬性,設置allocator的屬性可以用IMemAllocator::SetProperties函數。

這裏面最重要的就是設置buffer的大小,一般是從輸入pin的媒體類型的大小來決定,如果從這裏沒有得到固定的大小的話,我們就要猜buffer的大小了。

最後的函數是這樣的:

HRESULT CSplitFilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)

{

    CheckPointer(pAlloc,E_POINTER);

    CheckPointer(pProperties,E_POINTER);

 

    // Is the input pin connected

 

    if(m_pInput->IsConnected() == FALSE)

    {

        return E_UNEXPECTED;

    }

 

    HRESULT hr = NOERROR;

    pProperties->cBuffers = 1;

    pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize();

 

    ASSERT(pProperties->cbBuffer);

 

    // If we don't have fixed sized samples we must guess some size

 

    if(!m_pInput->CurrentMediaType().bFixedSizeSamples)

    {

        if(pProperties->cbBuffer < 100000)

        {

            // nothing more than a guess!!

            pProperties->cbBuffer = 100000;

        }

    }

 

    // Ask the allocator to reserve us some sample memory, NOTE the function

    // can succeed (that is return NOERROR) but still not have allocated the

    // memory that we requested, so we must check we got whatever we wanted

 

    ALLOCATOR_PROPERTIES Actual;

 

    hr = pAlloc->SetProperties(pProperties,&Actual);

    if(FAILED(hr))

    {

        return hr;

    }

 

    ASSERT(Actual.cBuffers == 1);

 

    if(pProperties->cBuffers > Actual.cBuffers ||

        pProperties->cbBuffer > Actual.cbBuffer)

    {

        return E_FAIL;

    }

 

    return NOERROR;

}

7、

處理數據

終於可以開始處理數據了,我們重載Transform(IMediaSample *pSource, IMediaSample *pDest)來實現對數據的處理,從字面上都可以看出這兩個sample的意義,因爲這裏我們什麼工作都不做,所以我們只需要把源sample複製到目的sample就可以了。這個函數如下:

HRESULT CSplitFilter::Transform(IMediaSample *pIn, IMediaSample *pOut)

{

        HRESULT hr = Copy(pIn, pOut);

        return hr;

}

其中用到了功能函數copy,其代碼如下:

HRESULT CSplitFilter::Copy(IMediaSample *pSource, IMediaSample *pDest) const

{

        CheckPointer(pSource,E_POINTER);

    CheckPointer(pDest,E_POINTER);

 

    // Copy the sample data

    BYTE *pSourceBuffer, *pDestBuffer;

    long lSourceSize = pSource->GetActualDataLength();

 

#ifdef DEBUG

    long lDestSize = pDest->GetSize();

    ASSERT(lDestSize >= lSourceSize);

#endif

 

    pSource->GetPointer(&pSourceBuffer);

    pDest->GetPointer(&pDestBuffer);

 

    CopyMemory((PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize);

 

    // Copy the sample times

 

    REFERENCE_TIME TimeStart, TimeEnd;

    if(NOERROR == pSource->GetTime(&TimeStart, &TimeEnd))

    {

        pDest->SetTime(&TimeStart, &TimeEnd);

    }

 

    LONGLONG MediaStart, MediaEnd;

    if(pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR)

    {

        pDest->SetMediaTime(&MediaStart,&MediaEnd);

    }

 

    // Copy the Sync point property

 

    HRESULT hr = pSource->IsSyncPoint();

    if(hr == S_OK)

    {

        pDest->SetSyncPoint(TRUE);

    }

    else if(hr == S_FALSE)

    {

        pDest->SetSyncPoint(FALSE);

    }

    else

    { // an unexpected error has occured...

        return E_UNEXPECTED;

    }

 

    // Copy the media type

 

    AM_MEDIA_TYPE *pMediaType;

    pSource->GetMediaType(&pMediaType);

    pDest->SetMediaType(pMediaType);

    DeleteMediaType(pMediaType);

 

    // Copy the preroll property

 

    hr = pSource->IsPreroll();

    if(hr == S_OK)

    {

        pDest->SetPreroll(TRUE);

    }

    else if(hr == S_FALSE)

    {

        pDest->SetPreroll(FALSE);

    }

    else

    { // an unexpected error has occured...

        return E_UNEXPECTED;

    }

 

    // Copy the discontinuity property

 

    hr = pSource->IsDiscontinuity();

 

    if(hr == S_OK)

    {

        pDest->SetDiscontinuity(TRUE);

    }

    else if(hr == S_FALSE)

    {

        pDest->SetDiscontinuity(FALSE);

    }

    else

    { // an unexpected error has occured...

        return E_UNEXPECTED;

    }

 

    // Copy the actual data length

 

    long lDataLength = pSource->GetActualDataLength();

    pDest->SetActualDataLength(lDataLength);

 

    return NOERROR;

}

8、

增加對com的支持

因爲filter是一個com組件,所以要符合com規範。

又因爲CTransformFilter是從CUnknown繼承而來的,所以不用再自己實現AddRef 和 Release,如果我們要是有自己定義的接口的話,就要自己實現QueryInterface,幸好我們這個filter不實現自己定義的接口,就不用那麼麻煩了,呵呵。

好了,閒話少續,開始了:

首先,創建一個靜態的方法來返回一個我們的filter的實例,這個方法的名字可以任意指定,但是一般都叫做CreateInstance,那我們也就取這個名字吧,這個函數的參數如下(內含一般代碼):

CUnknown * WINAPI CSplitFilter::CreateInstance(LPUNKNOWN pUnk, HRESULT *pHr)

{

        CSplitFilter *pFilter = new CSplitFilter();

    if (pFilter== NULL)

    {

        *pHr = E_OUTOFMEMORY;

    }

    return pFilter;

 

}

然後,聲明一個全局的CFactoryTemplate類數組,取名g_Templates,每一個CFactoryTemplate實例包含一個filter或者我們定義的接口的註冊信息,因爲我們這裏就只有一個filter,也沒有自定義接口,所以數組中就只有一項內容。

CFactoryTemplate g_Templates[] =

{

 {

    L"SplitFilter",

    &CLSID_SplitFilter,

    CSplitFilter::CreateInstance,

    NULL,

    NULL

 }

};

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

最後,實現dll的註冊函數

STDAPI DllRegisterServer()

{

    return AMovieDllRegisterServer2( TRUE );

}

STDAPI DllUnregisterServer()

{

    return AMovieDllRegisterServer2( FALSE );

}

 

再加上#include <Streams.h>

引入靜態庫Strmbasd.lib Msvcrtd.lib Winmm.lib

最後再建立其導出文件SplitFilter.def,內容如下:

LIBRARY     SplitFilter.ax

 

EXPORTS

            DllMain                 PRIVATE

            DllGetClassObject       PRIVATE

            DllCanUnloadNow         PRIVATE

            DllRegisterServer       PRIVATE

            DllUnregisterServer     PRIVATE

 

這樣我們就可以建立一個最基本的filter的框架了。

 

當然,如果你要播放高清視頻,如果在分配器上分配的內存不夠的話你可以加大內存,也就是剛開始猜內存需要多大的時候儘量大一點,我反正在測試2732×768的時候報錯,但是我把分配的內存加了個0就ok了。

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