原文: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 通信辅助类

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

    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实现用户列表实时刷新]

    <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]的更多相关文章

  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. CentOS搭建xfce桌面+VNC教程

    CentOS搭建xfce桌面+VNC教程 Linux的安全与性能向来为开发者所称道,你可以轻松地在搜索引擎中找到各种Linux优越性的说辞,其中不乏Linux的激进者.特别是当你步入VPS领域,更多地 ...

  2. openGLES(三)

    着色器语言 ​ 着色器语言基于c/c++语言,但是还是有区别的,它不是面向对象 数据类型概述 ​ 内建的数据类型:浮点型(float).布尔型(bool).整形(int),矩阵(matrix)以及向量 ...

  3. vue实现多语言国际化(vue-i18n),结合element ui、vue-router、echarts以及joint等。

    老板说我们的项目要和国际接轨,于是乎,加上了多语言(vue-i18n).项目用到的UI框架是element ui ,后续echarts.joint等全都得加上多语言. 一.言归正传,i18n在vue项 ...

  4. ThinkPHP5.0---URL访问

    ThinkPHP 5.0 在没有启用路由的情况下典型的URL访问规则是(采用 PATH_INFO 访问地址): http://serverName/index.php(或者其它应用入口文件)/模块/控 ...

  5. xpath使用方法详解id 、starts-with、contains、text()和last() 的用法

    1.XPATH使用方法 使用XPATH有如下几种方法定位元素(相比CSS选择器,方法稍微多一点): a.通过绝对路径定位元素(不推荐!) WebElement ele = driver.findEle ...

  6. Android 解决RecyclerView删除Item导致位置错乱的问题

    RecyclerView的刷新分为内容变化和结构变化,结构变化比如remove和insert等并不会导致viewholder的更新,所以有时候我们使用 notifyItemRemoved(positi ...

  7. 格式化时间的一个好方法(补充moment)

    /** * * 格式化时间 * @param {*} time * @param {*} fmt * @returns * time(new Date(), 'yyyy/MM/dd') ==> ...

  8. Visual studio编译器窗体重置

    针对vs2003: 第一种方法 在"工具"->"选项"对话框里面:  在"选项"以下的"常规"有个"重置 ...

  9. Git 基本使用方法

    Git有一个优点,在本地的每个项目都是一个完整的仓库,除了须要从网络拉取和推送到网络之外,其它全部的操作都能够在本地完毕. 本文简单地介绍怎样在本地使用Git来对文件进行管理,下一篇文章再来说一下分支 ...

  10. Sass(SCSS)中文手册——入门

    简书原文 https://www.jianshu.com/p/e82c27aa05c7 前言 该中文手册是我在Sass中文文档的基础上编辑的,或者也可以理解为就是Sass中文文档的翻版.之所以有这篇文 ...