本文旨在剖析开发基于P2P技术开发视频会议软件相关主要技术,并给出一个简单的例子。
一、 引言
我相信多数人听说过微软的NetMeeting,甚至有人直接使用过;而如今,众多的网虫沉迷于视频聊天。这类软件是怎样开发出来的呢?本文中,让我们来共同剖析开发基于P2P技术开发视频会议软件相关的主要技术,并给出一个简明的例子。本示例应用程序允许LAN/Intranet上的任何两个人举行视频会议。
凭直觉我们就会知道,开发这一类软件所涉及的主要问题,就是视频帧的大尺寸将极大地影响数据的传输质量。因而,这类软件的性能也主要依赖于视频帧编码和解码的质量。为此,在本例中,我们选用的是较快速的H.263编码器库,该库具有相当好的压缩比率,从而有效地克服了我们在图像传输中的速度矛盾。
请注意,有兴趣的读者可稍微修改本文中的示例程序以应用于因特网环境中。
二、 音频的录制与播放问题
这一部分的开发相对简单。其一,这种功能的API从Windows
//创建并启动录音线程
record=new
record->CreateThread();
//创建并启动播放线程
play=new
play->CreateThread();
//开始录制
record->PostThreadMessage(WM_RECORDSOUND_STARTRECORDING,0,0);
//开始播放
play->PostThreadMessage(WM_PLAYSOUND_STARTPLAYING,0,0);
//在音频录制期间,我们可以在RecordSound类的OnSoundData
//回调函数中使用这些数据。在此,你可以放置你要发送到远程宿主的数据……
//播放接收自远程宿主的音频数据
play->PostThreadMessage(WM_PLAYSOUND_PLAYBLOCK,size,(LPARAM)
//停止录制
record->PostThreadMessage(WM_RECORDSOUND_STOPRECORDING,0,0);
//停止播放
play->PostThreadMessage(WM_PLAYSOUND_STOPPLAYING,0,0);
//最后,停止录音线程
record->PostThreadMessage(WM_RECORDSOUND_ENDTHREAD,0,0);
//停止播放线程
play->PostThreadMessage(WM_PLAYSOUND_ENDTHREAD,0,0);
上面已经加了注释,使用方法一目了然。
三、 视频捕获的问题
当前,在Windows平台下开发视频应用一般采用两种方案。一种是基于视频采集卡所附带的二次软件开发包SDK进行。此方式的优点:帮助资料齐全,直接套用现成的API,易于上手;但缺点也是明显的:硬件依赖性强,缺乏应有的灵活性,因此,不能充分满足开发通用的视频应用的需要。
另一种方案是基于微软公司的VFW(Video
VFW以消息驱动方式实现对视频设备进行访问,便于开发者控制设备数据流的工作过程。简言之,这个框架主要包括VICAP.DLL、MSVIDEO.DLL、MCIAVI.DRV、AVIFILE.DLL、ICM、ACM等多个动态连接库,这些组件协同合作,共同完成视频的捕获、视频压缩及播放功能。有关这些模块的具体介绍见MSDN,在此略过。
(一)视频捕获
视频数据的实时采集,主要通过AVICAP模块中的消息、宏函数、结构以及回调函数来完成。视频捕获的大致过程如下:
(1)建立捕获窗口
利用函数capCreateCaptureWindow()建立视频捕获窗口,它是所有捕获工作及设置的基础。其主要功能包括:①动态地同视频和音频输入器连接或断开;②设置视频捕获速率;③提供视频源、视频格式以及是否采用视频压缩的对话框;④设置视频采集的显示模式为Overlay或为Preview;⑤实时获取每一帧视频数据;⑥将一视频流和音频流捕获并保存到一个AVI文件中;⑦捕获某一帧数字视频数据,并将单帧图像以DIB格式保存;⑧指定捕获数据的文件名,并能将捕获的内容拷贝到另一文件。
(2)登记回调函数
登记回调函数用来实现用户的一些特殊需要。在以一些实时监控系统或视频会议系统中,需要将数据流在写入磁盘以前就必须加以处理,达到实时功效。应用程序可用捕获窗来登记回调函数,以便及时处理以下情况:捕获窗状态改变、出错、使用视频或音频缓存、放弃控制权等,相应的回调函数分别为capStatusCallback(),capErrorCallback(),capVideoStreamCallback(),capWaveStreamCallback(),capYieldCallback()。
(3)获取捕获窗口的缺省设置
通过宏capCaptureGetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。
(4)设置捕获窗口的相关参数
通过宏capCaptureSetSetup(hWndCap,&m_Parms,sizeof(m_Parms))来完成。
(5)连接捕获窗口与视频捕获卡
通过宏capDriveConnect(hWndCap,0)来完成。
(6)获取采集设备的功能和状态
通过宏capDriverGetCaps(hWndCap,&m_CapDrvCap,sizeof(CAPDRIVERCAPS))来获取视频设备的能力,通过宏capGetStatus(hWndCap,&m_CapStatus,sizeof(m_CapStatus))来获取视频设备的状态。
(7)设置捕获窗口显示模式
视频显示有Overlay(叠加)和Preview(预览)两种模式。在叠加模式下,捕获视频数据布展系统资源,显示速度快,视频采集格式为YUV格式,可通过capOverlay(hWndCap,TRUE)来设置;预览模式下要占用系统资源,视频由系统调用GDI函数在捕获窗显示,显示速度慢,它支持RGB视频格式。
(8)捕获图像到缓存或文件并作相应处理
若要对采集数据进行实时处理,则应利用回调机制,由capSetCallbackOnFrame(hWndCap,FrameCallbackProc)完成单帧视频采集;由capSetCallbackOnVideoStr
(9)终止视频捕获断开与视频采集设备的连接
调用capCatureStop(hWndCap)停止采集,调用capDriverDisconnect(hWndCap),断开视频窗口与捕获驱动程序的连接。
由于上面这些API密切相关,所以为了使用方便,我们干脆把它们打包到一个视频捕获类VideoCapture中。
下面的代码片断展示了这个类的使用思路:
//创建视频捕获类的实例
vidcap=new
//当帧捕获完成时,下面这一句将用于调用主对话框类的显示函数
vidcap->SetDialog(this);
//下一行完成初始化工作:连接到驱动程序;设置使用的视频格式等。
//如果成功地连接到视频捕获设备返回TRUE。
vidcap->
//如果连接成功,那么,我们就可以得到与视频格式相关的BITMAPINFO
//结构。后面将用之显示捕获的帧
this->m_bmpinfo=&vidcap->m_bmpinfo;
//现在,你可以正式开始视频捕获了……
vidcap->StartCapture();
//一旦捕获开始,捕获的帧将到达回调函数—VideoCapture类的OnCaptureVideo函数。
//在此回调函数中,你可以调用显示函数实现帧显示(见下一节)
//停止捕获
vidcap->StopCapture();
//成功捕获后,释放视频捕获类
vidcap->Destroy();
【注意】为了顺利编译和链接,你需要在类实现文件(VideoCapture.cpp)的前面加上如下语句:
#pragma
#pragma
(二)显示捕获的视频帧
对于显示捕获的视频帧方面(也就是显示图像的问题),显然存在多种方案。例如,我们可以使用SetDIBitsToDevice()方法实现直接显示捕获的视频帧。但是,这种方案速度非常慢,因为它是基于图形设备接口(GDI)的函数。相比之下,更好一些的方法是使用DrawDib
下面的代码片断展示了如何使用DrawDib函数显示捕获的视频帧:
//初始化DIB以便绘制
HDRAWDIB
//然后,使用适当的参数调用这个函数……
::DrawDibBegin(hdib,...);
//现在,已经作好准备—可以调用这个函数进行帧显示了
::DrawDibDraw(hdib,...);
//最后,结束帧绘制
::DrawDibEnd(hdib);
::DrawDibClose(hdib);
其实,上面代码非常类似普通位图绘制过程。
四、 选择适当的编码/解码库
在本文中,我们选用Roalt
(一) 使用编码器代码示例
//初始化压缩器
CParam
cparams.format
InitH263Encoder(&cparams);
//如果你需要从RGB24转换到YUV420格式,那么应该调用下面的函数
InitLookupTable();
//创建回调函数
//OwnWriteFunction是编码期间返回编码数据时调用的全局函数
WriteByteFunction
//压缩数据必须使用YUV420格式
//在压缩之前调用下面这个方法
ConvertRGB2YUV(IMAGE_WIDTH,IMAGE_HEIGHT,data,yuv);
//压缩帧……
cparams.format=CPARAM_QCIF;
cparams.inter
cparams.Q_intra
cparams.data=yuv;
CompressFrame(&cparams,
//你可以从开始时你已经注册的回调函数中取得压缩的数据
//最后,终止编码器
//
(二) 解码器编程
注意,原始的H.263编码器库以C方式进行编码,而且提供了其它更多的细节实现。在本文中,我们以C++重新进行了改写。
下面是解码器的使用示例代码框架:
//初始化解码器
InitH263Decoder();
//解压帧……
//rgbdata必须足够大以便存储输出数据;
//解码器以YUV420格式生成图像数据;
//解码之后,把它再转换成RGB24格式……
DecompressFrame(data,size,rgbdata,buffersize);
//最后一步,终止解码器
ExitH263Decoder();
五、 运行应用程序
为了试验本文示例应用程序,你应该把可执行文件复制到一个LAN中的两台不同的机器上;然后,分别运行之。从一台机器上选择“连接”菜单项,并在弹出对话框内输入另一台机器的名字或IP地址,最后点击“连接”按钮。此时,在另一台机器上应该弹出一个“接受/拒绝”的对话框窗口,点击“接受”按钮。之后,在第一台机器上将显示通知对话框。按“OK”即可开始你的视频会议(聊天……)了。
六、 小结
我想通过本文向读者强调如下问题:Windows平台下的音频及视频开发并非那么复杂高深;有了本文的基础,你也完全可以据需要开发出自己的视频会议、实时监控系统、视频聊天等软件;另外,本文介绍的技术也可经修改并应用于因特网平台上。
同时,我们还看到微软的数字视频处理软件开发包Video