主要包括下面内容
1關于視訊捕捉(About Video Capture in Dshow) 2選擇一個視訊捕捉裝置(Select capture device) 3預覽視訊(Previewing Video) 4如何捕捉視訊流并儲存到檔案(Capture video to File) 5将裝置從系統中移走時的事件通知(Device remove Notify) 6如何控制Capture Graph(Controlling Capture Graph) 7如何配置一個視訊捕捉裝置 8從靜止圖像pin中捕捉圖檔
1關于視訊捕捉(About Video Capture in Dshow)
1視訊捕捉Graph的建構一個能夠捕捉音頻或者視訊的graph圖都稱之為捕捉graph圖。捕捉graph圖比一般的檔案回放graph圖要複雜許多,dshow提供了一個Capture Graph Builder COM元件使得捕捉graph圖的生成更加簡單。Capture Graph Builder提供了一個ICaptureGraphBuilder2接口,這個接口提供了一些方法用來建構和控制捕捉graph。首先建立一個Capture Graph Builder對象和一個graph manger對象,然後用filter graph manager 作參數,調用ICaptureGraphBuilder2::SetFiltergraph來初始化Capture Graph Builder。2視訊捕捉的裝置現在許多新的視訊捕捉裝置都采用的是WDM驅動方法,在WDM機制中,微軟提供了一個獨立于硬體裝置的驅動,稱為類驅動程式。驅動程式的供應商提供的驅動程式稱為minidrivers。Minidrivers提供了直接和硬體打交道的函數,在這些函數中調用了類驅動。在directshow的filter圖表中,任何一個WDM捕捉裝置都是做為一個WDM Video Capture 過濾器(Filter)出現。WDM Video Capture過濾器根據驅動程式的特征建構自己的filter
下面是陸其明的一篇有關于dshow和硬體的文章,可以拿來參考一下
//陸文章開始 大家知道,為了提高系統的穩定性,Windows作業系統對硬體操作進行了隔離;應用程式一般不能直接通路硬體。DirectShow Filter工作在使用者模式(User mode,作業系統特權級别為Ring 3),而硬體工作在核心模式(Kernel mode,作業系統特權級别為Ring 0),那麼它們之間怎麼協同工作呢?
DirectShow解決的方法是,為這些硬體設計包裝Filter;這種Filter能夠工作在使用者模式下,外觀、控制方法跟普通Filter一樣,而包裝Filter内部完成與硬體驅動程式的互動。這樣的設計,使得編寫DirectShow應用程式的開發人員,從為支援硬體而需做出的特殊進行中解脫出來。DirectShow已經內建的包裝Filter,包括Audio Capture Filter(qcap.dll)、VfW Capture Filter(qcap.dll,Filter的Class Id為CLSID_VfwCapture)、TV Tuner Filter(KSTVTune.ax,Filter的Class Id為CLSID_CTVTunerFilter)、Analog Video Crossbar Filter(ksxbar.ax)、TV Audio Filter(Filter的Class Id為CLSID_TVAudioFilter)等;另外,DirectShow為采用WDM驅動程式的硬體設計了KsProxy Filter(Ksproxy.ax,)。我們來看一下結構圖:
圖1 從上圖中,我們可以看出,Ksproxy.ax、Kstune.ax、Ksxbar.ax這些包裝Filter跟其它普通的DirectShow Filter處于同一個級别,可以協同工作;使用者模式下的Filter通過Stream Class控制硬體的驅動程式minidriver(由硬體廠商提供的實作對硬體控制功能的DLL);Stream Class和minidriver一起向上層提供系統底層級别的服務。值得注意的是,這裡的Stream Class是一種驅動模型,它負責調用硬體的minidriver;另外,Stream Class的功能還在于協調minidriver之間的工作,使得一些資料可以直接在Kernel mode下從一個硬體傳輸到另一個硬體(或同一個硬體上的不同功能子產品),提高了系統的工作效率。(更多的關于底層驅動程式的細節,請讀者參閱Windows DDK。)
下面,我們分别來看一下幾種常見的硬體。 VfW視訊采集卡。這類硬體在市場上已經處于一種淘汰的趨勢;新生産的視訊采集卡一般采用WDM驅動模型。但是,DirectShow為了保持向後相容,還是專門提供了一個包裝Filter支援這種硬體。和其他硬體的包裝Filter一樣,這種包裝Filter的建立不是像普通Filter一樣使用CoCreateInstance,而要通過系統枚舉,然後BindToObject。
音頻采集卡(聲霸卡)。聲霸卡的采集功能也是通過包裝Filter來實作的;而且現在的聲霸卡大部分都有混音的功能。這個Filter一般有幾個Input pin,每個pin都代表一個輸入,如Line In、Microphone、CD、MIDI等。值得注意的是,這些pin代表的是聲霸卡上的實體輸入端子,在Filter Graph中是永遠不會連接配接到其他Filter上的。聲霸卡的輸出功能,可以有兩個Filter供選擇:DirectSound Renderer Filter和Audio Renderer (WaveOut) Filter。注意,這兩個Filter不是上述意義上的包裝Filter,它們能夠同硬體互動,是因為它們使用了API函數:前者使用了DirectSound API,後者使用了waveOut API。這兩個Filter的差別,還在于後者輸出音頻的同時不支援混音。(順便說明一下,Video Renderer Filter能夠通路顯示卡,也是因為使用了GDI、DirectDraw或Direct3D API。)如果你的機器上有聲霸卡的話,你可以通過GraphEdit,在Audio Capture Sources目錄下看到這個聲霸卡的包裝Filter。
WDM驅動的硬體(包括視訊捕捉卡、硬體解壓卡等)。這類硬體都使用Ksproxy.ax這個包裝Filter。Ksproxy.ax實作了很多功能,是以有“瑞士軍刀”的美譽;它還被稱作為“變色龍Filter”,因為該Filter上定義了統一的接口,而接口的實作因具體的硬體驅動程式而異。在Filter Graph中,Ksproxy Filter顯示的名字為硬體的Friendly name(一般在驅動程式的.inf檔案中定義)。我們可以通過GraphEdit,在WDM Streaming開頭的目錄中找到本機系統中安裝的WDM硬體。因為KsProxy.ax能夠代表各種WDM的音視訊裝置,是以這個包裝Filter的工作流程有點複雜。這個Filter不會預先知道要代表哪種類型的裝置,它必須首先通路驅動程式的屬性集,然後動态配置Filter上應該實作的接口。當Ksproxy Filter上的接口方法被應用程式或其他Filter調用時,它會将調用方法以及參數傳遞給驅動程式,由驅動程式最終完成指定功能。除此以外,WDM硬體還支援核心流(Kernel Streaming),即核心模式下的資料傳輸,而無需經過到使用者模式的轉換。因為核心模式與使用者模式之間的互相轉換,需要花費很大的計算量。如果使用核心流,不僅可以避免大量的計算,還避免了核心資料與主機記憶體之間的拷貝過程。在這種情況下,使用者模式的Filter Graph中,即使pin之間是連接配接的,也不會有實際的資料流動。典型的情況,如帶有Video Port Pin的視訊捕捉卡,Preview時顯示的圖像就是在核心模式下直接傳送到顯示卡的顯存的。是以,你也休想在VP Pin後面截獲資料流。
講到這裡,我想大家應該對DirectShow對硬體的支援問題有了一個總體的認識。對于應用程式開發人員來說,這方面的内容不用研究得太透,而隻需作為背景知識了解一下就好了。其實,大量繁瑣的工作DirectShow已經幫我們做好了。 //陸其明文章結束
Direcshow中視訊捕捉的Filter Pin的種類捕捉Filter一般都有兩個或多個輸出pin,他們輸出的媒體類型都一樣,比如預覽pin和捕捉pin,是以根據媒體類型就不能很好的差別這些pin。此時就要根據pin的功能來差別每個pin了,每個pin都有一個GUID,稱為pin的種類。如果想仔細的了解pin的種類,請看後面的相關内容Working with Pin Categories。對于大多數的應用來說,ICaptureGraphBuilder2提供了一些函數可以自動确定pin的種類。預覽pin和捕捉pin 視訊捕捉Filter都提供了預覽和捕捉的輸出pin,預覽pin用來将視訊流在螢幕上顯示,捕捉pin用來将視訊流寫入檔案。預覽pin和輸出pin有下面的差別: 1 為了保證捕捉pin對視訊桢流量,預覽pin必要的時候可以停止。 2 經過捕捉pin的視訊桢都有時間戳,但是預覽pin的視訊流沒有時間戳。預覽pin的視訊流之是以沒有時間戳的原因在于filter圖表管理器在視訊流裡加一個很小的latency,如果捕捉時間被認為就是render時間的話,視訊renderFilter就認為視訊流有一個小小的延遲,如果此時render filter試圖連續播放的時候,就會丢桢。去掉時間戳就保證了視訊桢來了就可以播放,不用等待,也不丢桢。預覽pin的種類GUID為PIN_CATEGORY_PREVIEW 捕捉pin的種類GUID為PIN_CATEGORY_CAPTURE Video Port pin Video Port是一個介于視訊裝置(TV)和視訊卡之間的硬體裝置。同過Video Port,視訊資料可以直接發送到圖像卡上,通過硬體的覆寫,視訊可以直接在螢幕顯示出來。Video Port就是連接配接兩個裝置的。使用Video Port的最大好處是,不用CPU的任何工作,視訊流直接寫入記憶體中。當然它也有下面的缺點drawbacks:略如果捕捉裝置使用了Video Port,捕捉Filter就用一個video port pin代替預覽pin。 video port pin的種類GUID為PIN_CATEGORY_VIDEOPORT 一個捕捉filter至少有一個Capture pin,另外,它可能有一個預覽pin 和一個video port pin ,或者兩者都沒有,也許filter有很多的capture pin,和預覽pin,每一個pin都代表一種媒體類型,是以一個filter可以有一個視訊capture pin,視訊預覽pin,音頻捕捉pin,音頻預覽pin。 Upstream WDM Filters 在捕捉Filter之上,WDM裝置可能需要額外的filters,下面就是這些filter TV Tuner Filter TV Audio Filter. Analog Video Crossbar Filter 盡管這些都是一些獨立的filter,但是他們可能代表的是同一個硬體裝置,每個filter都控制裝置的不同函數,這些filter通過pin連接配接起來,但是在pin中沒有資料流動。是以,這些pin 的連接配接和媒體類型無關。他們使用一個GUID值來定義一個給定裝置的minidriver,例如:TV tuner Filter 和video capture filter都支援同一種medium。在實際應用中,如果你使用ICaptureGraphBuilder2來建立你的capture graphs,這些filters就會自動被添加到你的graph中。更多的詳細資料,可以參考WDM Class Driver Filters
2選擇一個視訊捕捉裝置(Select capture device)
如何選擇一個視訊捕捉裝置,可以采用系統裝置枚舉,詳細資料參見Using the System Device Enumerator 。enumerator可以根據filter的種類傳回一個裝置的monikers。Moniker是一個com對象,可以參見IMoniker的SDK。對于捕捉裝置,下面兩種類是相關的。 CLSID_AudioInputDeviceCategory 音頻裝置 CLSID_VideoInputDeviceCategory 視訊裝置下面的代碼示範了如何枚舉一個視訊捕捉裝置
ICreateDevEnum *pDevEnum = NULL; IEnumMoniker *pEnum = NULL; // Create the System Device Enumerator. HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, reinterpret_cast<void**>(&pDevEnum)); if (SUCCEEDED(hr)) { //建立一個枚舉器,枚舉視訊裝置 hr = pDevEnum->CreateClassEnumerator( CLSID_VideoInputDeviceCategory, &pEnum, 0); }
IEnumMoniker接口pEnum傳回一個IMoniker接口的清單,代表一系列的moniker,你可以顯示所有的裝置,然後讓使用者選擇一個。采用IMoniker::BindToStorage方法,傳回一個IPropertyBag接口指針。然後調用IPropertyBag::Read讀取moniker的屬性。下面看看都包含什麼屬性 1 FriendlyName 是裝置的名字 2 Description 屬性僅僅适用于DV和D-VHS/MPEG攝象機,如果這個屬性可用,這個屬性更詳細的描述了裝置的資料 3DevicePath 這個屬性是不可讀的,但是每個裝置都有一個獨一無二的。你可以用這個屬性來差別同一個裝置的不同執行個體下面的代碼示範了如何顯示周遊裝置的名稱 ,接上面的代碼
HWND hList; // Handle to the list box. IMoniker *pMoniker = NULL; while (pEnum->Next(1, &pMoniker, NULL) == S_OK) { IPropertyBag *pPropBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)(&pPropBag)); if (FAILED(hr)) { pMoniker->Release(); continue; // Skip this one, maybe the next one will work. } // Find the description or friendly name. VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"Description", &varName, 0); if (FAILED(hr)) { hr = pPropBag->Read(L"FriendlyName", &varName, 0); } if (SUCCEEDED(hr)) { // Add it to the application's list box. USES_CONVERSION; (long)SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)OLE2T(varName.bstrVal)); VariantClear(&varName); } pPropBag->Release(); pMoniker->Release(); }
如果使用者選中了一個裝置調用IMoniker::BindToObject為裝置生成filter,然後将filter加入到graph中。
IBaseFilter *pCap = NULL; hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap); if (SUCCEEDED(hr)) { hr = m_pGraph->AddFilter(pCap, L"Capture Filter"); } 3預覽視訊(Previewing Video)
為了建立可以預覽視訊的graph,可以調用下面的代碼
ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder // Initialize pBuild (not shown). IBaseFilter *pCap; // Video capture filter. hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);
4如何捕捉視訊流并儲存到檔案(Capture video to File)
1 将視訊流儲存到AVI檔案下面的圖表顯示了graph圖
圖2 AVI Mux filter接收從capture pin過來的視訊流,然後将其打包成AVI流。音頻流也可以連接配接到AVI Mux Filter上,這樣mux filter就将視訊流和視訊流合成AVI流。File writer将AVI流寫入到檔案中。可以像下面這樣建構graph圖
IBaseFilter *pMux; hr = pBuild->SetOutputFileName( &MEDIASUBTYPE_Avi, // Specifies AVI for the target file. L"C://Example.avi", // File name. &pMux, // Receives a pointer to the mux. NULL); // (Optional) Receives a pointer to the file sink.
第一個參數表明檔案的類型,這裡表明是AVI,第二個參數是制定檔案的名稱。對于AVI檔案,SetOutputFileName函數會建立一個AVI mux Filter 和一個 File writer Filter ,并且将兩個filter添加到graph圖中,在這個函數中,通過File Writer Filter 請求IFileSinkFilter接口,然後調用IFileSinkFilter::SetFileName方法,設定檔案的名稱。然後将兩個filter連接配接起來。第三個參數傳回一個指向 AVI Mux的指針,同時,它也通過第四個參數傳回一個IFileSinkFilter參數,如果你不需要這個參數,你可以将這個參數設定成NULL。然後,你應該調用下面的函數将capture filter 和AVI Mux連接配接起來。
hr = pBuild->RenderStream( &PIN_CATEGORY_CAPTURE, // Pin category. &MEDIATYPE_Video, // Media type. pCap, // Capture filter. NULL, // Intermediate filter (optional). pMux); // Mux or file sink filter. // Release the mux filter. pMux->Release();
第5個參數就是使用的上面函數傳回的pMux指針。當捕捉音頻的時候,媒體類型要設定為MEDIATYPE_Audio,如果你從兩個不同的裝置捕捉視訊和音頻,你最好将音頻設定成主流,這樣可以防止兩個資料流間drift,因為avi mux filter為同步音頻,會調整視訊的播放速度的。為了設定master 流,調用IConfigAviMux::SetMasterStream方法,可以采用如下的代碼:
IConfigAviMux *pConfigMux = NULL; hr = pMux->QueryInterface(IID_IConfigAviMux, (void**)&pConfigMux); if (SUCCEEDED(hr)) { pConfigMux->SetMasterStream(1); pConfigMux->Release(); } SetMasterStream的參數指的是資料流的數目,這個是由調用RenderStream的次序決定的。例如,如果你調用RenderStream首先用于視訊流,然後是音頻,那麼視訊流就是0,音頻流就是1。添加編碼filter
IBaseFilter *pEncoder; // Add it to the filter graph. pGraph->AddFilter(pEncoder, L"Encoder); // Render the stream. hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, pEncoder, pMux); pEncoder->Release();
2将視訊流儲存成wmv格式的檔案為了将視訊流儲存成并編碼成windows media video (WMV)格式的檔案,将capture pin連到WM ASF Writer filter。
圖3 建構graph圖最簡單的方法就是将在ICaptureGraphBuilder2::SetOutputFileName方法中指定MEDIASUBTYPE_Asf的filter。如下
IBaseFilter* pASFWriter = 0; hr = pBuild->SetOutputFileName( &MEDIASUBTYPE_Asf, // Create a Windows Media file. L"C://VidCap.wmv", // File name. &pASFWriter, // Receives a pointer to the filter. NULL); // Receives an IFileSinkFilter interface pointer (optional). 參數MEDIASUBTYPE_Asf 告訴graph builder,要使用wm asf writer作為檔案接收器,于是,pbuild 就建立這個filter,将其添加到graph圖中,然後調用IFileSinkFilter::SetFileName來設定輸出檔案的名字。第三個參數用來傳回一個ASF writer指針,第四個參數用來傳回檔案的指針。在将任何pin連接配接到WM ASF Writer之前,一定要對WM ASF Writer進行一下設定,你可以同過WM ASF Writer的IConfigAsfWriter接口指針來進行設定。
IConfigAsfWriter *pConfig = 0; hr = pASFWriter->QueryInterface(IID_IConfigAsfWriter, (void**)&pConfig); if (SUCCEEDED(hr)) { // Configure the ASF Writer filter. pConfig->Release(); }
然後調用ICaptureGraphBuilder2::RenderStream将capture Filter 和 ASF writer連接配接起來。
hr = pBuild->RenderStream( &PIN_CATEGORY_CAPTURE, // Capture pin. &MEDIATYPE_Video, // Video. Use MEDIATYPE_Audio for audio. pCap, // Pointer to the capture filter. 0, pASFWriter); // Pointer to the sink filter (ASF Writer).
3儲存成自定義的檔案格式如果你想将檔案儲存成自己的格式,你必須有自己的 file writer。看下面的代碼
IBaseFilter *pMux = 0; IFileSinkFilter *pSink = 0; hr = pBuild->SetOutputFileName( &CLSID_MyCustomMuxFilter, //自己開發的Filter L"C://VidCap.avi", &pMux, &pSink);
4如何将視訊流儲存進多個檔案當你将視訊流儲存進一個檔案後,如果你想開始儲存第二個檔案,這時,你應該首先将graph停止,然後通過IFileSinkFilter::SetFileName改變 File Writer 的檔案名稱。注意,IFileSinkFilter指針你可以在SetOutputFileName時通過第四個參數傳回的。看看儲存多個檔案的代碼吧
IBaseFilter *pMux; IFileSinkFilter *pSink hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C://YourFileName.avi", &pMux, &pSink); if (SUCCEEDED(hr)) { hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, NULL, pMux); if (SUCCEEDED(hr)) { pControl->Run(); pControl->Stop(); // Change the file name and run the graph again. pSink->SetFileName(L"YourFileName02.avi", 0); pControl->Run(); } pMux->Release(); pSink->Release(); }
5組合視訊的捕捉和預覽如果想組建一個既可以預覽視訊,又可以将視訊儲存成檔案的graph,隻需要兩次調用ICaptureGraphBuilder2::RenderStream即可。代碼如下:
// Render the preview stream to the video renderer. hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL); // Render the capture stream to the mux. hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, NULL, pMux);
在上面的代碼中,graph builder 其實隐藏了下面的細節。 1 如果capture Filter既有preview pin 也有captrue pin,那麼RenderStream僅僅将兩個pin和render filter接起來。如下圖
圖4 2如果caprture Filter隻有一個capture pin,那麼Capture Graph Builder就采用一個Smart Tee Filter将視訊流分流,graph圖如下
圖5
5如何控制Capture Graph(Controlling Capture Graph)
Filter圖表管理器可以通過IMediaControl接口控制整個graph的運作,停止和暫停。但是當一個graph有捕捉和預覽兩個資料流的時候,如果我們想單獨的控制其中的一個資料流話,我們可以通過ICaptureGraphBuilder2::ControlStream 。下面講一下如何來單獨控制捕捉和預覽資料流。 1 控制捕捉視訊流下面的代碼,讓捕捉資料流在graph開始運作1秒後開始,允運作4秒後結束。
// Control the video capture stream. REFERENCE_TIME rtStart = 1000 0000, rtStop = 5000 0000; const WORD wStartCookie = 1, wStopCookie = 2; // Arbitrary values. hr = pBuild->ControlStream( &PIN_CATEGORY_CAPTURE, // Pin category. &MEDIATYPE_Video, // Media type. pCap, // Capture filter. &rtStart, &rtStop, // Start and stop times. wStartCookie, wStopCookie // Values for the start and stop events. ); pControl->Run();
第一個參數表明需要控制的資料流,一般采用的是pin種類GUID,第二個參數表明了媒體類型。第三個參數指明了捕捉的filter。如果想要控制graph圖中的所有捕捉filter,第二個和第三個參數都要設定成NULL。第四和第五個參數表明了流開始和結束的時間,這是一個相對于graph開始的時間。隻有你調用IMediaControl::Run以後,這個函數才有作用。如果graph正在運作,這個設定立即生效。最後的兩個參數用來設定當資料流停止,開始能夠得到的事件通知。對于任何一個運用此方法的資料流,graph當流開始的時候,會發送EC_STREAM_CONTROL_STARTED通知,在流結束的時候,要發送EC_STREAM_CONTROL_STOPPED通知。wStartCookie和wStopCookie是作為第二個參數的。看看事件通知處理過程吧
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr)) { switch (evCode) { case EC_STREAM_CONTROL_STARTED: // param2 == wStartCookie break; case EC_STREAM_CONTROL_STOPPED: // param2 == wStopCookie break; } pEvent->FreeEventParams(evCode, param1, param2); }
ControlStream還定義了一些特定的值來表示開始和停止的時間。 MAXLONGLONG 從不開始,隻有在graph停止的時候才停止 NULL, 立即開始和停止例如,下面的代碼立即停止捕捉流。
pBuild->ControlStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, 0, 0, // Start and stop times. wStartCookie, wStopCookie); 2控制預覽視訊流隻要給ControlStream第一個參數設定成PIN_CATEGORY_PREVIEW就可以控制預覽pin,整個函數的使用和控制捕捉流一樣,但是唯一差別是在這裡你沒法設定開始和結束時間了,因為預覽的視訊流沒有時間戳,是以你必須使用NULL或者MAXLONGLONG。例子
Use NULL to start the preview stream: pBuild->ControlStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, // Start now. 0, // (Don't care.) wStartCookie, wStopCookie); Use MAXLONGLONG to stop the preview stream: pBuild->ControlStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, 0, // (Don't care.) MAXLONGLONG, // Stop now. wStartCookie, wStopCookie);
3關于資料流的控制 Pin的預設的行為是傳遞sample,例如,如果你對PIN_CATEGORY_CAPTURE使用了ControlStream,但是對于PIN_CATEGORY_PREVIEW沒有使用該函數,是以,當你run graph的時候,preview 流會立即運作起來,而capture 流則要等到你設定的時間運作。
6如何配置一個視訊捕捉裝置
1顯示VFW驅動的視訊裝置對話框如果視訊捕捉裝置采用的仍然是VFW方式的驅動程式,則必須支援下面三個對話框,用來設定視訊裝置。 1 Video Source 用來選擇視訊輸入裝置并且調整裝置的設定,比如亮度和對比度。 2Video Format 用來設定桢的大小和位 3Video Display 用來設定視訊的顯示參數為了顯示上面的三個對話框,你可以do the following 1 停止graph。 2向捕捉filter請求IAMVfwCaptureDialogs接口,如果成功,表明裝置支援VFW驅動。 3調用IAMVfwCaptureDialogs::HasDialog來檢查驅動程式是否支援你請求的對話框,如果支援,傳回S_OK,否則傳回S_FALSE。注意不要用SUCCEDED宏。 4如果驅動支援該對話框,調用IAMVfwCaptureDialogs::ShowDialog顯示該對話框。 5 重新運作graph 代碼如下
pControl->Stop(); // Stop the graph. // Query the capture filter for the IAMVfwCaptureDialogs interface. IAMVfwCaptureDialogs *pVfw = 0; hr = pCap->QueryInterface(IID_IAMVfwCaptureDialogs, (void**)&pVfw); if (SUCCEEDED(hr)) { // Check if the device supports this dialog box. if (S_OK == pVfw->HasDialog(VfwCaptureDialog_Source)) { // Show the dialog box. hr = pVfw->ShowDialog(VfwCaptureDialog_Source, hwndParent); } } pControl->Run();
2 調整視訊的品質 WDM驅動的裝置支援一些屬性可以用來調整視訊的品質,比如亮度,對比度,飽和度,等要向調整視訊的品質,do the following 1 從捕捉filter上請求IAMVideoProcAmp接口 2 對于你想調整的任何一個屬性,調用IAMVideoProcAmp::GetRange可以傳回這個屬性指派的範圍,預設值,最小的增量值。IAMVideoProcAmp::Get傳回目前正在使用的值。VideoProcAmpProperty枚舉每個屬性定義的标志。 3調用IAMVideoProcAmp::Set來設定這個屬性值。設定屬性的時候,不用停止graph。看看下面的代碼是如何調整視訊的品質的
HWND hTrackbar; // Handle to the trackbar control. // Initialize hTrackbar (not shown). // Query the capture filter for the IAMVideoProcAmp interface. IAMVideoProcAmp *pProcAmp = 0; hr = pCap->QueryInterface(IID_IAMVideoProcAmp, (void**)&pProcAmp); if (FAILED(hr)) { // The device does not support IAMVideoProcAmp, so disable the control. EnableWindow(hTrackbar, FALSE); } else { long Min, Max, Step, Default, Flags, Val; // Get the range and default value. hr = m_pProcAmp->GetRange(VideoProcAmp_Brightness, &Min, &Max, &Step, &Default, &Flags); if (SUCCEEDED(hr)) { // Get the current value. hr = m_pProcAmp->Get(VideoProcAmp_Brightness, &Val, &Flags); } if (SUCCEEDED(hr)) { // Set the trackbar range and position. SendMessage(hTrackbar, TBM_SETRANGE, TRUE, MAKELONG(Min, Max)); SendMessage(hTrackbar, TBM_SETPOS, TRUE, Val); EnableWindow(hTrackbar, TRUE); } else { // This property is not supported, so disable the control. EnableWindow(hTrackbar, FALSE); } }
3調整視訊輸出格式我們知道視訊流可以有多種輸出格式,一個裝置可以支援16-bit RGB, 32-bit RGB, and YUYV,在每一種格式下,裝置還可以調整視訊桢的大小。在WDM驅動裝置上,IAMStreamConfig 接口用來報告裝置輸出視訊的格式的,VFW裝置,可以采用對話框的方式來設定,參見前面的内容。捕捉Filter的捕捉pin和預覽pin都支援IAMStreamConfig 接口,可以通過ICaptureGraphBuilder2::FindInterface獲得IAMStreamConfig接口。
IAMStreamConfig *pConfig = NULL; hr = pBuild->FindInterface( &PIN_CATEGORY_PREVIEW, // Preview pin. 0, // Any media type. pCap, // Pointer to the capture filter. IID_IAMStreamConfig, (void**)&pConfig);
裝置還支援一系列的媒體類型,對于每一個媒體類型,裝置都要支援一系列的屬性,比如,桢的大小,圖像如何縮放,桢率的範圍等。通過IAMStreamConfig::GetNumberOfCapabilities獲得裝置所支援的媒體類型的數量。這個方法傳回兩個值,一個是媒體類型的數量,二是屬性所需結構的大小。這個結構的大小很重要,因為這個方法是用于視訊和音頻的,視訊采用的是VIDEO_STREAM_CONFIG_CAPS結構,音頻用AUDIO_STREAM_CONFIG_CAPS結構。通過函數IAMStreamConfig::GetStreamCaps來枚舉媒體類型,要給這個函數傳遞一個序号作為參數,這個函數傳回媒體類型和相應的屬性結構體。看代碼把
int iCount = 0, iSize = 0; hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize); // Check the size to make sure we pass in the correct structure. if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS) { // Use the video capabilities structure. for (int iFormat = 0; iFormat < iCount; iFormat++) { VIDEO_STREAM_CONFIG_CAPS scc; AM_MEDIA_TYPE *pmtConfig; hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc); if (SUCCEEDED(hr)) { // Delete the media type when you are done. hr = pConfig->SetFormat(pmtConfig);//重新設定視訊格式 DeleteMediaType(pmtConfig); } }
你可以調用IAMStreamConfig::SetFormat設定新的媒體類型 hr = pConfig->SetFormat(pmtConfig); 如果pin沒有連接配接,當連接配接的時候就試圖用新的格式,如果pin已經在連接配接了,它就會用的新的媒體格式重新連接配接。在任何一種情況下,下遊的filter都有可能拒絕新的媒體格式。在SetFormat前你可以修改VIDEO_STREAM_CONFIG_CAPS結構來重新設定媒體類型。例如:如果GetStreamCaps傳回的是24-bit RGB format,桢的大小是320 x 240 像素,你可以通過檢查媒體類型的major type,subtpye,和format等值
if ((pmtConfig.majortype == MEDIATYPE_Video) && (pmtConfig.subtype == MEDIASUBTYPE_RGB24) && (pmtConfig.formattype == FORMAT_VideoInfo) && (pmtConfig.cbFormat >= sizeof (VIDEOINFOHEADER)) && (pmtConfig.pbFormat != NULL)) { VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmtConfig.pbFormat; // pVih contains the detailed format information. LONG lWidth = pVih->bmiHeader.biWidth; LONG lHeight = pVih->bmiHeader.biHeight; }
VIDEO_STREAM_CONFIG_CAPS結構裡包含了該媒體類型的視訊長度和寬度的最大值和最小值,還有遞增的幅度值,就是每次調整視訊size的幅度,例如,裝置可能傳回如下的值 ? MinOutputSize: 160 x 120 ? MaxOutputSize: 320 x 240 ? OutputGranularityX: 8 pixels (horizontal step size) ? OutputGranularityY: 8 pixels (vertical step size) 這樣你可以在(160, 168, 176, ... 304, 312, 320) 範圍内設定寬度,在 (120, 128, 136, ... 104, 112, 120).設定高度值,
圖6 如果想設定新的值,直接修改在GetStreamCaps函數中傳回的值即可,
pVih->bmiHeader.biWidth = 160; pVih->bmiHeader.biHeight = 120; pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
然後将媒體類型傳遞給SetFormat函數,就可修改視訊格式了。
7将裝置從系統中移走時的事件通知(Device remove Notify)
如果使用者将一個graph正在使用的即插即用型的裝置從系統中去掉,filter圖表管理器就會發送一個EC_DEVICE_LOST事件通知,如果該裝置又可以使用了,filter圖表管理器就發送另外的一個EC_DEVICE_LOST通知,但是先前組建的捕捉filter graph圖就沒法用了,使用者必須重新組建graph圖。當系統中有新的裝置添加時,dshow是不會發送任何通知的,是以,應用程式如果想要知道系統中何時添加新的裝置,應用程式可以監控WM_DEVICECHANGE消息。
8從靜止圖像pin中捕捉圖檔
有些照相機,攝像頭除了可以捕獲視訊流以外還可以捕獲單張的,靜止的圖檔。通常,靜止的圖檔的品質要比流的品質要高。攝像頭一般都一個按鈕來觸發,或者是支援軟體觸發。支援輸出靜态圖檔的攝像頭一般都要提供一個靜态圖檔pin,這個pin的種類是PIN_CATEGORY_STILL。從裝置中擷取靜态圖檔,我們一般推薦使用windows Image Acquisition (WIA) APIs。當然,你也可以用dshow來擷取圖檔。在graph運作的時候利用IAMVideoControl::SetMode來觸發靜态的pin。代碼如下
pControl->Run(); // Run the graph. IAMVideoControl *pAMVidControl = NULL; hr = pCap->QueryInterface(IID_IAMVideoControl, (void**)&pAMVidControl); if (SUCCEEDED(hr)) { // Find the still pin. IPin *pPin = 0; hr = pBuild->FindPin(pCap, PINDIR_OUTPUT, &PIN_CATEGORY_STILL, 0, FALSE, 0, &pPin); if (SUCCEEDED(hr)) { hr = pAMVidControl->SetMode(pPin, VideoControlFlag_Trigger); pPin->Release(); } pAMVidControl->Release(); }
首先向capture Filter 請求IAMVideoContol,如果支援該接口,就調用ICaptureGraphBuilder2::FindPin請求指向靜止pin 的指針,然後調用pin的put_Mode方法。根據不同的攝像頭,你可能靜态pin連接配接前要render 該pin。捕捉靜态圖檔常用的filter是Sample Grabber filter,Sample Grabber使用了一個使用者定義的回調汗水來處理圖檔。關于這個filter的詳細用法,參見Using the Sample Grabber.。下面的例子假設靜态pin傳遞的是沒有壓縮的RGB圖檔。首先定義一個類,從ISampleGrabberCB繼承。
// Class to hold the callback function for the Sample Grabber filter. class SampleGrabberCallback : public ISampleGrabberCB { // Implementation is described later. } // Global instance of the class. SampleGrabberCallback g_StillCapCB;
然後将捕捉filter的靜态pin連接配接到Sample Grabber,将Sample Grabber連接配接到Null Renderer filter。Null Renderer僅僅是将她接收到的sample丢棄掉。實際的工作都是在回調函數裡進行,連接配接Null Renderer 僅僅是為了給Sample Grabber's 輸出pin上連接配接點東西。具體見下面的代碼
// Add the Sample Grabber filter to the graph. IBaseFilter *pSG_Filter; hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pSG_Filter); hr = pGraph->AddFilter(pSG_Filter, L"SampleGrab"); // Add the Null Renderer filter to the graph. IBaseFilter *pNull; hr = CoCreateInstance(CLSID_NullRendere, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pNull); hr = pGraph->AddFilter(pSG_Filter, L"NullRender");
然後通過RenderStream将still pin ,sample grabber ,null Renderer連接配接起來
hr = pBuild->RenderStream( &PIN_CATEGORY_STILL, // Connect this pin ... &MEDIATYPE_Video, // with this media type ... pCap, // on this filter ... pSG_Filter, // to the Sample Grabber ... pNull); // ... and finally to the Null Renderer.
然後調用ISampleGrabber指針,來通過這個指針可以配置設定記憶體。
// Configure the Sample Grabber. ISampleGrabber *pSG; hr = pSG_Filter->QueryInterface(IID_ISampleGrabber, (void**)&pSG); pSG->SetOneShot(FALSE); pSG->SetBufferSamples(TRUE);
設定你的回調對象
pSG->SetCallback(&g_StillCapCB, 0); // 0 = Use the SampleCB callback method
擷取靜态pin和sample grabber之間連接配接所用的媒體類型
// Store the media type for later use. AM_MEDIA_TYPE g_StillMediaType; hr = pSG->GetConnectedMediaType(&g_StillMediaType); pSG->Release();
媒體類型包含一個BITMAPINFOHEADER結構來定義圖檔的格式,在程式退出前一定要釋放媒體類型
// On exit, remember to release the media type. FreeMediaType(g_StillMediaType);
看看下面的回調類吧。這個類從ISampleGrabber接口派生,但是它沒有保持引用計數,因為應用程式在堆上建立這個對象,在整個graph的生存周期它都存在。所有的工作都在BufferCB函數裡完成,當有一個新的sample到來的時候,這個函數就會被sample Grabber調用到。在下面的例子裡,bitmap被寫入到一個檔案中
class SampleGrabberCallback : public ISampleGrabberCB { public: // Fake referance counting. STDMETHODIMP_(ULONG) AddRef() { return 1; } STDMETHODIMP_(ULONG) Release() { return 2; } STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) { if (NULL == ppvObject) return E_POINTER; if (riid == __uuidof(IUnknown)) { *ppvObject = static_cast<IUnknown*>(this); return S_OK; } if (riid == __uuidof(ISampleGrabberCB)) { *ppvObject = static_cast<ISampleGrabberCB*>(this); return S_OK; } return E_NOTIMPL; } STDMETHODIMP SampleCB(double Time, IMediaSample *pSample) { return E_NOTIMPL; } STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen) { if ((g_StillMediaType.majortype != MEDIATYPE_Video) || (g_StillMediaType.formattype != FORMAT_VideoInfo) || (g_StillMediaType.cbFormat < sizeof(VIDEOINFOHEADER)) || (g_StillMediaType.pbFormat == NULL)) { return VFW_E_INVALIDMEDIATYPE; } HANDLE hf = CreateFile("C://Example.bmp", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); if (hf == INVALID_HANDLE_VALUE) { return E_FAIL; } long cbBitmapInfoSize = g_StillMediaType.cbFormat - SIZE_PREHEADER; VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)g_StillMediaType.pbFormat; BITMAPFILEHEADER bfh; ZeroMemory(&bfh, sizeof(bfh)); bfh.bfType = 'MB'; // Little-endian for "MB". bfh.bfSize = sizeof( bfh ) + BufferLen + cbBitmapInfoSize; bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + cbBitmapInfoSize; // Write the file header. DWORD dwWritten = 0; WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL ); WriteFile(hf, HEADER(pVideoHeader), cbBitmapInfoSize, &dwWritten, NULL); WriteFile( hf, pBuffer, BufferLen, &dwWritten, NULL ); CloseHandle( hf ); return S_OK; } };
作者Blog:http://blog.csdn.net/flyfwater/archive/2008/06/13/2542338.aspx