程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WPF+WCF一步一步打造音頻聊天室(三):語音聊天

WPF+WCF一步一步打造音頻聊天室(三):語音聊天

編輯:關於.NET

前一篇文章中實現了文字聊天和共享白板的功能,這篇文章中,我將在前一篇文章的基礎上實現語音聊天的功能。語音聊天要比文字聊 天和共享白板難度要大一點。

實現的大概的流程為:

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://zhuqil.cnblogs.com

代碼:http://files.cnblogs.com/zhuqil/Zql_src.rar

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved