Directshow中的視頻捕捉(轉)感覺這個非常全,但真的不知道出處了...


 

 

本篇文檔主要描述關於用Directshow進行視頻開發的一些技術
主要包括下面內容



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。看下面的代碼把

HRESULT InitCaptureGraphBuilder(
IGraphBuilder **ppGraph, // Receives the pointer.
ICaptureGraphBuilder2 **ppBuild // Receives the pointer.
)
{
if (!ppGraph || !ppBuild)
{
return E_POINTER;
}
IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuild = NULL;

// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Initialize the Capture Graph Builder.
pBuild->SetFiltergraph(pGraph);

// Return both interface pointers to the caller.
*ppBuild = pBuild;
*ppGraph = pGraph; // The caller must release both interfaces.
return S_OK;
}
else
{
pBuild->Release();
}
}
return hr; // Failed
}

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.
/* Initialize pCap and add it to the filter graph (not shown). */

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;
/* Create the encoder filter (not shown). */
// Add it to the filter graph.
pGraph->AddFilter(pEncoder, L"Encoder);

 

/* Call SetOutputFileName as shown previously. */

// 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();
/* Wait awhile, then stop the graph. */
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, &param1, &param2, 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))
{
/* Examine the format, and possibly use it. */
// 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;

}
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM