WPF+WCF一步一步打造音频聊天室(三):语音聊天

系统 1489 0
前一篇文章中实现了文字聊天和共享白板的功能,这篇文章中,我将在前一篇文章的基础上实现语音聊天的功能。语音聊天要比文字聊天和共享白板难度要大一点。

实现的大概的流程为:

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、两个用户登录:
  
    
      1
    
  
    
      2
    
  
        2、选择小花,点击按钮。麒麟向小花同学发起语音聊天:
  
    
      3
    
  
        3、小花同学接受通知,选择是:
  
    
      4
    
  
        4、弹出通话中的窗体,双发都可以选择结束通话:
  
    
      5
    
  
        5、结束通话之后,消息框中会广告消息
  
    
      6
    
  
    
      
        总结:
      
    
  
    
    
    这个聊天程序主要都是用到了WCF的双工通信。没有用两台机子测试,我在我的笔记本上开了一个服务端和一个客户端,用了一个带耳麦的耳机,声音效果良好。
  
    其实这个东西做完整还有很多细活,这个只是Demo,非常的粗糙。最近会很忙,期待将来的某一天能空去完善。如果实现了视频的功能我会写第四篇的。
  
    
      
        代码:
      
    
    
      http://files.cnblogs.com/zhuqil/Zql_src.rar
    
  

WPF+WCF一步一步打造音频聊天室(三):语音聊天


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论