VFW视频采集
一、vfw概述
vfw是微软公司1992年推出的关于数字视频的一个软件包,它能使应用程序通过数字化设备从传统的模拟视频源得到数字化的视频剪辑。vfw的视频主要思想是在播放的过程中不需要专用硬件,为了解决数据量大的问题,需要对数据进行压缩。它引进一种叫AVI的文件格式,在该标准中并未规定对视频进行捕获、压缩及播放,仅规定视频和音频该如何存储在硬盘上,以及在AVI文件中交替存储视频帧和与之相匹配的音频数据。VFW使程序员可以通过发送消息或者设置属性来捕获、播放和编辑视频剪辑。在Windows 9x系统中,当用户在安装VFW时,安装程序会自动安装配置视频所需要的组件,如设备驱动器、视频压缩程序等。
VFW主要由以下6各模块组成:
AVICAP.DLL:包含执行视频捕获的函数,它给AVI文件的I/O处理和视频、音频设备驱动程序提供一个高级接口;
MSVIDEO.DLL:包含一套特殊的DrawDib函数,用来处理屏幕上的视频操作;
MCIAVI.DLL:包括对VFW的MCI命令解释器的驱动程序;
AVIFILE.DLL:包含由标准多媒体I/O函数提供的更高的命令,用来访问.AVI文件;
压缩管理器(ICM):用于管理视频压缩/解压缩的编译译码器(Codec);
音频压缩管理器ACM:提供与ICM相似的服务,适用于波形音频;
二、开发步骤
AVICap窗口类支持实时的视频流捕获和单帧捕获,并提供对视频源的控制。虽然MCI也提供数字视频服务,为视频叠加提供了Overlay命令集,但这些命令主要是基于文件的操作,他们不能满足实时地从视频缓存中读取数据的要求,对于使用没有视频叠加能力的捕获卡的PC机来说,用MCI提供的命令集是无法捕获视频流的。而AVICap窗口类在捕获视频方面具有一定的优势,它能直接访问视频缓冲区,不需要生成中间文件,实时性很强,效率很高。而且可以讲数字视频捕获到一个文件中。
1. 创建“捕获窗”
在进行视频捕获之前需要先创建一个“捕获窗”,并以它为基础进行所有的捕获及参数设置操作。“捕获窗”由AVICap窗口类的“CapCreateCaptureWindow”函数来创建,其窗口风格一般为WS_CHILD和WS_VISIBLE。
“捕获窗”类似于一个标准空间,具有以下功能:
将视频流和音频流捕获到一个AVI文件中;
动态的同视频和音频输入器连接或者断开;
以Overlay或者Preview的模式对输入的视频进行实时显示;
在捕获时,可以指定所用的文件名并能将捕获文件的内容拷贝到另一个文件;
设置捕获速率;
显示控制视频源、视频格式、压缩格式对话框;
创建、保存或者载入调色板;
将图像和相关的调色板拷贝到剪切板;
将捕获的单帧图像保存为DIB格式的文件。
2. 将捕获窗口和驱动程序进行关联
单独定义的捕获窗是不能工作的,需要将其与一个设备相关联,这样才能取得视频信号。用函数CapDriverConnect可使一个捕获窗和一个设备的驱动程序相关联。
3. 设置视频设备的属性
通过设置CAPTUREPARAMS结构体变量的各个成员变量,可以控制设备的采样频率、中断采样按键、状态行为等等。设置好CAPTUREPARAMS结构变量之后,可以用函数capCaptureSetSetup设置使之生效。之后还可以用CapPreviewScale、CapPreviewRate来设置预览比例与帧数,也可以使用设备的默认值。
4. 打开预览
利用函数CapOverlay选择是否采用叠加模式预览,这样占用系统资源小,并且视频显示的速度快。然后用CapPreview启动预览功能,这时就可以在屏幕上看见摄像机采集到的图片了。
通过以上四步就可以建立一个基本的视频捕获程序。但是如果想自己处理从设备捕获到的视频数据,则需要使用捕获窗回调函数来处理,比如一帧一帧地获得视频数据或者以流动的方式获得视频数据等等。
三、vfw视频采集中的四个结构体
1)CAPSTATUS:定义捕获窗口当前的状态;
uiImageWidth :图像宽度;
uiImageHeight :图像高度;
fLiveWindow:活动窗口标记,如果窗口正在以预览的方式展示图片,则该值为真;
fOverLayWindow:叠加窗口标志位,如果正在使用叠加窗口,则改位为真;
fScale:缩放标志位,如果在预览的过程中,输入视频被缩放到客户区的大小,则该值为真。当使用硬件叠加时,改位无效。
ptScall:被展示在窗口客户区左上角的那个像素点的x、y坐标的偏移量。
fUsingDefaultPalette:默认调色板标志位,如果捕获窗口正在使用当前默认的调色板,改值为真。
fAudioHardware:音频硬件标志位,如果系统安装了音频硬件,该值为真。
fCapFileExists:捕获文件标志位,如果一个捕获文件已经被标记,该值位真。
dwCurrentVideoFrame:当前或者最近流捕获过程中,所处理的帧的数目,包括丢弃的帧数。
dwCurrentVideoFrameDropped:当前流捕获过程中丢弃的帧数。
dwCurrentTimeElapsedMS:从当前流捕获开始计算,程序所用的时间,以毫秒为单位。
hPalCurrent:当前剪切板的句柄。
fCapturingNow:捕获标识位,当捕获是正在进行时,该位为真。
dwReturn:错误返回值,当应用程序不支持错误回调函数时可以用改位。
wNumVideoAllocated:被分配的视频缓存数目。
wNumAudioAllocated:被分配的音频缓存数目。
2)CAPDRIVERCAPS:定义捕获驱动器的能力,如有误视频叠加能力、有无控制视频源、视频格式的对话框等。
wDeviceIndex:捕获驱动器的索引值,改值可以在0-9变化。
fHasOverly:视频叠加标志位,如果视频支持视频叠加,该值为真。
fHasDlgVideoSource:视频资源对话框标志位,如果设备支持视频选择、控制对话框,该值为真。
fHasDlgVideoFormat:视频格式对话框标志位,如果设备支持对视频格式对话框的选择,该值为真。
FHasDlgVideoDisplay:视频展示对话框标志位,如果设备支持对视频捕获缓存区的重新播放,该位为真。
fCaptureInitialized:捕获安装标志位,如果捕获驱动器已经连接成功,该值为真。
fDriverSuppliesPalette:驱动器调色板标志位,如果驱动器能创建调色板,则该值为真。
3)CAPTUREPARAMS:包含控制视频流捕获过程中的参数,如捕获帧频、指定键盘或者鼠标键以终止捕获、捕获时间限制等等。
dwRequestMicroSecPerFrame:期望的帧播放率,以毫秒为单位,默认为66667,相当于15帧每秒。
fMakeUseHitOKToCapture:开始捕获标志位,如果为真,则在开始捕获前要产生一个询问对话框,默认值为假。
wPercentDropForError:所允许的最大丢帧百分比,可以从0到100变化,默认值10。
fYield:;另起线程标志位,如果为真,则程序重新启动一个线程用于视频流的捕获,默认值是假。但是如果设为真,则需要在程序中处理一些潜在的操作,因为当前视频捕获时,并没有屏蔽其他操作。
dwIndexSize:在AVI文件中所允许的最大数目的索引项。
wChunkGraunlarity:AVI文件的逻辑尺寸,以字节为单位。如果值是0,则说明该尺寸渐增,在win32程序中无用。
wNumVideoRequested:所允许分配的最大视频缓存。
fCaptureAudio:音频标志位,如果音频流正在捕获,则该值为真。
wNumAudioRequested:最大数量的音频缓存,默认值为10。
vKeyAbort:终止流捕获的虚拟键盘码,默认值为VK_ESCAPE。
fAbortLeftMouse:终止鼠标左键标志位,如果该值为真,则在流捕获过程中如果点击鼠标左键则该捕获终止,默认值为真。
fAbortRightMouse:终止鼠标右键标志位。
fLimitEnabled:捕获操作时间限制,如果值为真,则时间到了以后捕获结束,默认值为假。
wTimeLimit:具体终止时间,只有fLimitEnabled为真时,该位有效。
fMCIControl:MCI设备标志位。
wStepCaptureAverageFrames:当基于平均采样来创建图像帧时,帧的采样时间,典型值为5。
dwAudioBufferSize:音频缓存的尺寸,如果默认值为0,缓存尺寸是最大0.5秒,或者10k字节。
AVStreamMaster:音视频同步标志。
4)VIDEOHAR:定义视频数据块的头信息,在编写回调函数时常用到其数据成员lpData(指向数据缓存的指针)和dwBufferLength(数据缓存的大小)。
四、三种结构体的应用
capDriverGetCaps:得到驱动器的状态信息。
capDriverGetCaps(m_hCapWnd,&capDrivers,sizeof(CAPDRIVERCAPS));
capCaptureGetSetup(m_hCapWnd,&CapParams,sizeof(CAPTUREPARAMS)),得到CAPTUREPARAMS的状态信息。
capCaptureSetSetup(m_hCapWnd,&CapParams,sizeof(CAPTUREPARAMS)),把新设好的状态信息写入结构体,只有此时新的状态才会生效。
capGetStatus:得到CAPSTATUS的信息。
capGetStatus(hWnd,&CapStatus,sizeof(CAPSTATUS));
五、视频窗口的创建
首先创建一个父窗口句柄,该句柄一定要设置为类的成员变量,因为采集窗口的创建程序要用到该变量,如果设置为局部变量则在此函数外要销毁,那么采集窗口就找不到父窗口了。
m_hCapWnd = capCreateCaptureWindow(_T(“视频捕捉”),WS_CHILD | WS_VISIBLE | WS_SYSMENU |WS_CAPTION,150,150,375,270,hwndMain,0);
用于创建捕获窗口,注意句柄m_hCapWnd最好为全局变量,因为后面的回调函数会用到它,而回调函数只能是全局函数。注意WS_SYSMENU可以让捕获窗口带有关闭按钮,状态只能在捕获窗口中加入,而父窗口的类型将不能反映出来。
定义全局变量:全局变量不能再头文件中定义,因为头文件被多个源程序包含时,容易引起错误。全局变量可以在源程序的任意位置定义,只要在源程序的开始声明,就能在任意位置使用。
1)可以定义在一个单独的H文件中,但只能有一个类包含它,否则会报错。
2)在用到此变量的程序中定义此变量。
3)如果其他程序也要用到此变量,只是用之前,用extern声明。
定义在类外面的函数均是全局函数。全局函数定义在一个cpp中,在h中进行函数原型的声明!其他文件要用到此全局函数,只需在使用前extern声明一下。全局函数不用在头文件中声明,其他文件要用到此全局函数,只需在使用前extern声明一下。
六、与硬件驱动程序相连
通过函数capGetDriverDescription可以得到本地机器多连接的所有的视频读入设备的信息。如下:
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确定捕获设备的个数,最大值为9。
确定了可用的视频捕获设备以后,就可利用capDriverConnect(m_hCapWnd,0)来连接0号视频设备,0代表具体的视频设备的驱动接口。
capGetDriverDescription优先获取热插拔视频设备的驱动信息,比如USB驱动设备。
可以采用capGetVideoFormatSize函数来实现获取和设置视频的格式:
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);
//通过驱动程序截取过来的帧都是以上设置的格式。
通过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);
}
七、回调函数
回到函数是迄今为止最有用的编程机制之一,在windows中,回调函数是窗口过程、钩子过程、异步过程调用所必需的,在整个过程中自始至终的使用回调方法。人们可以注册回调方法以获得加载/卸载通知,未处理异常通知,数据库/窗口状态修改通知,文件系统修改通知,菜单选项,完成的异步操作通知等等。在VFW中有几条宏函数,如用于设置在发生某事件后能做出反应的回调函数的宏函数,它和中断服务机制很相似,满足条件时会自动进入相应的回调函数中,该回调函数需要实现哪些功能,由开发者借助其参数自行编程序来实现。利用VFW获取实时视频数据通常可以运用视频处理的回调机制获得实时数据缓冲区的首地址和长度并对图像数据进行处理,同时也可以进行视频数据的直接传输。
回调函数一定是公有函数,不可以位任何成员函数。在MFC下要把回调函数定义为公有函数,就是在.cpp文件中直接定义函数体,且不可定义为某个类的成员函数。在打算调用回调函数的程序中只需要声明回调函数体,以及注册该回调函数即可。
声明及注册回调函数:
LRESULT PASCAL FrameCallbackProc(HWND,LPVIDEOHDR);
capSetCallbackOnFrame(m_hCapWnd,FrameCallbackProc);
定义回调函数:
LRESULT CALLBACK FrameCallbackProc(HWNDhwnd,LPVIDEOHDR lpVHdr)
//LPVHdr指向数据结构VIDEOHEADERA,这里面是每一帧画面的数据,RGB格式,无论是否设置了压缩格式,在此函数中得到的始终是未经过压缩的原始bmp数据,所以加上bmp文件头写入文件即可。
LpVHdr->lpData中存储的是位图像素信息,必须加入头文件和像素信息后才能组成一组图像
{
if(….)
{return FALSE;}
else
return(LRESULT)TRUE;
}注意回调函数一定要有一个返回值。
回调函数是vfw里面的重要一环,很多重要的功能都要用回调函数实现。
状态回调:BOOL capSetCallbackOnStatus(hwnd,fpProc);
设置状态回调函数:LRESULT PASCAL StatusCallbackProc(HWNDhwnd,int nID,LPSTR lpStatusText);
//hwnd:窗口句柄;
//nID:状态码,一个状态码对应一个特定的状态改变。
//lpStatusText:与状态相关的文本信息。
错误回调:BOOL capSetCallbackOnError(hwnd, fpProc);
设置错误回调函数:LRESULT CALLBACK capErrorCallback(HWNDhWnd, int nID, LPCSTR lpsz);
参数的意义与上面相同。
视频流回调和帧回调(也是视频截取中最重要的部分):
帧回调:BOOL capSetCallbackOnFrame(hwnd, fpProc);
设置帧回调函数:LRESULT CALLBACK capFrameCallback(HWND hWnd,LPVIDEOHDR lpVHdr);
//hWnd:截取窗口的句柄值;
//lpVHdr:只关心成员lpData和dwBufferLength。lpVHdr->lpData是一个指针,指向进程空间内的视频缓冲区;lpVHdr->dwBufferLength是这个缓冲区的长度,但实际的长度需要通过strlen(lpVHdr->lpData)获取,lpVHdr->lpData指向的数据仅仅使图像的实际数据,不包括BITMAPINFO结构。如果需要的话,还需要获取当前驱动截取的视频格式capGetVideoFormat()来得到BITMAPINFO结构,然后把BITMAPINFO和lpVHdr->lpData数据组合起来成为一幅位图经过压缩后发送。
capFrameCallback函数只能在预览模式下,在视频从缓存区传送到窗口显示之前被调用。有两个条件:1视频帧要从内存缓存区出来(不是直接从驱动缓存区出来。在重叠模式下,视频不经过进程空间内的缓存区,而是直接由驱动缓存直接送到窗口显示,提高了效率。)。2帧要被送往窗口显示。
几个视频截取的函数:capCaptureSingleFrame()、capGrabFrame()、capGrabFrameNoStop()。
capCaptureSingleFrame():提取单帧图像,实现过程是从视频驱动缓存提取出视频帧数据,然后存入内存缓存中,然后从内存缓存送至窗口显示。
capGrabFrame():工作方式与capCaptureSingleFrame()一致,唯一不同是此函数知错能改完后会禁止重叠模式和预览模式。
capGrabFrameNoStop():与capGrabFrame()不同的是它会开辟一个新的线程来完成capGrabFrame()函数的功能。在采集过程中可以进行其他操作。
视频流回调:BOOL capSetCallbackOnVideoStream(hwnd, fpProc);
设置视频流回调函数:LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr);
此回调函数在进程视频缓存区可用(应该是满的时候)的时候才会被调用。
其他相关函数:capCaptureSequence(),capCaptureSequenceNoFile()
capCaptureSequence()从进程缓存区把视频流存入一个AVI文件(由capFileSetCaptureFile指定)。
capCaptureSequenceNoFile()的作用是通过不断的截取视频使内存缓冲区满,来使capVideoStreamCallback不断地被调用。这个函数不会把视频流存入文件。
八、视频采集保存中的线程问题
利用函数capCaptureSequence(m_hCapWnd)可将采集到的视频流保存成图像,系统将在C盘上默认一个捕获文件c:\NEWFILE.AVI,也可以通过capFileSetCaptureFile(m_hCapWnd, filename)来自定义捕获文件。在捕获的过程中,由于屏蔽了其他操作使得捕获过程有点卡,此时可采用两种方法解决;1自己另起一个线程采集,2把CAPTUREPARAMS中的fYield设为TRUE。由于另起了一个线程进行图像采集,所以必须处理一些键盘操作,如fAbortLeftMouse为真时,当点击鼠标左键时,采集就会停止。
九、将捕获的帧保存为图片
几个图像保存的函数:capCaptureSingleFrame()、capGrabFrame()、capGrabFrameNoStop()。
capCaptureSingleFrame():提取单帧图像,实现过程是从视频驱动缓存提取出视频帧数据,然后存入内存缓存中,然后从内存缓存送至窗口显示。
capGrabFrame():工作方式与capCaptureSingleFrame()一致,唯一不同是此函数知错能改完后会禁止重叠模式和预览模式。
capGrabFrameNoStop():与capGrabFrame()不同的是它会开辟一个新的线程来完成capGrabFrame()函数的功能。在采集过程中可以进行其他操作。
capFileSaveDIB(m_hCapWnd, filename)可以把视频缓冲区中的内容保存成文件。
如果要想在回调函数capFrameCallback(HWND hWnd, LPVIDEOHDR lpVHdr)中的信息保存成图片并不容易,因为lpVHdr->lpData中仅存放的是视频的像素信息,必须给该信息加入BITMAPFILEHEADER和BITMAPINFO两个信息头之后才能成为真正的图片信息。
例如:
用于获得信息头文件的各个变量值:
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;
利用vfw进行视频采集开发的具体流程可在msdn中搜索:Video Capture下的Using Video Capture。如下图所示: