SignalR+NAudio实现语音会话[WPF]

NAudio是一个功能很丰富的.NET语音处理类库,SignalR则微软推出的实时通信框架,结合两者来实现简单的语音会话聊天应用,主要功能包括在线终端列表刷新、会话请求、会话拒绝、会话繁忙、会话结束。和之前写的视频会话示例类似,只不过上个是视频,这个是语音。


1 服务端及辅助类

1.1 创建服务端

[ 使用WPF创建SignalR服务端]

1.2 在线终端列表刷新

[WPF+SignalR实现用户列表实时刷新]

1.3 通信辅助类

类似之前的视频会话,再添加几个枚举

    public enum MessageState
    {
        AudioApply,//语音会话请求
        AudioOpen,//语音会话开始
        AudioRefuse,//语音会话拒绝
        AudioBusy,//语音会话繁忙
        AudioOver,//语音会话结束
        VideoApply,//视频会话请求
        VideoOpen,//视频会话开始
        VideoRefuse,//视频会话拒绝
        VideoBusy,//视频会话繁忙
        VideoOver,//视频会话结束
        Null
    }

1.4 服务端方法

依然沿用视频会话中的方法,完全不用变

public Task MessageConnect(ClientModel sender,ClientModel receiver,string messageid,MessageState status)
        {
            return Clients.Client(receiver.ConnectionId).messageconnect(receiver, sender, messageid, status);
        }

2 客户端方法

客户端方法主要包括:发送会话请求、会话状态监听、打开语音、发送语音、接收语音、关闭语音。对语音进行操作的方法参考了NAudio的源码,有兴趣的可以到GitHub上查看(NAudio)。

1.2 在线终端列表刷新

[WPF+SignalR实现用户列表实时刷新]

    
        
            
            "240">
        
        
            
                
                
            
            "bdConnect" Visibility="Hidden" Width="320" Height="320" BorderThickness="2" BorderBrush="Khaki">
                "24">正在与"txtName">语音会话中...
            
            
            "1" Visibility="Hidden" x:Name="ComboDevices">
        
        "1" Name="dgList" AutoGenerateColumns="False" MouseDoubleClick="dgList_MouseDoubleClick">
            
                "IP" Binding="{Binding ClientIP,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
                "MAC" Binding="{Binding ClientMac,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
                "Name" Binding="{Binding ClientName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
            
        
    

2.2 发送会话请求

        private void dgList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (dgList.SelectedItem == null)
            {
                return;
            }
            client = (ClientModel)dgList.SelectedItem;
            var result = MessageBox.Show("确定要进行语音会话?", "会话提醒", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.OK);
            if (result == MessageBoxResult.OK && client != null)
            {
                string messageid = Guid.NewGuid().ToString();
                MyHub.Invoke("MessageConnect", MyClient, client, messageid, MessageState.AudioApply);
            }
        }

2.3 监听会话状态

包含了接受会话、拒绝会话、会话繁忙、结束会话状态的监听。

        private void ConnectListener()
        {
            try
            {
                MyHub.On("messageconnect", (sender, receiver, id, state) =>
                {
                    switch (state)
                    {
                        case MessageState.AudioApply:
                            {
                                if (messageid == "")
                                {
                                    var result = MessageBox.Show("是否接收" + sender.ClientName + "的会话请求?", "会话提醒", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
                                    if (result == MessageBoxResult.Yes)
                                    {
                                        this.Dispatcher.Invoke(delegate
                                        {
                                            client = sender;
                                            messageid = id;
                                            txtName.Text = sender.ClientName;
                                            _addressReturn = sender.ClientIP;
                                            bdConnect.Visibility = Visibility.Visible;
                                            btnClose.Visibility = Visibility.Visible;
                                            OpenAudio();
                                        });
                                    }
                                    else
                                    {
                                        MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.AudioRefuse);
                                    }
                                }
                                else
                                {
                                    MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.AudioBusy);
                                }
                            }
                            break;
                        case MessageState.AudioOpen:
                            {
                                this.Dispatcher.Invoke(delegate
                                {
                                    client = sender;
                                    messageid = id;
                                    txtName.Text = sender.ClientName;
                                    _addressReturn = sender.ClientIP;
                                    bdConnect.Visibility = Visibility.Visible;
                                    btnClose.Visibility = Visibility.Visible;
                                    OpenAudio();
                                });
                            }
                            break;
                        case MessageState.AudioRefuse:
                            {
                                MessageBox.Show(sender.ClientName+"拒绝了您的会话请求!");
                            }
                            break;
                        case MessageState.AudioBusy:
                            {
                                MessageBox.Show(sender.ClientName + "正忙,请稍后连接!");
                            }
                            break;
                        case MessageState.AudioOver:
                            {
                                this.Dispatcher.Invoke(delegate
                                {
                                    CloseAudio();
                                });
                            }
                            break;
                    }
                });
            }
            catch (Exception)
            {

                throw;
            }

        }

2.4 打开音频

定义变量:

        public int Port
        {
            get { return _port; }
            set { _port = value; }
        }

        private string _addressReturn = "";

        public string AddressReturn
        {
            get { return _addressReturn; }
            set { _addressReturn = value; }
        }

        private WaveIn _waveIn;
        private UdpClient _udpSender;
        private UdpClient _udpListener;
        private IWavePlayer _waveOut;
        private BufferedWaveProvider _waveProvider;
        private INetworkChatCodec _selectedCodec;
        private volatile bool _connected;
        IPEndPoint _endPoint;
        int _inputDeviceNumber;

具体方法:

        private void OpenAudio()
        {
            FindDevices();
            _selectedCodec = new UncompressedPcmChatCodec();
            //制定服务端的IP和端口
            _endPoint = new IPEndPoint(IPAddress.Parse(AddressReturn), Port);
            //发送消息
            Connect(_endPoint, _inputDeviceNumber, _selectedCodec);
        }
        private void FindDevices()
        {
            for (int n = 0; n < WaveIn.DeviceCount; n++)
            {
                var capabilities = WaveIn.GetCapabilities(n);
                ComboDevices.Items.Add(capabilities.ProductName);
            }
            if (ComboDevices.Items.Count > 1)
            {
                _inputDeviceNumber = 1;
            }
            else if (ComboDevices.Items.Count == 0)
            {
                _inputDeviceNumber = 0;
            }
            else
            {
                _inputDeviceNumber = -1;
            }
        } 

2.5 进行语音通话

        private void Connect(IPEndPoint endPoint, int inputDeviceNumber, INetworkChatCodec codec)
        {
            try
            {
                _waveIn = new WaveIn();
                _waveIn.BufferMilliseconds = 50;//延时 n
                _waveIn.DeviceNumber = inputDeviceNumber;
                _waveIn.WaveFormat = codec.RecordFormat;
                _waveIn.DataAvailable += waveIn_DataAvailable;
                _waveIn.StartRecording();

                _udpSender = new UdpClient();
                _udpListener = new UdpClient(endPoint.Port);

                // To allow us to talk to ourselves for test purposes:
                // http://stackoverflow.com/questions/687868/sending-and-receiving-udp-packets-between-two-programs-on-the-same-computer
                _udpListener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                //udpListener.Client.Bind(endPoint);

                _udpSender.Connect(endPoint);

                _waveOut = new WaveOut();
                _waveProvider = new BufferedWaveProvider(codec.RecordFormat);
                _waveOut.Init(_waveProvider);
                _waveOut.Play();

                _connected = true;
                var state = new ListenerThreadState { Codec = codec, EndPoint = endPoint };
                ThreadPool.QueueUserWorkItem(ListenerThread, state);
                //this.btnAudio.Content = "断开语音";
            }
            catch
            {
                MessageBox.Show("请检查您的设备连接是否正常~!");
            }
        }

        private void waveIn_DataAvailable(object sender, WaveInEventArgs e)
        {
            try
            {
                byte[] encoded = _selectedCodec.Encode(e.Buffer, 0, e.BytesRecorded);
                int num = _udpSender.Send(encoded, encoded.Length);
            }
            catch (System.Exception ex)
            {
                System.Console.WriteLine("Exception:" + ex.ToString());
            }

        }

2.6 监听端口接收语音

private void ListenerThread(object state)
        {
            var listenerThreadState = (ListenerThreadState)state;
            var endPoint = listenerThreadState.EndPoint;
            try
            {
                while (_connected)
                {
                    try
                    {
                        byte[] b = _udpListener.Receive(ref endPoint);
                        byte[] decoded = listenerThreadState.Codec.Decode(b, 0, b.Length);
                        _waveProvider.AddSamples(decoded, 0, decoded.Length);
                    }
                    catch
                    {
                        // ignored
                    }
                }
            }
            catch (SocketException)
            {
            }
        }

        class ListenerThreadState
        {
            public IPEndPoint EndPoint { get; set; }
            public INetworkChatCodec Codec { get; set; }
        }

2.7 关闭语音

        private void btnClose_Click(object sender, RoutedEventArgs e)
        {
            CloseAudio();
        }

        private void CloseAudio()
        {
            client = null;
            messageid = "";
            bdConnect.Visibility = Visibility.Hidden;
            btnClose.Visibility = Visibility.Hidden;

            _connected = false;
            _waveIn.DataAvailable -= waveIn_DataAvailable;
            _waveIn.StopRecording();
            _waveOut.Stop();

            _udpSender.Close();
            _udpListener.Close();
            if (_waveIn != null)
            {
                _waveIn.Dispose();
            }
            if (_waveOut != null)
            {
                _waveOut.Dispose();
            }
            if (_selectedCodec != null)
            {
                _selectedCodec.Dispose();
            }
        }

NAudio是个功能很丰富的语音处理类库,但比较遗憾的是没有回音消除及降噪之类的高级功能。NAudio包含了NSpeex项目,都是基于C#语言的,而NSpeex则是源自于C++的Speex项目,有兴趣的可以到GitHub上找。
GitHub是个好东西,对于程序员来讲就不用多说了吧!

你可能感兴趣的:(WPF,SignalR)