310 likes | 589 Views
DirectX Video Capture. 井民全製作 課程網頁 : http://debut.cis.nctu.edu.tw/~ching. About Video Capture. describes any application where video is received from a hardware device Cameras, TV tuner cards, video tape recorders the concepts Graph Builder Video Capture Devices
E N D
DirectX Video Capture 井民全製作 課程網頁: http://debut.cis.nctu.edu.tw/~ching
About Video Capture • describes any application where video is received from a hardware device • Cameras, TV tuner cards, video tape recorders • the concepts • Graph Builder • Video Capture Devices • DirectShow Video Capture Filters • 介紹 GraphEdit • 自動產生 Capture 程式
GraphEdit • 自動產生 Graph 播放 MPG 檔
GraphEdit • Video Capture Preview 將 video stream 轉成 avi stream 邊播放邊錄影
Writing a DirectShow Application • Three tasks must be performed 3 2 1 利用 Filter Graph Manager 建立 filter graph 利用 Filter Graph Manager 控制 filter graph 建立 Filter Graph Manager When processing is completed, the application releases the Filter Graph Manager and all of the filters
About the Capture Graph Builder • A Filter graph that perform video & audio capture 稱為 capture graph • ICaptureGraphBuilder2 interface • 提供 Building & controlling a capture graph 的方法 通常 我們都使用 Capture Graph Builder 輔助元件幫我們建立 capture graph 當然, 你也可以完全使用 IGraphBuilder 建立完整的 Capture Graph Capture Graph Builder exposes ICaptureGraphBuilder2 介面
初始化 CaptureGraphBuilder2 1 建立 Capture Graph Builder Create new instances 1. Capture Graph Builder 2. Filter Graph Manager 1 用來管理 Capture Graph 的元件 2 利用 ICaptureGraphBuilder2::SetFiltergraph 把 Filter Graph Manager 的指標設定給 Capture Graph Builder 2 建立連線
Create new instances 部分的程式碼 HRESULT InitCaptureGraphBuilder( IGraphBuilder **ppGraph, // 回傳給 呼叫者 ICaptureGraphBuilder2 **ppBuild // 回傳給 呼叫者 ) { IGraphBuilder *pGraph = NULL; ICaptureGraphBuilder2 *pBuild = NULL; // 建立 Capture Graph Builder. HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild); 1 目前為NULL 指定你要建立物件的Class ID 表示這個物件由DLL 實作並且由你的 process 執行 你希望傳回的 Interface 傳回的 Interface
注意: SDK 有誤 (已經修正) 更正 SetFiltergraph 部分的程式碼 if (SUCCEEDED(hr)) { // 建立 Filter Graph Manager hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph); if (SUCCEEDED(hr)) { pBuild->SetFiltergraph(pGraph); *ppBuild = pBuild; *ppGraph = pGraph; return S_OK; } else pBuild->Release(); } return hr; // Failed } 2 設定 pBuilder 要管理的目標為 Filter Graph Manager 將資料回傳
Preview Mode Capture Filters 的特性 • Pin Categories • 通常有 2 或更多輸出相同資料的 output pins • Pins 的分類由功能決定, 以 GUID 來 Identify • Preview Pins & Capture Pins • Preview Pins render video to the screen • 因為效能的關係會 drop frame • PIN_CATEGORY_PREVIEW • Capture Pins 把抓到的 video 存檔 • 每張 frame 會被加上 time-stamped • PIN_CATEGORY_CAPTURE Pin Category Pin Category
Overlay Mode Capture Filters 的特性 Video port 有可能是一條實際的 cable 線, 連接兩個不同的裝置. • Video Port Pins • a hardware connection ( video card 與 video device) • 允許 video device 直接送影像資料到 graph card, 使得影像直接使用 hardware overlay 方式顯示 • 缺點 • 只能使用 overlay surface • Frame 之間的 Flipping 是自動的. • Pin category: PIN_CATEGORY_VIDEOPORT 直接把影像資料傳到 video memory, 直接繞過 CPU 進行其他影像處理時,會有同步的問題
建立系統裝置列舉器 (目的是要找出 Moniker 元件) Step 1 Step 2 系統架構 利用 Moniker 元件, 幫我們把 Capture 裝置找出來 Selecting a Capture Device • 要抓資料,就要先決定要哪一台機器的資料 Step 1 利用 System Device Enumerator 傳回系統資料列舉器 取出 IMoniker Step 2 Moniker 是一種 COM 元件, 專門用來取得其他元件的資料. 1. (使用他的好處在於, 不需建立物件就可得知他的資料) 2. 我們也可以用他來建立 Filter Ref: IMoniker in PlatForm SDK 利用該列舉器找出 IMoniker 硬體資料儲存器 * BindToStorage method : 讀取裝置名稱 * BindToObject method : 建立裝置對應 Filter
程式碼範例解說 #include <atlbase.h> // for ATL 7.0 字串轉換巨集 #include <Dshow.h> // for DirectShow header file #pragma comment( lib, "Strmiids.lib" ) // for DirectShow library file #include <stdio.h> // for printf void EnumerateVideoInputDevice(); int main(){ // Step 1: 首先要先 initial COM CoInitialize(NULL); // Step 2: 呼叫列舉函式(見下頁) EnumerateVideoInputDevice(); // 最後 COM must be uninitialized CoUninitialize(); return 0; } Video Capture Device
取得裝置列舉集合介面 並非建立一個aggregation 元件 指定建立的物件的CLSID (使用 GUID表示) 包含 SystemDeviceEnum 元件的 Code 是放在 DLL 中 取得 Moniker 列舉集合 (只列舉 Video Input 相關裝置 Filter) 指明 我要列舉 VideoInputDevice void EnumerateVideoInputDevice(){ ICreateDevEnum *pDevEnum = NULL; // Step 1: 建立 System Device Enumerator 幫我們傳回 硬體 Moniker 集合 HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, reinterpret_cast<void**>(&pDevEnum)); IEnumMoniker *pEnum = NULL; if (SUCCEEDED(hr)){ // Step 2: 建立 Video capture 系統列舉物件 hr = pDevEnum->CreateClassEnumerator( \ CLSID_VideoInputDeviceCategory,\ &pEnum, 0); } • * CLSID: A globally unique identifier (GUID) associated with an OLE class object. • aggregation: 外部物件把建立內部包含物件當作是起始程序的部分, 而內部的 Interface 由外部物件 exposed. • * 你可以在 Filter Categories 看到更多的 Filter 接下頁
把資料放到袋子中 pMoniker pMoniker 裝置 1 的資料 裝置 2 的資料 利用 pPropBag 把硬體資訊讀出來 // Step 3: 利用列舉物件 Next method 取得硬體 Moniker 物件, 取得硬體資訊 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; } // Step 4: 讀取硬體名稱 VARIANT varName; VariantInit(&varName); hr = pPropBag->Read (L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { // 將 OLE 型態的字串轉換成 TCHAR 字串 COLE2T strName(varName.bstrVal); printf("裝置名稱 = %s \n",strName); VariantClear(&varName); } pPropBag->Release(); pMoniker->Release(); } // end of while } 列印 之前已經使用 要求列舉 Video Input Device, 所以袋子中 全部都是 Video Input 裝置的資訊
我們希望用 IBaseFilter 介面來與攝影機 通訊 利用 Moniker 建立 硬體物件 指向提供 Binding 過程中需要的資訊物件 為指向左邊的指標 針對一串 Moniker而言, 指向 left 鄰居 (多物件合作的情況) 對於單一Moniker,現在設定為 NULL composite moniker pMoniker pMoniker 建立 Capture Device 對應 Filter IBaseFilter *pCap = NULL; hr = pMoniker->BindToObject (0, 0, IID_IBaseFilter, (void**)&pCap); 選好後,請加入 Graph Filter 中 hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
管理 Capture Graph pGraph CaptureGraphBuilder 元件, 幫我們建立 Capture Graph pBuild 利用 Moniker 元件, 取得 Video Input 裝置的 Filter 元件 pGraph->AddFilter 建立整串 Graph pBuild->AddFilter Run Previewing Video Step 1: 首先要先 initial COM Step 2: 建立 Graph Manager Build help Ojbect Step 3: 取得 Video Capture Device Filter Step 4: 加入 Graph Filter 最簡單的 Graph Step 5: 執行 Graph Filter
系統動作 呼叫 VideoRender(this->m_hWnd); 呼叫 Clear() Video Previewing
VideoRender 程式講解 // 定義全域變數以方便處理 IGraphBuilder *pGraph; // 對應 Graph ICaptureGraphBuilder2 *pBuild; // 對應 Capture Graph Builder IBaseFilter *pCap; // 對應 Capture Filter IMediaControl *pControl; // 對應 Graph Controller int VideoRender(HWND hwnd){ // Step 1: 首先要先 initial COM CoInitialize(NULL); // Step 2: 呼叫初始化函式幫我們建立 Graph Manager 與 Build help Ojbect InitCaptureGraphBuilder(&pGraph,&pBuild); // Step 3: 取得 系統中的 Video Capture Device EnumerateVideoInputDevice(&pCap); pGraph->AddFilter(pCap,L"Capture Filter"); 接下頁
指定使用pCap 的 preview 類型的 pin 送資料 指定pCap 送出的是 Video source 中間 sink 額外的 Filter 例如: 解壓縮或壓縮的 filter // Step 4: 要求 Graph Builder 幫我們建立適當的 graph filter 圖形 HRESULT hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW , &MEDIATYPE_Video, pCap, NULL, NULL); // Step 5: 建立控制器用來執行我們的 graph filter hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); pControl->Run(); return 0; } 連接 pCap 和 預設的 Render 預設 Render (自動產生視窗顯示) // Release everything void Clear(){ pGraph->Release(); pCap->Release(); pBuild->Release(); pControl->Release(); // 最後 COM must be uninitialized CoUninitialize(); } 對於 DV 而言, Media Type 送出的是 Audio 與 Video MEDAITYPE_Interleaved
任意 media file 包含 avi, mpeg 從 Media File 中讀取一張 frame • 我們利用 Media Detector object (MediaDet) 提供抓取指定 media file frame • 利用這 helper object的好處 • 假設 file 是 seekable, 我們可以由任一點取出一張影像 • 你不需要使用 Filter Manager 或 建立 filter graph, Media Detector 幫你做好這些事 • 其實 Media Detector 是利用 Sample Grabber Filter. 幫我們抓資料 Capture Graph
// 1. CComPtr<IMediaDet> pDet; // 2. hr = pDet.CoCreateInstance(__uuidof(MediaDet)); if (FAILED(hr)) return hr; // 3. CComBSTR bstrFilename(pszFileName); hr = pDet->put_Filename(bstrFilename); if (FAILED(hr)) return hr; 指定 Media Detector Object 指定 media file 利用 Media Detector 取影像基本流程 Step 1: 利用 ATL Smart Pointer 幫我們管理 IMediaDet 物件 Step 2: 建立 MediaDet 物件 Step 3: 指定 source file
1 傳回 stream 數量 for (long i = 0; i < lStreams; i++) { GUID major_type; hr = pDet->put_CurrentStream(i); if (SUCCEEDED(hr)){ hr = pDet->get_StreamType(&major_type); } if (FAILED(hr)) break; if (major_type == MEDIATYPE_Video) { bFound = true; break; } } if (!bFound) return VFW_E_INVALIDMEDIATYPE; long lStreams; bool bFound = false; hr = pDet->get_OutputStreams(&lStreams); if (FAILED(hr)) return hr; 指定目前的 stream 2 取出 stream型態 3 研判是否為 Video Stream 指定目標 Stream 前面已經指定了 Source File 了, 現在要指定目標 stream Step 4: 傳回目前 Media file 中 stream 的數量 Step 5: 察看並且指定 Video stream
Sample 的大分類, 如: video 還是 audio 小分類, 如: RGB24 還是 RGB 32 或 YUV TRUE Sample 是固定大小 TRUE Samples 是被壓縮 Sample 的大小. 如 壓縮過的 frame sample size (byte) 由於 Sample 的種類不同,故也有不同的 結構描述 format 不使用 Format 結構的大小 指向 Sample Format 的內容 這裡是介紹 DirectShow 如何描述取樣的資料(資料有可能是 Video frame或 Audio) 取出目標 stream 的相關格式描述 • DirectShow 基本上使用AM_MEDIA_TYPE描述所有 filter graph 的 digital media formats 詳情參考: DirectX SDK: About Media Types
取出目標 stream 的相關格式描述 Step 6: 取出單一張 frame 的寬高 long width = 0, height = 0; AM_MEDIA_TYPE mt; hr = pDet->get_StreamMediaType(&mt); if (SUCCEEDED(hr)) { VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)(mt.pbFormat); width = pVih->bmiHeader.biWidth; height = pVih->bmiHeader.biHeight; //直接取 frame 高度 (top-down DIB = 負, bottom-up DIB =正) if (height < 0) height *= -1; } 取得 Sample 格式描述子 因為前面已經指定是 MEDIATYPE_Video, 所以 格式應該就是 VIDEOINFOHEADER 由 sample Format 中得到寬高資訊
Step 8: 取出 frame 的 bitmap 圖形資料 取出指定位置 frame 的圖形資料 Step 7: 先計算圖形資料所需的空間 long size; int second=15; hr = pDet->GetBitmapBits(second, &size, NULL,width, height); if (SUCCEEDED(hr)) { char *pBuffer = new char[size]; // 配置空間 if (!pBuffer) return E_OUTOFMEMORY; try { hr = pDet->GetBitmapBits(second, NULL, pBuffer, width,height); if (SUCCEEDED(hr)) { // Delete the old image, if any. if (*ppbmih) delete[] (*ppbmih); (*ppbmih) = (BITMAPINFOHEADER*) pBuffer; } else { delete [] pBuffer; // 取 frame 失敗, 刪除 buffer 空間 } } catch (...) { // 若擷取過程中發生任何 exception delete [] pBuffer; return E_OUTOFMEMORY; } 指定要取的位置 (秒) 表示要取 Frame size (因為目前是詢問,故不用 指定記憶體位址 == NULL) 我們利用圖形標頭檔格式描述資料
全部合起來 取出第 1 秒 的 frame 秀出來 Grabing_a_Post_Frame
附錄: 編譯 Header file // --------- Direct Show Header File & library ------------- #include <Dshow.h> #include <Qedit.h> // for IMediaDet Interface #pragma comment( lib, "Strmiids.lib" ) // 下面這東西放在 F:\Program Files\DX9\Samples\C++\DirectShow\BaseClasses 下面 // VC 設定: 加入 BaseClass header file 搜尋目錄 // F:\Program Files\DX9\Samples\C++\DirectShow\BaseClasses #include <Streams.h> #pragma comment(lib, "F:\\Program Files\\DX9\\Samples\\C++\\DirectShow\\BaseClasses\\Debug\\Strmbasd.lib" ) // debug build // 因為 BaseClass 中有使用到 Windows Multimedia library 的東西, 所以你要加入下面這行 #pragma comment(lib, "Winmm.lib" ) // --------- ATL Smart Pointer // Active Template Libary (ATL): 一系列的 c++ template classes, 幫助你建立簡單小型的 // Common Object Model (COM) 物件 #include <atlbase.h> #include <Commdlg.h> // for 開檔物件 設定正確的 Direct X 安裝目錄 你要修改的地方 1 你要修改的地方 2
mk:@MSITStore:F:\Program%20Files\DX9\Documentation\DirectX9\DirectX9_c.chm::/directX/htm/usingthesystemdeviceenumerator.htmmk:@MSITStore:F:\Program%20Files\DX9\Documentation\DirectX9\DirectX9_c.chm::/directX/htm/usingthesystemdeviceenumerator.htm 附錄: 標準 DirectShow Filter Category 你可以把他用在 System Device Enumerator
下載資源 • Windows Media Downloads • http://msdn.microsoft.com/library/default.asp?url=/downloads/list/winmedia.asp