* Rex merge, Voice Chat

0.6b-realxtend
mikkopa 2008-02-25 06:14:45 +00:00
parent 77d3e6f51e
commit cb9e41af4d
6 changed files with 573 additions and 1 deletions

View File

@ -37,6 +37,7 @@ using OpenSim.Framework.Servers;
using OpenSim.Region.Environment; using OpenSim.Region.Environment;
using OpenSim.Region.Environment.Scenes; using OpenSim.Region.Environment.Scenes;
using OpenSim.Region.Physics.Manager; using OpenSim.Region.Physics.Manager;
using OpenSim.Region.Communications.VoiceChat;
namespace OpenSim.Region.ClientStack namespace OpenSim.Region.ClientStack
{ {
@ -62,6 +63,8 @@ namespace OpenSim.Region.ClientStack
// Probably will be temporary until this stops being experimental. // Probably will be temporary until this stops being experimental.
protected bool m_storagePersistPrimInventories; protected bool m_storagePersistPrimInventories;
protected VoiceChatServer m_voiceChatServer;
public SceneManager SceneManager public SceneManager SceneManager
{ {
get { return m_sceneManager; } get { return m_sceneManager; }
@ -114,6 +117,7 @@ namespace OpenSim.Region.ClientStack
regionInfo.InternalEndPoint.Port = (int)port; regionInfo.InternalEndPoint.Port = (int)port;
Scene scene = CreateScene(regionInfo, m_storageManager, circuitManager); Scene scene = CreateScene(regionInfo, m_storageManager, circuitManager);
m_voiceChatServer = new VoiceChatServer(scene);
udpServer.LocalScene = scene; udpServer.LocalScene = scene;

View File

@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using OpenSim.Region.Environment.Scenes;
using OpenSim.Framework;
using libsecondlife;
namespace OpenSim.Region.Communications.VoiceChat
{
public class VoiceChatServer
{
private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Thread m_listenerThread;
Thread m_mainThread;
Scene m_scene;
Socket m_server;
Socket m_selectCancel;
Dictionary<Socket, VoiceClient> m_clients;
Dictionary<LLUUID, VoiceClient> m_uuidToClient;
public VoiceChatServer(Scene scene)
{
m_clients = new Dictionary<Socket, VoiceClient>();
m_uuidToClient = new Dictionary<LLUUID, VoiceClient>();
m_scene = scene;
m_scene.EventManager.OnNewClient += NewClient;
m_scene.EventManager.OnRemovePresence += RemovePresence;
try
{
CreateListeningSocket();
}
catch (Exception e)
{
m_log.Error("[VOICECHAT]: Unable to start listening");
return;
}
m_listenerThread = new Thread(new ThreadStart(ListenIncomingConnections));
m_listenerThread.IsBackground = true;
m_listenerThread.Start();
m_mainThread = new Thread(new ThreadStart(RunVoiceChat));
m_mainThread.IsBackground = true;
m_mainThread.Start();
Thread.Sleep(500);
m_selectCancel = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_selectCancel.Connect("localhost", 59214);
}
public void NewClient(IClientAPI client)
{
m_log.Info("[VOICECHAT]: New client: " + client.AgentId);
lock (m_uuidToClient)
{
m_uuidToClient[client.AgentId] = null;
}
}
public void RemovePresence(LLUUID uuid)
{
lock (m_uuidToClient)
{
if (m_uuidToClient.ContainsKey(uuid))
{
if (m_uuidToClient[uuid] != null)
{
RemoveClient(m_uuidToClient[uuid].m_socket);
}
m_uuidToClient.Remove(uuid);
}
else
{
m_log.Error("[VOICECHAT]: Presence not found on RemovePresence: " + uuid);
}
}
}
public bool AddClient(VoiceClient client, LLUUID uuid)
{
lock (m_uuidToClient)
{
if (m_uuidToClient.ContainsKey(uuid))
{
if (m_uuidToClient[uuid] != null) {
m_log.Warn("[VOICECHAT]: Multiple login attempts for " + uuid);
return false;
}
m_uuidToClient[uuid] = client;
return true;
}
}
return false;
}
public void RemoveClient(Socket socket)
{
m_log.Info("[VOICECHAT]: Removing client");
lock(m_clients)
{
VoiceClient client = m_clients[socket];
lock(m_uuidToClient)
{
if (m_uuidToClient.ContainsKey(client.m_clientId))
{
m_uuidToClient[client.m_clientId] = null;
}
}
m_clients.Remove(socket);
client.m_socket.Close();
}
}
protected void CreateListeningSocket()
{
IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 12000);
m_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
m_server.Bind(listenEndPoint);
m_server.Listen(50);
}
void ListenIncomingConnections()
{
m_log.Info("[VOICECHAT]: Listening connections...");
//ServerStatus.ReportThreadName("VoiceChat: Connection listener");
byte[] dummyBuffer = new byte[1];
while (true)
{
try
{
Socket connection = m_server.Accept();
lock (m_clients)
{
m_clients[connection] = new VoiceClient(connection, this);
m_selectCancel.Send(dummyBuffer);
m_log.Info("[VOICECHAT]: Voicechat connection from " + connection.RemoteEndPoint.ToString());
}
}
catch (SocketException e)
{
m_log.Error("[VOICECHAT]: During accept: " + e.ToString());
}
}
}
Socket ListenLoopbackSocket()
{
IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 59214);
Socket dummyListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
dummyListener.Bind(listenEndPoint);
dummyListener.Listen(1);
Socket socket = dummyListener.Accept();
dummyListener.Close();
return socket;
}
void RunVoiceChat()
{
m_log.Info("[VOICECHAT]: Connection handler started...");
//ServerStatus.ReportThreadName("VoiceChat: Connection handler");
//Listen a loopback socket for aborting select call
Socket dummySocket = ListenLoopbackSocket();
List<Socket> sockets = new List<Socket>();
byte[] buffer = new byte[65536];
while (true)
{
if (m_clients.Count == 0)
{
Thread.Sleep(100);
continue;
}
lock (m_clients)
{
foreach (Socket s in m_clients.Keys)
{
sockets.Add(s);
}
}
sockets.Add(dummySocket);
Socket.Select(sockets, null, null, 200000);
foreach (Socket s in sockets)
{
try
{
if (s.RemoteEndPoint != dummySocket.RemoteEndPoint)
{
ReceiveFromSocket(s, buffer);
}
}
catch(ObjectDisposedException e)
{
m_log.Warn("[VOICECHAT]: Connection has been already closed");
}
catch (Exception e)
{
m_log.Error("[VOICECHAT]: Exception: " + e.Message);
RemoveClient(s);
}
}
sockets.Clear();
}
}
private void ReceiveFromSocket( Socket s, byte[] buffer )
{
int byteCount = s.Receive(buffer);
if (byteCount <= 0)
{
m_log.Info("[VOICECHAT]: Connection lost to " + s.RemoteEndPoint);
lock (m_clients)
{
RemoveClient(s);
}
}
else
{
//ServerStatus.ReportInPacketTcp(byteCount);
lock (m_clients)
{
if (m_clients.ContainsKey(s))
{
m_clients[s].OnDataReceived(buffer, byteCount);
}
else
{
m_log.Warn("[VOICECHAT]: Got data from " + s.RemoteEndPoint +
", but source is not a valid voice client");
}
}
}
}
public void BroadcastVoice(VoicePacket packet)
{
libsecondlife.LLVector3 origPos = m_scene.GetScenePresence(packet.m_clientId).AbsolutePosition;
byte[] bytes = packet.GetBytes();
foreach (VoiceClient client in m_clients.Values)
{
if (client.IsEnabled() && client.m_clientId != packet.m_clientId &&
client.m_authenticated && client.IsCodecSupported(packet.m_codec))
{
ScenePresence presence = m_scene.GetScenePresence(client.m_clientId);
if (presence != null && Util.GetDistanceTo(presence.AbsolutePosition, origPos) < 20)
{
client.SendTo(bytes);
}
}
}
}
}
}

View File

@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using OpenSim.Region.Environment.Scenes;
using libsecondlife;
namespace OpenSim.Region.Communications.VoiceChat
{
/**
* Represents a single voiceclient instance
**/
public class VoiceClient
{
private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public Socket m_socket;
public LLUUID m_clientId;
public bool m_authenticated = false;
protected VoicePacketHeader m_header = null;
protected int m_headerBytesReceived = 0;
protected int m_offset = 0;
protected int m_supportedCodecs = 0;
protected byte[] m_buffer = null;
protected byte[] m_headerBytes = new byte[5];
protected bool m_enabled = true;
protected VoiceChatServer m_server;
public VoiceClient(Socket socket, VoiceChatServer server)
{
m_socket = socket;
m_server = server;
}
public void OnDataReceived(byte[] data, int byteCount)
{
int offset = 0;
while (offset < byteCount)
{
if (m_header == null)
{
if (m_headerBytesReceived < 5)
{
m_headerBytes[m_headerBytesReceived++] = data[offset++];
}
else if (m_headerBytesReceived == 5)
{
m_header = new VoicePacketHeader();
m_header.Parse(m_headerBytes);
if (m_header.length > 65535)
{
throw new Exception("Packet size " + m_header.length + " > 65535");
}
m_buffer = new byte[m_header.length];
m_offset = 0;
m_headerBytesReceived = 0;
}
}
else
{
int bytesToCopy = m_header.length-m_offset;
if (bytesToCopy > byteCount - offset)
bytesToCopy = byteCount - offset;
Buffer.BlockCopy(data, offset, m_buffer, m_offset, bytesToCopy);
offset += bytesToCopy;
m_offset += bytesToCopy;
if (m_offset == m_header.length)
{
ParsePacket(m_header.type, m_buffer);
m_header = null;
}
}
}
}
void ParsePacket(byte type, byte[] data)
{
switch (type)
{
case 0: //LOGIN
ParseLogin(data);
break;
case 1: //AUDIODATA
if (m_authenticated)
{
VoicePacket packet = new VoicePacket(data);
packet.m_clientId = m_clientId;
m_server.BroadcastVoice(packet);
}
else
{
m_log.Warn("[VOICECHAT]: Got unauthorized audio data from " +
m_socket.RemoteEndPoint.ToString());
m_socket.Close();
}
break;
case 3: //ENABLEVOIP
if (data[0] == 0)
{
m_log.Warn("[VOICECHAT]: VoiceChat has been disabled for " + m_clientId);
m_enabled = false;
}
else
{
m_log.Warn("[VOICECHAT]: VoiceChat has been enabled for " + m_clientId);
m_enabled = true;
}
break;
default:
throw new Exception("Invalid packet received");
}
}
void ParseLogin(byte[] data)
{
m_clientId = new LLUUID(data, 0);
m_supportedCodecs = data[16];
m_supportedCodecs |= data[17] << 8;
m_supportedCodecs |= data[18] << 16;
m_supportedCodecs |= data[19] << 24;
if (m_server.AddClient(this, m_clientId))
{
m_log.Info("[VOICECHAT]: Client authenticated succesfully: " + m_clientId);
m_authenticated = true;
}
else
{
throw new Exception("Unable to authenticate with id " + m_clientId);
}
}
public bool IsEnabled()
{
return m_enabled;
}
public bool IsCodecSupported(int codec)
{
if ((m_supportedCodecs & codec) != 0)
return true;
return false;
}
public void SendTo(byte[] data)
{
if (m_authenticated)
{
//ServerStatus.ReportOutPacketTcp(m_socket.Send(data));
m_socket.Send(data);
}
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
using libsecondlife;
namespace OpenSim.Region.Communications.VoiceChat
{
public enum VoiceCodec
{
None = 0,
PCM8 = 1 << 0,
PCM16 = 1 << 1,
PCM32 = 1 << 2,
Speex = 1 << 3,
}
public class VoicePacket
{
public LLUUID m_clientId;
byte[] m_audioData;
public int m_codec;
public VoicePacket(byte[] data)
{
int pos = 0;
m_codec = data[pos++];
m_codec |= data[pos++] << 8;
m_codec |= data[pos++] << 16;
m_codec |= data[pos++] << 24;
m_audioData = new byte[data.Length - pos];
Buffer.BlockCopy(data, pos, m_audioData, 0, data.Length - pos);
}
public byte[] GetBytes()
{
VoicePacketHeader header = new VoicePacketHeader();
byte[] bytes = new byte[5+16+4+m_audioData.Length];
header.length = bytes.Length-5;
//ToClient packets are type 2
header.type = 2;
int pos = 0;
header.CopyTo(bytes, pos); pos += 5;
m_clientId.GetBytes().CopyTo(bytes, pos); pos += 16;
bytes[pos++] = (byte)((m_codec) % 256);
bytes[pos++] = (byte)((m_codec << 8) % 256);
bytes[pos++] = (byte)((m_codec << 16) % 256);
bytes[pos++] = (byte)((m_codec << 24) % 256);
m_audioData.CopyTo(bytes, pos);
return bytes;
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace OpenSim.Region.Communications.VoiceChat
{
public class VoicePacketHeader
{
public byte type;
public int length;
public void Parse(byte[] data)
{
int offset = 0;
type = data[offset++];
length = data[offset++];
length |= data[offset++] << 8;
length |= data[offset++] << 16;
length |= data[offset++] << 24;
}
public void CopyTo(byte[] data, int offset)
{
data[offset + 0] = type;
data[offset + 1] = (byte)(length & 0x000000FF);
data[offset + 2] = (byte)((length & 0x0000FF00) >> 8);
data[offset + 3] = (byte)((length & 0x00FF0000) >> 16);
data[offset + 4] = (byte)((length & 0xFF000000) >> 24);
}
public int GetLength()
{
return 5;
}
}
}

View File

@ -753,6 +753,7 @@
<Reference name="OpenSim.Framework.Console"/> <Reference name="OpenSim.Framework.Console"/>
<Reference name="OpenSim.Framework.Communications"/> <Reference name="OpenSim.Framework.Communications"/>
<Reference name="OpenSim.Region.Communications.Local"/> <Reference name="OpenSim.Region.Communications.Local"/>
<Reference name="OpenSim.Region.Communications.VoiceChat"/>
<Reference name="OpenSim.Region.Physics.Manager"/> <Reference name="OpenSim.Region.Physics.Manager"/>
<Reference name="XMLRPC.dll"/> <Reference name="XMLRPC.dll"/>
<Reference name="Nini.dll" /> <Reference name="Nini.dll" />
@ -1536,6 +1537,34 @@
<Match pattern="*.cs" recurse="true"/> <Match pattern="*.cs" recurse="true"/>
</Files> </Files>
</Project> </Project>
<!-- Rex VoiceChat -->
<Project name="OpenSim.Region.Communications.VoiceChat" path="OpenSim/Region/Communications/VoiceChat" type="Library">
<Configuration name="Debug">
<Options>
<OutputPath>../../../../bin/</OutputPath>
</Options>
</Configuration>
<Configuration name="Release">
<Options>
<OutputPath>../../../../bin/</OutputPath>
</Options>
</Configuration>
<ReferencePath>../../../../bin/</ReferencePath>
<Reference name="System" localCopy="false"/>
<Reference name="OpenSim.Region.Environment" />
<Reference name="OpenSim.Framework"/>
<Reference name="Axiom.MathLib.dll" localCopy="false"/>
<Reference name="System.Web.Services"/>
<Reference name="libsecondlife.dll"/>
<Reference name="log4net"/>
<Files>
<Match pattern="*.cs" recurse="true"/>
</Files>
</Project>
</Solution> </Solution>
<!-- Prebuild tool --> <!-- Prebuild tool -->