DirectShow攝像頭和虛擬攝像頭

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)整個攝像頭邏輯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箫柳,一起剝皮案震驚了整個濱河市手形,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悯恍,老刑警劉巖库糠,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涮毫,居然都是意外死亡瞬欧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門罢防,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艘虎,“玉大人,你說我怎么就攤上這事咒吐∫敖ǎ” “怎么了属划?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長候生。 經(jīng)常有香客問我同眯,道長,這世上最難降的妖魔是什么唯鸭? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任须蜗,我火速辦了婚禮,結(jié)果婚禮上肿孵,老公的妹妹穿的比我還像新娘唠粥。我一直安慰自己,他們只是感情好停做,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布晤愧。 她就那樣靜靜地躺著,像睡著了一般蛉腌。 火紅的嫁衣襯著肌膚如雪官份。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天烙丛,我揣著相機(jī)與錄音舅巷,去河邊找鬼。 笑死河咽,一個胖子當(dāng)著我的面吹牛钠右,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忘蟹,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飒房,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媚值?” 一聲冷哼從身側(cè)響起狠毯,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褥芒,沒想到半個月后嚼松,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锰扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年献酗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片少辣。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡凌摄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漓帅,到底是詐尸還是另有隱情锨亏,我是刑警寧澤痴怨,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站器予,受9級特大地震影響浪藻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乾翔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一爱葵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧反浓,春花似錦萌丈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至月劈,卻和暖如春度迂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猜揪。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工惭墓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人而姐。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓腊凶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拴念。 傳聞我的和親對象是個殘疾皇子吭狡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容