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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lordwish/article/details/51811761

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


1 服务端及辅助类

1.1 创建服务端

[ 使用WPF创建SignalR服务端]

1.2 在线终端列表刷新


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

1.3 通信辅助类

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

  1. public enum MessageState
  2. {
  3. AudioApply,//语音会话请求
  4. AudioOpen,//语音会话开始
  5. AudioRefuse,//语音会话拒绝
  6. AudioBusy,//语音会话繁忙
  7. AudioOver,//语音会话结束
  8. VideoApply,//视频会话请求
  9. VideoOpen,//视频会话开始
  10. VideoRefuse,//视频会话拒绝
  11. VideoBusy,//视频会话繁忙
  12. VideoOver,//视频会话结束
  13. Null
  14. }

1.4 服务端方法

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

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

2 客户端方法

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

1.2 在线终端列表刷新

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

  1. <Grid>
  2. <Grid.ColumnDefinitions>
  3. <ColumnDefinition></ColumnDefinition>
  4. <ColumnDefinition Width="240"></ColumnDefinition>
  5. </Grid.ColumnDefinitions>
  6. <Grid>
  7. <Grid.RowDefinitions>
  8. <RowDefinition></RowDefinition>
  9. <RowDefinition></RowDefinition>
  10. </Grid.RowDefinitions>
  11. <Border Name="bdConnect" Visibility="Hidden" Width="320" Height="320" BorderThickness="2" BorderBrush="Khaki">
  12. <TextBlock FontSize="24"><Run>正在与</Run><Run Name="txtName"></Run><Run>语音会话中...</Run></TextBlock>
  13. </Border>
  14. <Button Name="btnClose" Visibility="Hidden" Width="90" Height="30" Click="btnClose_Click">关闭</Button>
  15. <ComboBox Grid.Row="1" Visibility="Hidden" x:Name="ComboDevices"></ComboBox>
  16. </Grid>
  17. <DataGrid Grid.Column="1" Name="dgList" AutoGenerateColumns="False" MouseDoubleClick="dgList_MouseDoubleClick">
  18. <DataGrid.Columns>
  19. <DataGridTextColumn Header="IP" Binding="{Binding ClientIP,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
  20. <DataGridTextColumn Header="MAC" Binding="{Binding ClientMac,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
  21. <DataGridTextColumn Header="Name" Binding="{Binding ClientName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
  22. </DataGrid.Columns>
  23. </DataGrid>
  24. </Grid>

2.2 发送会话请求

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

2.3 监听会话状态

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

  1. private void ConnectListener()
  2. {
  3. try
  4. {
  5. MyHub.On<ClientModel, ClientModel, string, MessageState>("messageconnect", (sender, receiver, id, state) =>
  6. {
  7. switch (state)
  8. {
  9. case MessageState.AudioApply:
  10. {
  11. if (messageid == "")
  12. {
  13. var result = MessageBox.Show("是否接收" + sender.ClientName + "的会话请求?", "会话提醒", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
  14. if (result == MessageBoxResult.Yes)
  15. {
  16. this.Dispatcher.Invoke(delegate
  17. {
  18. client = sender;
  19. messageid = id;
  20. txtName.Text = sender.ClientName;
  21. _addressReturn = sender.ClientIP;
  22. bdConnect.Visibility = Visibility.Visible;
  23. btnClose.Visibility = Visibility.Visible;
  24. OpenAudio();
  25. });
  26. }
  27. else
  28. {
  29. MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.AudioRefuse);
  30. }
  31. }
  32. else
  33. {
  34. MyHub.Invoke("MessageConnect", receiver, sender, id, MessageState.AudioBusy);
  35. }
  36. }
  37. break;
  38. case MessageState.AudioOpen:
  39. {
  40. this.Dispatcher.Invoke(delegate
  41. {
  42. client = sender;
  43. messageid = id;
  44. txtName.Text = sender.ClientName;
  45. _addressReturn = sender.ClientIP;
  46. bdConnect.Visibility = Visibility.Visible;
  47. btnClose.Visibility = Visibility.Visible;
  48. OpenAudio();
  49. });
  50. }
  51. break;
  52. case MessageState.AudioRefuse:
  53. {
  54. MessageBox.Show(sender.ClientName+"拒绝了您的会话请求!");
  55. }
  56. break;
  57. case MessageState.AudioBusy:
  58. {
  59. MessageBox.Show(sender.ClientName + "正忙,请稍后连接!");
  60. }
  61. break;
  62. case MessageState.AudioOver:
  63. {
  64. this.Dispatcher.Invoke(delegate
  65. {
  66. CloseAudio();
  67. });
  68. }
  69. break;
  70. }
  71. });
  72. }
  73. catch (Exception)
  74. {
  75. throw;
  76. }
  77. }

2.4 打开音频

定义变量:

  1. public int Port
  2. {
  3. get { return _port; }
  4. set { _port = value; }
  5. }
  6. private string _addressReturn = "";
  7. public string AddressReturn
  8. {
  9. get { return _addressReturn; }
  10. set { _addressReturn = value; }
  11. }
  12. private WaveIn _waveIn;
  13. private UdpClient _udpSender;
  14. private UdpClient _udpListener;
  15. private IWavePlayer _waveOut;
  16. private BufferedWaveProvider _waveProvider;
  17. private INetworkChatCodec _selectedCodec;
  18. private volatile bool _connected;
  19. IPEndPoint _endPoint;
  20. int _inputDeviceNumber;

具体方法:

  1. private void OpenAudio()
  2. {
  3. FindDevices();
  4. _selectedCodec = new UncompressedPcmChatCodec();
  5. //制定服务端的IP和端口
  6. _endPoint = new IPEndPoint(IPAddress.Parse(AddressReturn), Port);
  7. //发送消息
  8. Connect(_endPoint, _inputDeviceNumber, _selectedCodec);
  9. }
  10. private void FindDevices()
  11. {
  12. for (int n = 0; n < WaveIn.DeviceCount; n++)
  13. {
  14. var capabilities = WaveIn.GetCapabilities(n);
  15. ComboDevices.Items.Add(capabilities.ProductName);
  16. }
  17. if (ComboDevices.Items.Count > 1)
  18. {
  19. _inputDeviceNumber = 1;
  20. }
  21. else if (ComboDevices.Items.Count == 0)
  22. {
  23. _inputDeviceNumber = 0;
  24. }
  25. else
  26. {
  27. _inputDeviceNumber = -1;
  28. }
  29. }

2.5 进行语音通话

  1. private void Connect(IPEndPoint endPoint, int inputDeviceNumber, INetworkChatCodec codec)
  2. {
  3. try
  4. {
  5. _waveIn = new WaveIn();
  6. _waveIn.BufferMilliseconds = 50;//延时 n
  7. _waveIn.DeviceNumber = inputDeviceNumber;
  8. _waveIn.WaveFormat = codec.RecordFormat;
  9. _waveIn.DataAvailable += waveIn_DataAvailable;
  10. _waveIn.StartRecording();
  11. _udpSender = new UdpClient();
  12. _udpListener = new UdpClient(endPoint.Port);
  13. // To allow us to talk to ourselves for test purposes:
  14. // http://stackoverflow.com/questions/687868/sending-and-receiving-udp-packets-between-two-programs-on-the-same-computer
  15. _udpListener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  16. //udpListener.Client.Bind(endPoint);
  17. _udpSender.Connect(endPoint);
  18. _waveOut = new WaveOut();
  19. _waveProvider = new BufferedWaveProvider(codec.RecordFormat);
  20. _waveOut.Init(_waveProvider);
  21. _waveOut.Play();
  22. _connected = true;
  23. var state = new ListenerThreadState { Codec = codec, EndPoint = endPoint };
  24. ThreadPool.QueueUserWorkItem(ListenerThread, state);
  25. //this.btnAudio.Content = "断开语音";
  26. }
  27. catch
  28. {
  29. MessageBox.Show("请检查您的设备连接是否正常~!");
  30. }
  31. }
  32. private void waveIn_DataAvailable(object sender, WaveInEventArgs e)
  33. {
  34. try
  35. {
  36. byte[] encoded = _selectedCodec.Encode(e.Buffer, 0, e.BytesRecorded);
  37. int num = _udpSender.Send(encoded, encoded.Length);
  38. }
  39. catch (System.Exception ex)
  40. {
  41. System.Console.WriteLine("Exception:" + ex.ToString());
  42. }
  43. }

2.6 监听端口接收语音

  1. private void ListenerThread(object state)
  2. {
  3. var listenerThreadState = (ListenerThreadState)state;
  4. var endPoint = listenerThreadState.EndPoint;
  5. try
  6. {
  7. while (_connected)
  8. {
  9. try
  10. {
  11. byte[] b = _udpListener.Receive(ref endPoint);
  12. byte[] decoded = listenerThreadState.Codec.Decode(b, 0, b.Length);
  13. _waveProvider.AddSamples(decoded, 0, decoded.Length);
  14. }
  15. catch
  16. {
  17. // ignored
  18. }
  19. }
  20. }
  21. catch (SocketException)
  22. {
  23. }
  24. }
  25. class ListenerThreadState
  26. {
  27. public IPEndPoint EndPoint { get; set; }
  28. public INetworkChatCodec Codec { get; set; }
  29. }

2.7 关闭语音

  1. private void btnClose_Click(object sender, RoutedEventArgs e)
  2. {
  3. CloseAudio();
  4. }
  5. private void CloseAudio()
  6. {
  7. client = null;
  8. messageid = "";
  9. bdConnect.Visibility = Visibility.Hidden;
  10. btnClose.Visibility = Visibility.Hidden;
  11. _connected = false;
  12. _waveIn.DataAvailable -= waveIn_DataAvailable;
  13. _waveIn.StopRecording();
  14. _waveOut.Stop();
  15. _udpSender.Close();
  16. _udpListener.Close();
  17. if (_waveIn != null)
  18. {
  19. _waveIn.Dispose();
  20. }
  21. if (_waveOut != null)
  22. {
  23. _waveOut.Dispose();
  24. }
  25. if (_selectedCodec != null)
  26. {
  27. _selectedCodec.Dispose();
  28. }
  29. }

NAudio是个功能很丰富的语音处理类库,但比较遗憾的是没有回音消除及降噪之类的高级功能。NAudio包含了NSpeex项目,都是基于C#语言的,而NSpeex则是源自于C++的Speex项目,有兴趣的可以到GitHub上找。

GitHub是个好东西,对于程序员来讲就不用多说了吧!

SignalR+NAudio实现语音会话[WPF]的更多相关文章

  1. SignalR+AForge实现视频会话[WPF]

    原文:SignalR+AForge实现视频会话[WPF] 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lordwish/article/detai ...

  2. 【SignalR学习系列】5. SignalR WPF程序

    首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...

  3. AngularJS+ASP.NET MVC+SignalR实现消息推送

    原文:AngularJS+ASP.NET MVC+SignalR实现消息推送 背景 OA管理系统中,员工提交申请单,消息实时通知到相关人员及时进行审批,审批之后将结果推送给用户. 技术选择 最开始发现 ...

  4. 如何使用Microsoft技术栈

    Microsoft技术栈最近有大量的变迁,这使得开发人员和领导者都想知道他们到底应该关注哪些技术.Microsoft自己并不想从官方层面上反对Silverlight这样的技术,相对而言他们更喜欢让这种 ...

  5. being词典案例分析

    一.调研评测: 1.软件bug: 1.输入空格分号回车之后并不给用户报错,说明他的异常处理机制有问题. 2.对于中文的很多口头语和方言,并不能给出翻译或者说,也并没有给出网络搜索后的结果. 3.添加生 ...

  6. QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件

    QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...

  7. PHP微信公众号开发之自动回复

    先把源码类发出来 <?php /** 自己封装 微信 开发api */ header('Content-type: text/html; charset=utf-8');#设置头信息 class ...

  8. [SIP00]SIP 概念总结

    SIP ---------------------------   Session Initiation Protocol ---------------------------   create, ...

  9. chrome jssip

    WebRTC 实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力 ...

随机推荐

  1. shell脚本中的反引号,单引号,双引号与反斜杠

    转自:http://blog.sina.com.cn/s/blog_6561ca8c0102we2i.html 反引号位 (`)经常被忽略,而且容易与单引号弄混.它位于键盘的Tab键的上方.1键的左方 ...

  2. docker构建一个简易镜像

    一 下载centos镜像 docker pull centos 二 启动镜像 [root@Centos-node3 ~]# docker run -it --name my_ng centos bas ...

  3. uiautomator_python使用汇总

    1.判断按钮状态 if d(resourceId="id",enabled=False): #判断当前按钮是否为未激活状态,为True则为激活状态2.获取toast提示语 d.to ...

  4. TPS54232-------电源管理芯片

    TPS54232 DC DC开关稳压器 电源管理芯片 放大器俗称功放 注意看芯片的次序1~8是如何排布的,这个规律一般是固定的 也许我们整理多了就能发现引脚的宽度和长度都是规格好的. 下面是封装: 所 ...

  5. 使用PHP实现双向队列

    使用PHP实现双向队列 一.总结 就是几个array函数 push pop shift unshift n. 移动:变化:手段:轮班 vi. 移动:转变:转换 vt. 转移:改变:替换 二.使用PHP ...

  6. Angular7环境搭建报错

    昨天写的2019年Angular7——安装搭建路由方法不太正统,今天又去翻了下angular官网,跟着上面的环境搭建与部署走了一遍 从安装@angular/cli命令行工具开始 本篇主要记录下搭建过程 ...

  7. ZOJ 1101 Gamblers 二分

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=101 让你在一堆互不相同的数中查找是否有a=b+c+d,若有不同的解,则输出最大 ...

  8. 27、从零写UVC驱动之分析数据传输(设置ubuntu通过串口打印,指定打印到文件,ubuntu切换root用户)

    A. 设置ubuntu让它从串口0输出printk信息a. 设置vmware添加serial port, 使用文件作为串口(在vmware中设置,文件是保存在windows中)b. 启动ubuntu, ...

  9. 关于spyder的一些用法

    目前还不会用spyder,感觉不习惯,也没怎么用MATLAB 能记住几点算几点吧 1,双击程序左侧栏,加断点 1,按住Ctrl,点击函数,进入函数

  10. link和@import引入外部样式的区别

    原文: 简书原文:https://www.jianshu.com/p/14f99062f29a 大纲 前言 1.隶属上的差别 2.加载顺序的不同 3.兼容性上的差别 4.使用DOM控制样式时的差别 5 ...