建立一個filter執行個體(transform filter)(好好學習學習)

來源:互聯網
上載者:User

 

建立一個filter執行個體(transform 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) 這個函數是由輸入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了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.