资源说明:本程序,实现一个简单的语音聊天功能,将麦克风采集到的音频数据经G711压缩后,通过UDP协议传输。在本文中
不讨论如何通过麦克风采集音频数据,也不讨论G711压缩的细节,相关内容可以查看文章后参考文献的内容。
系统架构如下所示:
| Invite |
| --------------------------------> |
| OK |
| <-------------------------------- |
| |
| --------------------------------> |
| Audio flow |
| <-------------------------------- |
| Bye |
| --------------------------------> |
A B
系统所用到的命令如:Invite、OK、Bye等设计为一个枚举类型变量,
enum Command
{
Invite, //Make a call.
Bye, //End a call.
Busy, //User busy.
OK, //Response to an invite message. OK is sent to
//indicate that call is accepted.
Null, //No command.
}
当用户想要语音聊天时,发送Invite消息,等待对方的反馈OK应答。当收到OK应答后,开始发送/接受
语音信息。如果对方拒绝聊天,则发送Busy信息作为应答。要终止聊天时,发送Bye消息即可。
程序在端口1450同步实现连接建立消息的收发,在端口1550异步收发聊天信息。也就是说,本程序监听
两个端口,一个负责呼叫消息,另一个负责音频数据。
监听1450端口的代码:
//使用UDP
clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp);
EndPoint ourEP = new IPEndPoint(IPAddress.Any, 1450);
//Listen asynchronously on port 1450 for
//coming messages (Invite, Bye, etc).
clientSocket.Bind(ourEP);
//Receive data from any IP.
EndPoint remoteEP = (EndPoint)(new IPEndPoint(IPAddress.Any, 0));
byteData = new byte[1024];
//Receive data asynchornously.
clientSocket.BeginReceiveFrom(byteData,
0, byteData.Length,
SocketFlags.None,
ref remoteEP,
new AsyncCallback(OnReceive),
null);
当收到发送来的消息后,根据消息类型来进行对应的处理。
private void OnReceive(IAsyncResult ar)
{
try
{
EndPoint receivedFromEP = new IPEndPoint(IPAddress.Any, 0);
//Get the IP from where we got a message.
clientSocket.EndReceiveFrom(ar, ref receivedFromEP);
//Convert the bytes received into an object of type Data.
Data msgReceived = new Data(byteData);
//Act according to the received message.
switch (msgReceived.cmdCommand)
{
//We have an incoming call.
case Command.Invite:
{
if (bIsCallActive == false)
{
//We have no active call.
//Ask the user to accept the call or not.
if (MessageBox.Show("Call coming from " + msgReceived.strName + ".\r\n\r\nAccept it?",
"VoiceChat", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
SendMessage(Command.OK, receivedFromEP);
vocoder = msgReceived.vocoder;
otherPartyEP = receivedFromEP;
otherPartyIP = (IPEndPoint)receivedFromEP;
InitializeCall();
}
else
{
//The call is declined. Send a busy response.
SendMessage(Command.Busy, receivedFromEP);
}
}
else
{
//We already have an existing call. Send a busy response.
SendMessage(Command.Busy, receivedFromEP);
}
break;
}
//OK is received in response to an Invite.
case Command.OK:
{
//Start a call.
InitializeCall();
break;
}
//Remote party is busy.
case Command.Busy:
{
MessageBox.Show("User busy.", "VoiceChat",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
break;
}
case Command.Bye:
{
//Check if the Bye command has indeed come from the user/IP with which we have
//a call established. This is used to prevent other users from sending a Bye, which
//would otherwise end the call.
if (receivedFromEP.Equals (otherPartyEP) == true)
{
//End the call.
UninitializeCall();
}
break;
}
}
byteData = new byte[1024];
//Get ready to receive more commands.
clientSocket.BeginReceiveFrom(byteData,
0,
byteData.Length,
SocketFlags.None,
ref receivedFromEP,
new AsyncCallback (OnReceive), null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,
"VoiceChat-OnReceive ()",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
为了保证在收发音频数据时,不产生UI阻塞,采用多线程来异步收发数据。
private void InitializeCall()
{
try
{
//Start listening on port 1500.
udpClient = new UdpClient(1550);
Thread senderThread = new Thread(new ThreadStart(Send));
Thread receiverThread = new Thread(new ThreadStart(Receive));
bIsCallActive = true;
//Start the receiver and sender thread.
receiverThread.Start();
senderThread.Start();
btnCall.Enabled = false;
btnEndCall.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,
"VoiceChat-InitializeCall ()",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
异步发送从麦克风采集到的音频数据
* Send synchronously sends data captured from microphone across the network on port 1550.
*/
private void Send()
{
try
{
//The following lines get audio from microphone and then send them
//across network.
captureBuffer = new CaptureBuffer(captureBufferDescription, capture);
CreateNotifyPositions();
int halfBuffer = bufferSize / 2;
captureBuffer.Start(true);
bool readFirstBufferPart = true;
int offset = 0;
MemoryStream memStream = new MemoryStream(halfBuffer);
bStop = false;
while (!bStop)
{
autoResetEvent.WaitOne();
memStream.Seek(0, SeekOrigin.Begin);
captureBuffer.Read(offset, memStream, halfBuffer, LockFlag.None);
readFirstBufferPart = !readFirstBufferPart;
offset = readFirstBufferPart ? 0 : halfBuffer;
//TODO: Fix this ugly way of initializing differently.
//Choose the vocoder. And then send the data to other party at port 1550.
if (vocoder == Vocoder.ALaw)
{
byte[] dataToWrite = ALawEncoder.ALawEncode(memStream.GetBuffer());
udpClient.Send(dataToWrite, dataToWrite.Length, otherPartyIP.Address.ToString (), 1550);
}
else if (vocoder == Vocoder.uLaw)
{
byte[] dataToWrite = MuLawEncoder.MuLawEncode(memStream.GetBuffer());
udpClient.Send(dataToWrite, dataToWrite.Length, otherPartyIP.Address.ToString(), 1550);
}
else
{
byte[] dataToWrite = memStream.GetBuffer();
udpClient.Send(dataToWrite, dataToWrite.Length, otherPartyIP.Address.ToString(), 1550);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "VoiceChat-Send ()", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
captureBuffer.Stop();
//Increment flag by one.
nUdpClientFlag += 1;
//When flag is two then it means we have got out of loops in Send and Receive.
while (nUdpClientFlag != 2)
{ }
//Clear the flag.
nUdpClientFlag = 0;
//Close the socket.
udpClient.Close();
}
}
从端口1550处接受收到的音频数据
private void Receive()
{
try
{
bStop = false;
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
while (!bStop)
{
//Receive data.
byte[] byteData = udpClient.Receive(ref remoteEP);
//G711 compresses the data by 50%, so we allocate a buffer of double
//the size to store the decompressed data.
byte[] byteDecodedData = new byte[byteData.Length * 2];
//Decompress data using the proper vocoder.
if (vocoder == Vocoder.ALaw)
{
ALawDecoder.ALawDecode(byteData, out byteDecodedData);
}
else if (vocoder == Vocoder.uLaw)
{
MuLawDecoder.MuLawDecode(byteData, out byteDecodedData);
}
else
{
byteDecodedData = new byte[byteData.Length];
byteDecodedData = byteData;
}
//Play the data received to the user.
playbackBuffer = new SecondaryBuffer(playbackBufferDescription, devi
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。