本來想用TvideoCap的方法操作電腦攝像頭,如以下是打開攝像頭代碼,能在XP和2003系統里能正常打開攝像頭,但在win7里總是出各種問題,
const WM_CAP_START = WM_USER;
const WM_CAP_STOP = WM_CAP_START + 68;
const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
const WM_CAP_SAVEDIB = WM_CAP_START + 25;
const WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
const WM_CAP_SEQUENCE = WM_CAP_START + 62;
const WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;
const WM_CAP_SEQUENCE_NOFILE =WM_CAP_START+ 63 ;
const WM_CAP_SET_OVERLAY =WM_CAP_START+ 51 ;
const WM_CAP_SET_PREVIEW =WM_CAP_START+ 50 ;
const WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START +6;
const WM_CAP_SET_CALLBACK_ERROR=WM_CAP_START +2;
const WM_CAP_SET_CALLBACK_STATUSA= WM_CAP_START +3;
const WM_CAP_SET_CALLBACK_FRAME= WM_CAP_START +5;
const WM_CAP_SET_SCALE=WM_CAP_START+ 53 ;
const WM_CAP_SET_PREVIEWRATE=WM_CAP_START+ 52 ;
var
s:tpoint;
begin
s:=Pnl_View.ClientToScreen(point(0 ,0));
s:=ClientToScreen(point(Pnl_View.left,Pnl_View.Top));
hWndC := capCreateCaptureWindowA('My Own Capture Window',
WS_CHILD or WS_VISIBLE ,
Pnl_View.Left+3,Pnl_View.top+66,320,240,self.Handle,0);
if hWndC <> 0 then
SendMessage(hWndC, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_ERROR, 0, 0);
SendMessage(hWndC, WM_CAP_SET_CALLBACK_STATUSA, 0, 0);
SendMessage(hWndC, WM_CAP_DRIVER_CONNECT, 0, 0);
SendMessage(hWndC, WM_CAP_SET_SCALE, 1, 0);
SendMessage(hWndC, WM_CAP_SET_PREVIEWRATE, 66, 0);
SendMessage(hWndC, WM_CAP_SET_OVERLAY, 1, 0);
SendMessage(hWndC, WM_CAP_SET_PREVIEW, 1, 0);
OpenVideo.Enabled :=false;
CloseVideo.Enabled:=true;
看到帖子上說"這種方式打開攝像頭對攝像頭的兼容性有要求,在XP試過很多,有的好用有的不好用,換過攝像頭可能就好了。用DSPack控件,它是用Direct方式來做的,對攝像頭的兼容性比較好。基本上手機、MP4、攝像頭都可以用。"於是決定采用DSPack控件的方式。
DSPack,DSPack是一套使用微軟Direct Show和DirectX技術的類和組件,設計工作於DirectX 9,支持系統Win9X, ME, 2000和Windows xp。大概看了下Direct Show和DirectX的百科,應該比較常用,而自己之前居然沒有相關此方面的編程經驗,實在慚愧,有機會有多補習才行。
源代碼工程中有DSPack目錄,原來前輩們用過它把碼流轉成Mpeg2的,可憐我今天才注意到這個控件啊:DSPack。
二. 安裝
1 增加搜索路徑 (DSPackDir)\src\DirectX9 和 (DSPackDir)\src\DSPack
在delphi6中選擇菜單【Tools】-【Enviroment Options】,在打開的窗口中選擇Library頁簽,在Library Path一項中添加這兩個目錄\DSPACK234\src\Directx9 ;\DSPACK234\src\DSPack
(將其直接粘貼在原有內容的后面,或者點擊Library Path后面的...按鈕添加)
2 編譯 (DSPackDir)\packages\DirectX9_D6.dpk
雙擊(DSPackDir)\packages\DirectX9_D6.dpk,在delphi6中會顯示一個關於重新創建資源文件的對話框,點擊OK就可以了。
在delphi6中的打開窗口中點擊compile按鈕,完成編譯。
在delphi6中選擇菜單【File】-【Close All】,在提問是否保存時,選擇保存。
3 編譯 (DSPackDir)\packages\DSPack_D6.dpk
雙擊(DSPackDir)\packages\DSPack_D6.dpk,在delphi6中會顯示一個關於重新創建資源文件的對話框,點擊OK就可以了。
在delphi6中的打開窗口中,點擊compile按鈕,完成編譯。
在delphi6中選擇菜單【File】-【Close All】,在提問是否保存時,選擇保存。
4 編譯並安裝 (DSPackDir)\packages\DSPackDesign_D6.dpk
雙擊(DSPackDir)\packages\DSPackDesign_D6.dpk,在delphi6中會顯示一個關於重新創建資源文件的對話框,點擊OK就可以了。
在delphi6中的打開窗口中,點擊compile按鈕,完成編譯。 點擊Install按鈕,完成安裝。
在delphi6中選擇菜單【File】-【Close All】,在提問是否保存時,選擇保存。
至此,安裝已完成,在delphi的控件面板上可以找到DSPack的頁簽了。
(安裝過程中遇到了一個編譯錯誤,說找不到Jedi.inc。這個文件在src\Directx9目錄下,為什么不能搜索。原來文檔里寫的路徑是 (DSPackDir)\src\Directx9,而實際解壓縮出來的目錄名是DirectX9,所以添加搜索路徑的時候一定要注意這個問題。把x改成X后,編譯就通過了)
DSPack各種使用方法
一:用DSPack播放視頻
首先,要閱讀一下(DSPackDir)\help目錄下的help.chm文件,粗略地看了一下,內容太多看不出頭緒。
還是先學習一下(DSPackDir)\Demos\D6-D7目錄下的那些例子,邊動手做邊學習吧。
研究的第一個例子是PlayWin。研究了一下,主要使用TFilterGraph和TVideoWindow來完成。
TFilterGraph是DSPack中的核心類,其他類都要圍繞着它,但是怎么理解它還不清楚。TVideoWindow是個顯示播放視頻的控件。這兩個類的關系好像是數據庫控件中Dataset控件和DBGrid控件的關系一樣。
看得差不多后,自己照葫蘆畫瓢仿制一個。
1. 新建一個應用,在界面上先放4、5個按鈕。
2. 在控件面板上選擇DSPack那頁,把前兩個控件(TFilterGraph和TVideoWindow)在窗口上各放一個。
3. 關聯
選中VideoWindow1控件,在屬性窗口中將FilterGraph屬性設置為FilterGraph1。
選中FilterGraph1控件,確認屬性窗口中的Mode屬性為gmNormal。
4.為Form1增加一個onCreate事件處理程序。
內容為:
if not FilterGraph1.Active then FilterGraph1.Active := true;
FilterGraph1.ClearGraph;
FilterGraph1.RenderFile('E:\v\951.wmv'); // 簡化一點,這里用你本地硬盤上的一個視頻文件
5. 為Form1增加一個onCloseQuery事件處理程序。
內容為:
FilterGraph1.Active := false ;
6.把button1的Caption改為Start,並增加一個OnClick事件
內容為:
FilterGraph1.Play;
運行一下,就可以播放了。下面再增加幾個功能按鈕,如pause、stop。
7. 把button2的Caption改為Pause,並增加一個OnClick事件
內容為:
FilterGraph1.Pause;
8. 把button3的Caption改為Stop,並增加一個OnClick事件
內容為:
FilterGraph1.stop;
可以看出4-8步都是調用了TFilterGraph類的方法。
下面,再增加個全屏功能吧。
9. 把button4的Caption改為FullScreen,並增加一個OnClick事件
內容為:
VideoWindow1.FullScreen :=true ;
10. 為VideoWindow1增加一個OnClick事件
內容為:
if videowindow1.FullScreen then
videowindow1.FullScreen := false ; //退出全屏方式
一般的視頻播放創窗口都有一個進度條,現在我們也來加一個。
A.1. 在DSPack控件面板上選擇倒數第2個控件(TDSTrackBar),放到在窗口上。
A.2. 關聯
選中DSTrackBar1控件,在屬性窗口中將FilterGraph屬性設置為FilterGraph1。(這一步好像很熟悉哦)
重新運行程序,你就會看到一個進度條,並且能夠使用這一進度條來調整播放的進度。
二:使用DSPack打開攝像頭
現在我們來看看(DSPackDir)\Demos\D6-D7目錄下的PlayVideoCap,這是一個打開本機的視頻輸入設備的例子。
在這個例子中,又用到了一個新類:TFilter。
在我們依葫蘆畫瓢之前,你要裝個攝像頭或虛擬攝像頭。虛擬攝像頭可以用VCDCut、Softcam或Vcam等軟件,也可以使用9158(http://www.9158.com/)或MVBox(http://www.mvbox.cn/)的虛擬視頻。
先跟上次一樣:
1. 新建一個應用,在界面上先放4、5個按鈕,這次多放一個Listbox,這個列表框中將列出系統中安裝的視頻輸入設備。
2. 在控件面板上選擇DSPack那頁,把前兩個控件(TFilterGraph和TVideoWindow)在窗口上各放一個。
下面該有所不同了
3.在DSPack中選擇TFilter控件,放到窗口上。
4.選中FilterGraph1控件,在屬性窗口中將Mode屬性設為gmCapture。
5. 關聯
選中VideoWindow1控件,將FilterGraph屬性設置為FilterGraph1。
選中Filter1控件,將FilterGraph屬性設置為FilterGraph1。
6. 在代碼模式中,在Interface后的Uses中增加
DSUtil, DirectShow9,
在implementation前面的Var中增加
SysDev: TSysDevEnum;
7. 為Form1增加一個onCreate事件處理程序,讀取系統中的視頻輸入設備。
內容為:
var
i: integer;
begin
// 讀取系統中的視頻輸入設備
SysDev:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory);
if SysDev.CountFilters > 0 then
for i := 0 to SysDev.CountFilters - 1 do
begin
Listbox1.Items.Add(SysDev.Filters[i].FriendlyName)
end;
end;
7. 同前。為Form1增加一個onCloseQuery事件處理程序。
內容為:
SysDev.Free;
FilterGraph1.ClearGraph;
FilterGraph1.Active := false ;
8. 為Listbox1增加一個onClick事件處理程序
內容為:
FilterGraph1.ClearGraph;
FilterGraph1.Active := false;
//設filter為所選視頻輸入設備
Filter1.BaseFilter.Moniker := SysDev.GetMoniker(Listbox1.ItemIndex);
FilterGraph1.Active := true;
// 打開所選的視頻輸入設備
with FilterGraph1 as ICaptureGraphBuilder2 do
RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter1 as IBaseFilter, nil, VideoWindow1 as IbaseFilter);
// 顯示出來
FilterGraph1.Play;
運行一下試試,看是否能看到自己的攝像頭的內容。
幾個按鈕沒用,等到下一個例子用吧。
三:DSPack抓幀
在例子PlayVideoCap中,還有抓幀和回放的功能。這用到第4個控件TSampleGrabber。
我們以前兩個例子為基礎,增加抓幀的功能。
打開前面的例子,然后:
1. 在DSPack中選擇TSampleGrabber控件,放到窗口上。然后將其FilterGraph屬性設置為FilterGraph1。
2. 在選擇一個標准控件TImage(在Additional頁簽中),放在窗口上。
3.講一個沒用的按鈕的Caption改為"Snapshot",在它的OnClick事件中寫:
SampleGrabber1.GetBitmap(Image1.Picture.Bitmap);
前面兩個例子都可以這樣增加抓幀功能。不過,對於第二個例子(即操作攝像頭的例子),需要做額外的修改,就是:
將
RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter as IBaseFilter, nil, VideoWindow as IbaseFilter);
改為
RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter as IBaseFilter, SampleGrabber as IBaseFilter, VideoWindow as IbaseFilter);
這樣就可以了
一開始總是不能保存錄像文件,受到http://topic.csdn.net/t/20020519/11/734458.html 中這段話的啟發,"在vfw中,在你顯示設置屬性(source,format,display)的時候,是不能夠捕捉連接的.所以有你這個錯誤.而且在你在捕捉了以后,你也沒有辦法調用這幾個設置(會返回錯誤). 一般是這樣的:你先設置完成,再捕捉... 捕捉期間不能夠運行設置,必須先停止.由於dshow設置這些東西也是調用vfw的,所以有同樣的問題."明白原來是我在捕捉連接期間進行了設置,所以會返回錯誤,總是不能保存文件,修改為先停止設置完參數后再開始即可。
Delphi按鈕控件最上層顯示的方法:Button1.BringToFront;
摘錄一些文章
DirectShow入門之Directshow的基本技巧
作者:任雪梅發表於 2011-12-3 11:58:22
評論(0)
閱讀(266)
| |
摘要:本文主要講述了Directshow開發的一些基本概念和技巧,主要內容如下:
1、視頻播放(Video Rendering)
2、如何處理事件通知(Event Notification)
3、如何枚舉系統的設備和過慮器
4、如何枚舉Graph圖中的對象(filter,pin)
5、Seeking Filter graph
6、如何設置Graph時鍾(Setting Graph Clock)
視頻播放(Video Rendering)
dshow的視頻提交過濾器可以在窗口模式和無窗口模式下工作。在窗口模式下,過濾器創建一個自己的窗口,在里面播放視頻。在無窗口模式下,過濾器直接將視頻在應用程序提供的窗口上顯示,過濾器本身不創建窗口。
窗口模式
在窗口模式下,視頻提交過濾器創建一個窗口,然后將視頻禎帖到窗口上,你可以將這個窗口帖到你的應用程序的窗口。 Video Renderer只支持窗口模式,VMR-7 and VMR-9缺省的是窗口模式,也支持無窗口模式。
為了在你的應用程序中顯示視頻,你可以將視頻窗口設置成應用程序的子窗口。你可以通過
IVideoWindow *pVidWin = NULL; pGraph->QueryInterface(IID_IVideoWindow, (void **)&g_pVidWin); pVidWin->put_Owner((OAHWND)hwnd); pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS); RECT grc; GetClientRect(hwnd, &grc); pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);
結束時一定要清理現場:
pControl->Stop(); pVidWin->put_Visible(OAFALSE); pVidWin->put_Owner(NULL);
無窗口模式
當采用無窗口的模式時,就沒有必要暴露IVideoWindow接口了。
為了能夠使用VMR的缺省行為,在構建Graph圖之前必須要調整VMR。
1、創建一個過慮器圖表管理器
2、創建一個VMR,加入到graph中
3、調用VMR的IVMRFilterConfig::SetRenderingMode方法設置VMRMode_Windowless標志。
4、調用IVMRWindowlessControl::SetVideoClippingWindow 給視頻指定一個顯示窗口。
然后調用IGraphBuilder::RenderFile或者其他的方法來創建其他的Graph。
下面的代碼顯示了如何創建一個VMR,將其添加到Graph,如何設置無窗口模式
HRESULT InitWindowlessVMR( HWND hwndApp, // Window to hold the video. IGraphBuilder* pGraph, // Pointer to the Filter Graph Manager. IVMRWindowlessControl** ppWc, // Receives a pointer to the VMR. ) { if (!pGraph || !ppWc) return E_POINTER; IBaseFilter* pVmr = NULL; IVMRWindowlessControl* pWc = NULL; // Create the VMR. HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr); if (FAILED(hr)) { return hr; }
// Add the VMR to the filter graph. hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer"); if (FAILED(hr)) { pVmr->Release(); return hr; } // Set the rendering mode. IVMRFilterConfig* pConfig; hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig); if (SUCCEEDED(hr)) { hr = pConfig->SetRenderingMode(VMRMode_Windowless); pConfig->Release(); } if (SUCCEEDED(hr)) { // Set the window. hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc); if( SUCCEEDED(hr)) { hr = pWc->SetVideoClippingWindow(hwndApp); if (SUCCEEDED(hr)) { *ppWc = pWc; // Return this as an AddRef'd pointer. } else { // An error occurred, so release the interface. pWc->Release(); } } } pVmr->Release(); return hr; }
你也可以調用下面的函數
IVMRWindowlessControl *pWc = NULL; hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc); if (SUCCEEDED(hr)) { // Build the graph. For example: pGraph->RenderFile(wszMyFileName, 0); // Release the VMR interface when you are done. pWc->Release(); }
下面看看如何設置視頻的位置
有兩個矩形需要考慮,一個是源矩形,一個是目的矩形。源矩形決定開始播放視頻的位置,目的矩形決定在窗口顯示視頻的區域。VMR將源矩形按照目的矩形的大小進行擴展。
IVMRWindowlessControl::SetVideoPosition可以設置兩個矩形的大小,源矩形必須小於等於本地視頻大小。你可以通過IVMRWindowlessControl::GetNativeVideoSize獲取本地的視頻區域大小。
// Find the native video size. long lWidth, lHeight; HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);
if (SUCCEEDED(hr)) { RECT rcSrc, rcDest; // Set the source rectangle. SetRect(&rcSrc, 0, 0, lWidth/2, lHeight/2);
// Get the window client area. GetClientRect(hwnd, &rcDest); // Set the destination rectangle. SetRect(&rcDest, 0, 0, rcDest.right/2, rcDest.bottom/2);
// Set the video position. hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest); }
處理窗口消息
因為VMR沒有自己的窗口,所以當視頻需要重畫或者改變的時候你要通知它。
1、當你接到一個WM_PAINT消息,你就要調用IVMRWindowlessControl::RepaintVideo來重畫視頻
2、當你接到一個WM_DISPLAYCHANGE消息,你就要調用IVMRWindowlessControl::DisplayModeChanged.
3、當你接到一個WM_SIZE消息時,重新計算視頻的位置,然后調用SetVideoPostion。
下面的代碼演示了WM_PAINT消息的處理:
void OnPaint(HWND hwnd) { PAINTSTRUCT ps; HDC hdc; RECT rcClient; GetClientRect(hwnd, &rcClient); hdc = BeginPaint(hwnd, &ps); if (g_pWc != NULL) { // Find the region where the application can paint by subtracting // the video destination rectangle from the client area. // (Assume that g_rcDest was calculated previously.) HRGN rgnClient = CreateRectRgnIndirect(&rcClient); HRGN rgnVideo = CreateRectRgnIndirect(&g_rcDest); CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);
// Paint on window. HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE); FillRgn(hdc, rgnClient, hbr); // Clean up. DeleteObject(hbr); DeleteObject(rgnClient); DeleteObject(rgnVideo); // Request the VMR to paint the video. HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc); } else // There is no video, so paint the whole client area. { FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1)); } EndPaint(hwnd, &ps); }
盡管我們要自己處理onpaint消息,但是已經非常簡單了。
如何處理事件通知(Event Notification)
當一個Directshow的應用程序運行的時候,在 filter Graph內部就會發生各種各樣的事件,例如,一個filter也許發生數據流錯誤。Filter通過給graph mangaer發送事件通知來和graph通信,這個事件通知包括一個事件碼和兩個事件參數。事件碼表示發生事件的類型,兩個參數用來傳遞信息。
Filter發送的這些事件,其中的一部分可以被Manager直接處理,不通知應用程序,但有一部分事件,Manager將事件放入到一個隊列中,等待應用程序處理。這里我們主要討論在應用程序中經常遇到的三種事件
EC_COMPLETE表明回放已經結束
EC_USERABORT表明用戶中斷了回放。用戶關閉視頻播放窗口時,視頻Render會發生這個事件
EC_ERRORABORT表明出現了一個錯誤。
應用程序可以通知filter graph manager,在某個指定的事件發生時,向指定的窗口發生一個指定的消息。這樣應用程序就可以在消息循環中對發生的事件產生反應。
首先定義消息:
#define WM_GRAPHNOTIFY WM_APP + 1
然后向filter graph manager請求IMediaEventEx接口,然后調用IMediaEventEx::SetNotifyWindow方法來設置消息通知窗口:
IMediaEventEx *g_pEvent = NULL; g_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&g_pEvent); g_pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
然后在WindowProc函數增加一個處理WM_GRAPHNOTIFY消息的函數:
case WM_GRAPHNOTIFY: HandleGraphEvent(); break;
HandleGraphEvent()函數具體定義如下
void HandleGraphEvent() { // Disregard if we don't have an IMediaEventEx pointer. if (g_pEvent == NULL) { return; } // Get all the events long evCode; LONG_PTR param1, param2; HRESULT hr; while (SUCCEEDED(g_pEvent->GetEvent(&evCode, ?m1, ?m2,0))) { g_pEvent->FreeEventParams(evCode, param1, param2); switch (evCode) { case EC_COMPLETE: // Fall through. case EC_USERABORT: // Fall through. case EC_ERRORABORT: CleanUp(); PostQuitMessage(0); return; } } }
在釋放IMediaEventEx指針前,要取消事件通知消息,代碼如下:
// Disable event notification before releasing the graph. g_pEvent->SetNotifyWindow(NULL, 0, 0); g_pEvent->Release(); g_pEvent = NULL;
如何枚舉系統的設備和過慮器
有時,應用程序需要查看系統中所有的filter。例如,視頻應用程序需要列出系統中可用的捕捉設備。因為dshow基於com結構的,你在設計程序的時候是沒法知道系統中正在使用的過濾器。Directshow提供了兩種方法來枚舉系統中注冊的過慮器。
1、系統設備枚舉器
系統設備枚舉器提供了一個很好的方法根據種類來枚舉系統中注冊的過慮器。也許枚一種不同的硬件都會有自己的過慮器,或許所有的硬件設備共用同一個filter。這個對於采用WDM驅動程序的硬件很有用。
系統設備枚舉器根據不同的種類創建了一個枚舉器,例如,音頻壓縮,視頻捕捉。不同種類的枚舉器對於每一種設備返回一個獨立的名稱(moniker)。種類枚舉器自動將相關的即插即用,演播設備包括進來。
按照下面的步驟使用設備枚舉器:
1) 創建枚舉器組件,CLSID為CLSID_SystemDeviceEnum
2) 指定某一種類型設備,參數CLSID,通過ICreateDevEnum::CreateClassEnumerator獲取某一種類的枚舉器,這個函數返回一個IEnumMoniker接口指針,如果該種類的空或者不存在,這個方法就返回S_FALSE。因此,當你調用這個函數時一定要檢查返回值是否為S_OK,而不要用SUCCEEDED宏。
3) 然后IEnumMoniker::Next枚舉每一個moniker。這個方法返回一個IMoniker接口指針。
4) 要想知道設備的名稱,可以通過下面的函數IMoniker::BindToStorage
5) 然后利用IMoniker::BindToObject生成綁定道設備上的filter。調用IFilterGraph::AddFilter將filter添加到Graph圖中。

// Create the System Device Enumerator. HRESULT hr; ICreateDevEnum *pSysDevEnum = NULL; hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); if (FAILED(hr)) { return hr; } // Obtain a class enumerator for the video compressor category. IEnumMoniker *pEnumCat = NULL; hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat,0); if (hr == S_OK) { // Enumerate the monikers. IMoniker *pMoniker = NULL; ULONG cFetched; while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pPropBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);//知道設備的名稱 if (SUCCEEDED(hr)) { // To retrieve the filter's friendly name, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { // Display the name in your UI somehow. } VariantClear(&varName); // To create an instance of the filter, do the following: IBaseFilter *pFilter; hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,(void**)&pFilter); //生成一個filter綁定到設備上。 // Now add the filter to the graph. //Remember to release pFilter later. pPropBag->Release(); } pMoniker->Release(); } pEnumCat->Release(); } pSysDevEnum->Release();
在上面我們用IMoniker::BindToObject生成綁定道設備上的filter,當然我們還可以用另外的一種方法來生成綁定到設備上的filter 利用IMoniker::GetDisplayName得到moniker的名字。然后你把moniker的名字做參數傳遞給IFilterGraph2::AddSourceFilterForMoniker,就可以創建一個綁定到設備的filter了。在上面我們是調用IMoniker::BindToObject生成filter的,還是上面的簡單些。看看代碼吧。
LPOLESTR strName = NULL; IBaseFilter pSrc = NULL; hr = pMoniker->GetDisplayName(NULL, NULL, &strName); if (SUCCEEDED(hr)) { // Query the Filter Graph Manager for IFilterGraph2. IFilterGraph2 *pFG2 = NULL; hr = pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pFG2); if (SUCCEEDED(hr)) { hr = pFG2->AddSourceFilterForMoniker(pMoniker, 0, L"Source", &pSrc); pFG2->Release(); } CoTaskMemFree(strName); } // If successful, remember to release pSrc.
2、Filter Mapper
搜索系統中的filter的另一個方法就是采用Filer Mapper。Filter mapper是一個com對象,它按照一定的條件來搜索系統的filer,它比系統設備枚舉器(System Device Enumerator)的效率要低一些。所以當你要枚舉某特定種類的filter時,你應該使用系統設備枚舉器,但是當你搜索支持某種媒體類型的filter時,同時也找不到清晰的filter,你應該使用filter mapper。
Filter Mapper 暴露一個IFilerMapper2接口,要想搜索一個接口,你可以調用該接口的IFilterMapper2::EnumMatchingFilters方法,這個方法需要傳遞一些參數來定義搜索條件,同時該方法返回一個適合條件的filter的枚舉器,這個枚舉器提供一個IEnumMoniker接口,並且對於每個適合的filter都提供一個單獨的moniker。
下面的例子演示了,枚舉所有的支持DV,並且至少有一個輸出pin的filter,這個filter支持任何媒體類型。
IFilterMapper2 *pMapper = NULL; IEnumMoniker *pEnum = NULL; hr =CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2, (void **) &pMapper); if (FAILED(hr)) { // Error handling omitted for clarity. } GUID arrayInTypes[2]; arrayInTypes[0] = MEDIATYPE_Video; arrayInTypes[1] = MEDIASUBTYPE_dvsd; hr = pMapper->EnumMatchingFilters( &pEnum, 0, // Reserved. TRUE, // Use exact match? MERIT_DO_NOT_USE+1, // Minimum merit. TRUE, // At least one input pin? 1, // Number of major type/subtype pairs for input. arrayInTypes, // Array of major type/subtype pairs for input. NULL, // Input medium. NULL, // Input pin category. FALSE, // Must be a renderer? TRUE, // At least one output pin? 0, // Number of major type/subtype pairs for output. NULL, // Array of major type/subtype pairs for output. NULL, // Output medium. NULL); // Output pin category. // Enumerate the monikers. IMoniker *pMoniker; ULONG cFetched; //////////下面就是枚舉filter了,就是系統枚舉設備filter
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pPropBag = NULL; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag); if (SUCCEEDED(hr)) { // To retrieve the friendly name of the filter, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { // Display the name in your UI somehow. } VariantClear(&varName); // To create an instance of the filter, do the following: IBaseFilter *pFilter; hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter); // Now add the filter to the graph. Remember to release pFilter later.
// Clean up. pPropBag->Release(); } pMoniker->Release(); } // Clean up. pMapper->Release(); pEnum->Release();
// Create the System Device Enumerator. HRESULT hr; ICreateDevEnum *pSysDevEnum = NULL; hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); if (FAILED(hr)) { return hr; } // Obtain a class enumerator for the video compressor category. IEnumMoniker *pEnumCat = NULL; hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat,0); if (hr == S_OK) { // Enumerate the monikers. IMoniker *pMoniker = NULL; ULONG cFetched; while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pPropBag; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);//知道設備的名稱 if (SUCCEEDED(hr)) { // To retrieve the filter's friendly name, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { // Display the name in your UI somehow. } VariantClear(&varName); // To create an instance of the filter, do the following: IBaseFilter *pFilter; hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,(void**)&pFilter); //生成一個filter綁定到設備上。 // Now add the filter to the graph. //Remember to release pFilter later. pPropBag->Release(); } pMoniker->Release(); } pEnumCat->Release(); } pSysDevEnum->Release();
在上面我們用IMoniker::BindToObject生成綁定道設備上的filter,當然我們還可以用另外的一種方法來生成綁定到設備上的filter 利用IMoniker::GetDisplayName得到moniker的名字。然后你把moniker的名字做參數傳遞給IFilterGraph2::AddSourceFilterForMoniker,就可以創建一個綁定到設備的filter了。在上面我們是調用IMoniker::BindToObject生成filter的,還是上面的簡單些。看看代碼吧。
LPOLESTR strName = NULL; IBaseFilter pSrc = NULL; hr = pMoniker->GetDisplayName(NULL, NULL, &strName); if (SUCCEEDED(hr)) { // Query the Filter Graph Manager for IFilterGraph2. IFilterGraph2 *pFG2 = NULL; hr = pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pFG2); if (SUCCEEDED(hr)) { hr = pFG2->AddSourceFilterForMoniker(pMoniker, 0, L"Source", &pSrc); pFG2->Release(); } CoTaskMemFree(strName); } // If successful, remember to release pSrc.
2、Filter Mapper
搜索系統中的filter的另一個方法就是采用Filer Mapper。Filter mapper是一個com對象,它按照一定的條件來搜索系統的filer,它比系統設備枚舉器(System Device Enumerator)的效率要低一些。所以當你要枚舉某特定種類的filter時,你應該使用系統設備枚舉器,但是當你搜索支持某種媒體類型的filter時,同時也找不到清晰的filter,你應該使用filter mapper。
Filter Mapper 暴露一個IFilerMapper2接口,要想搜索一個接口,你可以調用該接口的IFilterMapper2::EnumMatchingFilters方法,這個方法需要傳遞一些參數來定義搜索條件,同時該方法返回一個適合條件的filter的枚舉器,這個枚舉器提供一個IEnumMoniker接口,並且對於每個適合的filter都提供一個單獨的moniker。
下面的例子演示了,枚舉所有的支持DV,並且至少有一個輸出pin的filter,這個filter支持任何媒體類型。
IFilterMapper2 *pMapper = NULL; IEnumMoniker *pEnum = NULL; hr =CoCreateInstance( CLSID_FilterMapper2,NULL, CLSCTX_INPROC, IID_IFilterMapper2, (void **) &pMapper); if (FAILED(hr)) { // Error handling omitted for clarity. } GUID arrayInTypes[2]; arrayInTypes[0] = MEDIATYPE_Video; arrayInTypes[1] = MEDIASUBTYPE_dvsd; hr = pMapper->EnumMatchingFilters( &pEnum, 0, // Reserved. TRUE, // Use exact match? MERIT_DO_NOT_USE+1, // Minimum merit. TRUE, // At least one input pin? 1, // Number of major type/subtype pairs for input. arrayInTypes, // Array of major type/subtype pairs for input. NULL, // Input medium. NULL, // Input pin category. FALSE, // Must be a renderer? TRUE, // At least one output pin? 0, // Number of major type/subtype pairs for output. NULL, // Array of major type/subtype pairs for output. NULL, // Output medium. NULL); // Output pin category. // Enumerate the monikers. IMoniker *pMoniker; ULONG cFetched; //////////下面就是枚舉filter了,就是系統枚舉設備filter
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pPropBag = NULL; hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag); if (SUCCEEDED(hr)) { // To retrieve the friendly name of the filter, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { // Display the name in your UI somehow. } VariantClear(&varName); // To create an instance of the filter, do the following: IBaseFilter *pFilter; hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter); // Now add the filter to the graph. Remember to release pFilter later.
// Clean up. pPropBag->Release(); } pMoniker->Release(); } // Clean up. pMapper->Release(); pEnum->Release();
3、查找媒體類型
每個pin都支持一個IPin::EnumMediaTypes方法,可以來枚舉pin支持的媒體類型。它返回一個IEnumMediaTypes接口,這個接口的方法IEnumMediaTypes::Next返回一個指向AM_MEDIA_TYPE類型的指針。可以參考上面的代碼來遍歷pin所支持的媒體類型。
Seeking Filter graph
主要講述了如何在一個媒體數據流中定位,任意指定開始播放的位置。
1、檢查是否支持seek
Directshow通過IMediaSeeking接口支持seeking。Filter graph管理器支持這個接口,但是實際seeking的功能是有graph中的filter來實現的。
有一些數據是不能seek的,例如,你不可能seek從照相機中采集的活動的視頻流。如果一個數據流可以被seek,但是,seek的類型還分以下幾種類型,可以給你的數據流選擇一種
1) 定位到數據流中的一個絕對位置
2) 返回數據流的持續時間
3) 返回數據流中的當前播放位置
4) 回放。
IMediaSeeking接口定義了一套標志AM_SEEKING_SEEKING_CAPABILITIES,用來描述可能支持的seek功能。
typedef enum AM_SEEKING_SeekingCapabilities { AM_SEEKING_CanSeekAbsolute = 0x1, AM_SEEKING_CanSeekForwards = 0x2, AM_SEEKING_CanSeekBackwards = 0x4, AM_SEEKING_CanGetCurrentPos = 0x8, AM_SEEKING_CanGetStopPos = 0x10, AM_SEEKING_CanGetDuration = 0x20, AM_SEEKING_CanPlayBackwards = 0x40, AM_SEEKING_CanDoSegments = 0x80, AM_SEEKING_Source = 0x100 } AM_SEEKING_SEEKING_CAPABILITIES;
可以通過IMediaSeeking::GetCapabilities查看數據流支持的seek能力都有哪些。應用程序可以采取 &測試每一項。例如,下面的代碼檢查了graph是否可以seek 一個任意的位置
DWORD dwCap = 0; HRESULT hr = pSeek->GetCapabilities(&dwCap); if (AM_SEEKING_CanSeekAbsolute & dwCap) { // Graph can seek to absolute positions. }
2、Setting and Retrieving the Position
Filter graph包含兩個位置,當前位置和停止位置,定義如下:
1) 當前位置,當一個graph正處於運行的時候,當前位置就是當前的回放位置,相對於開始的位置而言。如果graph處於停止或者暫停狀態的時候,當前位置就是數據流下次開始播放的位置點。
2) 停止位置,停止位置就是數據流將要停止的位置,當一個graph到達一個停止位置時,將沒有數據流,filter graph管理器將會發送一個EC_COMPLETE事件。
可以通過IMediaSeeking::GetPositions方法可以獲取這些位置值。返回值都是相對於原始的開始位置。
通過IMediaSeeking::SetPositions方法可以seek一個新的位置,見下面:
#define ONE_SECOND 10000000 REFERENCE_TIME rtNow = 2 * ONE_SECOND, rtStop = 5 * ONE_SECOND; hr = pSeek->SetPositions( &rtNow, AM_SEEKING_AbsolutePositioning, &rtStop, AM_SEEKING_AbsolutePositioning );
注:1秒是10,000,000參考時間單位。為了方便,這個例子將這個值定義為ONE_SECOND,如果你使用的dshow的基類,常量CUITS的值和這個值相等。
RtNow參數指定新的當前位置,第二個參數用來標示如何來定位rtNow參數。在這個例子中,AM_SEEKING_AbsolutePositioning 標志表示rtNow指定的位置是一個絕對的位置。RtStop參數指定了停止時間,最后一個參數也指定了絕對位置。
如果想指定一個相對的位置,可以指定一個AM_SEEKING_RelativePositioning參數,為了設置這個位置不能改變,可以指定一個AM_SEEKING_NoPositioning參數。此時,參考時間應該設置為NULL。下面的例子將位置向前seek 10秒,然后停止位置不變。
REFERENCE_TIME rtNow = 10 * ONE_SECOND; hr = pSeek->SetPositions( &rtNow, AM_SEEKING_RelativePositioning, NULL, AM_SEEKING_NoPositioning );
3、Setting the Playback Rate
調用IMediaSeeking::SetRate方法可以改變回放的速率。通過將新的速率設置成原來速率的倍數就可以設置新的速率,例如,pSeek->SetRate(2.0),將新的速率設置為原來速率的兩倍。比率大於1說明回放的速度比原來的大,如果介於0和1之間,就比正常的速度慢。
如果我們不考慮回放速率,當前位置和停止位置相對於開始位置都是不變的。舉個例子,如果我們有一個可以播放20秒的文件,將當前時間設置為10秒就會將播放位置設置到中間,如果播放的速率提高要原來的2倍,如果停止時間是20秒,你將播放位置設置到原來的10秒處,結果現在只能播放5秒了,因為速度提高了兩倍。
4、Time Formats For Seek Commands
IMediaSeeking接口中的許多函數的參數都要求指定一個位置值,比如當前位置,或者停止位置,缺省的情況下這些參數是以of 100 nanoseconds為時間單位的,稱為參考時間,任何支持seek的filter必須支持按參考時間來進行定位。一些filter也支持采取其他時間單位進行定位。例如,根據指定的楨的數量,或在數據流偏移的字節數進行定位。
這種用來定位的時間單位稱為時間格式,采用一個GUID來標示。Directshow定義了一系列的時間格式,詳細地可以參考SDK。第三方也可以定義自己的時間格式。
為了確定graph中的當前的filter是否支持特定的時間格式,可以調用 IMediaSeeking::IsFormatSupported方法,如果filter支持該時間格式,該函數返回ok否則返回false或者一個錯誤碼。如果filter支持某種指定的時間格式,可以調用IMediaSeeking::SetTimeFormat方法切換到其他的時間格式。如果SetTimeFormat方法成功,下面的seek命令就要使用新的時間格式。
下面的代碼檢查graph是否支持用楨的數量進行定位,如果支持,定位到第20楨。
hr = pSeek->IsFormatSupported(&TIME_FORMAT_FRAME); if (hr == S_OK) { hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME); if (SUCCEEDED(hr)) { // Seek to frame number 20. LONGLONG rtNow = 20; hr = pSeek->SetPositions(&rtNow, AM_SEEKING_AbsolutePositioning,0, AM_SEEKING_NoPositioning); } }
6、如何設置Graph時鍾(Setting Graph Clock)
當你構建了一個graph后,graph管理器會自動地給你的graph選擇一個參考時鍾的。Graph中的所有filter都同步於時鍾。特別的,Renderer filter還要根據參考時鍾的時間來決定每一個sample的Presentation 時間。
通常的情況下,應用程序是沒有必要重新設置graph管理器選擇好的參考時鍾的。但是,如果你想修改參考時鍾,你可以通過graph管理器提供的IMediaFilter::SetSyncSource方法來重新設置參考時鍾。這個方法的參數是一個時鍾的IReferenceClock接口指針。可以在graph停止的時候調用這個函數,下面的例子演示了如何指定一個時鍾
IGraphBuilder *pGraph = 0; IReferenceClock *pClock = 0; CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); // Build the graph. pGraph->RenderFile(L"C:\\Example.avi", 0); // Create your clock. hr = CreateMyPrivateClock(&pClock); if (SUCCEEDED(hr)) { // Set the graph clock. IMediaFilter *pMediaFilter = 0; pGraph->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter); pMediaFilter->SetSyncSource(pClock); pClock->Release(); pMediaFilter->Release(); }
這段代碼假定CreateMyPrivateClock 是應用程序定義的一個函數,用來創建一個時鍾,然后返回一個IReferenceClock接口。
你也可以在graph沒有設置時鍾的情況下運行graph。當SetSyncSource 函數的參數為NULL的時候就給graph設置了一個空的參考時鍾。如果graph沒有時鍾,graph將運行的快許多。因為renderer 不用再按照sample的presentation 時間了,只要sample到達了renderer filter,就可以立即被提交。所以,當你想處理數據盡可能快,而不是還要考慮預覽的實際時間,你就可以給graph設置一個空的時間。 |
DSPack各種使用方法http://hi.baidu.com/zhangyihappy/blog/item/2038b28aa30ddbd8fd1f1085.html
一:用DSPack播放視頻 首先,要閱讀一下(DSPackDir)\help目錄下的help.chm文件,粗略地看了一下,內容太多看不出頭緒。 還是先學習一下(DSPackDir)\Demos\D6-D7目錄下的那些例子,邊動手做邊學習吧。
研究的第一個例子是PlayWin。研究了一下,主要使用TFilterGraph和TVideoWindow來完成。 TFilterGraph是DSPack中的核心類,其他類都要圍繞着它,但是怎么理解它還不清楚。TVideoWindow是個顯示播放視頻的控件。這兩個類的關系好像是數據庫控件中Dataset控件和DBGrid控件的關系一樣。
看得差不多后,自己照葫蘆畫瓢仿制一個。
1. 新建一個應用,在界面上先放4、5個按鈕。 2. 在控件面板上選擇DSPack那頁,把前兩個控件(TFilterGraph和TVideoWindow)在窗口上各放一個。 3. 關聯 選中VideoWindow1控件,在屬性窗口中將FilterGraph屬性設置為FilterGraph1。 選中FilterGraph1控件,確認屬性窗口中的Mode屬性為gmNormal。
4.為Form1增加一個onCreate事件處理程序。 內容為: if not FilterGraph1.Active then FilterGraph1.Active := true; FilterGraph1.ClearGraph; FilterGraph1.RenderFile('E:\v\951.wmv'); // 簡化一點,這里用你本地硬盤上的一個視頻文件
5. 為Form1增加一個onCloseQuery事件處理程序。 內容為: FilterGraph1.Active := false ;
6.把button1的Caption改為Start,並增加一個OnClick事件 內容為: FilterGraph1.Play;
運行一下,就可以播放了。下面再增加幾個功能按鈕,如pause、stop。
7. 把button2的Caption改為Pause,並增加一個OnClick事件 內容為: FilterGraph1.Pause;
8. 把button3的Caption改為Stop,並增加一個OnClick事件 內容為: FilterGraph1.stop;
可以看出4-8步都是調用了TFilterGraph類的方法。
下面,再增加個全屏功能吧。 9. 把button4的Caption改為FullScreen,並增加一個OnClick事件 內容為: VideoWindow1.FullScreen :=true ;
10. 為VideoWindow1增加一個OnClick事件 內容為: if videowindow1.FullScreen then videowindow1.FullScreen := false ; //退出全屏方式
一般的視頻播放創窗口都有一個進度條,現在我們也來加一個。 A.1. 在DSPack控件面板上選擇倒數第2個控件(TDSTrackBar),放到在窗口上。 A.2. 關聯 選中DSTrackBar1控件,在屬性窗口中將FilterGraph屬性設置為FilterGraph1。(這一步好像很熟悉哦)
重新運行程序,你就會看到一個進度條,並且能夠使用這一進度條來調整播放的進度。 二:使用DSPack打開攝像頭 現在我們來看看(DSPackDir)\Demos\D6-D7目錄下的PlayVideoCap,這是一個打開本機的視頻輸入設備的例子。
在這個例子中,又用到了一個新類:TFilter。
在我們依葫蘆畫瓢之前,你要裝個攝像頭或虛擬攝像頭。虛擬攝像頭可以用VCDCut、Softcam或Vcam等軟件,也可以使用9158(http://www.9158.com/)或MVBox(http://www.mvbox.cn/)的虛擬視頻。
先跟上次一樣:
1. 新建一個應用,在界面上先放4、5個按鈕,這次多放一個Listbox,這個列表框中將列出系統中安裝的視頻輸入設備。 2. 在控件面板上選擇DSPack那頁,把前兩個控件(TFilterGraph和TVideoWindow)在窗口上各放一個。
下面該有所不同了
3.在DSPack中選擇TFilter控件,放到窗口上。 4.選中FilterGraph1控件,在屬性窗口中將Mode屬性設為gmCapture。 5. 關聯 選中VideoWindow1控件,將FilterGraph屬性設置為FilterGraph1。 選中Filter1控件,將FilterGraph屬性設置為FilterGraph1。
6. 在代碼模式中,在Interface后的Uses中增加 DSUtil, DirectShow9, 在implementation前面的Var中增加 SysDev: TSysDevEnum;
7. 為Form1增加一個onCreate事件處理程序,讀取系統中的視頻輸入設備。 內容為: var i: integer; begin // 讀取系統中的視頻輸入設備 SysDev:= TSysDevEnum.Create(CLSID_VideoInputDeviceCategory); if SysDev.CountFilters > 0 then for i := 0 to SysDev.CountFilters - 1 do begin Listbox1.Items.Add(SysDev.Filters[i].FriendlyName) end;
end;
7. 同前。為Form1增加一個onCloseQuery事件處理程序。 內容為: SysDev.Free; FilterGraph1.ClearGraph; FilterGraph1.Active := false ;
8. 為Listbox1增加一個onClick事件處理程序 內容為: FilterGraph1.ClearGraph; FilterGraph1.Active := false; //設filter為所選視頻輸入設備 Filter1.BaseFilter.Moniker := SysDev.GetMoniker(Listbox1.ItemIndex); FilterGraph1.Active := true; // 打開所選的視頻輸入設備 with FilterGraph1 as ICaptureGraphBuilder2 do RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter1 as IBaseFilter, nil, VideoWindow1 as IbaseFilter); // 顯示出來 FilterGraph1.Play;
運行一下試試,看是否能看到自己的攝像頭的內容。
幾個按鈕沒用,等到下一個例子用吧。 三:DSPack抓幀 在例子PlayVideoCap中,還有抓幀和回放的功能。這用到第4個控件TSampleGrabber。
我們以前兩個例子為基礎,增加抓幀的功能。 打開前面的例子,然后:
1. 在DSPack中選擇TSampleGrabber控件,放到窗口上。然后將其FilterGraph屬性設置為FilterGraph1。 2. 在選擇一個標准控件TImage(在Additional頁簽中),放在窗口上。 3.講一個沒用的按鈕的Caption改為"Snapshot",在它的OnClick事件中寫: SampleGrabber1.GetBitmap(Image1.Picture.Bitmap);
前面兩個例子都可以這樣增加抓幀功能。不過,對於第二個例子(即操作攝像頭的例子),需要做額外的修改,就是: 將 RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter as IBaseFilter, nil, VideoWindow as IbaseFilter); 改為 RenderStream(@PIN_CATEGORY_PREVIEW, nil, Filter as IBaseFilter, SampleGrabber as IBaseFilter, VideoWindow as IbaseFilter);
這樣就可以了 |
|
|