在windows 裡面實現視頻捕獲,微軟提供了兩個SDK庫,一個是傳統的avicap,一個是比較新的directshow。一、Video for Windows簡介
VFW是Microsoft 1992年推出的關於數位視訊的一個軟體包,它能使應用程式數字化並播放從傳統類比視頻源得到的視訊剪輯。VFW的一個關鍵思想是播放時不需要專用硬體,為瞭解決數位視訊資料量大的問題,需要對資料進行壓縮。它引進了一種叫AVI的檔案標準,該標準未規定如何對視頻進行捕獲、壓縮及播放,僅規定視頻和音頻該如何儲存在硬碟上,在AVI檔案中交替儲存視訊框架和與之相匹配的音頻資料。VFW給程式員提供.VBX和AVICap視窗類別的進階編程工具,使程式員能通過發送訊息或設定屬性來捕獲、播放和編輯視訊剪輯。現在使用者不必專門安裝VFW了,Windows95本身包括了Video for Windows1.1,當使用者在安裝Windows時,安裝程式會自動地安裝配置視頻所需的組件,如裝置驅動程式、視頻壓縮程式等。
VFW主要由以下六個模組組成:
(1)AVICAP.DLL:包含了執行視頻捕獲的函數,它給AVI檔案I/O和視頻、音訊裝置驅動程式提供一個進階介面;
(2)MSVIDEO.DLL:用一套特殊的DrawDib函數來處理螢幕上的視頻操作;
(3)MCIAVI.DRV:此驅動程式套件括對VFW的MCI命令的解譯器;
(4)AVIFILE.DLL:支援由標準多媒體I/O(mmio)函數提供的更高的命令來訪問.AVI檔案;
(5)壓縮管理器(ICM):管理用於視頻壓縮-解壓縮的轉碼器(CODEC);
(6)音頻壓縮管理器ACM:提供與ICM相似的服務,不同的是它適于波形音頻。
Visual C++在支援VFW方面提供有vfw32.lib、 msacm32.lib 、winmm.lib等類似的庫。特別是它提供了功能強大、簡單易行、類似於MCIWnd的視窗類別AVICap。AVICap為應用程式提供了一個簡單的、基於訊息的介面,使之能訪問視頻和波形音頻硬體,並能在將視頻流捕獲到硬碟上的過程中進行控制。
二、AVICap編程簡介
AVICap支援即時的視頻流捕獲和單幀捕獲並提供對視頻源的控制。雖然MCI也提供數位視訊服務,比如它為顯示.AVI檔案的視頻提供了 avivideo命令集,為視頻疊加提供了overlay命令集,但這些命令主要是基於檔案的操作,它不能滿足即時地直接從視頻緩衝中取資料的要求,對於使用沒有視頻疊加能力的捕獲卡的PC機來說,用MCI提供的命令集是無法捕獲視頻流的。而AVICap在捕獲視頻方面具有一定的優勢,它能直接存取視頻緩衝區,不需要產生中間檔案,即時性很強,效率很高。同時,它也可將數位視訊捕獲到檔案。
在視頻捕獲之前需要建立一個捕獲窗,所有的捕獲操作及其設定都以它為基礎。用AVICap視窗類別建立的視窗(通過 capCreateCaptureWindow函數建立)被稱為“捕獲窗”,其視窗風格一般為WS_CHILD和WS_VISIBLE。在概念上,捕獲窗類似於標準控制(如按鈕、列表框等)。捕獲窗具有下列功能:
(1)將一視頻流和音頻流捕獲到一個AVI檔案中;
(2)動態地同視頻和音頻輸入器件串連或斷開;
(3)以Overlay或Preview模式對輸入的視頻流進行即時顯示;
(4)在捕獲時可指定所用的檔案名稱並能將捕獲檔案的內容拷貝到另一個檔案;
(5)設定捕獲速率;
(6)顯示控制視頻源、視頻格式、視頻壓縮的對話方塊;
(7)建立、儲存或載入調色盤;
(8)將映像和相關的調色盤拷貝到剪貼簿;
(9)將捕獲的一個單幀映像儲存為DIB格式的檔案。
這裡需要解釋一下AVICap在顯示視頻時提供的兩種模式:
(A)預覽(Preview)模式:該模式使用CPU資源,視訊框架先從捕獲硬體傳到系統記憶體,接著採用GDI函數在捕獲窗中顯示。在物理上,這種模式需要通過VGA卡在監視器上顯示。
(B)疊加(Overlay)模式:該模式使用硬體疊加進行視頻顯示,疊加視頻的顯示不經過VGA卡,疊加視頻的硬體將VGA的輸出訊號與其自身的輸出訊號合并,形成組合訊號顯示在電腦的監視器上。只有部分視頻捕獲卡才具有視頻疊加能力。
除了利用捕獲窗的九個功能外,靈活編寫AVICap提供的回呼函數還可滿足一些特殊需求,比如將宏capCaptureSequenceNoFile同用capSetCallbackOnVideoStream登記的回呼函數一起使用可使應用程式直接使用視頻和音頻資料,在視頻會議的應用程式中可利用這一點來獲得視訊框架,回呼函數將捕獲的映像傳到遠端的電腦。應用程式可用捕獲窗來登記回呼函數(由使用者編寫,而由系統調用),以便在發生下列情況時它能通知應用程式作出相應的反應:
(1)捕獲窗狀態改變;
(2)出錯;
(3)視訊框架和音頻緩衝可以使用 ;
(4)在捕獲過程中,其它應用程式處於讓步(Yield)地位。
與普通SDK編程一樣,視頻捕獲編程也要用到涉及視頻捕獲的結構、宏、訊息和函數。讓編程人員感到輕鬆的是,發送AVICap視窗訊息所能完成的功能都能調用相應的宏來完成。例如,SendMessage(hWndCap,WM_CAP_DRIVER_CONNECT,0,0L)與capDriverConnect(hWndCap,0)的作用相同,都是將建立的捕獲窗同視頻輸入器件串連起來。
在利用AVICap編程時,應該熟悉與視頻捕獲相關的結構,下面對常用的四個結構作一簡要介紹,對於前三個結構都有對應的函數來設定和獲得結構包含的資訊:
(1)CAPSTATUS:定義了捕獲視窗的目前狀態,像的寬、高等;
(2)CAPDRIVERCAPS:定義了捕獲磁碟機的能力,如有無視頻疊加能力、有不控制視頻源、視頻格式的對話方塊等;
(3)CAPTUREPARMS:包含控制視頻流捕獲過程的參數,如捕獲幀頻、指定鍵盤或滑鼠鍵以終止捕獲、捕獲時間限制等;
(4)VIDEOHDR:定義了視頻資料區塊的頭資訊,在編寫回呼函數時常用到其資料成員lpData(指向資料緩衝的指標)和dwBufferLength(資料緩衝的大小)。
三、AVICap編程樣本
下面以一個簡單的應用程式為例說明AVICap的使用,該程式對輸入的視頻流進行即時的顯示和捕獲,示範需要一個視頻捕獲卡和網路攝影機。介面中的功能表項目1所示。其中,功能表項目Display可以以Preview 或Overlay模式顯示映像;功能表項目Setting可通過彈出AVICap提供的對話方塊Video Source、Video Format和Video Display來對捕獲進行設定,圖4 中的映像就是按照圖2、圖3的對話方塊所示進行設定、以Preview模式顯示的結果;功能表項目Capture可將視頻流或單幀映像捕獲到指定的檔案中去。
圖1 功能表項目
圖2 Video Format對話方塊
圖3 Video Source對話方塊
圖4 圖2和圖3設定下顯示的一幀圖
由於篇幅有限,下面僅介紹與視頻捕獲相關的編程。
1、定義全域變數:
CODE:
HWND ghWndCap ; //捕獲窗的控制代碼
CAPDRIVERCAPS gCapDriverCaps ; //視頻磁碟機的能力
CAPSTATUS gCapStatus ; //捕獲窗的狀態
2、處理WM_CREATE訊息:
CODE:
//建立捕獲窗,其中hWnd為主視窗控制代碼
ghWndCap = capCreateCaptureWindow((LPSTR)"Capture Window",WS_CHILD | WS_VISIBLE, 0, 0, 300,240, (HWND) hWnd, (int) 0);
//登記三個回呼函數,它們應被提前申明
capSetCallbackOnError(ghWndCap, (FARPROC)ErrorCallbackProc); capSetCallbackOnStatus(ghWndCap, (FARPROC)StatusCallbackProc); capSetCallbackOnFrame(ghWndCap, (FARPROC)FrameCallbackProc);
capDriverConnect(ghWndCap,0); // 將捕獲窗同磁碟機串連
//獲得磁碟機的能力,相關的資訊放在結構變數gCapDriverCaps中
capDriverGetCaps(ghWndCap,&gCapDriverCaps,sizeof(CAPDRIVERCAPS)) ;
3、處理WM_CLOSE訊息:
CODE:
//取消所登記的三個回呼函數
capSetCallbackOnStatus(ghWndCap, NULL);
capSetCallbackOnError(ghWndCap, NULL);
capSetCallbackOnFrame(ghWndCap, NULL);
capCaptureAbort(ghWndCap);//停止捕獲
capDriverDisconnect(ghWndCap); //將捕獲窗同磁碟機斷開
4、處理功能表項目Preview:
CODE:
capPreviewRate(ghWndCap, 66); // 設定Preview模式的顯示速率
capPreview(ghWndCap, TRUE); //啟動Preview模式
5、處理功能表項目Overlay:
CODE:
if(gCapDriverCaps.fHasOverlay) //檢查磁碟機是否有疊加能力
capOverlay(ghWndCap,TRUE); //啟動Overlay模式
6、處理功能表項目Exit:
CODE:
SendMessage(hWnd,WM_CLOSE,wParam,lParam);
7、分別處理Setting下的三個功能表項目,它們可分別控制視頻源、視頻格式及顯示:
CODE:
if (gCapDriverCaps.fHasDlgVideoSource)
capDlgVideoSource(ghWndCap); //Video source 對話方塊
if (gapDriverCaps.fHasDlgVideoFormat)
capDlgVideoFormat(ghWndCap); // Video format 對話方塊
if (CapDriverCaps.fHasDlgVideoDisplay)
capDlgVideoDisplay(ghWndCap); // Video display 對話方塊
8、處理Video Stream功能表項目,它捕獲視頻流到一個.AVI檔案:
CODE:
char szCaptureFile[] = "MYCAP.AVI";
capFileSetCaptureFile( ghWndCap, szCaptureFile); //指定捕獲檔案名稱
capFileAlloc( ghWndCap, (1024L * 1024L * 5)); //為捕獲檔案分配儲存空間
capCaptureSequence(ghWndCap); //開始捕獲視頻序列
9、處理Single Frame功能表項目:
CODE:
capGrabFrame(ghWndCap); //捕獲單幀映像
10、定義三個回呼函數:
CODE:
LRESULT CALLBACK StatusCallbackProc(HWND hWnd, int nID, LPSTR lpStatusText)
{
if (!ghWndCap) return FALSE;
//獲得捕獲窗的狀態
capGetStatus(ghWndCap, &gCapStatus, sizeof (CAPSTATUS));
//更新捕獲窗的大小
SetWindowPos(ghWndCap, NULL, 0, 0, gCapStatus.uiImageWidth,
gCapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE);
if (nID == 0) { // 清除舊的狀態資訊
SetWindowText(ghWndCap, (LPSTR) gachAppName);
return (LRESULT) TRUE;
}
// 顯示狀態 ID 和狀態文本
wsprintf(gachBuffer, "Status# %d: %s", nID, lpStatusText);
SetWindowText(ghWndCap, (LPSTR)gachBuffer);
return (LRESULT) TRUE;
}
LRESULT CALLBACK ErrorCallbackProc(HWND hWnd, int nErrID,LPSTR lpErrorText)
{
if (!ghWndCap)
return FALSE;
if (nErrID == 0)
return TRUE;// 清除舊的錯誤
wsprintf(gachBuffer, "Error# %d", nErrID); //顯示錯誤標識和文本
MessageBox(hWnd, lpErrorText, gachBuffer,MB_OK | MB_ICONEXCLAMATION);
return (LRESULT) TRUE;
}
LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
{
if (!ghWndCap)
return FALSE;
//假設fp為一開啟的.dat檔案指標
fwrite(fp,lpVHdr->lpData,lpVHdr->dwBufferLength,1);
return (LRESULT) TRUE ;
}
值得注意的是:應在.cpp檔案中加入#include 一句,在Link設定中加入vfw32.lib。
上述的回呼函數FrameCallbackProc是將視頻資料直接從緩衝寫入檔案,也可利用memcpy函數將視頻資料直接拷貝到另一緩衝。同理,可定義VideoStreamCallbackProc。capSetCallbackOnVideoStream的使用比 capSetCallbackOnFrame稍微複雜一些。在捕獲過程中,當一個新的視頻緩衝可得時,系統就調用它所登記的回呼函數。在預設情況下,捕獲窗在捕獲過程中不允許其它應用程式繼續運行。為了取消這個限制,可以設定CAPTUREPARMS的成員fYield為TRUE或建立一個Yield回呼函數。為瞭解決潛在的重入(reentry)問題,可在YieldCallbackProc中用PeekMessage過濾掉一些訊息,例如滑鼠訊息。