實現的大概的流程為:
1、一個聊天室成員向另外一個成員發起語音聊天請求
2、這個請求將被送至WCF服務端,WCF的雙工通知被邀請人。
3、被邀請人接到通知,他可以選擇接受或者拒絕語音聊天的請求。
4、如果拒絕,將通知請求者拒絕語音聊天
5、如果同意,邀請者和被邀請者的客戶端將進行語音聊天,此時客戶端會開啟一個播放聲音和接受聲音的線程。這里用到了一個開源的wave類庫,在 http://www.lumisoft.ee/lswww/download/downloads/Examples/ 可以下載。聲音的通信使用到了UDPClient 類。這個類使用 UDP 與網絡服務通訊。UDP 的優點是簡單易用,并且能夠同時向多個地址廣播消息。UdpClient 類提供了一些簡單的方法,用于在阻止同步模式下發送和接收無連接 UDP 數據報。因為 UDP 是無連接傳輸協議,所以不需要在發送和接收數據前建立遠程主機連接。但您可以選擇使用下面兩種方法之一來建立默認遠程主機:
-
使用遠程主機名和端口號作為參數創建 UdpClient 類的實例。
-
創建 UdpClient 類的實例,然后調用 Connect 方法。
可以使用在UdpClient 中提供的任何一種發送方法將數據發送到遠程設備。使用 Receive 方法可以從遠程主機接收數據。
這篇文章使用了 Receive 方法從客戶端接受數據。然后通過WCF中存儲的IP地址,通過Send方法將其發送給客戶端。
下面我將在前一篇文章的基礎上實現這個語音聊天的功能。首先在客戶端添加聲音管理的類CallManager,這個類使用到了開源的wave類庫,代碼如下:
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 ; } } }
在服務端添加將接受到的聲音,發送給接受者的類,使用到了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服務中添加兩個方法:初始化語音通信和結束語音通信。
[OperationContract(IsOneWay = false )] bool InitiateCall( string username); [OperationContract(IsOneWay = true )] void EndCall(); 具體是實現代碼:
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 ; } }
還有部分做了修改的代碼見附件代碼。
下面看下演示的截圖:
1、兩個用戶登錄:
2、選擇小花,點擊按鈕。麒麟向小花同學發起語音聊天:
3、小花同學接受通知,選擇是:
4、彈出通話中的窗體,雙發都可以選擇結束通話:
5、結束通話之后,消息框中會廣告消息
總結:
這個聊天程序主要都是用到了WCF的雙工通信。沒有用兩臺機子測試,我在我的筆記本上開了一個服務端和一個客戶端,用了一個帶耳麥的耳機,聲音效果良好。
其實這個東西做完整還有很多細活,這個只是Demo,非常的粗糙。最近會很忙,期待將來的某一天能空去完善。如果實現了視頻的功能我會寫第四篇的。
代碼: http://files.cnblogs.com/zhuqil/Zql_src.rar
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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