實(shí)現(xiàn)DirectShow source filter有兩大類方法
一、從CSource類派生出一個(gè)source filter。在這個(gè)類中內(nèi)嵌一個(gè)CSourceStream的派生類用于實(shí)際數(shù)據(jù)幀的產(chǎn)生與傳遞。這類實(shí)現(xiàn)可以參考DirectShow SDK push source的例子
https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/directshow/filters/pushsource/PushSourceBitmap.cpp
https://msdn.microsoft.com/en-us/library/windows/desktop/dd757807(v=vs.85).aspx
http://blog.csdn.net/rageliu/article/details/621157
二、從CBaseFilter和CBaseOutputPin兩個(gè)更基礎(chǔ)的類派生,從而實(shí)現(xiàn)source filter。這類實(shí)現(xiàn)可以參考codeproject的文章和pjsip的實(shí)現(xiàn)
https://www.codeproject.com/articles/158053/directshow-filters-development-part-live-source
https://github.com/chakrit/pjsip/blob/master/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
先研究第一種實(shí)現(xiàn)方法。從DirectShow SDK push source的例子來(lái)看,CSource派生類的實(shí)現(xiàn)都非常簡(jiǎn)單,無(wú)非在構(gòu)造函數(shù)中生成一個(gè)CSourceStream派生的pin對(duì)象,在析構(gòu)函數(shù)中將pin刪除。主要的代碼實(shí)現(xiàn)都在CSourceStream的派生類。而CSourceStream要重載的主要方法包括
HRESULT GetMediaType(CMediaType *pMediaType);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
HRESULT FillBuffer(IMediaSample *pSample);
GetMediaType接口用于返回pin接受的媒體格式。GetMediaType有兩種形式的接口,一種是一個(gè)參數(shù)的,另外一種是兩個(gè)參數(shù)的
HRESULT GetMediaType(CMediaType *pMediaType);
HRESULT GetMediaType(int iPosition, CMediaType *pmt);
一個(gè)參數(shù)的版本適用于pin只提供一種輸出格式的情況。如果pin支持多種格式的輸出,需要使用第二種接口,同時(shí)重載CSourceStream::CheckMediaType接口。
CMediaType類是對(duì)AM_MEDIA_TYPE結(jié)構(gòu)體的封裝。這個(gè)結(jié)構(gòu)體要賦值的成員還是挺多的,如何設(shè)置可以參考例子當(dāng)中的GetMediaType函數(shù)
http://blog.csdn.net/zhengxinwcking/article/details/30475869
DecideBufferSize接口用于設(shè)置需要多大的buffer。CSourceStream類的父類CBasePin中有個(gè)m_mt的成員保存了GetMediaType設(shè)置的媒體格式。一份buffer的大小通常就是GetMediaType中設(shè)置的pmt->lSampleSize。一般用一份buffer也夠了。
FillBuffer接口用于填寫每一幀的音視頻數(shù)據(jù)。這個(gè)接口一般先調(diào)用IMediaSample::GetPointer獲得新一幀數(shù)據(jù)的緩沖,然后把數(shù)據(jù)寫入這個(gè)緩沖。接著調(diào)用IMediaSample::SetTime設(shè)置幀的時(shí)間戳。最后調(diào)用IMediaSample::SetSyncPoint設(shè)置該幀是否為關(guān)鍵幀。對(duì)于提供非壓縮數(shù)據(jù)的source filter,每一幀都應(yīng)該設(shè)置為TRUE。
如果source filter的下游是vmr7(vmr9測(cè)試沒有這個(gè)問(wèn)題),還需要多實(shí)現(xiàn)兩個(gè)接口
HRESULT CheckMediaType(const CMediaType *pMediaType)
STDMETHODIMP Notify(IBaseFilter *pSelf, Quality q)
vmr7要自己實(shí)現(xiàn)CheckMediaType的原因是,如果不重載CheckMediaType,DecideBufferSize函數(shù)中調(diào)用IMemAllocator::SetProperties會(huì)報(bào)錯(cuò),返回E_FAIL,從而導(dǎo)致FillBuffer不會(huì)被調(diào)用(更正,如果IMemAllocator::SetProperties,則DecideBufferSize也應(yīng)該報(bào)錯(cuò),這樣仍可以運(yùn)行)。stackoverflow上面有一個(gè)類似的問(wèn)題
https://stackoverflow.com/questions/7928828/negotiating-an-allocator-between-directshow-filters-fails
上面的解釋是,VMR/EVR filter所需要的buffer stride會(huì)跟source filter GetMediaType設(shè)置的圖像寬度不一致(顯存需要字節(jié)對(duì)齊),導(dǎo)致格式發(fā)生改變。當(dāng)VMR/EVR改變格式數(shù)據(jù)的時(shí)候,它會(huì)調(diào)用IPin::QueryAccept詢問(wèn)上游filter是否接受這個(gè)格式。CSourceStream實(shí)現(xiàn)的CheckMediaType是這樣的
HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType)
{
CAutoLock lock(m_pFilter->pStateLock());
CMediaType mt;
GetMediaType(&mt);
if (mt == *pMediaType) {
return NOERROR;
}
return E_FAIL;
}
它會(huì)調(diào)用我們派生類實(shí)現(xiàn)的GetMediaType獲得一個(gè)CMediaType,然后將這個(gè)CMediaType和下游傳過(guò)來(lái)的CMediaType作比較。如果不一致,就返回E_FAIL。而CMediaType的==運(yùn)算符是這樣定義的
BOOL
CMediaType::operator == (const CMediaType& rt) const
{
// I don't believe we need to check sample size or
// temporal compression flags, since I think these must
// be represented in the type, subtype and format somehow. They
// are pulled out as separate flags so that people who don't understand
// the particular format representation can still see them, but
// they should duplicate information in the format block.
return ((IsEqualGUID(majortype,rt.majortype) == TRUE) &&
(IsEqualGUID(subtype,rt.subtype) == TRUE) &&
(IsEqualGUID(formattype,rt.formattype) == TRUE) &&
(cbFormat == rt.cbFormat) &&
( (cbFormat == 0) ||
pbFormat != NULL && rt.pbFormat != NULL &&
(memcmp(pbFormat, rt.pbFormat, cbFormat) == 0)));
}
通過(guò)調(diào)試,發(fā)現(xiàn)上游傳過(guò)來(lái)的格式是用VIDEOINFO結(jié)構(gòu)體的,而且biHeight是負(fù)數(shù),因此會(huì)被誤判為格式不相同。因此需要我們自己重載實(shí)現(xiàn)CheckMediaType
Notify雖然沒什么鳥用,但是必須實(shí)現(xiàn)。否則CBasePin::Notify會(huì)拋出斷言錯(cuò)誤
CBasePin::Notify(IBaseFilter * pSender, Quality q)
{
UNREFERENCED_PARAMETER(q);
UNREFERENCED_PARAMETER(pSender);
DbgBreak("IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)");
return E_NOTIMPL;
} //Notify