實現的大概的流程為:
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<IPEndPoint> _users =
new
List<IPEndPoint>();
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<IZqlChartServiceCallback>()];
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<IZqlChartServiceCallback>()];
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,非常的粗糙。最近會很忙,期待將來的某一天能空去完善。如果實現了視頻的功能我會寫第四篇的。
代碼:
http://files.cnblogs.com/zhuqil/Zql_src.rar
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

