在桌面视频会议、可视电话等多媒体应用中,获得数字视频是一个关键的前提。在Video for Windows(VFW) 出现之前,捕获数字视频是一项极其复杂的工作。Microsoft 的Visual C++自从4.0版就开始支持Video for Windows(简称VFW),这给视频捕获编程带来了很大的方便。关于多媒体应用开发,市面流行资料中介绍较多的是MCI(媒体控制接口),而本文着重介绍的是如何使用Visual C++提供的AVICap窗口类进行视频捕获以及其中涉及到的概念和关键问题。
一、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、定义全局变量:
HWND ghWndCap ; //捕获窗的句柄
CAPDRIVERCAPS gCapDriverCaps ; //视频驱动器的能力
CAPSTATUS gCapStatus ; //捕获窗的状态
2、处理WM_CREATE消息:
//创建捕获窗,其中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消息:
//取消所登记的三个回调函数
capSetCallbackOnStatus(ghWndCap, NULL);
capSetCallbackOnError(ghWndCap, NULL);
capSetCallbackOnFrame(ghWndCap, NULL);
capCaptureAbort(ghWndCap);//停止捕获
capDriverDisconnect(ghWndCap); //将捕获窗同驱动器断开
4、处理菜单项Preview:
capPreviewRate(ghWndCap, 66); // 设置Preview模式的显示速率
capPreview(ghWndCap, TRUE); //启动Preview模式
5、处理菜单项Overlay:
if(gCapDriverCaps.fHasOverlay) //检查驱动器是否有叠加能力
capOverlay(ghWndCap,TRUE); //启动Overlay模式
6、处理菜单项Exit:
SendMessage(hWnd,WM_CLOSE,wParam,lParam);
7、分别处理Setting下的三个菜单项,它们可分别控制视频源、视频格式及显示:
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文件:
char szCaptureFile[] = "MYCAP.AVI";
capFileSetCaptureFile( ghWndCap, szCaptureFile); //指定捕获文件名
capFileAlloc( ghWndCap, (1024L * 1024L * 5)); //为捕获文件分配存储空间
capCaptureSequence(ghWndCap); //开始捕获视频序列
9、处理Single Frame菜单项:
capGrabFrame(ghWndCap); //捕获单帧图像
10、定义三个回调函数:
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过滤掉一些消息,例如鼠标消息。
四、结束语
Visual C++提供的AVICap窗口类为捕获数字视频流及其相关操作提供了很大的方便,灵活编写其中的回调函数可满足实时视频传输的需要,例如应用程序可直接从缓冲中取得数字视频并对其进行压缩编码后实时地传到远端的计算机。笔者所从事的电话网上的可视电话系统就是采用AVICap进行视频捕获的,这种方法同样可用于其它多媒体会议系统中,如ISDN、局域网上的会议系统等。(南京邮电学院70#信箱刘 涛 郭 戈 杨玉森 210003 )