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))能力 ...
随机推荐
- 1.1 Introduction中 Consumers官网剖析(博主推荐)
不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ Consumers 消费者(Consumers) Consumers label t ...
- JS如何动态生成变量名[重点]
解决方案: function create_variable(num){ var name = "test_"+num; //生成函数名 ...
- Flask项目之手机端租房网站的实战开发(十一)
说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 接着上一篇博客继续往下写 :https://blog.csdn.net/qq_41782425/article/details/8 ...
- Redis源代码分析(八)--- t_hash哈希转换
在上次的zipmap分析完之后,事实上关于redis源码结构体部分的内容事实上已经所有结束了.由于以下还有几个和结构体相关的操作类,就页把他们归并到struct包下了.这类的文件有:t_hash.c, ...
- Lucy_Hedgehog techniques
在project euler 的第\(10\)题的 \(forum\) 中 Lucy Hedgehog 提到的这种方法. 求 \(n\) 以内素数个数以及求 \(n\) 以内素数和的算法. 定义\(S ...
- 微信支付v2开发(7) 告警通知
本文介绍微信支付中如何获得告警通知. 一.告警通知 为了及时通知商户异常,提高商户在微信平台的服务质量.微信后台会向商户推送告警通知,包括发货延迟.调用失败.通知失败等情况,通知的地址是商户在申请支付 ...
- angular 创建服务
一:新建服务模块和服务文件 ng g module services --spec=false ng g service services/quote --spec=false 二:在quote.se ...
- java程序猿经常使用的工具名称--知道中文意思吗
在学习java的时候常常会碰到一些单词,可是一般的时候也不是非常在意这个单词的意思,而是能够了解到这个工具或者框架能够做什么就能够了.偶尔总结了一下还蛮有意思的.例如以下, 假设有遗漏,各位能够帮忙补 ...
- MySQL參数binlog-do-db对binlogs写入的影响
1. 环境描写叙述 目的:当数据库中设置了binlog-do-db时.在不同的binlog_format=statement | row | mixed 下对binlog的写入影响,这个在主从复制中会 ...
- 自旋锁spinlock解析
1 基础概念 自旋锁与相互排斥锁有点类似,仅仅是自旋锁不会引起调用者睡眠.假设自旋锁已经被别的运行单元保持.调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁."自旋"一词就 ...