Window 下 VFW 視頻采集與顯示
引言
經過幾天的努力終于將VFW視頻采集與顯示功能完整實現了,不得不說網上對這方面完整的詳細講解文章是在太少了。所以就要本人來好好總結一下讓后來者不再像我一樣折騰好久。在本文中我將詳細講解VFW視頻采集過程的實現,以及采集后視頻的顯示方法。
VFW簡介
雖然這是篇技術博文,但是我覺得用一個東西,那么關于它的概述還是不能少,所以特從百度上copy了下VFW的概念描述,如果讀者不想看可以直接去觀看正文部分。
VFW(Video for Windows)是Microsoft推出的關于數字視頻的一個軟件開發包,VFW的核心是AVI文件標準。AVI(Audio Video Interleave)文件中的音、視頻數據幀交錯存放。圍繞AVI文件,VFW推出了一整套完整的視頻采集、壓縮、解壓縮、回放和編輯的應用程序接口(API)。它引進AVI的文件標準,該標準未規定如何對視頻進行捕獲、壓縮及播放,僅規定視頻和音頻該如何存儲在硬盤上,在AVI文件中交替存儲視頻幀和與之相匹配的音頻數據。VFW給程序員提供.VBX和AVICap窗口類的高級編程工具,使程序員能通過發送消息或設置屬性來捕獲、播放和編輯視頻剪輯。現在用戶不必專門安裝VFW了,Windows95本身包括了Video for Windows1.1,當用戶在安裝Windows時,安裝程序會自動地安裝配置視頻所需的組件,如設備驅動程序、視頻壓縮程序等。 由于AVI文件格式推出較早且在數字視頻技術中有廣泛的應用,所以VFW仍然有很大的實用價值,而且進一步發展的趨勢。
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為應用程序提供了一個簡單的、基于消息的接口,使之能訪問視頻和波形音頻硬件,并能在將視頻流捕獲到硬盤上的過程中進行控制
在VC++開發環境中調用VFW和使用其它開發包沒有什么不同,只是需要將VFW32.lib文件加入工程中,但在開放視頻捕捉與壓縮管理程序時需要其它軟件硬件設置。VFW為AVI文件提供了豐富的處理函數和宏定義,AVI文件的特點在于它是典型的數據流文件,它由視頻流、音頻流、文本流組成。所以對AVI文件的處理主要是處理文件流。
正文
關于VFW采集的過程我大致歸納如下幾個步驟:
一、capCreateCaptureWindow 創建視頻采集窗口,注意此處創建的窗口并不是MFC中的窗口。
//創建視頻采集窗口(注意此窗口與我們所說的顯示窗口不同),并設置預覽窗口//IDC_VIDEO_LOCAL 參數是采集窗口的ID,此處直接使用的是顯示窗口的id
m_CapWnd = capCreateCaptureWindow(TEXT("My Video Capture"), WS_CHILD | WS_VISIBLE, 0, 0, LocalRect.Width(),
LocalRect.Height(), LocalWnd->GetSafeHwnd(), IDC_VIDEO_LOCAL);
二、設置回調函數,在VFW中可以設置的回調函數有以下幾種,可以根據程序需要設置:
1、BOOL capSetCallbackOnCapControl(hwnd, fpProc ); 此宏可以設置用于精確控制采集的開始和結束的控制函數,此回調函數原型為:
LRESULT CALLBACK capControlCallback(HWND hWnd, int nState );
hWnd參數為第一步創建的采集窗口句柄, nState 當前Capture的狀態,可取值為CONTROLCALLBACK_PREROLL(等待Capture開始) 和CONTROLCALLBACK_CAPTURING(Capture正在采集),程序要控制Capture的開啟和關閉時通過對當前Capture狀態返回適當的值,當為CONTROLCALLBACK_PREROLL是返回TRUE則代表要開啟Capture的捕捉,返回FALSE代表中止Capture,當為CONTROLCALLBACK_CAPTURING時,返回TRUE表示要繼續采集,返回FALSE表示要停止采集。
2、BOOL capSetCallbackOnError(hwnd, fpProc );此宏用于設置當Capture采集過程中出錯的時候反饋給程序處理的回調函數,回調函數原型為:
LRESULT CALLBACK capErrorCallback( HWND hWnd, int nID, LPCSTR lpsz );
hWnd參數為第一步創建的采集窗口句柄,nID 為當前出錯的錯誤ID標識,lpsz 代表出錯原因的一個文本描述內容
3、BOOL capSetCallbackOnFrame(hwnd, fpProc ); 此宏用于設置當Capture采集過程中每采集到一幀圖像的時候,反饋給程序處理的回調函數,回調函數原型為:
LRESULT (CALLBACK* CAPVIDEOCALLBACK) (HWND hWnd, LPVIDEOHDR lpVHdr);
hWnd參數為第一步創建的采集窗口句柄,lpVHdr 為采集到的一幀數據,LPVIDEOHDR 結構體定義如下:
typedef struct videohdr_tag {
LPBYTE lpData; //指向采集到的數據buffer
DWORD dwBufferLength; //buffer的長度
DWORD dwBytesUsed; //buffer實際使用的Byte數
DWORD dwTimeCaptured; //從開始采集到當前幀采集時經過的毫秒數
DWORD dwUser; //用戶通過 capSetUserData 設定的自定義參數
DWORD dwFlags; //當前幀的標識,可取如下值
/*
VHDR_DONE Done bit
VHDR_PREPARED Set if this header has been prepared
VHDR_INQUEUE Reserved for driver
VHDR_KEYFRAME Key Frame
*/
DWORD_PTR dwReserved[4]; //保留給驅動使用的空間
} VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;
4、BOOL capSetCallbackOnStatus(hwnd, fpProc ); 此宏用于設置監控Capture狀態改變的回調函數,函數原型為:
LRESULT CALLBACK capStatusCallback( HWND hWnd, int nID, LPCSTR lpsz );
hWnd參數為第一步創建的采集窗口句柄,nID 定義的狀態消息值,關于當前更新的狀態的一個文本描述。
5、BOOL capSetCallbackOnVideoStream(hwnd, fpProc ); 此宏設置當Capture采集到一個Video buffer(其實也是一幀數據)數據后反饋給程序的功能函數,函數原型為:
LRESULT CALLBACK capVideoStreamCallback( HWND hWnd, LPVIDEOHDR lpVHdr );
hWnd參數為第一步創建的采集窗口句柄,lpVHdr 為采集到的一幀數據,具體意義可以參看第三個
6、BOOL capSetCallbackOnWaveStream(hwnd, fpProc );此宏設置當采集到一個Audio音頻數據buffer時,反饋給程序的函數,函數原型為:
LRESULT CALLBACK capWaveStreamCallback( HWND hWnd, LPWAVEHDR lpWHdr );
hWnd參數為第一步創建的采集窗口句柄,lpWHdr 為采集到Audio音頻數據,其結構體定義如下:
typedef struct {
LPSTR lpData; //指向采集的音頻數據buffer
DWORD dwBufferLength; //buffer長度
DWORD dwBytesRecorded; //采集到的數據Byte數
DWORD_PTR dwUser; //用戶通過capSetUserDate自定義的數據
DWORD dwFlags; //
DWORD dwLoops; //播放時長
struct wavehdr_tag * lpNext;
DWORD_PTR reserved; //保留字段
} WAVEHDR;
7、BOOL capSetCallbackOnYield(hwnd, fpProc ); 此宏用于設置一個回調函數當每采集一幀圖像時反饋給程序,回調函數原型為:
LRESULT CALLBACK capYieldCallback( HWND hWnd );
hWnd參數為第一步創建的采集窗口句柄
三、capGetDriverDescription 獲得當前可用的Capture設備驅動的版本信息。我們可以通過此函數枚舉當前系統中可用的驅動。方法如下:
BOOL VFWAPI capGetDriverDescription(
WORD wDriverIndex,//要獲取的設備驅動的索引值,取值范圍為0-9
LPSTR lpszName, //指向保存獲取到的設備名字buffer
INT cbName,//設備名字buffer的長度
LPSTR lpszVer,//指向保存獲取到的設備版本信息buffer
INT cbVer //設備版本信息buffer長度
);
我們可以通過循環枚舉索引值為 0-9 時函數的返回值,如果返回為真則此索引對應的設備存在,并可以獲得設備的描述信息。
四、capDriverConnect 連接指定索引值的設備驅動
五、配置Capture采集參數,可設置參數所需函數如下(注意要先連接后配置參數):
1、BOOL capCaptureSetSetup(hwnd, psCapParms, wSize ); 用于設置視頻流采集過程的配置參數。hwnd 采集窗口句柄, psCapParms 為配置結構體 CAPTUREPARMS 其結構體定義如下,wSize 為psCapParms結構體的大小:
typedef struct {
DWORD dwRequestMicroSecPerFrame;//請求的幀率,默認為66667,即每秒15幀。
BOOL fMakeUserHitOKToCapture; //如果為TRUE,將顯示一個對話框幫助用戶快速地進行捕捉設置,默認為false
UINT wPercentDropForError; //在捕捉過程中允許棄幀的最大百分比
BOOL fYield; //如果為TRUE,將產生一個后臺線程來進行視頻捕捉
DWORD dwIndexSize; //表示AVI文件最大的索引入口數
UINT wChunkGranularity; //以字節為單位表示AVI文件的大小
BOOL fUsingDOSMemory; //未使用
UINT wNumVideoRequested; //分配視頻緩沖區的最大數量
BOOL fCaptureAudio; //為TRUE,表示音頻被捕捉,默認值依賴于安裝的音頻設備
UINT wNumAudioRequested; //表示分配的音頻緩沖區的最大數量
UINT vKeyAbort; //表示終止捕捉的虛擬鍵
BOOL fAbortLeftMouse; //為TRUE,表示單擊鼠標左鍵停止捕捉
BOOL fAbortRightMouse; //為TRUE,表示單擊鼠標右鍵停止捕捉
BOOL fLimitEnabled; //為TRUE,表示設置捕捉時間限制
UINT wTimeLimit; //以秒為單位設置捕捉的超時時間
BOOL fMCIControl; //為TRUE,控制MCI(媒體設備接口)兼容的視頻源
BOOL fStepMCIDevice; //為TRUE,使用MCI設備使用步進幀進行捕捉,為FALSE,使用MCI設備進行時時捕捉,如果fMCIControl成員為FALSE,該成員被忽略
DWORD dwMCIStartTime; //以毫秒為單位標識MCI設備視頻捕捉序列的起始位置,如果fMCIControl成員為FALSE,該成員被忽略
DWORD dwMCIStopTime; //以毫秒為單位標識MCI設備視頻捕捉序列的停止位置,如果fMCIControl成員為FALSE,該成員被忽略
BOOL fStepCaptureAt2x; //為TRUE,捕捉的視頻幀使用兩個分辨率,它可以使用軟件在某個分辨率的基礎上改寫像素,將其該為高清晰度的圖像
UINT wStepCaptureAverageFrames; //在捕捉時每幀圖像使用的時間大小
DWORD dwAudioBufferSize; //音頻緩沖區大小
BOOL fDisableWriteCache;//未使用
UINT AVStreamMaster; //確定在寫入AVI文件時,音頻流是否控制時鐘
} CAPTUREPARMS;
2、BOOL capSetVideoFormat( hwnd, psVideoFormat, wSize ); 設置Video每幀圖像的格式,hwnd為采集窗口句柄,psVideoFormat為 BITMAPINFO 結構體,wSize為BITMAPINFO 結構體的大小。
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader; //位圖圖像格式
RGBQUAD bmiColors[1]; //調色板,
} BITMAPINFO;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //BITMAPINFOHEADER 結構體的大小
LONG biWidth; //圖像的寬度,按像素
LONG biHeight; //圖像的高度,按像素
WORD biPlanes; //設備的面數,必須設置為1
WORD biBitCount; //每個像素所包含的bit數
DWORD biCompression; //圖像的壓縮格式
DWORD biSizeImage; //圖像的大小
LONG biXPelsPerMeter; //圖像的水平分辨率,單位為每米的像素個數
LONG biYPelsPerMeter; //圖像的垂直分辨率,單位為每米的像素個數
DWORD biClrUsed; //圖像所用到的顏色數,為0則為biBitCount對應的數目
DWORD biClrImportant; //圖像中重要的顏色數,為0則所有顏色都重要
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
3、BOOL capSetAudioFormat( hwnd, psAudioFormat, wSize); 設置采集的音頻數據格式 hwnd為采集窗口句柄,psAudioFormat為 WAVEFORMATEX 或 PCMWAVEFORMAT 結構體,wSize為psAudioFormat 結構體的大小。
typedef struct {
WORD wFormatTag; //音頻數據格式
WORD nChannels; //音頻的聲道數,為1為單聲道,2為立體聲
DWORD nSamplesPerSec; //每秒的采樣頻率
DWORD nAvgBytesPerSec; //每秒的數據傳輸頻率
WORD nBlockAlign; //一個塊的大小,采樣bit的字節數乘以聲道數
WORD wBitsPerSample; //每個采樣數據的比特數
WORD cbSize; //一般為0
} WAVEFORMATEX;
typedef struct {
WORD wFormatTag; //音頻數據格式
WORD nChannels; //音頻的聲道數,為1為單聲道,2為立體聲
DWORD nSamplesPerSec; //每秒的采樣頻率
DWORD nAvgBytesPerSec; //每秒的數據傳輸頻率
WORD nBlockAlign; //一個塊的大小,采樣bit的字節數乘以聲道數
} WAVEFORMAT;
typedef struct {
WAVEFORMAT wf;
WORD wBitsPerSample; //每個采樣數據的比特數
} PCMWAVEFORMAT;
4、BOOL capSetScrollPos( hwnd, lpP );設置視頻幀的客戶區卷軸位置 hwnd 為采集窗口句柄,lpP 為卷軸位置的指針
上面4中參數設備函數,都有相應的參數獲取函數,在編寫程序時,我們可以先獲取設備原先設定的參數,然后在修改我們關心的參數在進行設置,相應的獲取函數只要將對應函數的Set換成Get就可以了。
六、預覽配置,在設置好預覽后我們運行程序就能在我們capCreateCaptureWindow 中指定的窗口中預覽到采集到視頻數據了。
1、BOOL capPreviewRate( hwnd, wMS ); 設置預覽時的采集頻率, hwnd為采集窗口句柄,wMS為設定的頻率
2、BOOL capPreviewScale( hwnd, f ); 設置預覽時圖像是否 可伸縮,即根據顯示窗口大小顯示,hwnd為采集窗口句柄,f 為BOOL值,為true代表圖像可伸縮
3、BOOL capPreview( hwnd, f );設置是否使用預覽模式,hwnd為采集窗口句柄,f為bool值,為true則使用預覽模式
七、一幀圖像的顯示。如果想要處理每一幀采集到的圖像,那么程序必須調用 capSetCallbackOnVideoStream或capSetCallbackOnFrame設置回調函數。在設置的回調函數中將圖像數據發送給圖像顯示的窗口,在這里要注意的是,我們在設置參數的時候在capSetVideoFormat函數中設置的圖像壓縮格式如果為BI_RGB那么采集的圖像就為RGB數據,RGB的位數為設定的biBitCount。在顯示窗口中進行如下操作:
1、用GetDC獲得顯示窗口的顯示設備句柄
CDC* pDC = ShowWnd->GetDC();
2、創建窗口顯示設備句柄的兼容句柄
CDC* pDC = ShowWnd->GetDC();
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
3、創建一個設備相關的bitmap
CBitmap bmp;
bmp.CreateCompatibleBitmap(pDC, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight);
CBitmap *pOldBmp = MemDC.SelectObject(&bmp);
4、將采集到的位圖數據發送給設備,然后將其刷在屏幕上
::SetDIBitsToDevice(MemDC.GetSafeHdc(),0, 0, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight, 0, 0, 0,
bmpInfo.bmiHeader.biHeight, VideoDate, &bmpInfo, DIB_RGB_COLORS);
pDC->BitBlt(0, 0, WndRect.Width()-6, WndRect.Height(), &MemDC, 0, 0, SRCCOPY);
八、拍照功能實現。其實所謂的拍照只是將采集到的一幀圖像數據寫入一個文件中,只是在圖像數據前還要加上一些圖像格式和參數的說明參數。具體實現如下:
1、設置圖像文件參數說明結構體 BITMAPFILEHEADER , 和 圖像格式信息結構體 BITMAPINFOHEADER
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //圖像的類型,必須為BM即 0x4d42即十進制的19778
DWORD bfSize; //圖像文件的大小 即 圖像數據大小 + 54(兩個參數說明結構體的大小)
WORD bfReserved1; //保留字段必須為0
WORD bfReserved2; //保留字段必須為0
DWORD bfOffBits; //從文件開始到 圖像數據的偏移
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //結構體的大小
LONG biWidth; //圖像的寬度
LONG biHeight; //圖像的高度
WORD biPlanes; //圖像目標設備的面數,必須為1
WORD biBitCount; //每個像素的bit數
DWORD biCompression; //圖像壓縮格式
DWORD biSizeImage; //圖像數據大小
LONG biXPelsPerMeter; //圖像的水平分辨率
LONG biYPelsPerMeter; //圖像的垂直分辨率
DWORD biClrUsed; //圖像所使用到的顏色數
DWORD biClrImportant; //圖像中重要的顏色數
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
注意這兩個結構體的參數一定要根據你采集圖像前設置的圖像格式來設置,或者直接用當時設置的數據。設置好這兩個結構體后就將他們寫入文件,然后將采集到的圖像數據也寫入文件就形成了一張照片了。
CFile filePic(PicPath_one, CFile::modeCreate|CFile::modeWrite);//存放報警圖片
filePic.Write(pBmpfilehd,14);
filePic.Write(&m_BmpInfo.bmiHeader, 40);
filePic.Write(m_pImageTmp->lpData, m_pImageTmp->dwBytesUsed);
九、采集結束,關閉設備。這個操作 可以通過capSetCallbackOnCapControl設置的回調函數實現,也可以直接調用 capCaptureStop、capCaptureAbort停止采集,然后再將原先設置的功能回調函數給關閉,即將函數指針傳遞NULL值。然后capDriverDisconnect斷開和設備的連接。
源程序下載
筆者自己編寫的一個測試源程序 http://pan.baidu.com/share/link?shareid=190628&uk=2735225556 中下載 VideoPlay–視頻語音采集完整版.rar
本文,筆者耗時幾個小時認真編寫,如果有什么錯誤,歡迎討論。如若轉載請標明出處 www.xzben.com
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成