aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region')
-rw-r--r--OpenSim/Region/Environment/Modules/VoiceChat/VoiceChatServer.cs328
-rw-r--r--OpenSim/Region/Environment/Modules/VoiceChat/VoiceClient.cs169
-rw-r--r--OpenSim/Region/Environment/Modules/VoiceChat/VoicePacket.cs58
-rw-r--r--OpenSim/Region/Environment/Modules/VoiceChat/VoicePacketHeader.cs38
4 files changed, 593 insertions, 0 deletions
diff --git a/OpenSim/Region/Environment/Modules/VoiceChat/VoiceChatServer.cs b/OpenSim/Region/Environment/Modules/VoiceChat/VoiceChatServer.cs
new file mode 100644
index 0000000..1ac2d33
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/VoiceChat/VoiceChatServer.cs
@@ -0,0 +1,328 @@
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading;
5using System.Net.Sockets;
6using System.Net;
7using OpenSim.Region.Environment.Scenes;
8using OpenSim.Framework;
9using OpenSim.Region.Environment.Modules;
10using OpenSim.Region.Environment.Interfaces;
11using Nini;
12using libsecondlife;
13
14namespace OpenSim.Region.Environment.Modules.VoiceChat
15{
16 public class VoiceChatServer : IRegionModule
17 {
18 private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
19
20 int m_dummySocketPort = 53134;
21
22 Thread m_listenerThread;
23 Thread m_mainThread;
24 Scene m_scene;
25 Socket m_server;
26 Socket m_selectCancel;
27 bool m_enabled = false;
28
29 Dictionary<Socket, VoiceClient> m_clients;
30 Dictionary<LLUUID, VoiceClient> m_uuidToClient;
31
32
33 #region IRegionModule Members
34
35 public void Initialise(Scene scene, Nini.Config.IConfigSource source)
36 {
37 m_scene = scene;
38
39 try
40 {
41 m_enabled = source.Configs["Voice"].GetBoolean("enabled", m_enabled);
42 }
43 catch (Exception)
44 { }
45 }
46
47 public void PostInitialise()
48 {
49 if (m_enabled != true)
50 return;
51
52 m_clients = new Dictionary<Socket, VoiceClient>();
53 m_uuidToClient = new Dictionary<LLUUID, VoiceClient>();
54
55 m_scene.EventManager.OnNewClient += NewClient;
56 m_scene.EventManager.OnRemovePresence += RemovePresence;
57
58 try
59 {
60 CreateListeningSocket();
61 }
62 catch (Exception e)
63 {
64 m_log.Error("[VOICECHAT]: Unable to start listening");
65 return;
66 }
67
68 m_listenerThread = new Thread(new ThreadStart(ListenIncomingConnections));
69 m_listenerThread.IsBackground = true;
70 m_listenerThread.Start();
71
72 m_mainThread = new Thread(new ThreadStart(RunVoiceChat));
73 m_mainThread.IsBackground = true;
74 m_mainThread.Start();
75
76 Thread.Sleep(500);
77 m_selectCancel = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
78 m_selectCancel.Connect("localhost", m_dummySocketPort);
79 }
80
81 public void Close()
82 {
83 throw new NotImplementedException();
84 }
85
86 public string Name
87 {
88 get { return "VoiceChatModule"; }
89 }
90
91 public bool IsSharedModule
92 {
93 get { return true; } // I think we can share this one.
94 }
95
96 #endregion
97
98 public void NewClient(IClientAPI client)
99 {
100 m_log.Info("[VOICECHAT]: New client: " + client.AgentId);
101 lock (m_uuidToClient)
102 {
103 m_uuidToClient[client.AgentId] = null;
104 }
105 }
106
107 public void RemovePresence(LLUUID uuid)
108 {
109 lock (m_uuidToClient)
110 {
111 if (m_uuidToClient.ContainsKey(uuid))
112 {
113 if (m_uuidToClient[uuid] != null)
114 {
115 RemoveClient(m_uuidToClient[uuid].m_socket);
116 }
117 m_uuidToClient.Remove(uuid);
118 }
119 else
120 {
121 m_log.Error("[VOICECHAT]: Presence not found on RemovePresence: " + uuid);
122 }
123 }
124 }
125
126 public bool AddClient(VoiceClient client, LLUUID uuid)
127 {
128 lock (m_uuidToClient)
129 {
130 if (m_uuidToClient.ContainsKey(uuid))
131 {
132 if (m_uuidToClient[uuid] != null) {
133 m_log.Warn("[VOICECHAT]: Multiple login attempts for " + uuid);
134 return false;
135 }
136 m_uuidToClient[uuid] = client;
137 return true;
138 }
139 }
140 return false;
141 }
142
143 public void RemoveClient(Socket socket)
144 {
145 m_log.Info("[VOICECHAT]: Removing client");
146 lock(m_clients)
147 {
148 VoiceClient client = m_clients[socket];
149
150 lock(m_uuidToClient)
151 {
152 if (m_uuidToClient.ContainsKey(client.m_clientId))
153 {
154 m_uuidToClient[client.m_clientId] = null;
155 }
156 }
157
158 m_clients.Remove(socket);
159 client.m_socket.Close();
160 }
161 }
162
163 protected void CreateListeningSocket()
164 {
165 IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 12000);
166 m_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
167 m_server.Bind(listenEndPoint);
168 m_server.Listen(50);
169 }
170
171 void ListenIncomingConnections()
172 {
173 m_log.Info("[VOICECHAT]: Listening connections...");
174 //ServerStatus.ReportThreadName("VoiceChat: Connection listener");
175
176 byte[] dummyBuffer = new byte[1];
177
178 while (true)
179 {
180 try
181 {
182 Socket connection = m_server.Accept();
183 lock (m_clients)
184 {
185 m_clients[connection] = new VoiceClient(connection, this);
186 m_selectCancel.Send(dummyBuffer);
187 m_log.Info("[VOICECHAT]: Voicechat connection from " + connection.RemoteEndPoint.ToString());
188 }
189 }
190 catch (SocketException e)
191 {
192 m_log.Error("[VOICECHAT]: During accept: " + e.ToString());
193 }
194 }
195 }
196
197 Socket ListenLoopbackSocket()
198 {
199 IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), m_dummySocketPort);
200 Socket dummyListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
201 dummyListener.Bind(listenEndPoint);
202 dummyListener.Listen(1);
203 Socket socket = dummyListener.Accept();
204 dummyListener.Close();
205 return socket;
206 }
207
208 void RunVoiceChat()
209 {
210 m_log.Info("[VOICECHAT]: Connection handler started...");
211 //ServerStatus.ReportThreadName("VoiceChat: Connection handler");
212
213 //Listen a loopback socket for aborting select call
214 Socket dummySocket = ListenLoopbackSocket();
215
216 List<Socket> sockets = new List<Socket>();
217 byte[] buffer = new byte[65536];
218
219 while (true)
220 {
221 if (m_clients.Count == 0)
222 {
223 Thread.Sleep(100);
224 continue;
225 }
226
227 lock (m_clients)
228 {
229 foreach (Socket s in m_clients.Keys)
230 {
231 sockets.Add(s);
232 }
233 }
234 sockets.Add(dummySocket);
235
236 try
237 {
238 Socket.Select(sockets, null, null, 200000);
239 }
240 catch (SocketException e)
241 {
242 m_log.Warn("[VOICECHAT]: " + e.Message);
243 }
244
245 foreach (Socket s in sockets)
246 {
247 try
248 {
249 if (s.RemoteEndPoint != dummySocket.RemoteEndPoint)
250 {
251 ReceiveFromSocket(s, buffer);
252 }
253 else
254 {
255 //Receive data and check if there was an error with select abort socket
256 if (s.Receive(buffer) <= 0)
257 {
258 //Just give a warning for now
259 m_log.Error("[VOICECHAT]: Select abort socket was closed");
260 }
261 }
262 }
263 catch(ObjectDisposedException e)
264 {
265 m_log.Warn("[VOICECHAT]: Connection has been already closed");
266 }
267 catch (Exception e)
268 {
269 m_log.Error("[VOICECHAT]: Exception: " + e.Message);
270
271 RemoveClient(s);
272 }
273 }
274
275 sockets.Clear();
276 }
277 }
278
279 private void ReceiveFromSocket( Socket s, byte[] buffer )
280 {
281 int byteCount = s.Receive(buffer);
282 if (byteCount <= 0)
283 {
284 m_log.Info("[VOICECHAT]: Connection lost to " + s.RemoteEndPoint);
285 lock (m_clients)
286 {
287 RemoveClient(s);
288 }
289 }
290 else
291 {
292 //ServerStatus.ReportInPacketTcp(byteCount);
293 lock (m_clients)
294 {
295 if (m_clients.ContainsKey(s))
296 {
297 m_clients[s].OnDataReceived(buffer, byteCount);
298 }
299 else
300 {
301 m_log.Warn("[VOICECHAT]: Got data from " + s.RemoteEndPoint +
302 ", but source is not a valid voice client");
303 }
304 }
305 }
306 }
307
308 public void BroadcastVoice(VoicePacket packet)
309 {
310 libsecondlife.LLVector3 origPos = m_scene.GetScenePresence(packet.m_clientId).AbsolutePosition;
311
312 byte[] bytes = packet.GetBytes();
313 foreach (VoiceClient client in m_clients.Values)
314 {
315 if (client.IsEnabled() && client.m_clientId != packet.m_clientId &&
316 client.m_authenticated && client.IsCodecSupported(packet.m_codec))
317 {
318 ScenePresence presence = m_scene.GetScenePresence(client.m_clientId);
319
320 if (presence != null && Util.GetDistanceTo(presence.AbsolutePosition, origPos) < 20)
321 {
322 client.SendTo(bytes);
323 }
324 }
325 }
326 }
327 }
328}
diff --git a/OpenSim/Region/Environment/Modules/VoiceChat/VoiceClient.cs b/OpenSim/Region/Environment/Modules/VoiceChat/VoiceClient.cs
new file mode 100644
index 0000000..7eb2177
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/VoiceChat/VoiceClient.cs
@@ -0,0 +1,169 @@
1using System;
2using System.IO;
3using System.Collections.Generic;
4using System.Text;
5using System.Net.Sockets;
6using OpenSim.Region.Environment.Scenes;
7using libsecondlife;
8
9namespace OpenSim.Region.Environment.Modules.VoiceChat
10{
11 /**
12 * Represents a single voiceclient instance
13 **/
14 public class VoiceClient
15 {
16 private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
17
18 public Socket m_socket;
19 public LLUUID m_clientId;
20 public bool m_authenticated = false;
21
22 protected VoicePacketHeader m_header = null;
23 protected int m_headerBytesReceived = 0;
24
25 protected int m_offset = 0;
26 protected int m_supportedCodecs = 0;
27
28 protected byte[] m_buffer = null;
29 protected byte[] m_headerBytes = new byte[5];
30
31 protected bool m_enabled = true;
32
33 protected VoiceChatServer m_server;
34
35 public VoiceClient(Socket socket, VoiceChatServer server)
36 {
37 m_socket = socket;
38 m_server = server;
39 }
40
41 public void OnDataReceived(byte[] data, int byteCount)
42 {
43 int offset = 0;
44 while (offset < byteCount)
45 {
46 if (m_header == null)
47 {
48 if (m_headerBytesReceived < 5)
49 {
50 m_headerBytes[m_headerBytesReceived++] = data[offset++];
51 }
52 else if (m_headerBytesReceived == 5)
53 {
54 m_header = new VoicePacketHeader();
55 m_header.Parse(m_headerBytes);
56 if (m_header.length > 65535)
57 {
58 throw new Exception("Packet size " + m_header.length + " > 65535");
59 }
60
61 m_buffer = new byte[m_header.length];
62 m_offset = 0;
63 m_headerBytesReceived = 0;
64 }
65 }
66 else
67 {
68 int bytesToCopy = m_header.length-m_offset;
69 if (bytesToCopy > byteCount - offset)
70 bytesToCopy = byteCount - offset;
71
72 Buffer.BlockCopy(data, offset, m_buffer, m_offset, bytesToCopy);
73
74 offset += bytesToCopy;
75 m_offset += bytesToCopy;
76
77 if (m_offset == m_header.length)
78 {
79 ParsePacket(m_header.type, m_buffer);
80 m_header = null;
81 }
82 }
83 }
84 }
85
86 void ParsePacket(byte type, byte[] data)
87 {
88 switch (type)
89 {
90 case 0: //LOGIN
91 ParseLogin(data);
92 break;
93
94 case 1: //AUDIODATA
95 if (m_authenticated)
96 {
97 VoicePacket packet = new VoicePacket(data);
98 packet.m_clientId = m_clientId;
99 m_server.BroadcastVoice(packet);
100 }
101 else
102 {
103 m_log.Warn("[VOICECHAT]: Got unauthorized audio data from " +
104 m_socket.RemoteEndPoint.ToString());
105 m_socket.Close();
106 }
107 break;
108
109 case 3: //ENABLEVOIP
110 if (data[0] == 0)
111 {
112 m_log.Warn("[VOICECHAT]: VoiceChat has been disabled for " + m_clientId);
113 m_enabled = false;
114 }
115 else
116 {
117 m_log.Warn("[VOICECHAT]: VoiceChat has been enabled for " + m_clientId);
118 m_enabled = true;
119 }
120 break;
121
122
123 default:
124 throw new Exception("Invalid packet received");
125 }
126 }
127
128 void ParseLogin(byte[] data)
129 {
130 m_clientId = new LLUUID(data, 0);
131
132 m_supportedCodecs = data[16];
133 m_supportedCodecs |= data[17] << 8;
134 m_supportedCodecs |= data[18] << 16;
135 m_supportedCodecs |= data[19] << 24;
136
137 if (m_server.AddClient(this, m_clientId))
138 {
139 m_log.Info("[VOICECHAT]: Client authenticated succesfully: " + m_clientId);
140 m_authenticated = true;
141 }
142 else
143 {
144 throw new Exception("Unable to authenticate with id " + m_clientId);
145 }
146 }
147
148 public bool IsEnabled()
149 {
150 return m_enabled;
151 }
152
153 public bool IsCodecSupported(int codec)
154 {
155 if ((m_supportedCodecs & codec) != 0)
156 return true;
157
158 return false;
159 }
160
161 public void SendTo(byte[] data)
162 {
163 if (m_authenticated)
164 {
165 //ServerStatus.ReportOutPacketTcp(m_socket.Send(data));
166 }
167 }
168 }
169}
diff --git a/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacket.cs b/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacket.cs
new file mode 100644
index 0000000..e92fa43
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacket.cs
@@ -0,0 +1,58 @@
1using System;
2using System.Collections.Generic;
3using System.Text;
4using libsecondlife;
5
6namespace OpenSim.Region.Environment.Modules.VoiceChat
7{
8 public enum VoiceCodec
9 {
10 None = 0,
11 PCM8 = 1 << 0,
12 PCM16 = 1 << 1,
13 PCM32 = 1 << 2,
14 Speex = 1 << 3,
15 }
16
17 public class VoicePacket
18 {
19 public LLUUID m_clientId;
20 byte[] m_audioData;
21 public int m_codec;
22
23 public VoicePacket(byte[] data)
24 {
25 int pos = 0;
26 m_codec = data[pos++];
27 m_codec |= data[pos++] << 8;
28 m_codec |= data[pos++] << 16;
29 m_codec |= data[pos++] << 24;
30
31 m_audioData = new byte[data.Length - pos];
32 Buffer.BlockCopy(data, pos, m_audioData, 0, data.Length - pos);
33 }
34
35 public byte[] GetBytes()
36 {
37 VoicePacketHeader header = new VoicePacketHeader();
38 byte[] bytes = new byte[5+16+4+m_audioData.Length];
39
40 header.length = bytes.Length-5;
41
42 //ToClient packets are type 2
43 header.type = 2;
44
45 int pos = 0;
46 header.CopyTo(bytes, pos); pos += 5;
47 m_clientId.GetBytes().CopyTo(bytes, pos); pos += 16;
48
49 bytes[pos++] = (byte)((m_codec) % 256);
50 bytes[pos++] = (byte)((m_codec << 8) % 256);
51 bytes[pos++] = (byte)((m_codec << 16) % 256);
52 bytes[pos++] = (byte)((m_codec << 24) % 256);
53
54 m_audioData.CopyTo(bytes, pos);
55 return bytes;
56 }
57 }
58}
diff --git a/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacketHeader.cs b/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacketHeader.cs
new file mode 100644
index 0000000..2f9ec55
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/VoiceChat/VoicePacketHeader.cs
@@ -0,0 +1,38 @@
1using System;
2using System.Collections.Generic;
3using System.Text;
4
5namespace OpenSim.Region.Environment.Modules.VoiceChat
6{
7 public class VoicePacketHeader
8 {
9 public byte type;
10 public int length;
11
12 public void Parse(byte[] data)
13 {
14 int offset = 0;
15 type = data[offset++];
16
17 length = data[offset++];
18 length |= data[offset++] << 8;
19 length |= data[offset++] << 16;
20 length |= data[offset++] << 24;
21 }
22
23 public void CopyTo(byte[] data, int offset)
24 {
25 data[offset + 0] = type;
26
27 data[offset + 1] = (byte)(length & 0x000000FF);
28 data[offset + 2] = (byte)((length & 0x0000FF00) >> 8);
29 data[offset + 3] = (byte)((length & 0x00FF0000) >> 16);
30 data[offset + 4] = (byte)((length & 0xFF000000) >> 24);
31 }
32
33 public int GetLength()
34 {
35 return 5;
36 }
37 }
38}