SignalR+NAudio实现语音会话[WPF]
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lordwish/article/details/51811761
NAudio是一个功能很丰富的.NET语音处理类库,SignalR则微软推出的实时通信框架,结合两者来实现简单的语音会话聊天应用,主要功能包括在线终端列表刷新、会话请求、会话拒绝、会话繁忙、会话结束。和之前写的视频会话示例类似,只不过上个是视频,这个是语音。
1 服务端及辅助类
1.1 创建服务端
客户端方法主要包括:发送会话请求、会话状态监听、打开语音、发送语音、接收语音、关闭语音。对语音进行操作的方法参考了NAudio的源码,有兴趣的可以到GitHub上查看(NAudio)。
1.2 在线终端列表刷新
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="240"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Border Name="bdConnect" Visibility="Hidden" Width="320" Height="320" BorderThickness="2" BorderBrush="Khaki">
<TextBlock FontSize="24"><Run>正在与</Run><Run Name="txtName"></Run><Run>语音会话中...</Run></TextBlock>
</Border>
<Button Name="btnClose" Visibility="Hidden" Width="90" Height="30" Click="btnClose_Click">关闭</Button>
<ComboBox Grid.Row="1" Visibility="Hidden" x:Name="ComboDevices"></ComboBox>
</Grid>
<DataGrid Grid.Column="1" Name="dgList" AutoGenerateColumns="False" MouseDoubleClick="dgList_MouseDoubleClick">
<DataGrid.Columns>
<DataGridTextColumn Header="IP" Binding="{Binding ClientIP,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="MAC" Binding="{Binding ClientMac,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Name" Binding="{Binding ClientName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
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<ClientModel, ClientModel, string, MessageState>("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是个好东西,对于程序员来讲就不用多说了吧!
SignalR+NAudio实现语音会话[WPF]的更多相关文章
- SignalR+AForge实现视频会话[WPF]
原文:SignalR+AForge实现视频会话[WPF] 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lordwish/article/detai ...
- 【SignalR学习系列】5. SignalR WPF程序
首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...
- AngularJS+ASP.NET MVC+SignalR实现消息推送
原文:AngularJS+ASP.NET MVC+SignalR实现消息推送 背景 OA管理系统中,员工提交申请单,消息实时通知到相关人员及时进行审批,审批之后将结果推送给用户. 技术选择 最开始发现 ...
- 如何使用Microsoft技术栈
Microsoft技术栈最近有大量的变迁,这使得开发人员和领导者都想知道他们到底应该关注哪些技术.Microsoft自己并不想从官方层面上反对Silverlight这样的技术,相对而言他们更喜欢让这种 ...
- being词典案例分析
一.调研评测: 1.软件bug: 1.输入空格分号回车之后并不给用户报错,说明他的异常处理机制有问题. 2.对于中文的很多口头语和方言,并不能给出翻译或者说,也并没有给出网络搜索后的结果. 3.添加生 ...
- QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件
QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...
- PHP微信公众号开发之自动回复
先把源码类发出来 <?php /** 自己封装 微信 开发api */ header('Content-type: text/html; charset=utf-8');#设置头信息 class ...
- [SIP00]SIP 概念总结
SIP --------------------------- Session Initiation Protocol --------------------------- create, ...
- chrome jssip
WebRTC 实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力 ...
随机推荐
- Incapsula免费日本CDN加速和CDNZZ香港CDN节点加速
Incapsula免费日本CDN加速和CDNZZ香港CDN节点加速 免费的CDN对于那些将空间放在美国的博客网站加速效果是最好的,CDN可以解决国内连接美国的网络线路经常抽风和访问速度时好时坏的问题, ...
- poi完美word转html(表格、图片、样式)
直入正题,需求为页面预览word文档,用的是poi3.8,以下代码支持表格.图片,不支持分页,只支持doc,不支持docx: /** * */ import java.io.BufferedWrite ...
- 9. ZooKeeper之搭建单机模式。
转自:https://blog.csdn.net/en_joker/article/details/78673456 在集群和单机两种模式下,我们基本完成了分别针对生产环境和开发环境ZooKeeper ...
- 1.1 Introduction中 Topics and Logs官网剖析(博主推荐)
不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Topics and Logs 话题和日志 (Topic和Log) Let's fi ...
- PythonNET网络编程4
本地套接字 Linux 文件 b(块设备文件) c(字符设备文件) d(目录) -(普通文件) l(链接) s(套接字) p(管道) 作用:用于本地不同的程序间进行通信 创建流程 创建本地套接字 so ...
- 在CentOS7 开发与部署 asp.net core app笔记
原文:在CentOS7 开发与部署 asp.net core app笔记 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lihongzhai/art ...
- easy ui 验证
$('#IdentityCertificate').validatebox({required:true}); $('#memberName').validatebox({required:true} ...
- 【CS Round #43 D】Bad Triplet
[链接]点击打开链接 [题意] 给你n个点m条边的无权无向联通图; 让你找3个点A,B,C 使得A->B=B->C=A->C 这里X->Y表示点X到点Y的最短路长度. [题解] ...
- 一次性能优化将filter转换
有一条SQL性能有问题,在运行计划中发现filter.遇到它要小心了,类似于nestloop.我曾经的blog对它有研究探索运行计划中filter的原理.用exists极易引起filter. 优化前: ...
- C++学习笔记(达内视频版)
达内C++(陈宗权主讲) 第一天: 课程分为Core C++(标准C++.不依赖操作系统)和Unix C++. 1.配置bash,运行.sh文件. vi bash_profile 在"pat ...