Direcshow之視頻捕捉

關於視頻捕捉(About Video Capture inDshow

1. 視頻捕捉Graph的構建

一個能夠捕捉音頻或者視頻的graph圖都稱之爲捕捉graph圖。捕捉graph圖比一般的文件回放graph圖要複雜許多,dshow提供了一個CaptureGraph Builder COM組件使得捕捉graph圖的生成更加簡單。Capture GraphBuilder提供了一個ICaptureGraphBuilder2接口,這個接口提供了一些方法用來構建和控制捕捉graph

首先創建一個Capture GraphBuilder對象和一個graphmanger對象,然後用filter graph manager作參數,調用ICaptureGraphBuilder2::SetFiltergraph來初始化CaptureGraph Builder。看下面的代碼把

HRESULTInitCaptureGraphBuilder(

IGraphBuilder **ppGraph, // Receives thepointer.

ICaptureGraphBuilder2 **ppBuild // Receives thepointer.

)

{

if (!ppGraph || !ppBuild)

{

return E_POINTER;

}

IGraphBuilder *pGraph = NULL;

ICaptureGraphBuilder2 *pBuild =NULL;

 // Create the Capture GraphBuilder.

 HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2,NULL,

 CLSCTX_INPROC_SERVER,IID_ICaptureGraphBuilder2,(void**)&pGraph);

 if(SUCCEEDED(hr))

 {

 // Create the Filter GraphManager.

 hr = CoCreateInstance(CLSID_FilterGraph, 0,CLSCTX_INPROC_SERVER,

 IID_IGraphBuilder,(void**)&pGraph);

 if(SUCCEEDED(hr))

 {

 // Initialize the CaptureGraph Builder.

 pBuild->SetFiltergraph(pGraph);

 // Return both interfacepointers to the caller.

 *ppBuild =pBuild;

 *ppGraph = pGraph; // Thecaller must release both interfaces.

 return S_OK;

 }

 Else

 {

pBuild->Release();

}

}

 return hr; //Failed

}

2.     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試圖連續播放,就會丟。去掉時間戳就保證了視頻來了就可以播放,不用等待,也不丟楨。

Video Port pin

VideoPort是一個介於視頻設備(TV)和視頻卡之間的硬件設備。同過Video Port,視頻數據可以直接發送到圖像卡上,通過硬件的覆蓋,視頻可以直接在屏幕顯示出來。VideoPort就是連接兩個設備的。
   使用VideoPort的最大好處是,不用CPU的任何工作,視頻流直接寫入內存中。如果捕捉設備使用了Video Port,捕捉Filter就用一個videoport pin代替預覽pin

預覽pin的種類GUIDPIN_CATEGORY_PREVIEW

捕捉pin的種類GUIDPIN_CATEGORY_CAPTURE

video portpin的種類GUIDPIN_CATEGORY_VIDEOPORT

一個捕捉filter至少有一個Capture pin,另外,它可能有一個預覽pin 和一個videoport pin
,或者兩者都沒有。一些filter有很多的capture pin,和預覽pin,每一個pin都代表一種媒體類型,因此一個filter可以有一個視頻capture pin,視頻預覽pin,音頻捕捉pin,音頻預覽pin

3.     預覽視頻(PreviewingVideo

爲了創建可以預覽視頻的graph,可以調用下面的代碼

ICaptureGraphBuilder2 *pBuild; // Capture GraphBuilder

// Initialize pBuild (notshown).

IBaseFilter *pCap; // Video capturefilter.

 

hr =pBuild->RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,  pCap, NULL, NULL);

 

4.     如何捕捉視頻流並保存到文件(Capture video toFile

1)     將視頻流保存到AVI文件

AVI Muxfilter接收從capturepin過來的視頻流,然後將其打包成AVI流。音頻流也可以連接到AVIMux Filter上,這樣mux filter就將視頻流和視頻流合成AVI流。FilewriterAVI流寫入到文件中。

構建graph

IBaseFilter *pMux;

hr = pBuild->SetOutputFileName(

 &MEDIASUBTYPE_Avi,// Specifies AVI for the target file.

 L"C:""Example.avi", // Filename.

 &pMux, //Receives a pointer to the mux.

 NULL); // (Optional) Receivesa pointer to the file sink.

第一個參數表明文件的類型,這裏表明是AVI,第二個參數是制定文件的名稱。對於AVI文件,SetOutputFileName函數會創建一個AVImux Filter 和一個 File writer Filter,並且將兩個filter添加到graph圖中,在這個函數中,通過FileWriter Filter 請求IFileSinkFilter接口,然後調用IFileSinkFilter::SetFileName方法,設置文件的名稱。然後將兩個filter連接起來。第三個參數返回一個指向AVIMux的指針,同時,它也通過第四個參數返回一個IFileSinkFilter參數,如果你不需要這個參數,你可以將這個參數設置成NULL

然後,你應該調用下面的函數將capturefilter AVI Mux連接起來。

hr = pBuild->RenderStream(

 &PIN_CATEGORY_CAPTURE,// Pin category.

 &MEDIATYPE_Video,// Media type.

 pCap, // Capturefilter.

 NULL, // Intermediate filter(optional).

 pMux); // Mux or file sinkfilter.

 // Release the muxfilter.

  pMux->Release();

5個參數就是使用的上面函數返回的pMux指針。

當捕捉音頻的時候,媒體類型要設置爲MEDIATYPE_Audio。如果你從兩個不同的設備捕捉視頻和音頻,最好將音頻設置成主流,avimux filter爲了同步音頻,會調整視頻的播放速度,這樣可以防止兩個數據流間drift

爲了設置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 filtergraph.

 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 videoWMV)格式的文件,將capturepin連到WMASF Writer filter

 構建graph圖最簡單的方法就是將在ICaptureGraphBuilder2::SetOutputFileName方法中指定MEDIASUBTYPE_Asffilter。如下

 IBaseFilter* pASFWriter =0;

 hr =pBuild->SetOutputFileName(

 &MEDIASUBTYPE_Asf,// Create a Windows Media file.

 L"C:""VidCap.wmv", // Filename.

 &pASFWriter,// Receives a pointer to the filter.

 NULL); // Receives anIFileSinkFilter interface pointer (optional).

參數MEDIASUBTYPE_Asf告訴graphbuilder,要使用wm asfwriter作爲文件接收器,於是pbuild 就創建這個filter,將其添加到graph圖中,然後調用IFileSinkFilter::SetFileName來設置輸出文件的名字。第三個參數用來返回一個ASFwriter指針,第四個參數用來返回文件的指針。

在將任何pin連接到WM ASFWriter之前,一定要對WM ASFWriter進行一下設置。可以通過WMASF WriterIConfigAsfWriter接口指針來進行設置。

 IConfigAsfWriter*pConfig = 0;

 hr =pASFWriter->QueryInterface(IID_IConfigAsfWriter,(void**)&pConfig);

 if(SUCCEEDED(hr))

 {

 // Configure the ASF Writerfilter.

 pConfig->Release();

 }

然後調用ICaptureGraphBuilder2::RenderStreamcaptureFilter ASF writer連接起來。

 hr =pBuild->RenderStream(

 &PIN_CATEGORY_CAPTURE,// Capture pin.

 &MEDIATYPE_Video,// Video. Use MEDIATYPE_Audio for audio.

 pCap, // Pointer to thecapture filter.

 0,

 pASFWriter);// Pointer to the sink filter (ASF Writer).

3) 保存成自定義的文件格式

如果你想將文件保存成自己的格式,你必須有自己的filewriter。看下面的代碼

 IBaseFilter *pMux =0;

 IFileSinkFilter *pSink =0;

 hr =pBuild->SetOutputFileName(

 &CLSID_MyCustomMuxFilter,//自己開發的Filter

 L"C:""VidCap.avi",&pMux, &pSink);

4) 如何將視頻流保存進多個文件

當你將視頻流保存進一個文件後,如果你想開始保存第二個文件,這時,應該首先將graph停止,然後再通過IFileSinkFilter::SetFileName改變FileWriter 的文件名稱。注意,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 andrun the graph again.

 pSink->SetFileName(L"YourFileName02.avi",0);

 pControl->Run();

 }

 pMux->Release();

 pSink->Release();

}

5)  組合視頻的捕捉和預覽

如果想組建一個既可以預覽視頻,又可以將視頻保存成文件的graph,只需要兩次調用ICaptureGraphBuilder2::RenderStream即可。

 // Render the preview streamto 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 如果captureFilter既有preview pin也有captruepin,那麼RenderStream僅僅將兩個pinrenderfilter接起來。

如果caprtureFilter只有一個capturepin,那麼CaptureGraph Builder就自動採用一個Smart TeeFilter將視頻流分流。

 

5.  如何控制CaptureGraphControlling CaptureGraph

Filter圖表管理器可以通過IMediaControl接口控制整個graph的運行,停止和暫停。

但是,當一個graph有捕捉和預覽兩個數據流的時,如果想單獨的控制其中一個數據流,可以通過ICaptureGraphBuilder2::ControlStream

如何單獨控制捕捉和預覽數據流。

控制捕捉視頻流

下面的代碼,讓捕捉數據流在graph開始運行1秒後開始,運行4秒後結束。

// Control the video capturestream.

REFERENCE_TIME rtStart = 1000 0000, rtStop =5000 0000;

const WORD wStartCookie = 1, wStopCookie = 2; //Arbitrary values. 

hr =pBuild->ControlStream(

&PIN_CATEGORY_CAPTURE, // Pincategory.

&MEDIATYPE_Video, // Mediatype.

pCap, // Capture filter.

&rtStart,&rtStop, // Start and stop times.

wStartCookie, wStopCookie // Values for thestart and stop events.
   );

pControl->Run();

注:只有調用IMediaControl::Run以後,這個函數纔有作用。如果graph正在運行,這個設置立即生效。


ICaptureGraphBuilder2::ControlStream參數說明:

第一個參數表明需要控制的數據流,一般採用的是pin種類GUID

第二個參數表明了媒體類型。

第三個參數指明瞭捕捉的filter。如果想要控制graph圖中的所有捕捉filter,第二個和第三個參數都要設置成NULL

第四和第五個參數表明了流開始和結束的時間,這是一個相對於graph開始的時間。

最後的兩個參數用來設置當數據流開始和停止能夠得到的事件通知。


   對於任何一個運用此方法的數據流,在graph流開始的時候,會發送EC_STREAM_CONTROL_STARTED通知,在流結束的時候,要發送EC_STREAM_CONTROL_STOPPED通知。wStartCookiewStopCookie是作爲第二個參數的。

事件通知處理過程

 while (hr =pEvent->GetEvent(&evCode,&param1, &param2, 0),SUCCEEDED(hr))

 {

 switch(evCode)

 {

 caseEC_STREAM_CONTROL_STARTED:

 // param2 ==wStartCookie

 break;

 caseEC_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);

控制預覽視頻流

   只要給ControlStream第一個參數設置成PIN_CATEGORY_PREVIEW就可以控制預覽pin,整個函數的使用和控制捕捉流一樣,唯一區別是,在這裏沒法設置開始和結束時間,因爲預覽的視頻流沒有時間戳,因此必須使用NULL或者MAXLONGLONG

// Use NULL to start the previewstream:

 pBuild->ControlStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video, pCap, NULL, //Start now.

 0, // (Don'tcare.)

 wStartCookie,wStopCookie);

 //Use MAXLONGLONG to stop thepreview stream:

 pBuild->ControlStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video, pCap, 0, //(Don't care.)

 MAXLONGLONG, // Stopnow.

 wStartCookie,wStopCookie);

關於數據流的控制

Pin的缺省行爲是傳遞sample

例如,如果對 PIN_CATEGORY_CAPTURE 使用了ControlStream,但對 PIN_CATEGORY_PREVIEW 沒有使用該函數,當rungraph的時候,preview 流會立即運行起來,而capture 流則要等到設置的時間運行。

 

6.      如何配置一個視頻捕捉設備

1、顯示VFW驅動的視頻設備對話框

如果視頻捕捉設備採用的仍然是VFW方式的驅動程序,則必須支持下面三個對話框,用來設置視頻設備。

 

  • VideoSource

用來選擇視頻輸入設備並且調整設備的設置,比如亮度和對比度。

 

  • VideoFormat

用來設置幀的大小和位

 

  • VideoDisplay

用來設置視頻的顯示參數

爲了顯示上面的三個對話框,你可以dothe 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 forthe IAMVfwCaptureDialogsinterface. IAMVfwCaptureDialogs *pVfw =0;

 hr =pCap->QueryInterface(IID_IAMVfwCaptureDialogs,(void**)&pVfw); 

 if(SUCCEEDED(hr))

 {

 // Check if the device supportsthis dialog box.

 if (S_OK ==pVfw->HasDialog(VfwCaptureDialog_Source))

 {

 // Show the dialogbox.

 hr =pVfw->ShowDialog(VfwCaptureDialog_Source,hwndParent);

 }

 }

 pControl->Run();

2、調整視頻的質量

WDM驅動的設備支持一些屬性可以用來調整視頻的質量,比如亮度,對比度,飽和度,等要向調整視頻的質量,dothe following

1)從捕捉filter上請求IAMVideoProcAmp接口

2)對於需要調整的任何一個屬性,調用IAMVideoProcAmp::GetRange可以返回這個屬性賦值的範圍,缺省值,最小的增量值。IAMVideoProcAmp::Get返回當前正在使用的值。VideoProcAmpProperty枚舉每個屬性定義的標誌。

3)調用IAMVideoProcAmp::Set來設置這個屬性值。設置屬性的時候,不用停止graph

看看下面的代碼是如何調整視頻的質量的

HWND hTrackbar; // Handle to the trackbarcontrol.

// Initialize hTrackbar (notshown).

// Query the capture filter for theIAMVideoProcAmp interface.

 IAMVideoProcAmp *pProcAmp =0;

 hr =pCap->QueryInterface(IID_IAMVideoProcAmp,(void**)&pProcAmp);

 if(FAILED(hr))

 {

 // The device does not supportIAMVideoProcAmp, so disable the control.

 EnableWindow(hTrackbar,FALSE);

 }

 Else

 {

 long Min, Max, Step, Default,Flags, Val;

  // Get therange and default value.

 hr =m_pProcAmp->GetRange(VideoProcAmp_Brightness,&Min, &Max,&Step,

 &Default,&Flags);

 if(SUCCEEDED(hr))

 {

 // Get the currentvalue.

 hr =m_pProcAmp->Get(VideoProcAmp_Brightness,&Val, &Flags);

 }

 if(SUCCEEDED(hr))

 {

 // Set the trackbar range andposition.

 SendMessage(hTrackbar,TBM_SETRANGE, TRUE, MAKELONG(Min, Max));

 SendMessage(hTrackbar,TBM_SETPOS, TRUE, Val);

 EnableWindow(hTrackbar,TRUE);

 }

 Else

 {

 // This property is notsupported, so disable the control.

 EnableWindow(hTrackbar,FALSE);

 }

 }

3、調整視頻輸出格式

我們知道視頻流可以有多種輸出格式,一個設備可以支持16-bitRGB, 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 mediatype.

 pCap, // Pointer to the capturefilter.

 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 thecorrect structure.

 if (iSize ==sizeof(VIDEO_STREAM_CONFIG_CAPS)

 {

 // Use the video capabilitiesstructure.

 for (int iFormat = 0; iFormat< iCount; iFormat++)

 {

 VIDEO_STREAM_CONFIG_CAPSscc;

 AM_MEDIA_TYPE*pmtConfig;

 hr =pConfig->GetStreamCaps(iFormat,&pmtConfig,(BYTE*)&scc);

 if(SUCCEEDED(hr))

 {

 

 // Delete the media type whenyou are done.

 hr =pConfig->SetFormat(pmtConfig);//重新設置視頻格式

 DeleteMediaType(pmtConfig);

 }

 }

可以調用IAMStreamConfig::SetFormat設置新的媒體類型

hr =pConfig->SetFormat(pmtConfig);

如果pin沒有連接,當連接的時候就試圖用新的格式,如果pin已經在連接了,它就會用的新的媒體格式重新連接。在任何一種情況下,下游的filter都有可能拒絕新的媒體格式。

SetFormat前你可以修改VIDEO_STREAM_CONFIG_CAPS結構來重新設置媒體類型。

例如:

如果GetStreamCaps返回的是24-bitRGB format,幀的大小是320 x240 像素,你可以通過檢查媒體類型的majortypesubtpye,和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 detailedformat 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 stepsize)

OutputGranularityY: 8 pixels (vertical stepsize)

這樣你可以在(160, 168, 176, ... 304, 312,320) 範圍內設置寬度,在 (120, 128,136, ... 104, 112, 120).設置高度值,

如果想設置新的值,直接修改在GetStreamCaps函數中返回的值即可,

 pVih->bmiHeader.biWidth= 160;

 pVih->bmiHeader.biHeight= 120;

 pVih->bmiHeader.biSizeImage= DIBSIZE(pVih->bmiHeader);

然後將媒體類型傳遞給SetFormat函數,就可修改視頻格式了。

 

7.      將設備從系統中移走時的事件通知(Deviceremove Notify

如果用戶將一個graph正在使用的即插即用型的設備從系統中去掉,filter圖表管理器就會發送一個EC_DEVICE_LOST事件通知,如果該設備又可以使用了,filter圖表管理器就發送另外的一個EC_DEVICE_LOST通知,但是先前組建的捕捉filtergraph圖就沒法用了,用戶必須重新組建graph圖。

當系統中有新的設備添加時,dshow是不會發送任何通知的,所以,應用程序如果想要知道系統中何時添加新的設備,應用程序可以監控WM_DEVICECHANGE消息。

8.      從靜止圖像pin中捕捉圖片

有些照相機,攝像頭除了可以捕獲視頻流以外還可以捕獲單張的,靜止的圖片。通常,靜止的圖片的質量要比流的質量要高。攝像頭一般都一個按鈕來觸發,或者是支持軟件觸發。支持輸出靜態圖片的攝像頭一般都要提供一個靜態圖片pin,這個pin的種類是PIN_CATEGORY_STILL

從設備中獲取靜態圖片,我們一般推薦使用windowsImage 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();

 }

首先向captureFilter 請求IAMVideoContol,如果支持該接口,就調用ICaptureGraphBuilder2::FindPin請求指向靜止pin 的指針,然後調用pinput_Mode方法。

根據不同的攝像頭,你可能靜態pin連接前要render pin

捕捉靜態圖片常用的filterSample GrabberfilterSampleGrabber使用了一個用戶定義的回調函數來處理圖片。關於這個filter的詳細用法,參見Usingthe Sample Grabber.

下面的例子假設靜態pin傳遞的是沒有壓縮的RGB圖片。首先定義一個類,從ISampleGrabberCB繼承。

 //Class to hold the callback function for the Sample Grabberfilter. class SampleGrabberCallback : publicISampleGrabberCB

 {

 //Implementation is described later.

 }

// Global instance of theclass.

SampleGrabberCallbackg_StillCapCB;

然後將捕捉filter的靜態pin連接到SampleGrabber,將SampleGrabber連接到Null RendererfilterNullRenderer僅僅是將接收到的sample丟棄掉。實際的工作都是在回調函數裏進行,連接NullRenderer 僅僅是爲了給SampleGrabber's 輸出pin上連接點東西。具體見下面的代碼

 //Add the Sample Grabber filter to thegraph.

 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 thegraph.

IBaseFilter *pNull;

hr = CoCreateInstance(CLSID_NullRendere, NULL,CLSCTX_INPROC_SERVER, IID_IBaseFilter,(void**)&pNull);

hr = pGraph->AddFilter(pSG_Filter,L"NullRender");

然後通過RenderStreamstillpin samplegrabber nullRenderer連接起來

 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 SampleCBcallback  method

獲取靜態pinsamplegrabber之間連接所用的媒體類型

 //Store the media type for later use.

 AM_MEDIA_TYPEg_StillMediaType;

 hr=pSG->GetConnectedMediaType(&g_StillMediaType);

 pSG->Release();

媒體類型包含一個BITMAPINFOHEADER結構來定義圖片的格式,在程序退出前一定要釋放媒體類型

 //On exit, remember to release the media type.

 FreeMediaType(g_StillMediaType);

看看下面的回調類吧。這個類從ISampleGrabber接口派生,但是它沒有保持引用計數,因爲應用程序在堆上創建這個對象,在整個graph的生存週期它都存在。

所有的工作都在BufferCB函數裏完成,當有一個新的sample到來的時候,這個函數就會被sampleGrabber調用到。在下面的例子裏,bitmap被寫入到一個文件中

class SampleGrabberCallback : publicISampleGrabberCB

 {

 public:

 // Fake referancecounting.

 STDMETHODIMP_(ULONG) AddRef() {return 1; }

 STDMETHODIMP_(ULONG) Release(){ return 2; }

 STDMETHODIMPQueryInterface(REFIID riid, void **ppvObject)

 {

 if (NULL == ppvObject) returnE_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;

 }

 returnE_NOTIMPL;

 }

 STDMETHODIMP SampleCB(doubleTime, IMediaSample *pSample)

 {

 returnE_NOTIMPL;

 }

 STDMETHODIMP BufferCB(doubleTime, BYTE *pBuffer, long BufferLen)

 {

 if ((g_StillMediaType.majortype!= MEDIATYPE_Video) ||

 (g_StillMediaType.formattype !=FORMAT_VideoInfo) ||

 (g_StillMediaType.cbFormat< sizeof(VIDEOINFOHEADER)) ||

 (g_StillMediaType.pbFormat ==NULL))

 {

 returnVFW_E_INVALIDMEDIATYPE;

 }

 HANDLE hf =CreateFile("C:""Example.bmp", GENERIC_WRITE,

 FILE_SHARE_WRITE, NULL,CREATE_ALWAYS, 0, NULL);

 if (hf ==INVALID_HANDLE_VALUE)

 {

 returnE_FAIL;

 }

 long cbBitmapInfoSize =g_StillMediaType.cbFormat - SIZE_PREHEADER;

VIDEOINFOHEADER *pVideoHeader =(VIDEOINFOHEADER*)g_StillMediaType.pbFormat;

 BITMAPFILEHEADERbfh;

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;

}

};

發佈了4 篇原創文章 · 獲贊 4 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章