联系方式: 微信:biyebang QQ: 629001810
摘 要
视频聊天系统作为一种新型的通信和交流工具,突破了地域的限制,可以提供更为便捷、灵活、全面的音、视频信息的传递和服务,具有极其广泛的发展前景。
本文介绍了采用JAVA编程开发视频聊天系统的一套比较常用的解决方案。文字聊天采用TCP模式;语音视频聊天采用UDP模式,在客户端之间点对点的进行。在该方案中,通过函数库VFW来实现视频捕获、影像压缩以及影像播放。微软公司提供的专门用于视频捕获开发的工具包VFW,为在Windows操作系统中实现视频捕获提供了标准的接口,从而大大降低了程序的开发难度。在视频传输方面,则通过组建视频帧,将位图形式的视频帧压缩成帧格式的Mpeg4流,传输到客户端后,解压并显示影像。同时,在本方案中,采用了线程来实现语音录制和语音回放,最终实现了通过服务器中转的文字聊天、点对点的语音视频聊天。
系统设计 该系统采用的是Server/Client结构,服务器端是一台PC机,而客户端是PC机和一个数字摄像头、耳机和麦克风。它们进行文字聊天时,要经过服务器进行中转,而当进行语音视频聊天时是客户端与客户端之间直接进行的点对点的连接,它们之间的网络拓扑结构如图1。在图中,为了简便,没有画出麦克风、音箱或耳机等外部设备。 通过需求调研并分析,确定系统具备的基本功能,包括:文字聊天、语音视频聊天。 (1) 文字聊天: 文字聊天采用的是TCP模式,包括服务器端和客户端。首先启动服务器端,客户端通过用户名和密码登录服务器,服务器响应客户端登录并提示有用户登录,此时两个用户就可以进行文字聊天,在文字聊天时通过服务器中转,而每个用户可以同时与多个用户进行文字聊天。当有用户退出时,服务器做出响应,提示在线用户,××用户下线。 (2) 语音视频聊天: 语音视频聊天时采用的是UCP模式,客户端与客户端点对点的进行,不需要经过服务器端中转。在文字聊天的基础上,客户端之间自行处理的语音视频聊天,运用VFW函数库中的函数对USB口输入的数字视频信息进行相关处理,比如:视频捕获、影像压缩以及影像播放等,同时利用线程来处理声音部分的录制、回放等。A客户端向B客户端请求语音视频聊天是通过B客户端的用户名来获得B客户端的IP地址,并向B客户端发送语音视频聊天请求,当B客户端接受后捕获视频,并进行压缩传输到A客户端解压并进行显示,在B客户端接受视频的同时,A客户端也捕获视频,压缩传输到B客户端解压并进行显示。 该系统分为服务器端和客户端,完成了文字聊天和语音视频聊天,使用上只有文字聊天时才会通过服务器端,而对于语音视频聊天就只需要对整个在线客户端两两之间进行点对点的视频聊天。而在语音视频时包括了视频捕获、视频压缩、解压缩、语音录制、语音回放以及视频传输等。整个系统的功能模块图如图2。 在整个系统中主要运行两个功能:图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,以用来发送和接收数据。 ChatServer服务器运行时,利用一个CSocket对象CSocketServer启动服务器,用函数gethostname来获得服务器端主机名和IP,同时在服务器对话框中显示服务器IP,并将分配的固定端口号8123显示在对话框中。用一个list列表显示在线用户,随时更新用户登录情况,用一个edit box显示客户端的聊天内容以及系统提示消息。每一个ChatClient客户端启动时,利用服务器内定的用户号和密码来登录(如图6)。在整个系统中,利用链表来处理所有的用户信息:当有用户登录时,在链表尾部加入该用户信息;当用户下线时,在该链表中删除用户,并提示所有用户,该用户下线。在对链表进行操作的同时,要更新list列表中的信息。 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。 在实现语音视频聊天时,采用的是基于UDP套接字的点对点模式,而UDP面向的是无连接的数据服务,其套接字的使用如图10所示。 利用VFW接口,视频捕获可以分为以下几个步骤: (1) 建立视频采集窗口:该窗口用来接收视频捕捉驱动程序传来的数据和消息。 (2) 连接视频驱动程序:将建立的视频捕捉窗口与视频设备驱动程序相连。 (3) 视频捕获初始化。 (4) 视频捕捉设置:VFW下视频捕捉参数的设置可以通过调用函数或弹出对话框的形式来实现。一般视频驱动程序允许设置的参数包括视频源选择、视频格式、视频显示格式等。 (5) 设置回调函数:通过回调函数来通知程序视频事件的发生,比如捕捉一帧图像成功的消息,捕捉出错的消息等。 (6) 结束捕捉:结束捕捉是应该有一些清除工作。如释放分配的内存,断开捕捉窗口与视频捕捉驱动程序的连接,清除视频捕捉窗口等。 窗口类为捕获数字视频流及其相关操作提供了很大的方便,灵活编写其中的回调函数可满足实时视频传输的需要,例如应用程序可直接从缓冲中取得数字视频并对其进行压缩编码后实时地传到远端的客户端。 在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() { CAPTUREPARMSCapParms={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("\nFailed 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; BOOLret=capSetVideoFormat(m_capwnd,&m_bmpinfo,sizeof(m_bmpinfo)); // log.WriteString("\nVideo parameters set properly"); return ret; } //终止一个捕获任务 BOOL VideoCapture::StopCapture() { capCaptureStop(m_capwnd); capCaptureAbort(m_capwnd); Sleep(500); return TRUE; } 在捕获前必须创建一个捕获窗口(CaptureWidnow),下面介绍有关捕获窗口的情况:创建一个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 callbackfunctions ret = capSetUserData(m_capwnd,this); //Change destroyfunctions also........ ret1 =capSetCallbackOnVideoStream(m_capwnd,OnCaptureVideo); //得到已安装的捕获设备的名称及版本 ret2 =capGetDriverDescription(index,devname,100,devversion,100); // Connect to webcamdriver //使一个捕获窗口与一个捕获设备连接或关联 ret3 =capDriverConnect(m_capwnd,index); if(!(ret && ret1&& ret2 && ret3)) { // Device may beopen already or it may not have been // closedproperly last time. AfxMessageBox("Unableto open Video Capture Device"); // log.WriteString("\n Unable to connectdriver to the window"); m_capwnd=NULL; return FALSE; } // Set the captureparameters 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=newWAVEHDR; 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) { deletelpHdr;delete p; return mmResult; } mmResult =::waveOutWrite(m_hPlay, lpHdr, sizeof(WAVEHDR));//写数据(放音) if(mmResult){deletelpHdr;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所示。 相对于视频的传输,语音的传输就简单得多了,在这里建立了两个线程来处理,先来用一个语音录制线程在一个客户端录制语音,再通过用G729a对语音进行编码,然后传输到另一客户端,同样用G729a对语音进行解码,然后用一个语音回放线程将语音播放出来。 将视频语音信息在客户端显示出来,如图12所示。 源文件1.1 硬件结构
1.1 软件结构
1.1.1 功能需求
1.1.2 系统功能模块图
1.1 系统各模块流程图
1.1 文字聊天
1.1.1 TCP套接字的运用
1.1.1 文字聊天实现
1.1 语音视频聊天
1.1.1 UDP套接字的运用
1.1.1 视频的捕获
1.1.2 捕获窗口
1.1.3 视频捕获驱动
1.1.4 语音录制
1.1.5 语音回放
1.1.6 视音频的传输
版权所有© 帮我毕业网 并保留所有权利