實(shí)現(xiàn)的大概的流程為:
1、一個(gè)聊天室成員向另外一個(gè)成員發(fā)起語(yǔ)音聊天請(qǐng)求
2、這個(gè)請(qǐng)求將被送至WCF服務(wù)端,WCF的雙工通知被邀請(qǐng)人。
3、被邀請(qǐng)人接到通知,他可以選擇接受或者拒絕語(yǔ)音聊天的請(qǐng)求。
4、如果拒絕,將通知請(qǐng)求者拒絕語(yǔ)音聊天
5、如果同意,邀請(qǐng)者和被邀請(qǐng)者的客戶端將進(jìn)行語(yǔ)音聊天,此時(shí)客戶端會(huì)開(kāi)啟一個(gè)播放聲音和接受聲音的線程。這里用到了一個(gè)開(kāi)源的wave類庫(kù),在 http://www.lumisoft.ee/lswww/download/downloads/Examples/ 可以下載。聲音的通信使用到了UDPClient 類。這個(gè)類使用 UDP 與網(wǎng)絡(luò)服務(wù)通訊。UDP 的優(yōu)點(diǎn)是簡(jiǎn)單易用,并且能夠同時(shí)向多個(gè)地址廣播消息。UdpClient 類提供了一些簡(jiǎn)單的方法,用于在阻止同步模式下發(fā)送和接收無(wú)連接 UDP 數(shù)據(jù)報(bào)。因?yàn)?UDP 是無(wú)連接傳輸協(xié)議,所以不需要在發(fā)送和接收數(shù)據(jù)前建立遠(yuǎn)程主機(jī)連接。但您可以選擇使用下面兩種方法之一來(lái)建立默認(rèn)遠(yuǎn)程主機(jī):
-
使用遠(yuǎn)程主機(jī)名和端口號(hào)作為參數(shù)創(chuàng)建 UdpClient 類的實(shí)例。
-
創(chuàng)建 UdpClient 類的實(shí)例,然后調(diào)用 Connect 方法。
可以使用在UdpClient 中提供的任何一種發(fā)送方法將數(shù)據(jù)發(fā)送到遠(yuǎn)程設(shè)備。使用 Receive 方法可以從遠(yuǎn)程主機(jī)接收數(shù)據(jù)。
這篇文章使用了 Receive 方法從客戶端接受數(shù)據(jù)。然后通過(guò)WCF中存儲(chǔ)的IP地址,通過(guò)Send方法將其發(fā)送給客戶端。
下面我將在前一篇文章的基礎(chǔ)上實(shí)現(xiàn)這個(gè)語(yǔ)音聊天的功能。首先在客戶端添加聲音管理的類CallManager,這個(gè)類使用到了開(kāi)源的wave類庫(kù),代碼如下:
public class CallManager { private WaveIn _waveIn; private WaveOut _waveOut; private IPEndPoint _serverEndPoint; private Thread _playSound; private UdpClient _socket; public CallManager(IPEndPoint serverEndpoint) { _serverEndPoint = serverEndpoint; } public void Start() { if (_waveIn != null || _waveOut != null ) { throw new Exception(" Call is allready started "); } int waveInDevice = (Int32)Application.UserAppDataRegistry.GetValue(" WaveIn ", 0); int waveOutDevice = (Int32)Application.UserAppDataRegistry.GetValue(" WaveOut ", 0); _socket = new UdpClient(0); // opens a random available port on all interfaces _waveIn = new WaveIn(WaveIn.Devices[waveInDevice], 8000, 16, 1, 400); _waveIn.BufferFull += new BufferFullHandler(_waveIn_BufferFull); _waveIn.Start(); _waveOut = new WaveOut(WaveOut.Devices[waveOutDevice], 8000, 16, 1); _playSound = new Thread( new ThreadStart(playSound)); _playSound.IsBackground = true ; _playSound.Start(); } private void playSound() { try { while ( true ) { lock (_socket) { if (_socket.Available != 0) { IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); byte [] received = _socket.Receive( ref endpoint); // todo: add codec _waveOut.Play(received, 0, received.Length); } } Thread.Sleep(1); } } catch (ThreadAbortException) { } catch { this .Stop(); } } void _waveIn_BufferFull( byte [] buffer) { lock (_socket) { //todo: add codec _socket.Send(buffer, buffer.Length, _serverEndPoint); } } public void Stop() { if (_waveIn != null ) { _waveIn.Dispose(); } if (_waveOut != null ) { _waveOut.Dispose(); } if (_playSound.IsAlive) { _playSound.Abort(); } if (_socket != null ) { _socket.Close(); _socket = null ; } } }
在服務(wù)端添加將接受到的聲音,發(fā)送給接受者的類,使用到了UDPClient類:
public class UdpServer { private Thread _listenerThread; private List<IPEndPoint> _users = new List<IPEndPoint>(); private UdpClient _udpSender = new UdpClient(); public IPAddress ServerAddress { get ; set ; } public UdpClient UdpListener { get ; set ; } public UdpServer() { try { ServerAddress = IPAddress.Parse(" 127.0.0.1 "); } catch { throw new Exception(" Configuration not set propperly. View original source code "); } } public void Start() { UdpListener = new System.Net.Sockets.UdpClient(0); _listenerThread = new Thread( new ThreadStart(listen)); _listenerThread.IsBackground = true ; _listenerThread.Start(); } private void listen() { while ( true ) { IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); byte [] received = UdpListener.Receive( ref sender); if (!_users.Contains(sender)) { _users.Add(sender); } foreach (IPEndPoint endpoint in _users) { if (!endpoint.Equals(sender)) { _udpSender.Send(received, received.Length, endpoint); } } } } public void EndCall() { _listenerThread.Abort(); } }
在WCF服務(wù)中添加兩個(gè)方法:初始化語(yǔ)音通信和結(jié)束語(yǔ)音通信。
[OperationContract(IsOneWay = false )] bool InitiateCall( string username); [OperationContract(IsOneWay = true )] void EndCall(); 具體是實(shí)現(xiàn)代碼:
public bool InitiateCall( string username) { ClientCallBack clientCaller = s_dictCallbackToUser[OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>()]; ClientCallBack clientCalee = s_dictCallbackToUser.Values.Where(p => p.JoinChatUser.NickName == username).First(); if (clientCaller.Callee != null || clientCalee.Callee != null ) // callee or caller is in another call { return false ; } if (clientCaller == clientCalee) { return false ; } if (clientCalee.Client.AcceptCall(clientCaller.JoinChatUser.NickName)) { clientCaller.Callee = clientCalee.Client; clientCalee.Callee = clientCaller.Client; clientCaller.UdpCallServer = new UdpServer(); clientCaller.UdpCallServer.Start(); EmtpyDelegate separateThread = delegate () { IPEndPoint endpoint = new IPEndPoint(clientCaller.UdpCallServer.ServerAddress, ((IPEndPoint)clientCaller.UdpCallServer.UdpListener.Client.LocalEndPoint).Port); clientCalee.Client.CallDetailes(endpoint, clientCaller.JoinChatUser.NickName, username); clientCaller.Client.CallDetailes(endpoint, clientCaller.JoinChatUser.NickName, username); foreach (var callback in s_dictCallbackToUser.Keys) { callback.NotifyMessage(String.Format(" System:User \"{0}\" and user \"{1}\" have started a call ",clientCaller.JoinChatUser.NickName, username)); } }; separateThread.BeginInvoke( null , null ); return true ; } else { return false ; } } public void EndCall() { ClientCallBack clientCaller = s_dictCallbackToUser[OperationContext.Current.GetCallbackChannel<IZqlChartServiceCallback>()]; ClientCallBack ClientCalee = s_dictCallbackToUser[clientCaller.Callee]; if (clientCaller.UdpCallServer != null ) { clientCaller.UdpCallServer.EndCall(); } if (ClientCalee.UdpCallServer != null ) { ClientCalee.UdpCallServer.EndCall(); } if (clientCaller.Callee != null ) { foreach (var callback in s_dictCallbackToUser.Keys) { callback.NotifyMessage(String.Format(" System:User \"{0}\" and user \"{1}\" have ended the call ", clientCaller.JoinChatUser.NickName, ClientCalee.JoinChatUser.NickName)); } clientCaller.Callee.EndCallClient(); clientCaller.Callee = null ; } if (ClientCalee.Callee != null ) { ClientCalee.Callee.EndCallClient(); ClientCalee.Callee = null ; } }
還有部分做了修改的代碼見(jiàn)附件代碼。
下面看下演示的截圖:
1、兩個(gè)用戶登錄:
2、選擇小花,點(diǎn)擊按鈕。麒麟向小花同學(xué)發(fā)起語(yǔ)音聊天:
3、小花同學(xué)接受通知,選擇是:
4、彈出通話中的窗體,雙發(fā)都可以選擇結(jié)束通話:
5、結(jié)束通話之后,消息框中會(huì)廣告消息
總結(jié):
這個(gè)聊天程序主要都是用到了WCF的雙工通信。沒(méi)有用兩臺(tái)機(jī)子測(cè)試,我在我的筆記本上開(kāi)了一個(gè)服務(wù)端和一個(gè)客戶端,用了一個(gè)帶耳麥的耳機(jī),聲音效果良好。
其實(shí)這個(gè)東西做完整還有很多細(xì)活,這個(gè)只是Demo,非常的粗糙。最近會(huì)很忙,期待將來(lái)的某一天能空去完善。如果實(shí)現(xiàn)了視頻的功能我會(huì)寫第四篇的。
代碼: http://files.cnblogs.com/zhuqil/Zql_src.rar
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
