引言
经过几天的努力终于将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 结构体定义如下:
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音频数据,其结构体定义如下:
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设备驱动的版本信息。我们可以通过此函数枚举当前系统中可用的驱动。方法如下:
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结构体的大小:
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 结构体的大小。
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 结构体的大小。
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 MemDC;
MemDC.CreateCompatibleDC(pDC);
3、创建一个设备相关的bitmap
bmp.CreateCompatibleBitmap(pDC, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight);
CBitmap *pOldBmp = MemDC.SelectObject(&bmp);
4、将采集到的位图数据发送给设备,然后将其刷在屏幕上
bmpInfo.bmiHeader.biHeight, VideoDate, &bmpInfo, DIB_RGB_COLORS);
pDC->BitBlt(0, 0, WndRect.Width()-6, WndRect.Height(), &MemDC, 0, 0, SRCCOPY);
八、拍照功能实现。其实所谓的拍照只是将采集到的一帧图像数据写入一个文件中,只是在图像数据前还要加上一些图像格式和参数的说明参数。具体实现如下:
1、设置图像文件参数说明结构体 BITMAPFILEHEADER , 和 图像格式信息结构体 BITMAPINFOHEADER
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;
注意这两个结构体的参数一定要根据你采集图像前设置的图像格式来设置,或者直接用当时设置的数据。设置好这两个结构体后就将他们写入文件,然后将采集到的图像数据也写入文件就形成了一张照片了。
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