登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

面包会有的

... ...

 
 
 

日志

 
 

探讨基于P2P技术开发视频会议软件  

2008-11-21 21:37:30|  分类: VC++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

本文旨在剖析开发基于P2P技术开发视频会议软件相关主要技术,并给出一个简单的例子。

一、 引言

我相信多数人听说过微软的NetMeeting,甚至有人直接使用过;而如今,众多的网虫沉迷于视频聊天。这类软件是怎样开发出来的呢?本文中,让我们来共同剖析开发基于P2P技术开发视频会议软件相关的主要技术,并给出一个简明的例子。本示例应用程序允许LAN/Intranet上的任何两个人举行视频会议。

凭直觉我们就会知道,开发这一类软件所涉及的主要问题,就是视频帧的大尺寸将极大地影响数据的传输质量。因而,这类软件的性能也主要依赖于视频帧编码和解码的质量。为此,在本例中,我们选用的是较快速的H.263编码器库,该库具有相当好的压缩比率,从而有效地克服了我们在图像传输中的速度矛盾。

请注意,有兴趣的读者可稍微修改本文中的示例程序以应用于因特网环境中。

二、 音频的录制与播放问题

这一部分的开发相对简单。其一,这种功能的API从Windows 3.1开始就已经提供(winmm.lib+mmsystem.h);其二,如今借助于方便的因特网,我们完全可以搜到现成的包装类。在本文中,我们直接借用了提供了两个现成的RecordSound与PlaySound类。这两个类都派生于CWinThread类,用户可以“死搬硬套”地使用它们。下面代码展示了这两个类的使用,具体包装类定义请参考下载源码文件。

//创建并启动录音线程

record=new RecordSound(this);

record->CreateThread();

//创建并启动播放线程

play=new PlaySound1(this);

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) data);

//停止录制

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 for Windows)进行。这个SDK为开发Windows平台下的视频应用程序提供也现成的软件工具包(一组API),开发人员可以通过它们很方便地实现视频捕获、视频编辑及视频播放功能,特别是可利用其中内置的回调函数开发出更为复杂的视频应用程序。因此,这种方案的优点是播放视频时不需要专用的硬件设备(大多数的视频采集卡驱动程序都支持VFW接口),应用灵活,可以满足视频应用程序开发的需要。值得庆幸的是,如今的Windows版本都内置安装了VFW相关组件,而VC++自4.0以来就支持VFW,从而大大简化了视频应用程序的开发。目前,基于PC的多媒体应用程序的视频部分,大都是利用VFW API开发的。

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)完成单帧视频采集;由capSetCallbackOnVideoStream(hWndCap,VideoCallbackProc)完成视频流采集。如果要保存采集数据,则可调用capCaptureSequence(hWnd);要指定文件名,可调用capFileSetCapture(hwnd,Filename)。

(9)终止视频捕获断开与视频采集设备的连接

调用capCatureStop(hWndCap)停止采集,调用capDriverDisconnect(hWndCap),断开视频窗口与捕获驱动程序的连接。

由于上面这些API密切相关,所以为了使用方便,我们干脆把它们打包到一个视频捕获类VideoCapture中。

下面的代码片断展示了这个类的使用思路:

//创建视频捕获类的实例

vidcap=new VideoCapture(); 

//当帧捕获完成时,下面这一句将用于调用主对话框类的显示函数

vidcap->SetDialog(this);

//下一行完成初始化工作:连接到驱动程序;设置使用的视频格式等。

//如果成功地连接到视频捕获设备返回TRUE。

vidcap-> Initialize(); 

//如果连接成功,那么,我们就可以得到与视频格式相关的BITMAPINFO

//结构。后面将用之显示捕获的帧

this->m_bmpinfo=&vidcap->m_bmpinfo; 

//现在,你可以正式开始视频捕获了……

vidcap->StartCapture(); 

//一旦捕获开始,捕获的帧将到达回调函数—VideoCapture类的OnCaptureVideo函数。

//在此回调函数中,你可以调用显示函数实现帧显示(见下一节)

//停止捕获

vidcap->StopCapture(); 

//成功捕获后,释放视频捕获类

vidcap->Destroy();

【注意】为了顺利编译和链接,你需要在类实现文件(VideoCapture.cpp)的前面加上如下语句:

#pragma comment(lib,"vfw32") 

#pragma comment(lib,"winmm")

(二)显示捕获的视频帧

对于显示捕获的视频帧方面(也就是显示图像的问题),显然存在多种方案。例如,我们可以使用SetDIBitsToDevice()方法实现直接显示捕获的视频帧。但是,这种方案速度非常慢,因为它是基于图形设备接口(GDI)的函数。相比之下,更好一些的方法是使用DrawDib API来绘制帧,因为这个函数可以直接写向视频内存,因此能够提供更好的性能。

下面的代码片断展示了如何使用DrawDib函数显示捕获的视频帧:

//初始化DIB以便绘制

HDRAWDIB hdib=::DrawDibOpen(); 

//然后,使用适当的参数调用这个函数……

::DrawDibBegin(hdib,...); 

//现在,已经作好准备—可以调用这个函数进行帧显示了

::DrawDibDraw(hdib,...); 

//最后,结束帧绘制

::DrawDibEnd(hdib); 

::DrawDibClose(hdib);

其实,上面代码非常类似普通位图绘制过程。

四、 选择适当的编码/解码库

在本文中,我们选用Roalt Aalmoes的开源的快速H.263编码器库。

(一) 使用编码器代码示例

//初始化压缩器

CParam cparams; 

cparams.format = CPARAM_QCIF;

InitH263Encoder(&cparams);

//如果你需要从RGB24转换到YUV420格式,那么应该调用下面的函数

InitLookupTable(); 

//创建回调函数

//OwnWriteFunction是编码期间返回编码数据时调用的全局函数

WriteByteFunction = OwnWriteFunction; 

//压缩数据必须使用YUV420格式

//在压缩之前调用下面这个方法

ConvertRGB2YUV(IMAGE_WIDTH,IMAGE_HEIGHT,data,yuv); 

//压缩帧……

cparams.format=CPARAM_QCIF; 

cparams.inter = CPARAM_INTRA; 

cparams.Q_intra = 8; 

cparams.data=yuv; //数据是YUV格式

CompressFrame(&cparams, &bits); 

//你可以从开始时你已经注册的回调函数中取得压缩的数据

//最后,终止编码器

// ExitH263Encoder();

(二) 解码器编程

注意,原始的H.263编码器库以C方式进行编码,而且提供了其它更多的细节实现。在本文中,我们以C++重新进行了改写。

下面是解码器的使用示例代码框架:

//初始化解码器

InitH263Decoder(); 

//解压帧……

//rgbdata必须足够大以便存储输出数据;

//解码器以YUV420格式生成图像数据;

//解码之后,把它再转换成RGB24格式……

DecompressFrame(data,size,rgbdata,buffersize); 

//最后一步,终止解码器

ExitH263Decoder();

五、 运行应用程序

为了试验本文示例应用程序,你应该把可执行文件复制到一个LAN中的两台不同的机器上;然后,分别运行之。从一台机器上选择“连接”菜单项,并在弹出对话框内输入另一台机器的名字或IP地址,最后点击“连接”按钮。此时,在另一台机器上应该弹出一个“接受/拒绝”的对话框窗口,点击“接受”按钮。之后,在第一台机器上将显示通知对话框。按“OK”即可开始你的视频会议(聊天……)了。

六、 小结

我想通过本文向读者强调如下问题:Windows平台下的音频及视频开发并非那么复杂高深;有了本文的基础,你也完全可以据需要开发出自己的视频会议、实时监控系统、视频聊天等软件;另外,本文介绍的技术也可经修改并应用于因特网平台上。

同时,我们还看到微软的数字视频处理软件开发包Video for Windows的确为我们帮了大忙,而借助于因特网上的开源多媒体包能更快地加快这类软件的开发。 

  评论这张
 
阅读(668)| 评论(0)

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018