前一篇文章中实现了文字聊天和共享白板的功能,这篇文章中,我将在前一篇文章的基础上实现语音聊天的功能。语音聊天要比文字聊天和共享白板难度要大一点。

实现的大概的流程为:

1、一个聊天室成员向另外一个成员发起语音聊天请求

2、这个请求将被送至WCF服务端,WCF的双工通知被邀请人。

3、被邀请人接到通知,他可以选择接受或者拒绝语音聊天的请求。

4、如果拒绝,将通知请求者拒绝语音聊天

5、如果同意,邀请者和被邀请者的客户端将进行语音聊天,此时客户端会开启一个播放声音和接受声音的线程。这里用到了一个开源的wave类库,在http://www.lumisoft.ee/lswww/download/downloads/Examples/可以下载。声音的通信使用到了UDPClient 类。这个类使用 UDP 与网络服务通讯。UDP 的优点是简单易用,并且能够同时向多个地址广播消息。UdpClient 类提供了一些简单的方法,用于在阻止同步模式下发送和接收无连接 UDP 数据报。因为 UDP 是无连接传输协议,所以不需要在发送和接收数据前建立远程主机连接。但您可以选择使用下面两种方法之一来建立默认远程主机:

使用远程主机名和端口号作为参数创建UdpClient 类的实例。

创建 UdpClient 类的实例,然后调用 Connect 方法。

可以使用在UdpClient 中提供的任何一种发送方法将数据发送到远程设备。使用 Receive 方法可以从远程主机接收数据。

这篇文章使用了Receive 方法从客户端接受数据。然后通过WCF中存储的IP地址,通过Send方法将其发送给客户端。

下面我将在前一篇文章的基础上实现这个语音聊天的功能。首先在客户端添加声音管理的类CallManager,这个类使用到了开源的wave类库,代码如下:

public class CallManager

{

private WaveIn _waveIn;

private WaveOut _waveOut;

private IPEndPoint _serverEndPoint;

private Thread _playSound;

private UdpClient _socket;

public CallManager(IPEndPoint serverEndpoint)

{

_serverEndPoint = serverEndpoint;

}

public void Start()

{

if (_waveIn != null || _waveOut != null)

{

throw new Exception("Call is allready started");

}

int waveInDevice = (Int32)Application.UserAppDataRegistry.GetValue("WaveIn", 0);

int waveOutDevice = (Int32)Application.UserAppDataRegistry.GetValue("WaveOut", 0);

_socket = new UdpClient(0); // opens a random available port on all interfaces

_waveIn = new WaveIn(WaveIn.Devices[waveInDevice], 8000, 16, 1, 400);

_waveIn.BufferFull += new BufferFullHandler(_waveIn_BufferFull);

_waveIn.Start();

_waveOut = new WaveOut(WaveOut.Devices[waveOutDevice], 8000, 16, 1);

_playSound = new Thread(new ThreadStart(playSound));

_playSound.IsBackground = true;

_playSound.Start();

}

private void playSound()

{

try

{

while (true)

{

lock (_socket)

{

if (_socket.Available != 0)

{

IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);

byte[] received = _socket.Receive(ref endpoint);

// todo: add codec

_waveOut.Play(received, 0, received.Length);

}

}

Thread.Sleep(1);

}

}

catch (ThreadAbortException)

{

}

catch

{

this.Stop();

}

}

void _waveIn_BufferFull(byte[] buffer)

{

lock (_socket)

{

//todo: add codec

_socket.Send(buffer, buffer.Length, _serverEndPoint);

}

}

public void Stop()

{

if (_waveIn != null)

{

_waveIn.Dispose();

}

if (_waveOut != null)

{

_waveOut.Dispose();

}

if (_playSound.IsAlive)

{

_playSound.Abort();

}

if (_socket != null)

{

_socket.Close();

_socket = null;

}

}

}

在服务端添加将接受到的声音,发送给接受者的类,使用到了UDPClient类:

public class UdpServer

{

private Thread _listenerThread;

private List _users = new List();

private UdpClient _udpSender = new UdpClient();

public IPAddress ServerAddress

{

get;

set;

}

public UdpClient UdpListener

{

get;

set;

}

public UdpServer()

{

try

{

ServerAddress = IPAddress.Parse("127.0.0.1");

}

catch

{

throw new Exception("Configuration not set propperly. View original source code");

}

}

public void Start()

{

UdpListener = new System.Net.Sockets.UdpClient(0);

_listenerThread = new Thread(new ThreadStart(listen));

_listenerThread.IsBackground = true;

_listenerThread.Start();

}

private void listen()

{

while (true)

{

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);

byte[] received = UdpListener.Receive(ref sender);

if (!_users.Contains(sender))

{

_users.Add(sender);

}

foreach (IPEndPoint endpoint in _users)

{

if (!endpoint.Equals(sender))

{

_udpSender.Send(received, received.Length, endpoint);

}

}

}

}

public void EndCall()

{

_listenerThread.Abort();

}

}

在WCF服务中添加两个方法:初始化语音通信和结束语音通信。

[OperationContract(IsOneWay = false)]

bool InitiateCall(string username);

[OperationContract(IsOneWay = true)]

void EndCall();

具体是实现代码:

public bool InitiateCall(string username)

{

ClientCallBack clientCaller = s_dictCallbackToUser[OperationContext.Current.GetCallbackChannel()];

ClientCallBack clientCalee = s_dictCallbackToUser.Values.Where(p => p.JoinChatUser.NickName == username).First();

if (clientCaller.Callee != null || clientCalee.Callee != null) // callee or caller is in another call

{

return false;

}

if (clientCaller == clientCalee)

{

return false;

}

if (clientCalee.Client.AcceptCall(clientCaller.JoinChatUser.NickName))

{

clientCaller.Callee = clientCalee.Client;

clientCalee.Callee = clientCaller.Client;

clientCaller.UdpCallServer = new UdpServer();

clientCaller.UdpCallServer.Start();

EmtpyDelegate separateThread = delegate()

{

IPEndPoint endpoint = new IPEndPoint(clientCaller.UdpCallServer.ServerAddress,

((IPEndPoint)clientCaller.UdpCallServer.UdpListener.Client.LocalEndPoint).Port);

clientCalee.Client.CallDetailes(endpoint, clientCaller.JoinChatUser.NickName, username);

clientCaller.Client.CallDetailes(endpoint, clientCaller.JoinChatUser.NickName, username);

foreach (var callback in s_dictCallbackToUser.Keys)

{

callback.NotifyMessage(String.Format("System:User \"{0}\" and user \"{1}\" have started a call",clientCaller.JoinChatUser.NickName, username));

}

};

separateThread.BeginInvoke(null, null);

return true;

}

else

{

return false;

}

}

public void EndCall()

{

ClientCallBack clientCaller = s_dictCallbackToUser[OperationContext.Current.GetCallbackChannel()];

ClientCallBack ClientCalee = s_dictCallbackToUser[clientCaller.Callee];

if (clientCaller.UdpCallServer != null)

{

clientCaller.UdpCallServer.EndCall();

}

if (ClientCalee.UdpCallServer != null)

{

ClientCalee.UdpCallServer.EndCall();

}

if (clientCaller.Callee != null)

{

foreach (var callback in s_dictCallbackToUser.Keys)

{

callback.NotifyMessage(String.Format("System:User \"{0}\" and user \"{1}\" have ended the call", clientCaller.JoinChatUser.NickName, ClientCalee.JoinChatUser.NickName));

}

clientCaller.Callee.EndCallClient();

clientCaller.Callee = null;

}

if (ClientCalee.Callee != null)

{

ClientCalee.Callee.EndCallClient();

ClientCalee.Callee = null;

}

}

还有部分做了修改的代码见附件代码。

下面看下演示的截图:

1、两个用户登录:

2、选择小花,点击按钮。麒麟向小花同学发起语音聊天:

3、小花同学接受通知,选择是:

4、弹出通话中的窗体,双发都可以选择结束通话:

5、结束通话之后,消息框中会广告消息

总结:

这个聊天程序主要都是用到了WCF的双工通信。没有用两台机子测试,我在我的笔记本上开了一个服务端和一个客户端,用了一个带耳麦的耳机,声音效果良好。

其实这个东西做完整还有很多细活,这个只是Demo,非常的粗糙。最近会很忙,期待将来的某一天能空去完善。如果实现了视频的功能我会写第四篇的。

相关文章:

wpf 语音通话_WPF+WCF一步一步打造音频聊天室(三):语音聊天相关推荐

  1. wpf 语音通话_WPF+WCF一步一步打造音频聊天室(二):文字聊天和白板共享

    这篇文章将讲述实现WPF的UI和WCF中的双工通信.实现文字部分的聊天功能和实现共享白板的功能. 画WPF的界面其实是一件麻烦的事情.虽然WPF和WindowsForm一样,能将控件拖到哪,它就在哪. ...

  2. java 语音聊天室_java语音聊天室原形的实现

    原本以为从 麦克风 上获得音频输入很复杂,原来javaSound已经封装的很简单了. 可以使用AudioCapture来完成. 聊天室音频的回放一般用流来完成:AudioPlayStream 类负责. ...

  3. 云豹直播2022带货语音聊天室三端app源码

    2021版云豹直播app源码免费分享-附开发文档 更新 语音聊天室 整体UI更新 动态话题分类 app内置商城

  4. 三星电子推出X-net架构用于语音通话

    X-net 影音探索 #003 作者 | Teresa 近日,三星电子推出X-net,这是一种联合学习的Scale-down和Scale-up架构,用于语音编码中的预处理和后处理,作为在带宽受限的语音 ...

  5. android studio语音聊天,实现语音通话

    本文介绍如何使用 Agora Unity SDK 快速实现语音通话. 示例项目 Agora 在 GitHub 上提供开源的实时语音通话示例项目 Hello-Unity3D-Agora.在实现相关功能前 ...

  6. ios开发 多人语音聊天_iOS语音通话功能实现流程(实时语音通话二)

    上一篇我们讲述了iOS语音通话SDK集成指引,今天就来看下iOS下实时语音通话功能实现的流程.实时语音场景的典型之一是同一会话中的成员进行实时语音对话. 以 2 人间的实时语音为例,主要流程如下: 请 ...

  7. iOS语音通话功能实现流程(实时语音通话二)

    上一篇我们讲述了iOS语音通话SDK集成指引,今天就来看下iOS下实时语音通话功能实现的流程.实时语音场景的典型之一是同一会话中的成员进行实时语音对话. 以 2 人间的实时语音为例,主要流程如下: 请 ...

  8. 语音通话是“地漏”还是“风口”?

    近日发布的中国移动业绩显示,2015年,中国移动的数据业务收入达3034.25亿元,首次超过语音业务收入(2618.96亿元),占通信服务收入的比例上升至52%,而语音业务收入则同比下降了16.5%. ...

  9. 从0搭建在线聊天室,只需4步!

    Vol. 5 聊天室不同于单聊和群聊,是一类集成了多种 IM 功能一体的大规模实时消息分发系统.在跨入新世纪的2000年,聊天室作为新型的即时通讯场景迅速在年轻人群体中火热起来,"网易聊天室 ...

最新文章

  1. 数据库基础笔记(MySQL)4 —— 基础约束
  2. this调用语句必须是构造函数中的第一个可执行语句_谈谈JavaScript中的函数构造式和new关键字...
  3. Logistic regression (逻辑回归) 概述
  4. OpenGL Tessellated Triangle镶嵌三角形的实例
  5. Scala的自定义类型标记
  6. linux命令数据盘分多个区,pvmove命令 – 移动物理盘区
  7. 常用模块(collections模块,时间模块,random模块,os模块,sys模块,序列化模块,re模块,hashlib模块,configparser模块,logging模块)...
  8. Vue+Element UI 商城后台管理系统
  9. rk3399 调试一款新的摄像头驱动
  10. 【Java面试题】利用5升和6升两个水桶得到3升水
  11. 狄拉克函数和广义函数 | 线性泛函
  12. linux设备模型七(device_driver细节)
  13. 选择system bus还是session bus?
  14. linux远程连接硬件加速,Linux中为谷歌Chrome浏览器启用视频硬件加速的方法
  15. IO模型_阻塞_非阻塞_多路复用
  16. TransE论文第4节:实验
  17. 计算机组成原理-唐朔飞 学习指导与习题解答 第2版
  18. 产品读书《怪诞行为学》
  19. 姓名性格分析--强烈的心理暗示
  20. 消防报警系统服务器,消防报警系统

热门文章

  1. 删除我的电脑里面软件快捷方式
  2. 【超清视频】CCNA系列课程之二:IP地址介绍及VLSM子网划分
  3. seajs 的研究二 -- 无题
  4. 某pl/sql培训练习题
  5. JS中document和window的区别
  6. JS实现各种复制到剪贴板
  7. Android 读取文件内容
  8. rabbitMQ消息队列 – 面板介绍及简单demo
  9. 已解决AttributeError set object has no attribute items(亲测)
  10. Python MySQLdb 循环插入execute与批量插入executemany性能分析(list批量写法亲测成功)