项目报告
基于局域网的视频聊天室系统的设计与实现
视频聊天系统作为一种新型的通信和交流工具,突破了地域的限制,可以提供更为便捷、灵活、全面的音、视频信息的传递和服务,具有极其广泛的发展前景。
本文介绍了采用JAVA编程开发视频聊天系统的一套比较常用的解决方案。文字聊天采用TCP模式;语音视频聊天采用UDP模式,在客户端之间点对点的进行。在该方案中,通过函数库VFW来实现视频捕获、影像压缩以及影像播放。微软公司提供的专门用于视频捕获开发的工具包VFW,为在Windows操作系统中实现视频捕获提供了标准的接口,从而大大降低了程序的开发难度。在视频传输方面,则通过组建视频帧,将位图形式的视频帧压缩成帧格式的Mpeg4流,传输到客户端后,解压并显示影像。同时,在本方案中,采用了线程来实现语音录制和语音回放,最终实现了通过服务器中转的文字聊天、点对点的语音视频聊天。
文字聊天;VFW;视频捕获;视频传输;语音录制;语音回放
目 录
论文总页数:24页
随着Internet的不断发展普及,网络通讯越来越被千家万户所接受,成为人们生活中的一部分。网络聊天已和手机等一样,成为人们运用最为广泛的通信工具之一。本毕业设计的目的主要是为了满足人们通讯交流的便捷,实现灵活、全面的音、视频信息的传递和服务。模拟腾讯公司的QQ聊天软件,开发一个多功能的聊天系统软件,本毕业设计主要实现视频语音聊天、文字聊天等功能。
随着网络宽带业务的推广与普及,加之视频产品设备(如摄像头、耳机、麦克风等)的成熟,普通用户可以轻松地借助网络视频通讯软件,实现“面对面”的网络交流。信息的无限量扩大,交通工具的便捷,视频技术的充分应用等导致了行业间竞争的全球化,这就要求现代部门、企业要具备更加灵敏的神经,更扁平化的管理,更快速的反应和决策,更贴切的市场宣传和服务。所有这一切是由信息技术的发展所带来的,同样也要求有先进的信息技术来提高部门、企业的竞争力。现代通讯已经是越来越普及了,必须有效合理的运用视频产品类设备来提高信息的传递和交流。在同一个局域网中,充分、合理的运用摄像头、耳机、麦克风等设备来实现文字聊天和语音视频聊天更是我们生活、学习、工作的便利所在。
- 理论知识介绍
- VFW简介
VFW是Microsoft 1992年推出的关于数字视频的一个软件包,它能使应用程序数字化并播放从传统模拟视频源得到的视频剪辑。VFW的一个关键思想是播放时不需要专用硬件,为了解决数字视频数据量大的问题,需要对数据进行压缩。它引进了一种叫AVI的文件标准,该标准未规定如何对视频进行捕获、压缩及播放,仅规定视频和音频该如何存储在硬盘上,以及在AVI文件中交替存储视频帧和与之相匹配的音频数据。VFW给程序员提供VBX和AVICap窗口类的高级编程工具,使程序员能通过发送消息或设置属性来捕获、播放和编辑视频剪辑。用户不必专门安装VFW,在安装Windows时,安装程序会自动地安装配置视频所需的组件,如设备驱动程序、视频压缩程序等。
VFW主要由以下六个模块组成:
- AVICAP.DLL:包含了执行视频捕获的函数,它给AVI文件、I/O和视频音频设备驱动程序提供一个高级接口;
- MSVIDEO.DLL:用一套特殊的DrawDib函数来处理屏幕上的视频操作;
- MCIAVI.DRV:此驱动程序包括对VFW的MCI命令的解释器;
- AVIFILE.DLL:支持由标准多媒体I/O(mmio)函数提供的更高的命令来访问AVI文件;
- 压缩管理器(ICM):管理用于视频压缩/解压缩的编解码器(CODEC);
- 音频压缩管理器ACM:提供与ICM相似的服务,不同的是它适于波形音频。
Visual C++在支持VFW方面提供有vfw32.lib、msacm32.lib、winmm.lib等库。特别是它提供了功能强大、简单易行、类似于MCIWnd的窗口类AVICap。AVICap为应用程序提供了一个简单的、基于消息的接口,使之能访问视频和波形音频硬件,并能在将视频流捕获到硬盘上的过程中进行控制。
AVICap支持实时的视频流捕获和单帧捕获,并提供对视频源的控制。虽然MCI也提供数字视频服务,比如,它为显示AVI文件的视频提供了AVIVideo命令集,为视频叠加提供了overlay命令集,但这些命令主要是基于文件的操作,不能满足实时地直接从视频缓存中获取数据的要求。对于使用没有视频叠加能力的捕获卡的PC机来说,用MCI提供的命令集是无法捕获视频流的。而AVICap在捕获视频方面具有一定的优势,它能直接访问视频缓冲区,不需要生成中间文件,实时性很强,效率很高。同时,它也可将数字视频捕获到文件。
在视频捕获之前需要创建一个捕获窗,所有的捕获操作及其设置都以它为基础。用AVICap窗口类创建的窗口(通过capCreateCaptureWindow函数创建)被称为“捕获窗”,其窗口风格一般为WS_CHILD和WS_VISIBLE。实际上,捕获窗类似于标准控制(如按钮、列表框等)。捕获窗具有下列功能:
- 将视频流和音频流捕获到一个AVI文件中;
- 动态地同视频和音频输入器件连接或断开;
- 以Overlay或Preview模式对输入的视频流进行实时显示;
- 在捕获时可指定所用的文件名并能将捕获文件的内容拷贝到另一个文件;
- 设置捕获速率;
- 显示控制视频源、视频格式、视频压缩的对话框;
- 创建、保存或载入调色板;
- 将图像和相关的调色板拷贝到剪贴板;
- 将捕获的一个单帧图像保存为DIB格式的文件。
AVICap在显示视频时提供的两种模式:
(A)预览(Preview)模式:该模式使用CPU资源,视频帧先从捕获硬件传到系统内存,接着采用GDI函数在捕获窗中显示。在物理上,这种模式需要通过VGA卡在监视器上显示。
(B)叠加(Overlay)模式:该模式使用硬件叠加进行视频显示,叠加视频的显示不经过VGA卡,叠加视频的硬件将VGA的输出信号与其自身的输出信号合并,形成组合信号显示在计算机的监视器上。只有部分视频捕获卡才具有视频叠加能力。
灵活编写AVICap提供的回调函数还可满足一些特殊需求。比如,将宏capCaptureSequenceNoFile同用capSetCallbackOnVideoStream登记的回调函数一起使用,可使应用程序直接使用视频和音频数据。在视频聊天的应用程序中可利用这一点来获得视频帧,回调函数将捕获的图像传到远端的计算机。应用程序可用捕获窗来登记回调函数(由用户编写,而由系统调用),以便在发生下列情况时,它能通知应用程序,作出相应的反应:捕获窗状态改变;出错;视频帧和音频缓存可以使用;在捕获过程中,其它应用程序处于让步(Yield)地位。
视频捕获编程也要用到涉及视频捕获的结构、宏、消息和函数。令人高兴的是,发送AVICap窗口消息所能完成的功能都能调用相应的宏来完成。例如,SendMessage(hWndCap,WM_CAP_DRIVER_CONNECT,0,0L)与capDriverConnect(hWndCap,0)的作用相同,都是将创建的捕获窗同视频输入器件连接起来。
视频部分主要是利用Video Capture函数库来获取影像的。Video Capture主要提供下列功能:连接驱动程序;获取影像、声音资料,并显示在屏幕上或者是存成AVI文件;获取单张影像显示在屏幕上,拷贝至剪贴簿,或者是存成DIB(Device-Independent Bitmap)文件。
Video Capture的主要结构:
结构体CAPTUREPARAMS主要包含一些获取图像的参数:DWORD dwRequestMicroSecPerFrame代表相邻两个frame的获取时间间隔;BOOL fYield值为TRUE,则表示Windows会以另一个thread来捕获影像,值为FALSE,程序会在捕捉影像后显示忙碌状态;BOOL fCaptureAudio其值表示是否需要同时获取声音资料。
结构体BITMAPINFO和点阵图有关,主要定义了影像获取之后显示在屏幕上、存储在文件中的格式,它包含两个成员:BITMAPINFOHEADER bmiHeader描述影像性质的结构,其成员记载了影像的大小、颜色深度和压缩的方式,该成员在Video Capture、Video Compression Manager和DrawDib函数库中,以及有关于点阵图的应用中;RGBQUAD bmiColors指向color table第一个元素的位置。
结构体COMPVARS主要是记录所有和压缩相关的信息,重要的成员:DWORD fccHandler为compressor句柄;LPBITMAPINFO lpbiIn指向待压缩影像BITMAPINFO的指标;LPBITMAPINFO lpbitOut:指向压缩完影像BITMAPINFO的指标;LONG lKey代表key-frame rate,而所谓key frame是指此frame在解压缩时不需要依赖前面的frame;LONG lQ代表影像压缩后的品质,取值为1~10000的整数。
Video Compression Functions主要记录压缩功能相关的信息,其包含的比较重要的成员:ICLocate输入指向压缩前后BITMAPINFO的指标,以及欲使用的codecs;ICCompressorChoose呼叫一个系统内建的对话,其中包含所有可能使用的codes以及其相关参数;ICCompressQuery询问compressor是否支持某种压缩方式,输入参数为compressor handle及指向压缩前后BITMAPINFO的指标,此函数会传回询问结果;ICCompressBegin要求系统准备相关资源以供压缩之用;ICCompress压缩某个frame;ICCompressEnd归还相关资源给系统;ICDompressQuery询问decompressor是否支持某种解压缩方式;ICDompressBegin要求系统准备相关资源以供解压缩之用;ICDompress解压缩某一个frame;ICDompressEnd归还相关资源给系统;ICDompressFree归还COMPVARS所占用的资源。
线程是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CWinThread类对象。单独一个执行程序运行时,缺省地包含了一个主线程,主线程以函数地址的形式出现,提供程序的启动点,当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
Windows提供了两种线程:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinApp对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
这里主要介绍用户界面线程的运用:
- 线程的启动
创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread (CRuntimeClass* pThreadClass,int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL)启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。
- 线程的优先级
CwinThread类的成员函数用于线程优先级的操作:
int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);
- 线程的挂起和恢复
CWinThread类中包含了应用程序挂起和恢复它所创建的线程的函数,其中SuspendThread()用来挂起线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。
- 结束线程
终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode)来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。
电脑两台以上,分别配有摄像头、麦克风、音箱或耳机等外部设备;Windows XP系统,Microsoft Visual Studio 6.0编程开发系统等。
在同一个局域网中,如何根据自身业务的要求,量身定制,对视频设备进行合理搭配,选择一套合理的视频聊天室系统。如何来满足局域网内部用户的通讯要求,在考虑到网络带宽的同时,提高视频清晰度,动态画面的流畅,语音的实时传输等,正是现代社会通讯所必需的。设计本系统时,分析网络承载、整个系统结构的组建等是实现局域网文字聊天和语音视频聊天所必需的。
文字、语音视频聊天作为一种广泛的网络应用对其基础的承载网络环境有着较高的网络要求。其中应重点考虑的是网络的带宽情况、端到端的时延、时延抖动、丢包率等问题。
- 网络带宽需求,视频聊天对网络的带宽需求为“视频带宽+IP包头开销”,计算方法为:网络带宽 = 视频带宽 × 1.2。
- 端到端的时延,网络传输不可避免的会发生传输时延,通常建议视频聊天的通用时延小于150ms。
- 时延抖动,由于音频/视频的传输为实时的交互,因此网络的时延抖动更为重要,一般,视频聊天的时延抖动控制在50ms内。
- 丢包率,网络数据传输经常会出现丢包现象,视频传输过程中数据丢包严重的话会影响在线视频聊天质量。因此,在设计上应将网络上的丢包率控制在1%以内。
视频聊天对实时性要求较高的网络应用,作为其基础的承载网络有较高的宽带和对网络中的业务流量有较高的控制能力。而视频聊天系统本身对带宽的要求为62kbit/s~2Mbit/s,但是为了满足流畅的视音频效果,要求带宽不低于384kbit/s。
文字、视频聊天的功能和应用效果体现在客户端,而服务器端则是必不可少的,对于系统的需求分析在系统设计的过程中应该明确、细致:
- 文字聊天:首先启动服务器端,当用户启动客户端时,用服务器保存的用户名和密码来验证客户是否已经登录到服务器。只有当有两个以上的用户启动客户端时,才可以进行文字聊天,聊天内容要经过服务器中转,分别在服务器端和两个聊天的客户端显示聊天。
- 语音视频聊天:在有两个客户在线的情况下,才能进行语音视频聊天。两个用户要进行语音视频聊天时,一个用户选中另一个用户的用户名,根据该用户名在后台链表中的对应IP地址查找用户并请求视频连接。当双方确认视频连接后就进行视频传输,并显示在客户端,同时用线程实现语音录制和回放。这样,两个用户就实现了点对点的语音视频聊天。在进行语音视频聊天的过程中,不能再与其它用户进行语音视频聊天,但能够与其它用户进行文字聊天。
该系统采用的是Server/Client结构,服务器端是一台PC机,而客户端是PC机和一个数字摄像头、耳机和麦克风。它们进行文字聊天时,要经过服务器进行中转,而当进行语音视频聊天时是客户端与客户端之间直接进行的点对点的连接,它们之间的网络拓扑结构如图1。在图中,为了简便,没有画出麦克风、音箱或耳机等外部设备。
图1 网络拓扑结构图
通过需求调研并分析,确定系统具备的基本功能,包括:文字聊天、语音视频聊天。
- 文字聊天:
文字聊天采用的是TCP模式,包括服务器端和客户端。首先启动服务器端,客户端通过用户名和密码登录服务器,服务器响应客户端登录并提示有用户登录,此时两个用户就可以进行文字聊天,在文字聊天时通过服务器中转,而每个用户可以同时与多个用户进行文字聊天。当有用户退出时,服务器做出响应,提示在线用户,××用户下线。
- 语音视频聊天:
语音视频聊天时采用的是UCP模式,客户端与客户端点对点的进行,不需要经过服务器端中转。在文字聊天的基础上,客户端之间自行处理的语音视频聊天,运用VFW函数库中的函数对USB口输入的数字视频信息进行相关处理,比如:视频捕获、影像压缩以及影像播放等,同时利用线程来处理声音部分的录制、回放等。A客户端向B客户端请求语音视频聊天是通过B客户端的用户名来获得B客户端的IP地址,并向B客户端发送语音视频聊天请求,当B客户端接受后捕获视频,并进行压缩传输到A客户端解压并进行显示,在B客户端接受视频的同时,A客户端也捕获视频,压缩传输到B客户端解压并进行显示。
该系统分为服务器端和客户端,完成了文字聊天和语音视频聊天,使用上只有文字聊天时才会通过服务器端,而对于语音视频聊天就只需要对整个在线客户端两两之间进行点对点的视频聊天。而在语音视频时包括了视频捕获、视频压缩、解压缩、语音录制、语音回放以及视频传输等。整个系统的功能模块图如图2。
图2 系统功能模块图
在整个系统中主要运行两个功能:图3 文字聊天流程图和图4 语音视频聊天流程图。
图3 文字聊天流程图
图4 语音视频聊天流程图
在文字聊天时,服务器端与客户端的连接是采用的TCP套接节进行连接。TCP套接字的使用如图5。创建CSocket对象CSocketServer来处理服务器端与客户端的连接,CSocket继承于CasyncSocket,是Windows Socket API的高层抽象。CSocket通常和CsocketFile以及Carchive类混合使用,这两个类负责数据的发送和接收。要使用CSocket对象,首先要调用构造函数,然后调用Create函数创建一个Socket句柄。CSocket函数缺省是创建一个流Socket;如果没有使用CArchive类,那么还可以创建一个数据报Socket。服务器端调用Accept,客户端调用Connect,然后创建一个CsocketFile去关联CSocket。接下来的操作可以创建CArchive对象关联CsocketFile,以用来发送和接收数据。
图5 TCP套接字的使用
ChatServer服务器运行时,利用一个CSocket对象CSocketServer启动服务器,用函数gethostname来获得服务器端主机名和IP,同时在服务器对话框中显示服务器IP,并将分配的固定端口号8123显示在对话框中。用一个list列表显示在线用户,随时更新用户登录情况,用一个edit box显示客户端的聊天内容以及系统提示消息。每一个ChatClient客户端启动时,利用服务器内定的用户号和密码来登录(如图6)。在整个系统中,利用链表来处理所有的用户信息:当有用户登录时,在链表尾部加入该用户信息;当用户下线时,在该链表中删除用户,并提示所有用户,该用户下线。在对链表进行操作的同时,要更新list列表中的信息。
图6 用户登录界面
ChatServer服务器端响应客户端文字聊天时的中转情况如图7。
图7 服务器响应文字聊天信息中转
ChatClient客户端两两间进行文字聊天时,发送信息的处理函数如下:
void CChatClientDlg::OnChatBtSend() //发送信息按键
{
if( !m_bConnect)
{
SetMessageBox("请连接服务器!\r\n");
return ;
}
CString str;
CString szUserName;
CMesg msg;
GetDlgItemText(IDC_MESSAGE,str);
GetDlgItemText(IDC_USERNAME,szUserName);
if( str.GetLength() <= 0 )
{
SetMessageBox("请输入想要发送的信息!\r\n");
return ;
}
if ( szUserName.GetLength() <= 0)
{
SetMessageBox("请选择说话对象!\r\n");
return ;
}
//消息封装
msg.m_szCommand.Format("Message");
msg.m_szRecObject.Format(szUserName);
msg.m_szText.Format(str);
m_csClient->SendM(&msg);
AddReceiver(szUserName , true);
AddChatMessage(str);
}
在聊天两个客户端的信息情况如图8和图9。
图8 接收文字信息
图9 发送文字信息
在实现语音视频聊天时,采用的是基于UDP套接字的点对点模式,而UDP面向的是无连接的数据服务,其套接字的使用如图10所示。
图10 UDP套接字的使用
利用VFW接口,视频捕获可以分为以下几个步骤:
- 建立视频采集窗口:该窗口用来接收视频捕捉驱动程序传来的数据和消息。
- 连接视频驱动程序:将建立的视频捕捉窗口与视频设备驱动程序相连。
- 视频捕获初始化。
- 视频捕捉设置:VFW下视频捕捉参数的设置可以通过调用函数或弹出对话框的形式来实现。一般视频驱动程序允许设置的参数包括视频源选择、视频格式、视频显示格式等。
- 设置回调函数:通过回调函数来通知程序视频事件的发生,比如捕捉一帧图像成功的消息,捕捉出错的消息等。
- 结束捕捉:结束捕捉是应该有一些清除工作。如释放分配的内存,断开捕捉窗口与视频捕捉驱动程序的连接,清除视频捕捉窗口等。
窗口类为捕获数字视频流及其相关操作提供了很大的方便,灵活编写其中的回调函数可满足实时视频传输的需要,例如应用程序可直接从缓冲中取得数字视频并对其进行压缩编码后实时地传到远端的客户端。
在VC++中,采用VFW技术,客户端通过capSetCallbackOnFrame()注册回调函数,当采集卡采集到一幅图像后,系统就会自动调用回调函数,然后再回调函数中使用ICSeqCompressFrame()函数进行压缩。然后再通过Winsock将压缩后的数据发送到另一客户端。该客户端接收完一帧以后,交给ICDecompress()解压,最后用SetDIBitsToDevice()将图像显示出来。
基本的捕获设置包括设置捕获速度(每秒捕获多少帧)、是否同时捕获声频、捕获缓冲、允许最大丢失多少帧和是否使用DOS内存,以及使用键盘的哪个键或鼠标的哪个键来终止捕获等内容,这些设置使用CAPTUREPARAMS结构来描述,capCaptureGetSetup宏来得到当前的设置,然后改变此结构的成员变量,再使用capCaptureSetSetup宏设置新的设置。
设置捕获速度,通过使用capCaptureGetSetup宏来得到当前的捕捉速度,将当前的捕捉速度保存在CAPTUREPARAMS结构的dwRequestMicroSecPerFrame成员变量中,也可以通过设置此变量来改变当前设置值。
设置终止捕获,同样通过使用capCaptureGetSetup宏来得到当前的设置,当前按键设置保存在CAPTUREPARAMS结构的vKeyAbort成员中,鼠标设置保存在fAbortLeftMouse和fAbortRightMouse成员中,通过修改可以设置新的热健或者鼠标左右键,修改完成后,使用capCaptureSetSetup宏来进行更新。
捕获的时间限制,用CAPTUREPARAMS结构中的fLimitEnabled表示捕获是否有时间的限制,wTimeLimit用来设置指示捕获最大的持续时间,其单位为秒。使用capCaptureGetSetup宏来得到当前的设置值。
下面程序为设置CAPTUREPARAMS结构的实现代码:
BOOL VideoCapture::SetCapturePara()
{
CAPTUREPARMS CapParms={0};
capCaptureGetSetup(m_capwnd,&CapParms,sizeof(CapParms));
//得到当前的捕获速度
CapParms.fAbortLeftMouse = FALSE;
CapParms.fAbortRightMouse = FALSE;
CapParms.fYield = TRUE;
CapParms.fCaptureAudio = FALSE;
CapParms.wPercentDropForError = 80;
if(!capCaptureSetSetup(m_capwnd,&CapParms,sizeof(CapParms)))
{
// log.WriteString("\n Failed to set the capture parameters ");
return FALSE;
}
// Set Video Format
capGetVideoFormat(m_capwnd,&m_bmpinfo,sizeof(m_bmpinfo));
m_bmpinfo.bmiHeader.biWidth=IMAGE_WIDTH;
m_bmpinfo.bmiHeader.biHeight=IMAGE_HEIGHT;
BOOL ret=capSetVideoFormat(m_capwnd,&m_bmpinfo,sizeof(m_bmpinfo));
// log.WriteString("\n Video parameters set properly");
return ret;
}
//终止一个捕获任务
BOOL VideoCapture::StopCapture()
{
capCaptureStop(m_capwnd);
capCaptureAbort(m_capwnd);
Sleep(500);
return TRUE;
}
在捕获前必须创建一个捕获窗口(Capture Widnow),下面介绍有关捕获窗口的情况:创建一个AVICap捕获窗口,用capCreateCaptureWindow函数并返回一个句柄。将捕获窗口连接至捕获设备,用capDriverConnect函数来使一个捕获窗口与一个捕获设备连接或关联连接上后,就可以通过捕获窗口向捕获设备发送各种消息,可以使用函数capGetDriverDescription来获得已安装的捕获设备名称及版本,将其列举在实现程序过程中。再利用capDriverGetName函数来得到捕获设备的名称将获得的版本发送到capDriverGetVersion。如果断开捕获窗口与捕获设备的连接用capDriverDisconnect。
捕获窗口的状态,用capGetStatus函数来获得当前捕获窗口的状态,得到一个CAPSTATUS结构的拷贝。该结构的内容包含了图片的尺寸、卷轴的当前位置、overlay和preview是否已设置。由于其信息是动态的,每当捕获的视频流的尺寸发生改变,程序应该在获取捕获设备的视频格式以后及时进行刷新。而捕获窗口尺寸的改变并不影响实际的捕获视频流的尺寸。该尺寸由视频捕获设备的格式和视频对话框决定。
//捕获窗口
BOOL VideoCapture::Initialize()
{
char devname[128]={0},devversion[128]={0};
int index=0;
BOOL ret = TRUE, ret1 = TRUE, ret2 = TRUE, ret3 = TRUE;
TRACE("VideoCapture::Initialize\n");
//创建一个AVICap捕获窗口
m_capwnd = capCreateCaptureWindow("Capture",WS_POPUP,0,0,1,1,0,0);
if(!m_capwnd)
{
return FALSE;
}
//connect callback functions
ret = capSetUserData(m_capwnd,this);
//Change destroy functions also........
ret1 = capSetCallbackOnVideoStream(m_capwnd,OnCaptureVideo);
//得到已安装的捕获设备的名称及版本
ret2 = capGetDriverDescription(index,devname,100,devversion,100);
// Connect to webcam driver
//使一个捕获窗口与一个捕获设备连接或关联
ret3 = capDriverConnect(m_capwnd,index);
if(!(ret && ret1 && ret2 && ret3))
{
// Device may be open already or it may not have been
// closed properly last time.
AfxMessageBox("Unable to open Video Capture Device");
// log.WriteString("\n Unable to connect driver to the window");
m_capwnd=NULL;
return FALSE;
}
// Set the capture parameters
if(SetCapturePara()==FALSE)
{
// log.WriteString("\n Setting capture parameters failed");
capDriverDisconnect(m_capwnd); //使捕获窗口与一个捕获设备断开
return FALSE;
}
return TRUE;
}
视频捕获必须具有视频捕获驱动才能进行,其相关内容如下:
视频捕获驱动的性能,capDriverGetCap函数得到当前连接视频驱动的硬件性能,该信息保存在CAPDRIVERCAPS结构中;视频对话框,每个视频驱动能够提供4个对话框来控制视频捕获和数字化处理视频对话框定义的视频压缩率和图像品质等。视频对话框都在视频捕获驱动中定义。这个四个对话框分别为:Video Source对话框用于控制选择视频来源(capDlgVideoSource);Video Format对话框定义视频帧的尺寸和精度,以及视频捕获卡的压缩设置(capDlgVideoFormat);Video Display对话框控制在视频捕获期间相关显示器上的显示(capDlgVideoDisplay);Video Compression对话框控制压缩和图像品质(caoDlgVideoCompression)。
在音频的录制和播放时,采用的用户界面线程来处理,是CWinThread对象,根据前面线程的介绍,一步一步的来实现。录音用的一个CWinThread对象CAudioRec来实现,部分实现代码:
LRESULT CAudioRec::OnStartRecording(WPARAM wp, LPARAM lp)
{
if(recording) return FALSE;
//打开录音设备
MMRESULT mmReturn = ::waveInOpen( &m_hRecord, WAVE_MAPPER,
&m_WaveFormatEx, ::GetCurrentThreadId(), 0, CALLBACK_THREAD);
if(mmReturn!=MMSYSERR_NOERROR ) return FALSE;
if(mmReturn==MMSYSERR_NOERROR )
{
for(int i=0; i < MAXRECBUFFER ; i++)
{
//为录音设备准备缓存
mmReturn = ::waveInPrepareHeader(m_hRecord,
rechead[i], sizeof(WAVEHDR));
//给输入设备增加一个缓存
mmReturn = ::waveInAddBuffer(m_hRecord,
rechead[i], sizeof(WAVEHDR));
}
mmReturn = ::waveInStart(m_hRecord); //开始录音
if(mmReturn==MMSYSERR_NOERROR ) recording=TRUE;
}
return TRUE;
}
相对录音而言,播放就简单多了,同样用的一个CWinThread对象CAudioPlay来实现,部分实现代码:
LRESULT CAudioPlay::OnWriteSoundData(WPARAM wParam, LPARAM lParam)
{
// TRACE("CAudioPlay::OnWriteSoundData\n");
MMRESULT mmResult = FALSE;
char *p=NULL;
int length=(int) wParam;
if(Playing==FALSE) return FALSE;
if(length<=0) return FALSE;
WAVEHDR *lpHdr=new WAVEHDR;
if(!lpHdr) return FALSE;
p=new char [length];
if(!p) {delete lpHdr;
return FALSE;}
ZeroMemory(lpHdr,sizeof(WAVEHDR));
ZeroMemory(p,length);
CopyMemory(p,(char*)lParam,length);
lpHdr->lpData=p;
lpHdr->dwBufferLength = length;
mmResult = ::waveOutPrepareHeader(m_hPlay, lpHdr, sizeof(WAVEHDR));
//为回放设备准备内存块
if(mmResult)
{
delete lpHdr;delete p;
return mmResult;
}
mmResult = ::waveOutWrite(m_hPlay, lpHdr, sizeof(WAVEHDR));//写数据(放音)
if(mmResult){delete lpHdr;delete p;
return mmResult; }
m_Count++;
return MMSYSERR_NOERROR;
}
视频采集采用AVICap从视频采集卡捕获视频图像,得到的是位图形式的视频帧,然后用Divx编码器进行压缩,压缩以后形成以帧为格式的Mpeg4流。通过Winsock实现压缩后的视频数据在局域网中的实时传输,接收完的数据交给Divx解码器,以帧的格式解压,最后实现视频显示。所以提出以帧为单位发送视频数据流。为了在接收端能够方便地提取出一帧,提出如表1所示的格式组建帧。完整的一帧由5个字段组成,各个字段的意义如下:帧开始标志:标志着一帧地开始,占用4个字节的空间;帧大小:表示整个帧的大小,包括5个字段的大小,占用4个字节的空间;帧编号:表示帧的顺序编号,占用4个字节的空间;帧类型:标志此帧是否是关键帧,占用1个字节的空间;帧数据:存放压缩后一帧的完整数据。
表1 视频帧的格式
帧开始标志 | 帧大小 | 帧编号 | 帧类型 | 帧数据 |
0 4 8 12 13 2012
处理视频传输如图11所示。
图11 视频的传输
相对于视频的传输,语音的传输就简单得多了,在这里建立了两个线程来处理,先来用一个语音录制线程在一个客户端录制语音,再通过用G729a对语音进行编码,然后传输到另一客户端,同样用G729a对语音进行解码,然后用一个语音回放线程将语音播放出来。
将视频语音信息在客户端显示出来,如图12所示。
图12 语音视频聊天
结 论
随着通讯技术的普及和网络的发展,社会上越来越重视信息交流的方便、及时和准确,借助网络视频通讯软件实现“面对面”的网络交流。由于信息量的无限扩大,交通工具的便捷,视频技术的充分应用已经成为现代通讯必不可少的一项技术运用。基于局域网的视频聊天室系统可以跨越空间距离、灵活多样的面对面的交互,适应现代社会的方便、快捷、高效、快节奏。
在编写程序的过程中,我也遇到了很多的问题。通过老师的指点和查阅资料等得到了解决,并圆满完成了整个程序的开发工作,同时积累了许多解决经验。这次的毕业设计达到了预期的目的,实现了文字聊天和语音视频聊天。通过这次毕业设计,使我从理论到实践迈出了坚实的一步。在学习理论、分析和组织程序结构以及具体的实现等整个过程中,我体会到了编写程序的酸、甜、苦、辣。要编写出一个好的程序,必须要有缜密的思维,谨慎的作风和坚忍不拔的毅力。
参考文献
[1] 谢希仁.计算机网络[M].北京:电子工业出版社,2004。
[2] W. Richard Stevens.TCP/IP详解[M].北京:机械工业出版社,2005。
[3] 张炯.Unix网络编程[M].北京:清华大学出版社,2002。
[4] 求是科技,王正军.Visual C++ 6.0从入门到精通[M].北京:人民邮电出版社,2006。
[5] 孙鑫,余安萍.VC++深入详解[M].北京:电子工业出版社,2006。
[6] 陈坚,陈伟.Visual C++ 网络高级编程[M].北京:人民邮电出版社,2001。
[7] 吴志军.Visual C++视频会议开发技术与实例[M].北京:人民邮电出版社,2006。
参考资料
java毕业设计——JAVA基于局域网的聊天室系统(源代码+论文).zip
Java毕业设计——基于spring boot的桌面聊天室系统设计与实现(源码+数据库).7z