* Added the beginnings of VoiceManager to libsecondlife.Utilities

* Added VoiceTest to test VoiceManager. For now you will have to manually run the voice daemon (shipped with Second Life) each time

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1345 52acb1d6-8a22-11de-b505-999d5b087335
This commit is contained in:
John Hurliman
2007-08-03 07:07:49 +00:00
parent 5aa0e5b6cc
commit fa9b7830a3
10 changed files with 632 additions and 2 deletions

43
VoiceTest/VoiceTest.cs Normal file
View File

@@ -0,0 +1,43 @@
using System;
using libsecondlife;
using libsecondlife.Utilities;
namespace VoiceTest
{
class VoiceTest
{
static void Main(string[] args)
{
SecondLife client = new SecondLife();
VoiceManager voice = new VoiceManager(client);
if (voice.ConnectToDaemon())
{
voice.RequestCaptureDevices();
voice.RequestRenderDevices();
voice.RequestSetRenderDevice("Speakers (Realtek High Definiti");
voice.RequestSetSpeakerVolume(75);
voice.RequestSetCaptureVolume(75);
voice.RequestStartTuningMode(10000);
//voice.RequestCreateConnector();
voice.RequestRenderAudioStart("bugsbunny1.wav", true);
voice.RequestRenderAudioStart("bugsbunny1.wav", false);
}
Console.ReadKey();
voice.RequestRenderAudioStop();
voice.RequestStopTuningMode();
Console.ReadKey();
}
}
}

View File

@@ -0,0 +1,59 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B69597A7-5DC5-4564-9089-727D0348EB4E}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>VoiceTest</RootNamespace>
<AssemblyName>VoiceTest</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="VoiceTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libsecondlife\libsecondlife.csproj">
<Project>{D9CDEDFB-8169-4B03-B57F-0DF638F044EC}</Project>
<Name>libsecondlife</Name>
</ProjectReference>
<ProjectReference Include="..\libsecondlife\libsecondlife.Utilities\libsecondlife.Utilities.csproj">
<Project>{CE5E06C2-2428-416B-ADC1-F1FE16A0FB27}</Project>
<Name>libsecondlife.Utilities</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -2034,6 +2034,8 @@ namespace libsecondlife
{
Client.Log("Got EstablishAgentCommunication for unknown sim " + ipAndPort,
Helpers.LogLevel.Error);
// FIXME: Should we use this opportunity to connect to the simulator?
}
else
{

View File

@@ -664,7 +664,7 @@ namespace libsecondlife
Simulator oldSim = CurrentSim;
lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators
simulator.SetSeedCaps(seedcaps);
simulator.SetSeedCaps(seedcaps);
// If the current simulator changed fire the callback
if (OnCurrentSimChanged != null && simulator != oldSim)

View File

@@ -423,7 +423,7 @@ namespace libsecondlife
SimCaps = null;
}
if (Client.Settings.ENABLE_CAPS) // [TODO] Implement caps
if (Client.Settings.ENABLE_CAPS)
{
// Connect to the new CAPS system
if (!String.IsNullOrEmpty(seedcaps))

View File

@@ -0,0 +1,156 @@
using System;
using System.Net;
using System.Net.Sockets;
public class TCPPipe
{
protected class SocketPacket
{
public System.Net.Sockets.Socket TCPSocket;
public byte[] DataBuffer = new byte[1];
}
public delegate void OnReceiveLineCallback(string line);
public delegate void OnDisconnectedCallback(SocketException se);
public event OnReceiveLineCallback OnReceiveLine;
public event OnDisconnectedCallback OnDisconnected;
protected Socket _TCPSocket;
protected IAsyncResult _Result;
protected AsyncCallback _Callback;
protected string _Buffer = String.Empty;
public bool Connected
{
get
{
if (_TCPSocket != null && _TCPSocket.Connected)
return true;
else
return false;
}
}
public TCPPipe()
{
}
public SocketException Connect(string address, int port)
{
if (_TCPSocket != null && _TCPSocket.Connected)
Disconnect();
try
{
IPAddress ip;
if (!IPAddress.TryParse(address, out ip))
{
IPAddress[] ips = Dns.GetHostAddresses(address);
ip = ips[0];
}
_TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(ip, port);
_TCPSocket.Connect(ipEndPoint);
if (_TCPSocket.Connected)
{
WaitForData();
return null;
}
else
{
return new SocketException(10000);
}
}
catch (SocketException se)
{
return se;
}
}
public void Disconnect()
{
_TCPSocket.Disconnect(true);
}
public void SendData(byte[] data)
{
if (Connected)
_TCPSocket.Send(data);
else
throw new InvalidOperationException("socket is not connected");
}
public void SendLine(string message)
{
if (Connected)
{
byte[] byData = System.Text.Encoding.ASCII.GetBytes(message + "\n");
_TCPSocket.Send(byData);
}
else
{
throw new InvalidOperationException("socket is not connected");
}
}
void WaitForData()
{
try
{
if (_Callback == null) _Callback = new AsyncCallback(OnDataReceived);
SocketPacket packet = new SocketPacket();
packet.TCPSocket = _TCPSocket;
_Result = _TCPSocket.BeginReceive(packet.DataBuffer, 0, packet.DataBuffer.Length, SocketFlags.None, _Callback, packet);
}
catch (SocketException se)
{
Console.WriteLine(se.Message);
}
}
void ReceiveData(string data)
{
if (OnReceiveLine == null) return;
string[] splitNull = { "\0" };
string[] line = data.Split(splitNull, StringSplitOptions.None);
_Buffer += line[0];
string[] splitLines = { "\r\n", "\r", "\n" };
string[] lines = _Buffer.Split(splitLines, StringSplitOptions.None);
if (lines.Length > 1)
{
int i;
for (i = 0; i < lines.Length - 1; i++)
{
if (lines[i].Trim().Length > 0) OnReceiveLine(lines[i]);
}
_Buffer = lines[i];
}
}
void OnDataReceived(IAsyncResult asyn)
{
try
{
SocketPacket packet = (SocketPacket)asyn.AsyncState;
int end = packet.TCPSocket.EndReceive(asyn);
char[] chars = new char[end + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(packet.DataBuffer, 0, end, chars, 0);
System.String data = new System.String(chars);
ReceiveData(data);
WaitForData();
}
catch (ObjectDisposedException)
{
Console.WriteLine("WARNING: Socket closed unexpectedly");
}
catch (SocketException se)
{
if (!_TCPSocket.Connected) OnDisconnected(se);
}
}
}

View File

@@ -22,6 +22,36 @@ namespace libsecondlife.Utilities
Underwater
}
public enum Platform
{
Unknown,
Windows,
Linux,
OSX
}
public static class SLPlatformTools
{
public const string OSX_CHECK_FILE = "/Library/Extensions.kextcache";
public static Platform GetRunningPlatform()
{
int plat = (int)Environment.OSVersion.Platform;
if ((plat != 4) && (plat != 128))
{
return Platform.Windows;
}
else
{
if (System.IO.File.Exists(OSX_CHECK_FILE))
return Platform.OSX;
else
return Platform.Linux;
}
}
}
public static class Realism
{
/// <summary>

View File

@@ -0,0 +1,332 @@
using System;
using System.Net.Sockets;
using System.Text;
using libsecondlife;
namespace libsecondlife.Utilities
{
public enum VoiceStatus
{
StatusLoginRetry,
StatusLoggedIn,
StatusJoining,
StatusJoined,
StatusLeftChannel,
BeginErrorStatus,
ErrorChannelFull,
ErrorChannelLocked,
ErrorNotAvailable,
ErrorUnknown
}
public enum VoiceServiceType
{
/// <summary>Unknown voice service level</summary>
Unknown,
/// <summary>Spatialized local chat</summary>
TypeA,
/// <summary>Remote multi-party chat</summary>
TypeB,
/// <summary>One-to-one and small group chat</summary>
TypeC
}
public class VoiceManager
{
public const string DAEMON_ARGS = " -p tcp -h -c -ll ";
public const int DAEMON_LOG_LEVEL = 1;
public const int DAEMON_PORT = 44124;
public const string VOICE_RELEASE_SERVER = "bhr.vivox.com";
public const string VOICE_DEBUG_SERVER = "bhd.vivox.com";
public const string REQUEST_TERMINATOR = "\n\n\n";
public SecondLife Client;
protected TCPPipe _DaemonPipe;
protected VoiceStatus _Status;
protected int _CommandCookie = 0;
protected string _TuningSoundFile = String.Empty;
protected string _VoiceServer = VOICE_RELEASE_SERVER;
public VoiceManager(SecondLife client)
{
Client = client;
}
public bool IsDaemonRunning()
{
return false;
}
public bool StartDaemon()
{
return false;
}
public void StopDaemon()
{
}
public bool ConnectToDaemon()
{
return ConnectToDaemon("127.0.0.1", DAEMON_PORT);
}
public bool ConnectToDaemon(string address, int port)
{
_DaemonPipe = new TCPPipe();
_DaemonPipe.OnDisconnected += new TCPPipe.OnDisconnectedCallback(_DaemonPipe_OnDisconnected);
_DaemonPipe.OnReceiveLine += new TCPPipe.OnReceiveLineCallback(_DaemonPipe_OnReceiveLine);
SocketException se = _DaemonPipe.Connect(address, port);
if (se == null)
{
return true;
}
else
{
Console.WriteLine("Connection failed: " + se.Message);
return false;
}
}
public string VoiceAccountFromUUID(LLUUID id)
{
string result = "x" + Convert.ToBase64String(id.GetBytes());
return result.Replace('+', '-').Replace('/', '_');
}
public LLUUID UUIDFromVoiceAccount(string accountName)
{
if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=')
{
accountName = accountName.Replace('/', '_').Replace('+', '-');
byte[] idBytes = Convert.FromBase64String(accountName);
if (idBytes.Length == 16)
return new LLUUID(idBytes, 0);
else
return LLUUID.Zero;
}
else
{
return LLUUID.Zero;
}
}
public string SIPURIFromVoiceAccount(string account)
{
return String.Format("sip:{0}@{1}", account, _VoiceServer);
}
public void RequestCaptureDevices()
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.GetCaptureDevices.1\"></Request>{1}",
_CommandCookie++,
REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestRenderDevices()
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.GetRenderDevices.1\"></Request>{1}",
_CommandCookie++,
REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestCreateConnector()
{
RequestCreateConnector(VOICE_RELEASE_SERVER);
}
public void RequestCreateConnector(string voiceServer)
{
if (_DaemonPipe.Connected)
{
_VoiceServer = voiceServer;
string accountServer = String.Format("https://www.{0}/api2/", _VoiceServer);
string logPath = ".";
StringBuilder request = new StringBuilder();
request.Append(String.Format("<Request requestId=\"{0}\" action=\"Connector.Create.1\">", _CommandCookie++));
request.Append("<ClientName>V2 SDK</ClientName>");
request.Append(String.Format("<AccountManagementServer>{0}</AccountManagementServer>", accountServer));
request.Append("<Logging>");
request.Append("<Enabled>false</Enabled>");
request.Append(String.Format("<Folder>{0}</Folder>", logPath));
request.Append("<FileNamePrefix>vivox-gateway</FileNamePrefix>");
request.Append("<FileNameSuffix>.log</FileNameSuffix>");
request.Append("<LogLevel>0</LogLevel>");
request.Append("</Logging>");
request.Append("</Request>");
request.Append(REQUEST_TERMINATOR);
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
}
else
{
Client.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestLogin(string accountName, string password, string connectorHandle)
{
if (_DaemonPipe.Connected)
{
StringBuilder request = new StringBuilder();
request.Append(String.Format("<Request requestId=\"{0}\" action=\"Account.Login.1\">", _CommandCookie++));
request.Append(String.Format("<ConnectorHandle>{0}</ConnectorHandle>", connectorHandle));
request.Append(String.Format("<AccountName>{0}</AccountName>", accountName));
request.Append(String.Format("<AccountPassword>{0}</AccountPassword>", password));
request.Append("<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>");
request.Append("</Request>");
request.Append(REQUEST_TERMINATOR);
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
}
else
{
Client.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestSetRenderDevice(string deviceName)
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.SetRenderDevice.1\"><RenderDeviceSpecifier>{1}</RenderDeviceSpecifier></Request>{2}",
_CommandCookie, deviceName, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestStartTuningMode(int duration)
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.CaptureAudioStart.1\"><Duration>{1}</Duration></Request>{2}",
_CommandCookie, duration, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestStopTuningMode()
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.CaptureAudioStop.1\"></Request>{1}",
_CommandCookie, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestSetSpeakerVolume(int volume)
{
if (volume < 0 || volume > 100)
throw new ArgumentException("volume must be between 0 and 100", "volume");
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.SetSpeakerLevel.1\"><Level>{1}</Level></Request>{2}",
_CommandCookie, volume, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestSetCaptureVolume(int volume)
{
if (volume < 0 || volume > 100)
throw new ArgumentException("volume must be between 0 and 100", "volume");
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.SetMicLevel.1\"><Level>{1}</Level></Request>{2}",
_CommandCookie, volume, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
/// <summary>
/// Does not appear to be working
/// </summary>
/// <param name="fileName"></param>
/// <param name="loop"></param>
public void RequestRenderAudioStart(string fileName, bool loop)
{
if (_DaemonPipe.Connected)
{
_TuningSoundFile = fileName;
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.RenderAudioStart.1\"><SoundFilePath>{1}</SoundFilePath><Loop>{2}</Loop></Request>{3}",
_CommandCookie++, _TuningSoundFile, (loop ? "1" : "0"), REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
public void RequestRenderAudioStop()
{
if (_DaemonPipe.Connected)
{
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
"<Request requestId=\"{0}\" action=\"Aux.RenderAudioStop.1\"><SoundFilePath>{1}</SoundFilePath></Request>{2}",
_CommandCookie++, _TuningSoundFile, REQUEST_TERMINATOR)));
}
else
{
Client.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
}
}
private void _DaemonPipe_OnDisconnected(SocketException se)
{
if (se != null) Console.WriteLine("Disconnected! " + se.Message);
else Console.WriteLine("Disconnected!");
}
private void _DaemonPipe_OnReceiveLine(string line)
{
Client.DebugLog("VOICE: " + line);
}
}
}

View File

@@ -37,8 +37,10 @@
<Compile Include="RegistrationApi.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="TCPPipe.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VoiceManager.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\libsecondlife.csproj">

View File

@@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "importprimscript", "..\impo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SLImageUpload", "..\SLImageUpload\SLImageUpload.csproj", "{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceTest", "..\VoiceTest\VoiceTest.csproj", "{B69597A7-5DC5-4564-9089-727D0348EB4E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -131,6 +133,10 @@ Global
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Release|Any CPU.Build.0 = Release|Any CPU
{B69597A7-5DC5-4564-9089-727D0348EB4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B69597A7-5DC5-4564-9089-727D0348EB4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B69597A7-5DC5-4564-9089-727D0348EB4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B69597A7-5DC5-4564-9089-727D0348EB4E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE