DShow簡介
??????? DirectShow(簡稱 DShow) 是一個 Windows 平臺上的流媒體框架,提供了高質(zhì)量的多媒體流采集和回放功能折联。支持使用 WDM 驅(qū)動或早期的 VFW 驅(qū)動來進(jìn)行多媒體流的采集。橫跨WINXP峻村,WIN7窖壕,WIN8,WIN10垫言,適配性好贰剥,穩(wěn)定性高。DirectShow位于應(yīng)用層中筷频。它使用一種叫Filter Graph的模型來管理整個數(shù)據(jù)流的處理過程蚌成;參與數(shù)據(jù)處理的各個功能模塊叫Filter崭庸;各個Filter 在Filter Graph中按一定的順序連接成一條"流水線"協(xié)同工作酌儒。( 可以看出TFilterGraph是個Filter的容器 )按照功能來分与倡,F(xiàn)ilter大致分為三類:Source Filters彪见、Transform Filters和Rendering Filters腌歉。
Source Filters主要負(fù)責(zé)取得數(shù)據(jù)暇唾,數(shù)據(jù)源可以是文件进统、因特網(wǎng)乖寒、或者計算機(jī)里的采集卡坡锡、數(shù)字?jǐn)z像機(jī)等蓬网;
Transform Fitlers主要負(fù)責(zé)數(shù)據(jù)的格式轉(zhuǎn)換窒所、傳輸;
Rendering Filtes主要負(fù)責(zé)數(shù)據(jù)的最終去向帆锋,我們可以將數(shù)據(jù)送給聲卡吵取、顯卡進(jìn)行多媒體的演示,也可以輸出到文件進(jìn)行存儲锯厢。
下圖簡單展示了DShow工作流過程
DirectShow操作攝像頭流程
1. 使用CoCreateInstance創(chuàng)建 IGraphBuilder接口(所有接口的“總管”)皮官。
2. 從IGraphBuilder查詢出IMediaControl控制接口。
3. 創(chuàng)建ICreateDevEnum接口实辑,枚舉出系統(tǒng)所有安裝的攝像頭捺氢。
4. 選擇攝像頭,并且獲取這個攝像頭的IBaseFilter接口剪撬, 把這個接口添加到IGraphBuilder中 摄乒。
5. 選擇其他Filter,比如壓縮的Filter残黑,Render Filter等馍佑,加到IGraphBuilder中。
6. 定義SourceFilter梨水,TransformFilter拭荤,RenderFilter,用RenderStream將這些鏈接起來冰木。這樣就構(gòu)成了一個DShow的連接圖穷劈。
?? 7.運(yùn)行 IMediaControl 的Run函數(shù),要使整個“”圖“” 動起來踊沸,這樣攝像頭的數(shù)據(jù)就會流經(jīng)每個Filter歇终,最終到達(dá)RenderFilter并在終端顯示出來。
主要代碼實(shí)現(xiàn)如下:
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,IID_IGraphBuilder, (void**)&graphBuilder); ///創(chuàng)建 IGraphBuilder接口
hr = graphBuilder->QueryInterface(IID_IMediaControl, (void**)&control); //查詢IMediaControl
CComPtr<ICreateDevEnum> DevEnum; ///創(chuàng)建枚舉攝像頭設(shè)備接口
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&DevEnum);
CComPtr<IEnumMoniker> pEM;//枚舉
IMoniker* pM; //查詢到的每個設(shè)備
hr = DevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEM, 0);
while (pEM->Next(1, &pM, &fetch) == S_OK) {
?????///開始枚舉每個設(shè)備逼龟,如果是我們的虛擬DSHOW攝像頭评凝,也會被枚舉到
?????........
????///選擇我們感興趣的攝像頭, 獲取Filter接口,比如deviceFilter名字
????pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&deviceFilter);
}
//調(diào)用RenderStream把graph里的filter鏈接起來
m_pCaptureGB->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
deviceFilter, m_pSampleGrabberFilter, NULL);?調(diào)用control->Run 腺律, 即可讓其運(yùn)行起來
虛擬攝像頭
1.虛擬攝像頭注冊
????? ? 在windows系統(tǒng)中奕短,虛擬攝像頭的注冊是通過在注冊表中添加攝像頭信息實(shí)現(xiàn)的,windows規(guī)定修改注冊表的程序需要在DLL動態(tài)庫中實(shí)現(xiàn)匀钧, 這個DLL要具備COM接口動態(tài)庫的基本條件翎碑,需要實(shí)現(xiàn)DllRegisterServer, DllUnregisterServer之斯, DllGetClassObject日杈,DllCanUnloadNow四個導(dǎo)出函數(shù)。并且在DllRegisterServer函數(shù)中實(shí)現(xiàn)虛擬攝像頭注冊,然后就可以使用regsvr32命名進(jìn)行注冊表寫入莉擒,?? 其主要代碼如下:
IFilterMapper2* pFM = NULL;
hr = CoCreateInstance(CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER, IID_IFilterMapper2, (void**)&pFM);
REGFILTERPINS VCamPins = {
????L"Pins",
????FALSE, ///
????TRUE, ?/// output
????FALSE, /// can hav none
????FALSE, /// can have many
????&CLSID_NULL, // obs
????L"PIN",
????1,
????&PinTypes
};
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 1;
rf2.rgPins = &VCamPins;
?//根據(jù)上邊提供的信息酿炸,調(diào)用RegisterFilter 注冊。
hr = pFM->RegisterFilter(CLSID_VCamDShow, L"TAL_Camera", &pMoniker, &CLSID_VideoInputDeviceCategory, NULL, &rf2);
2.虛擬攝像頭實(shí)現(xiàn)
??????? DShow虛擬攝像頭涨冀,除了必須實(shí)現(xiàn)的?DllRegisterServer填硕, DllUnregisterServer, DllGetClassObject鹿鳖,DllCanUnloadNow四個導(dǎo)出函數(shù)外扁眯,還需要開發(fā)虛擬攝像頭類,這個類必須繼承IBaseFilter接口栓辜,IBaseFilter是DShow?Filter的基礎(chǔ)導(dǎo)出接口恋拍,每個Filter下有一個或者多個PIN接口,因此還必須實(shí)現(xiàn)IPIN接口藕甩,大致數(shù)據(jù)結(jié)構(gòu)如下:
class VCamDShowFilter: public IUnknown,public IBaseFilter, public IAMovieSetup
{
protected:
。周荐。狭莱。//內(nèi)部數(shù)據(jù)變量和私有函數(shù)
??????VCamStream* ????m_Stream; /// 這個就是我們的 IPin接口, 就只需要一個就可以了概作,????????? VCamStream數(shù)據(jù)結(jié)構(gòu)下面會描述腋妙。
public:
????????//IUnknow 接口
。讯榕。骤素。。
???????// IBaseFilter 接口
??????STDMETHODIMP GetClassID(...);///
??????STDMETHODIMP Stop() ;/// 停止愚屁, IMediaControl接口調(diào)用
??????STDMETHODIMP Pause(); ///暫停济竹,
??????STDMETHODIMP Run(); ?///運(yùn)行
??????STDMETHODIMP GetState(...); ///獲取運(yùn)行,暫停霎槐,停止等狀態(tài)
??????STDMETHODIMP GetSyncSouce(...); ??
??????STDMETHODIMP SetSyncSource(...);
??????STDMETHODIMP ?EnumPins(...); ????查詢當(dāng)前filter 提供的IPin 接口信息送浊, DirectShow庫通過此函數(shù)獲取當(dāng)前Filter提供的IPin信息
??????STDMETHODIMP ?FindPin(...); ?//
??????STDMETHODIMP QueryFilterInfo(...); ///獲取當(dāng)前Filter信息
??????STDMETHODIMP JoinFIlterGraph(...); /// 把當(dāng)前filter加入到DirectShow圖中,其實(shí)就是對應(yīng) IGraphBuilder->AddFilter 調(diào)用時候被調(diào)用丘跌。
??????............
};
class VCamStreamPin : public IUnknown,public IPin, public IQualityControl, public IAMStreamConfig, public IKsPropertySet
{
protected:
袭景。。闭树。//內(nèi)部數(shù)據(jù)變量和私有函數(shù)
?????? VCamDShowFilter* ??m_pFilter; ????????// 所屬的Filter耸棒,對應(yīng)上面定義的VCamDShow數(shù)據(jù)結(jié)構(gòu)。
???????/ 下面是數(shù)據(jù)源相關(guān)的線程报辱,在 StreamTreadLoop 中循環(huán)采集數(shù)據(jù)与殃,并且通過 IMemInputPin 把數(shù)據(jù)傳輸給輸入PIN。
?????? HANDLE ?m_hThread; ///
?????? HANDLE ?m_event;
???? ? BOOL ???m_quit; ??
???? ? static DWORD CALLBACK thread(void* _p)
?????? {
? ? ????????? VCamStreamPin* p = (VCamStreamPin*)_p;
????? ? ? ? ? CoInitializeEx(NULL, COINIT_MULTITHREADED);
? ? ????????? p->StreamTreadLoop();
????????????? CoUninitialize();
? ? ? ? ? ? ? return 0;
? ? ?? }
? ? ? void StreamTreadLoop();
public:
??????//IUnknow 接口
??????.....
??????IPin 接口
??????STDMETHODIMP ?Connect(....); 把 輸入PIN和輸出PIN連接起來,這個是主要函數(shù)奈籽,其實(shí)就是對應(yīng) ?
??????????????????????????????????????????????????????????????????????IGraphBuilder->Connect(devicePin,renderPin);
??????STDMETHODIMP ?ReceiveConnection(...); ///接收連接
??????STDMETHODIMP ?DIsconnect(...); ?///斷開與其他PIN的連接
??????STDMETHODIMP ?ConnectTo(...); ?以下基本都是一些狀態(tài)和數(shù)據(jù)信息查詢
??????STDMETHODIMP ?ConnectionMediaType(...); ///
??????STDMETHODIMP ?QueryPinInfo(....);
??????STDMETHODIMP ?QueryDirection(...); ///
??????.............
??????IQualityControl
??????....
??????/ IAMStreamConfig...
??????STDMETHODIMP SetFormat(...); ///
??????STDMETHODIMP ?GetFormat(...); ///
??????STDMETHODIMP ?GetNumberOfCapabilities(...); ///
??????STDMETHODIMP ?GetStreamCaps(....);
??????/// IKsPropertySet
??????STDMETHODIMP ?Get(...); ///
??????STDMETHODIMP ?Set(...);
??????STDMETHODIMP ?QuerySupported(...); /
};
?????? 正如上面的查詢攝像頭的偽代碼所說饥侵,?ICreateDevEnum 接口查詢到我們感興趣的攝像頭,
當(dāng)綁定到這個攝像頭獲取IBaseFilter接口衣屏,調(diào)用 ?IMoniker 的 ?BindToObject 函數(shù)躏升,
雖然沒有?BindToObject 源代碼,但可以知道大致流程:
BindToObject查找CLSID_VCamDShow(我們自定義的GUID)等信息狼忱,
調(diào)用系統(tǒng)函數(shù)CoCreateInstance函數(shù)創(chuàng)建我們的對象并且獲取IBaseFilter接口膨疏,
CoCreateInstance 系統(tǒng)函數(shù)通過注冊表查找我們注冊的DLL所在位置,找到并且加載DLL钻弄,同時調(diào)用DllGetClassObject獲取
類工廠佃却,調(diào)用類工廠的CreateInstance創(chuàng)建我們的類,也就是上面的 VCamDShowFilter類窘俺, 從而獲取到IBaseFilter接口饲帅。
找到并且獲取到IBaseFilter指針后,接下來就是調(diào)用 IGraphBuilder->AddFilter 添加到 DirectShow的Graph中瘤泪,
這個時候?IBaseFilter的JoinFilterGraph方法被調(diào)用灶泵,我們在此方法中其實(shí)簡單保存IFilterGraph接口指針。
兩個PIN連接对途, 當(dāng)外部調(diào)用 IGraphBuilder ->Connect(vcamerPin , renderPin); vcamerPin就是我們的攝像頭的輸出PIN赦邻。
對應(yīng)IPin的Connect或者ReceiveConnection接口函數(shù)就會被調(diào)用。
在Connect函數(shù)中实檀,我們查找各種合適的MediaType做匹配惶洲,找到后就可開始連接,
ReceiveConnection函數(shù)中根據(jù)提供的MediaType直接進(jìn)行連接操作膳犹,
假設(shè)執(zhí)行具體連接的函數(shù)是?HRESULT doConnect(IPin* pRecvPin, const AM_MEDIA_TYPE* mt )恬吕;
因?yàn)槲覀兪翘摂MDSHOW攝像頭,我們的PIN是輸出PIN镣奋,是數(shù)據(jù)源币呵。
我們必須把我們的數(shù)據(jù)源傳輸給連接上來的輸入PIN,否則就是廢品侨颈,如何實(shí)現(xiàn)這個核心要求呢余赢。
其實(shí)輸入PIN必須要實(shí)現(xiàn)IMemInputPin 接口,這個接口就是用來傳遞數(shù)據(jù)的哈垢。
我們在獲取輸入PIN的IMemInputPin接口后妻柒,調(diào)用Receive方法就能把數(shù)據(jù)傳輸給輸入PIN了。
而Receive方法需要傳遞 IMediaSample 接口作為參數(shù)耘分,IMediaSample需要通過 IMemAllocator 接口的GetBuffer方法獲取举塔。
因此我們在?doConnect函數(shù)中绑警,除了獲取IMemInputPin接口外,還必須創(chuàng)建IMemAllocator 接口央渣。
doConnect大致偽代碼如下:
HRESULT VCamDShow::doConnect(IPin* pRecvPin, const AM_MEDIA_TYPE* mt )
{
???????.....
???????pRecvPin->QueryInterface(IID_IMemInputPin, (void**)&m_pInputPin); // 從輸入PIN 獲取IMEMInputPIN接口计盒,
???????...... 其他一些判斷處理,比如判斷MediaType是否匹配等
???????m_ConnectedPin = pRecvPin; ?///保存 輸入PIN指針芽丹。
???????m_ConnectedPin->AddRef();
???????///創(chuàng)建 IMemAllocator接口
???????hr = m_pInputPin->GetAllocator(&m_pAlloc);
???????if(FAILED(hr)) {
??????????????hr = CoCreateInstance(CLSID_MemoryAllocator,0,CLSCTX_INPROC_SERVER,IID_IMemAllocator,(void **)&m_pAlloc);
???????}
???????///通知輸入PIN北启,完成連接
???????hr = pRecvPin->ReceiveConnection((IPin*)this, mt);
}
其連接過程如下圖:
最后,我們要取得數(shù)據(jù)源
我們可以在VCamStreamPin 類里邊創(chuàng)建一個線程拔第,在這個線程里定時循環(huán)采集數(shù)據(jù)咕村,
并且通過?IMemInputPin接口把采集的數(shù)據(jù)傳輸給連接上來的輸入PIN。
如上面VCamStreamPin 數(shù)據(jù)結(jié)構(gòu)申明的一樣蚊俺。StreamTreadLoop 大致代碼如下:
void VCamStream::StreamTreadLoop()
{
??????? DWORD TMO = 33;
?///
? ? ?? while (!m_quit) {
? ? ? ? ? ? ? WaitForSingleObject(m_event, TMO);
????????????? if (m_quit)break;
? ? ? ? ? ? ? if (m_pFilter->m_State != State_Running) { //不是運(yùn)行狀態(tài)
? ? ? ? ? ? ? ? ? ?? continue;
?????? }
? ? ?? IMediaSample* sample = NULL;
?????? HRESULT hr = E_FAIL;
? ? ?? if (m_pAlloc) {
??????????? hr = m_pAlloc->GetBuffer(&sample, NULL, NULL, 0);
?????? }
????????????????.......................省略其他處理
?????? LONG length = sample->GetSize();
????? char* buffer = NULL;
????? hr = sample->GetPointer((BYTE**)&buffer);
???? //這個是一個回調(diào)函數(shù)懈涛,我們可以自定義這個回調(diào)函數(shù),并且在里邊填寫視頻幀數(shù)據(jù)泳猬。
????? m_pFilter->m_callback( buffer批钠, length ,暂殖。价匠。。); ?
????? m_pInputPin->Receive(sample); ?獲取到的視頻數(shù)據(jù)呛每,傳遞給輸入PIN。
}
數(shù)據(jù)幀的數(shù)據(jù)通過SourceFilter的輸入pin坡氯,流到RenderFilter晨横,實(shí)現(xiàn)整個攝像頭邏輯。