基于VFW视频采集及数据处理
准备工作:建立基于对话框的程序,并添加控件及需要的成员变量;
1. 采用capCreateCaptureWindow函数创建视频采集窗口
m_CapWnd =capCreateCaptureWindow(_T("My Video Capture"), WS_CHILD | WS_VISIBLE,0, 0, LocalRect.Width(),LocalRect.Height(), LocalWnd->GetSafeHwnd(),IDC_VIDEO_LOCAL);
// m_CapWnd为添加的视频采集窗口控件(picture控件)的句柄;
// IDC_VIDEO_LOCAL采集窗口的标识,此处采用的是窗口的句柄;
2. 设置回调函数,VFW中主要有以下几种回调函数:
1)BOOL capSetCallbackOnCapControl(hwnd,fpProc );此宏可以精确的控制视频采集的开始和结束时间,其回调函数的原型如下:
LRESULT CALLBACK capControlCallback(HWNDhWnd, 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 );此宏用于设置当视频采集过程中出现错误的时候反馈给程序处理的回调函数;其回调函数的原型为:
LRESULT CALLBACKcapErrorCallback( HWND hWnd, int nID, LPCSTR lpsz );
hWnd参数为第一步创建的采集窗口句柄,nID 为当前出错的错误ID标识,lpsz 代表出错原因的一个文本描述内容。
3)BOOL capSetCallbackOnFrame(hwnd, fpProc); 此宏的作用是设置一个视频预览的回调函数,捕捉窗口会在预览图像在窗口显示之前调用此函数,可以对视频数据进行特定的处理,在视频采集的过程中不会调用此函数。其回调函数的原型是:
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; //当前帧的标识
DWORD_PTR dwReserved[4]; //保留给驱动使用的空间
} VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;
采集到的视频数据保存在lpData变量中,其数据的格式由采集设备支持的格式确定,也可以在初始化过程中进行设置;如果所设置的格式设备不支持,则仍然采用默认值。设备所支持的采集数据的保存格式可以通过调用函数capDlgVideoFormat(m_CapWnd);显示视频格式对话框进行查看。
4)BOOL capSetCallbackOnStatus(hwnd, fpProc );此宏用于设置状态回调函数,用于检测捕捉状态的变化。其函数原型为:
LRESULT CALLBACKcapStatusCallback(HWND hWnd, int nID, LPCSTR lpsz );
hWnd参数为第一步创建的采集窗口句柄,nID 定义的状态消息值,关于当前更新的状态的一个文本描述。
5)BOOL capSetCallbackOnVideoStream(hwnd,fpProc );此宏用于设置在视频采集过程中与capCaptureSequenceNoFile()函数配合使用,对采集到的数据进行特定的处理。其函数的原型如下:
LRESULT CALLBACK capVideoStreamCallback( HWND hWnd, LPVIDEOHDR lpVHdr );
hWnd参数为第一步创建的采集窗口句柄,lpVHdr 为采集到的一帧数据,具体意义可以参看第三个。
6)BOOL capSetCallbackOnWaveStream(hwnd,fpProc );此宏用于设置当采集到音频数据时的回调函数。其函数的原型如下:
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_PTRreserved; //保留字段
} WAVEHDR;
结构体各变量的意义可参考CSDN;
7)BOOL capSetCallbackOnYield(hwnd, fpProc );此宏用于设置每采集一帧视频的回调函数。其函数原型如下:
LRESULTCALLBACK capYieldCallback( HWND hWnd );
hWnd参数为第一步创建的采集窗口句柄。
3. 采用capGetDriverDescription获取当前连接设备驱动器的版本信息。函数原型如下:
BOOL VFWAPIcapGetDriverDescription(
WORD wDriverIndex,//要获取的设备驱动的索引值,取值范围为0-9
LPSTR lpszName, //指向保存获取到的设备名字buffer
INT cbName,//设备名字buffer的长度
LPSTR lpszVer,//指向保存获取到的设备版本信息buffer
INT cbVer //设备版本信息buffer长度
);
vfw最多支持9个视频采集设备,所以wDriverIndex参数的取值范围为0-9.
可以通过如下程序获取当前所有连接的视频采集设备:
char achDeviceVersion[80];//设备版本信息
char achDeviceAndVersion[160];//设备名及版本信息
int uIndex;
int DriverCount = 0;
for(uIndex = 0; uIndex < 9;uIndex++)
{
if(capGetDriverDescription(uIndex,(LPSTR)achDeviceAndVersion,sizeof(achDeviceAndVersion),(LPSTR)achDeviceVersion,sizeof(achDeviceVersion)))
{
Strcat(achDeviceAndVersion,”,”);
Strcat(achDeviceAndVersion,achDeviceVersion);
DriverCount++;
}
}// DriverCount中存储当前采集设备的个数;
4. 采用函数capDriverConnect来指定索引值的驱动设备;
连接到索引值为0的采集设备:capDriverConnect(m_hCapWnd,0)
5. 配置视频采集设备的参数(采集参数、视频格式参数、音频格式参数):
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;
其实现程序如下:
capCaptureGetSetup(m_hCapWnd,&CapParams,sizeof(CAPTUREPARAMS)),得到CAPTUREPARAMS的状态信息。
/*….*/对CAPTUREPARMS结构体的成员进行设置;
capCaptureSetSetup(m_hCapWnd,&CapParams,sizeof(CAPTUREPARAMS)),把新设好的状态信息写入结构体,只有此时新的状态才会生效。
2)采用函数BOOL capSetVideoFormat( hwnd, psVideoFormat, wSize );设置采集到的视频格式。hwnd为采集窗口句柄,psVideoFormat为 BITMAPINFO 结构体,wSize为BITMAPINFO 结构体的大小。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;
其实现程序如下:
DWORD dwSize = capGetVideoFormatSize(m_hCapWnd);
LPBITMAPINFO lpbi = (LPBITMAPINFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMMORY|HEAP_NO_SERIALIZE,dwSize);
//由于LPBITMAPINFO结构体的长度可变,所以应用程序中先需要获取结构体的大小,然后分配内存。
capGetVideoFormat(m_hCapWnd,lpbi,dwSize);
……//对视频格式的参数进行设置;
capSetVideoFormat(m_hCapWnd,lpbi,dwSize);
//通过驱动程序截取过来的帧都是以上设置的格式。
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;
6. 预览配置,配置好预览程序后,就可以在视频窗口中看见采集到的图像。
可以通过capDriverGetCaps获取当前设备支持的预览格式,如下:
capDriverGetCaps(m_hCapWnd,&capDrivers,sizeof(CAPDRIVERCAPS));
设置预览格如下:
if(capDrivers.fHasOverlay)//驱动支持重叠格式,选择重叠格式,效率速度比预览模式要高一些。
{
capOverlay(m_hCapWnd,TRUE);
}
else
{
capPreviewScale(m_hCapWnd,TRUE);
capPreviewRate(m_hCapWnd,20);
capPreview(m_hCapWnd,TRUE);
}
也可以直接设置为后面的这种预览方式。前一种方式直接从设备驱动器内存中显示到控件,比较节省空间,效率高。
7. 采集图像
几个图像保存的函数:capCaptureSingleFrame()、capGrabFrame()、capGrabFrameNoStop()。
capCaptureSingleFrame():提取单帧图像,实现过程是从视频驱动缓存提取出视频帧数据,然后存入内存缓存中,然后从内存缓存送至窗口显示。
capGrabFrame():工作方式与capCaptureSingleFrame()一致,唯一不同是此函数知错能改完后会禁止重叠模式和预览模式。
capGrabFrameNoStop():与capGrabFrame()不同的是它会开辟一个新的线程来完成capGrabFrame()函数的功能。在采集过程中可以进行其他操作。
capFileSaveDIB(m_hCapWnd,filename)可以把视频缓冲区中的内容保存成文件。
如果要想在回调函数capFrameCallback(HWND hWnd, LPVIDEOHDRlpVHdr)中的信息保存成图片并不容易,因为lpVHdr->lpData中仅存放的是视频的像素信息,必须给该信息加入BITMAPFILEHEADER和BITMAPINFO两个信息头,并进行相应的数据格式(取决于采集设备支持的数据格式和在参数设置过程中设置的数据格式,如果支持RGB,最好采用RGB格式,在此处就不用进行数据格式转换。)转换之后才能成为真正的图片信息。
例如:
用于获得信息头文件的各个变量值:
BITMAPINFO bihI;
DWORD dwSize;
dwsize =capGetVideoFormatSize(m_hCapWnd);
capGetVideoFormat(m_hCapWnd,&bihl, dwSize);
用来给文件头的各个变量赋值;
BITMAPFILEHEADER bfh;
bfh.bfType = 0X4d42;
bfh.bfSize =sizeof(BITMAPFILEHEADER) +bihl.bmiHeader.biSIze + bihl.bmiHeader.biSizeImage;
bfh.bfReserved1 = bfh.bfReserved2= 0;
bfh.bfOffBits =(DWORD)sizeof(BITMAPFILEHEADER) +bihl.bmiHeader.biSize;
如果需要数据的格式装换,需要根据对应的数据格式进行相应的转换。
8. 视频保存
相关函数:capCaptureSequence(),capCaptureSequenceNoFile()
capCaptureSequence()从进程缓存区把视频流存入一个AVI文件(由capFileSetCaptureFile指定)。
capCaptureSequenceNoFile()的作用是通过不断的截取视频使内存缓冲区满,来使capVideoStreamCallback不断地被调用,从而实现视频数据的处理。这个函数不会把视频流存入文件。
capFileSaveAs可以将一个视频文件另存为另外一个视频文件。
9. 显示图像
将采集到的图像实时显示在其他图像控件。首先根据保存的数据格式对原始采集到的数据进行转换,转化为RGB格式。并且根据RGB数据格式定义位图文件的信息头BITMAPINFO。
1)用GetDC函数获得显示窗口的显示设备句柄;
CDC* pDC = ShowWnd->GetDC();
2)创建窗口显示设备句柄的兼容句柄;
CDC MemDC;
MenDC.CreateCompatibleDC(pDC);
3)创建一个设备相关的位图
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,RGBBuff, &bmpInfo, DIB_RGB_COLORS);
// RGBBuff为格式转换后的rgb数据;
// bmpInfo为与rgb数据格式相匹配的位图文件信息头;
pDC->BitBlt(0,0, WndRect.Width(), WndRect.Height(), &MemDC, 0, 0, SRCCOPY);由于SetDIBitsToDevice函数不能自适应显示窗口的大小,可以将上述函数换为:StretchDIBits(MemDC.GetSafeHdc(),0, 0,WndRect.Width(), WndRect.Height(), 0, 0, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight,RGBBuff, &bmpInfo, DIB_RGB_COLORS,SRCCOPY);
10. 图像保存;
需要在回调函数中直接将采集到的原始数据保存为图片,除了需要进行数据的格式转换以外,还需要添加位图文件的文件头和信息头。
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;
注意这两个结构体的参数一定要根据你采集图像前设置的图像格式来设置,或者直接用当时设置的数据。设置好这两个结构体后就将他们写入文件,然后将采集到的图像数据也写入文件就形成了一张照片了。
CFilefilePic(PicPath_one,CFile::modeCreate|CFile::modeWrite);
filePic.Write(pBmpfilehd,14);
filePic.Write(&m_BmpInfo.bmiHeader, 40);
filePic.Write(RGBBuff, RGBBuff的大小);
11. 采集结束,关闭设备。
这个操作 可以通过capSetCallbackOnCapControl设置的回调函数实现,也可以直接调用 capCaptureStop、capCaptureAbort停止采集,然后再将原先设置的功能回调函数给关闭,即将函数指针传递NULL值。然后capDriverDisconnect断开和设备的连接。
本博客对DoubleLi的博客进行了总结和补充,原博客:http://www.cnblogs.com/lidabo/p/3701954.html
DoubleLi的博客上面有对应采集程序的连接,可以参考学习。
对于不同格式的视频数据进行转换参考博文:http://blog.csdn.net/u014260892/article/details/51887793
阑珊寻梦的博客:http://www.cnblogs.com/ltfbk/p/4591971.html