@@ -141,6 +141,12 @@ | |||
</Compile> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\Opus.Net.Net45\Opus.Net.csproj"> | |||
<Project>{114c8c10-7354-4ec3-819a-33e83aa57768}</Project> | |||
<Name>Opus.Net</Name> | |||
</ProjectReference> | |||
</ItemGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<Import Project="..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets" Condition="Exists('..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets')" /> | |||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | |||
@@ -1320,6 +1320,13 @@ namespace Discord | |||
return DiscordAPI.Undeafen(serverId, userId); | |||
} | |||
#if !DNXCORE50 | |||
public void SendVoiceWAV(byte[] buffer, int count) | |||
{ | |||
_voiceWebSocket.SendWAV(buffer, count); | |||
} | |||
#endif | |||
//Profile | |||
/// <summary> Changes your username to newName. </summary> | |||
public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) | |||
@@ -1360,7 +1367,7 @@ namespace Discord | |||
private string GenerateNonce() | |||
{ | |||
lock (_rand) | |||
return _rand.Next(0, int.MaxValue).ToString(); | |||
return _rand.Next().ToString(); | |||
} | |||
/// <summary> Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications. </summary> | |||
@@ -10,6 +10,10 @@ using System.Net.Sockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; | |||
using System.Text; | |||
#if !DNXCORE50 | |||
using Opus.Net; | |||
#endif | |||
namespace Discord | |||
{ | |||
@@ -23,13 +27,24 @@ namespace Discord | |||
private byte[] _secretKey; | |||
private string _mode; | |||
private bool _isFirst; | |||
private ushort _sequence; | |||
private uint _ssrc; | |||
private long _startTicks; | |||
private readonly Random _rand = new Random(); | |||
#if !DNXCORE50 | |||
private OpusEncoder _encoder; | |||
#endif | |||
public DiscordVoiceSocket(int timeout, int interval) | |||
: base(timeout, interval) | |||
{ | |||
_connectWaitOnLogin = new ManualResetEventSlim(false); | |||
_sendQueue = new ConcurrentQueue<byte[]>(); | |||
} | |||
#if !DNXCORE50 | |||
_encoder = OpusEncoder.Create(24000, 1, Application.Voip); | |||
#endif | |||
} | |||
protected override void OnConnect() | |||
{ | |||
@@ -58,7 +73,7 @@ namespace Discord | |||
_connectWaitOnLogin.Reset(); | |||
_myIp = (await Http.Get("http://ipinfo.io/ip")).Trim(); | |||
_sequence = 0; | |||
VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | |||
msg.Payload.ServerId = serverId; | |||
@@ -138,13 +153,18 @@ namespace Discord | |||
//_mode = payload.Modes.LastOrDefault(); | |||
_mode = "plain"; | |||
_udp.Connect(_endpoint); | |||
var ssrc = payload.SSRC; | |||
lock(_rand) | |||
{ | |||
_sequence = (ushort)_rand.Next(0, ushort.MaxValue); | |||
_startTicks = DateTime.UtcNow.Ticks - _rand.Next(); | |||
} | |||
_ssrc = payload.SSRC; | |||
_sendQueue.Enqueue(new byte[70] { | |||
(byte)((ssrc >> 24) & 0xFF), | |||
(byte)((ssrc >> 16) & 0xFF), | |||
(byte)((ssrc >> 8) & 0xFF), | |||
(byte)((ssrc >> 0) & 0xFF), | |||
(byte)((_ssrc >> 24) & 0xFF), | |||
(byte)((_ssrc >> 16) & 0xFF), | |||
(byte)((_ssrc >> 8) & 0xFF), | |||
(byte)((_ssrc >> 0) & 0xFF), | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, | |||
@@ -179,6 +199,8 @@ namespace Discord | |||
int port = buffer[68] | buffer[69] << 8; | |||
_myIp = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); | |||
var login2 = new VoiceWebSocketCommands.Login2(); | |||
login2.Payload.Protocol = "udp"; | |||
login2.Payload.SocketData.Address = _myIp; | |||
@@ -189,7 +211,7 @@ namespace Discord | |||
else | |||
{ | |||
//Parse RTP Data | |||
if (length < 12) | |||
/*if (length < 12) | |||
throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | |||
byte flags = buffer[0]; | |||
@@ -227,11 +249,38 @@ namespace Discord | |||
byte[] newBuffer = new byte[buffer.Length - 12]; | |||
Buffer.BlockCopy(buffer, 12, newBuffer, 0, newBuffer.Length); | |||
buffer = newBuffer; | |||
} | |||
}*/ | |||
//TODO: Use Voice Data | |||
} | |||
} | |||
} | |||
#if !DNXCORE50 | |||
public void SendWAV(byte[] buffer, int count) | |||
{ | |||
int encodedLength; | |||
buffer = _encoder.Encode(buffer, count, out encodedLength); | |||
byte[] packet = new byte[12 + encodedLength]; | |||
Buffer.BlockCopy(buffer, 0, packet, 12, encodedLength); | |||
ushort sequence = _sequence++; | |||
long timestamp = (DateTime.UtcNow.Ticks - _startTicks) >> 2; //200ns resolution | |||
packet[0] = 0x80; //Flags; | |||
packet[1] = 0x78; //Payload Type | |||
packet[2] = (byte)((sequence >> 8) & 0xFF); | |||
packet[3] = (byte)((sequence >> 0) & 0xFF); | |||
packet[4] = (byte)((timestamp >> 24) & 0xFF); | |||
packet[5] = (byte)((timestamp >> 16) & 0xFF); | |||
packet[6] = (byte)((timestamp >> 8) & 0xFF); | |||
packet[7] = (byte)((timestamp >> 0) & 0xFF); | |||
packet[8] = (byte)((_ssrc >> 24) & 0xFF); | |||
packet[9] = (byte)((_ssrc >> 16) & 0xFF); | |||
packet[10] = (byte)((_ssrc >> 8) & 0xFF); | |||
packet[11] = (byte)((_ssrc >> 0) & 0xFF); | |||
} | |||
#endif | |||
protected override object GetKeepAlive() | |||
{ | |||
return new VoiceWebSocketCommands.KeepAlive(); | |||
@@ -1,54 +1,56 @@ | |||
{ | |||
"version": "0.5.0-*", | |||
"description": "An unofficial .Net API wrapper for the Discord client.", | |||
"authors": [ "RogueException" ], | |||
"tags": [ "discord", "discordapp" ], | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
}, | |||
"configurations": { | |||
"FullDebug": { | |||
"compilationOptions": { | |||
"define": [ "DEBUG", "TRACE", "TEST_RESPONSES" ] | |||
} | |||
} | |||
}, | |||
"version": "0.5.0-*", | |||
"description": "An unofficial .Net API wrapper for the Discord client.", | |||
"authors": [ "RogueException" ], | |||
"tags": [ "discord", "discordapp" ], | |||
"projectUrl": "https://github.com/RogueException/Discord.Net", | |||
"licenseUrl": "http://opensource.org/licenses/MIT", | |||
"repository": { | |||
"type": "git", | |||
"url": "git://github.com/RogueException/Discord.Net" | |||
}, | |||
"configurations": { | |||
"FullDebug": { | |||
"compilationOptions": { | |||
"define": [ "DEBUG", "TRACE", "TEST_RESPONSES" ] | |||
} | |||
} | |||
}, | |||
"dependencies": { | |||
"Newtonsoft.Json": "7.0.1" | |||
}, | |||
"dependencies": { | |||
"Newtonsoft.Json": "7.0.1" | |||
}, | |||
"frameworks": { | |||
"net45": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0" | |||
} | |||
}, | |||
"dnx451": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0" | |||
} | |||
}, | |||
"dnxcore50": { | |||
"dependencies": { | |||
"System.Collections.Concurrent": "4.0.10", | |||
"System.Diagnostics.Debug": "4.0.10", | |||
"System.IO.Compression": "4.0.0", | |||
"System.Linq": "4.0.0", | |||
"System.Net.Requests": "4.0.10", | |||
"System.Net.Sockets": "4.0.10-beta-23019", | |||
"System.Net.WebSockets.Client": "4.0.0-beta-23123", | |||
"System.Runtime": "4.0.20", | |||
"System.Text.RegularExpressions": "4.0.10", | |||
"System.Net.NameResolution": "4.0.0-beta-23019" | |||
} | |||
} | |||
} | |||
"frameworks": { | |||
"net45": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0", | |||
"Opus.Net": "0.1.0" | |||
} | |||
}, | |||
"dnx451": { | |||
"dependencies": { | |||
"Microsoft.Net.Http": "2.2.22", | |||
"libsodium-net": "0.8.0", | |||
"Baseclass.Contrib.Nuget.Output": "2.1.0", | |||
"Opus.Net": "0.1.0" | |||
} | |||
}, | |||
"dnxcore50": { | |||
"dependencies": { | |||
"System.Collections.Concurrent": "4.0.10", | |||
"System.Diagnostics.Debug": "4.0.10", | |||
"System.IO.Compression": "4.0.0", | |||
"System.Linq": "4.0.0", | |||
"System.Net.Requests": "4.0.10", | |||
"System.Net.Sockets": "4.0.10-beta-23019", | |||
"System.Net.WebSockets.Client": "4.0.0-beta-23123", | |||
"System.Runtime": "4.0.20", | |||
"System.Text.RegularExpressions": "4.0.10", | |||
"System.Net.NameResolution": "4.0.0-beta-23019" | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||
<PropertyGroup> | |||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||
<ProjectGuid>{114C8C10-7354-4EC3-819A-33E83AA57768}</ProjectGuid> | |||
<OutputType>Library</OutputType> | |||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||
<RootNamespace>Discord.Net</RootNamespace> | |||
<AssemblyName>Discord.Net</AssemblyName> | |||
<FileAlignment>512</FileAlignment> | |||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | |||
<NuGetPackageImportStamp> | |||
</NuGetPackageImportStamp> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||
<DebugSymbols>true</DebugSymbols> | |||
<DebugType>full</DebugType> | |||
<Optimize>false</Optimize> | |||
<OutputPath>bin\Debug\</OutputPath> | |||
<DefineConstants>DEBUG;TRACE</DefineConstants> | |||
<ErrorReport>prompt</ErrorReport> | |||
<WarningLevel>2</WarningLevel> | |||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
</PropertyGroup> | |||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||
<DebugType>pdbonly</DebugType> | |||
<Optimize>true</Optimize> | |||
<OutputPath>bin\Release\</OutputPath> | |||
<DefineConstants>TRACE</DefineConstants> | |||
<ErrorReport>prompt</ErrorReport> | |||
<WarningLevel>4</WarningLevel> | |||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | |||
<HintPath>..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Sodium, Version=0.8.0.0, Culture=neutral, processorArchitecture=MSIL"> | |||
<HintPath>..\..\packages\libsodium-net.0.8.0\lib\Net40\Sodium.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System" /> | |||
<Reference Include="System.Net.Http" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="packages.config" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="..\Opus.Net\API.cs"> | |||
<Link>API.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Opus.Net\OpusDecoder.cs"> | |||
<Link>OpusDecoder.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Opus.Net\OpusEncoder.cs"> | |||
<Link>OpusEncoder.cs</Link> | |||
</Compile> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
</ItemGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<Import Project="..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets" Condition="Exists('..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets')" /> | |||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | |||
<PropertyGroup> | |||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | |||
</PropertyGroup> | |||
<Error Condition="!Exists('..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Baseclass.Contrib.Nuget.Output.1.0.0\build\net40\Baseclass.Contrib.Nuget.Output.targets'))" /> | |||
</Target> | |||
<!-- 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> |
@@ -0,0 +1,17 @@ | |||
using System.Reflection; | |||
using System.Runtime.InteropServices; | |||
[assembly: AssemblyTitle("Opus.Net")] | |||
[assembly: AssemblyDescription("Opus .NET Wrapper")] | |||
[assembly: AssemblyConfiguration("")] | |||
[assembly: AssemblyCompany("John Carruthers")] | |||
[assembly: AssemblyProduct("Opus.Net")] | |||
[assembly: AssemblyCopyright("Copyright © 2013")] | |||
[assembly: AssemblyTrademark("")] | |||
[assembly: AssemblyCulture("")] | |||
[assembly: ComVisible(false)] | |||
[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] | |||
[assembly: AssemblyVersion("0.1.0.0")] | |||
[assembly: AssemblyFileVersion("0.1.0.0")] |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<packages> | |||
<package id="Baseclass.Contrib.Nuget.Output" version="1.0.0" targetFramework="net45" /> | |||
<package id="libsodium-net" version="0.8.0" targetFramework="net45" /> | |||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net45" /> | |||
</packages> |
@@ -0,0 +1,108 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using System.Threading.Tasks; | |||
namespace Opus.Net | |||
{ | |||
internal class API | |||
{ | |||
static API() | |||
{ | |||
if (LoadLibrary(Environment.Is64BitProcess ? "lib/x64/opus.dll" : "lib/x86/opus.dll") == IntPtr.Zero) | |||
throw new FileNotFoundException("Unable to find opus.dll", "opus.dll"); | |||
} | |||
[DllImport("kernel32.dll")] | |||
private static extern IntPtr LoadLibrary(string dllToLoad); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern IntPtr opus_encoder_create(int Fs, int channels, int application, out IntPtr error); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern void opus_encoder_destroy(IntPtr encoder); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern int opus_encode(IntPtr st, byte[] pcm, int frame_size, IntPtr data, int max_data_bytes); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern IntPtr opus_decoder_create(int Fs, int channels, out IntPtr error); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern void opus_decoder_destroy(IntPtr decoder); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern int opus_decode(IntPtr st, byte[] data, int len, IntPtr pcm, int frame_size, int decode_fec); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern int opus_encoder_ctl(IntPtr st, Ctl request, int value); | |||
[DllImport("opus.dll", CallingConvention = CallingConvention.Cdecl)] | |||
internal static extern int opus_encoder_ctl(IntPtr st, Ctl request, out int value); | |||
} | |||
public enum Ctl : int | |||
{ | |||
SetBitrateRequest = 4002, | |||
GetBitrateRequest = 4003, | |||
SetInbandFECRequest = 4012, | |||
GetInbandFECRequest = 4013 | |||
} | |||
/// <summary> | |||
/// Supported coding modes. | |||
/// </summary> | |||
public enum Application | |||
{ | |||
/// <summary> | |||
/// Best for most VoIP/videoconference applications where listening quality and intelligibility matter most. | |||
/// </summary> | |||
Voip = 2048, | |||
/// <summary> | |||
/// Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to input. | |||
/// </summary> | |||
Audio = 2049, | |||
/// <summary> | |||
/// Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used. | |||
/// </summary> | |||
Restricted_LowLatency = 2051 | |||
} | |||
public enum Errors | |||
{ | |||
/// <summary> | |||
/// No error. | |||
/// </summary> | |||
OK = 0, | |||
/// <summary> | |||
/// One or more invalid/out of range arguments. | |||
/// </summary> | |||
BadArg = -1, | |||
/// <summary> | |||
/// The mode struct passed is invalid. | |||
/// </summary> | |||
BufferToSmall = -2, | |||
/// <summary> | |||
/// An internal error was detected. | |||
/// </summary> | |||
InternalError = -3, | |||
/// <summary> | |||
/// The compressed data passed is corrupted. | |||
/// </summary> | |||
InvalidPacket = -4, | |||
/// <summary> | |||
/// Invalid/unsupported request number. | |||
/// </summary> | |||
Unimplemented = -5, | |||
/// <summary> | |||
/// An encoder or decoder structure is invalid or already freed. | |||
/// </summary> | |||
InvalidState = -6, | |||
/// <summary> | |||
/// Memory allocation has failed. | |||
/// </summary> | |||
AllocFail = -7 | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | |||
<PropertyGroup Label="Globals"> | |||
<ProjectGuid>42ab6a2d-2f2c-4003-80ef-33b5b5b0ed8e</ProjectGuid> | |||
<RootNamespace>Opus.Net</RootNamespace> | |||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | |||
<OutputPath Condition="'$(OutputPath)'=='' ">..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<SchemaVersion>2.0</SchemaVersion> | |||
</PropertyGroup> | |||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" /> | |||
</Project> |
@@ -0,0 +1,133 @@ | |||
using System; | |||
namespace Opus.Net | |||
{ | |||
/// <summary> | |||
/// Opus audio decoder. | |||
/// </summary> | |||
public class OpusDecoder : IDisposable | |||
{ | |||
/// <summary> | |||
/// Creates a new Opus decoder. | |||
/// </summary> | |||
/// <param name="outputSampleRate">Sample rate to decode at (Hz). This must be one of 8000, 12000, 16000, 24000, or 48000.</param> | |||
/// <param name="outputChannels">Number of channels to decode.</param> | |||
/// <returns>A new <c>OpusDecoder</c>.</returns> | |||
public static OpusDecoder Create(int outputSampleRate, int outputChannels) | |||
{ | |||
if (outputSampleRate != 8000 && | |||
outputSampleRate != 12000 && | |||
outputSampleRate != 16000 && | |||
outputSampleRate != 24000 && | |||
outputSampleRate != 48000) | |||
throw new ArgumentOutOfRangeException("inputSamplingRate"); | |||
if (outputChannels != 1 && outputChannels != 2) | |||
throw new ArgumentOutOfRangeException("inputChannels"); | |||
IntPtr error; | |||
IntPtr decoder = API.opus_decoder_create(outputSampleRate, outputChannels, out error); | |||
if ((Errors)error != Errors.OK) | |||
{ | |||
throw new Exception("Exception occured while creating decoder"); | |||
} | |||
return new OpusDecoder(decoder, outputSampleRate, outputChannels); | |||
} | |||
private IntPtr _decoder; | |||
private OpusDecoder(IntPtr decoder, int outputSamplingRate, int outputChannels) | |||
{ | |||
_decoder = decoder; | |||
OutputSamplingRate = outputSamplingRate; | |||
OutputChannels = outputChannels; | |||
MaxDataBytes = 4000; | |||
} | |||
/// <summary> | |||
/// Produces PCM samples from Opus encoded data. | |||
/// </summary> | |||
/// <param name="inputOpusData">Opus encoded data to decode, null for dropped packet.</param> | |||
/// <param name="dataLength">Length of data to decode.</param> | |||
/// <param name="decodedLength">Set to the length of the decoded sample data.</param> | |||
/// <returns>PCM audio samples.</returns> | |||
public unsafe byte[] Decode(byte[] inputOpusData, int dataLength, out int decodedLength) | |||
{ | |||
if (disposed) | |||
throw new ObjectDisposedException("OpusDecoder"); | |||
IntPtr decodedPtr; | |||
byte[] decoded = new byte[MaxDataBytes]; | |||
int frameCount = FrameCount(MaxDataBytes); | |||
int length = 0; | |||
fixed (byte* bdec = decoded) | |||
{ | |||
decodedPtr = new IntPtr((void*)bdec); | |||
if (inputOpusData != null) | |||
length = API.opus_decode(_decoder, inputOpusData, dataLength, decodedPtr, frameCount, 0); | |||
else | |||
length = API.opus_decode(_decoder, null, 0, decodedPtr, frameCount, (ForwardErrorCorrection) ? 1 : 0); | |||
} | |||
decodedLength = length * 2; | |||
if (length < 0) | |||
throw new Exception("Decoding failed - " + ((Errors)length).ToString()); | |||
return decoded; | |||
} | |||
/// <summary> | |||
/// Determines the number of frames that can fit into a buffer of the given size. | |||
/// </summary> | |||
/// <param name="bufferSize"></param> | |||
/// <returns></returns> | |||
public int FrameCount(int bufferSize) | |||
{ | |||
// seems like bitrate should be required | |||
int bitrate = 16; | |||
int bytesPerSample = (bitrate / 8) * OutputChannels; | |||
return bufferSize / bytesPerSample; | |||
} | |||
/// <summary> | |||
/// Gets the output sampling rate of the decoder. | |||
/// </summary> | |||
public int OutputSamplingRate { get; private set; } | |||
/// <summary> | |||
/// Gets the number of channels of the decoder. | |||
/// </summary> | |||
public int OutputChannels { get; private set; } | |||
/// <summary> | |||
/// Gets or sets the size of memory allocated for decoding data. | |||
/// </summary> | |||
public int MaxDataBytes { get; set; } | |||
/// <summary> | |||
/// Gets or sets whether forward error correction is enabled or not. | |||
/// </summary> | |||
public bool ForwardErrorCorrection { get; set; } | |||
~OpusDecoder() | |||
{ | |||
Dispose(); | |||
} | |||
private bool disposed; | |||
public void Dispose() | |||
{ | |||
if (disposed) | |||
return; | |||
GC.SuppressFinalize(this); | |||
if (_decoder != IntPtr.Zero) | |||
{ | |||
API.opus_decoder_destroy(_decoder); | |||
_decoder = IntPtr.Zero; | |||
} | |||
disposed = true; | |||
} | |||
} | |||
} |
@@ -0,0 +1,198 @@ | |||
using System; | |||
namespace Opus.Net | |||
{ | |||
/// <summary> | |||
/// Opus codec wrapper. | |||
/// </summary> | |||
public class OpusEncoder : IDisposable | |||
{ | |||
/// <summary> | |||
/// Creates a new Opus encoder. | |||
/// </summary> | |||
/// <param name="inputSamplingRate">Sampling rate of the input signal (Hz). This must be one of 8000, 12000, 16000, 24000, or 48000.</param> | |||
/// <param name="inputChannels">Number of channels (1 or 2) in input signal.</param> | |||
/// <param name="application">Coding mode.</param> | |||
/// <returns>A new <c>OpusEncoder</c></returns> | |||
public static OpusEncoder Create(int inputSamplingRate, int inputChannels, Application application) | |||
{ | |||
if (inputSamplingRate != 8000 && | |||
inputSamplingRate != 12000 && | |||
inputSamplingRate != 16000 && | |||
inputSamplingRate != 24000 && | |||
inputSamplingRate != 48000) | |||
throw new ArgumentOutOfRangeException("inputSamplingRate"); | |||
if (inputChannels != 1 && inputChannels != 2) | |||
throw new ArgumentOutOfRangeException("inputChannels"); | |||
IntPtr error; | |||
IntPtr encoder = API.opus_encoder_create(inputSamplingRate, inputChannels, (int)application, out error); | |||
if ((Errors)error != Errors.OK) | |||
{ | |||
throw new Exception("Exception occured while creating encoder"); | |||
} | |||
return new OpusEncoder(encoder, inputSamplingRate, inputChannels, application); | |||
} | |||
private IntPtr _encoder; | |||
private OpusEncoder(IntPtr encoder, int inputSamplingRate, int inputChannels, Application application) | |||
{ | |||
_encoder = encoder; | |||
InputSamplingRate = inputSamplingRate; | |||
InputChannels = inputChannels; | |||
Application = application; | |||
MaxDataBytes = 4000; | |||
} | |||
/// <summary> | |||
/// Produces Opus encoded audio from PCM samples. | |||
/// </summary> | |||
/// <param name="inputPcmSamples">PCM samples to encode.</param> | |||
/// <param name="sampleLength">How many bytes to encode.</param> | |||
/// <param name="encodedLength">Set to length of encoded audio.</param> | |||
/// <returns>Opus encoded audio buffer.</returns> | |||
public unsafe byte[] Encode(byte[] inputPcmSamples, int sampleLength, out int encodedLength) | |||
{ | |||
if (disposed) | |||
throw new ObjectDisposedException("OpusEncoder"); | |||
int frames = FrameCount(inputPcmSamples); | |||
IntPtr encodedPtr; | |||
byte[] encoded = new byte[MaxDataBytes]; | |||
int length = 0; | |||
fixed (byte* benc = encoded) | |||
{ | |||
encodedPtr = new IntPtr((void*)benc); | |||
length = API.opus_encode(_encoder, inputPcmSamples, frames, encodedPtr, sampleLength); | |||
} | |||
encodedLength = length; | |||
if (length < 0) | |||
throw new Exception("Encoding failed - " + ((Errors)length).ToString()); | |||
return encoded; | |||
} | |||
/// <summary> | |||
/// Determines the number of frames in the PCM samples. | |||
/// </summary> | |||
/// <param name="pcmSamples"></param> | |||
/// <returns></returns> | |||
public int FrameCount(byte[] pcmSamples) | |||
{ | |||
// seems like bitrate should be required | |||
int bitrate = 16; | |||
int bytesPerSample = (bitrate / 8) * InputChannels; | |||
return pcmSamples.Length / bytesPerSample; | |||
} | |||
/// <summary> | |||
/// Helper method to determine how many bytes are required for encoding to work. | |||
/// </summary> | |||
/// <param name="frameCount">Target frame size.</param> | |||
/// <returns></returns> | |||
public int FrameByteCount(int frameCount) | |||
{ | |||
int bitrate = 16; | |||
int bytesPerSample = (bitrate / 8) * InputChannels; | |||
return frameCount * bytesPerSample; | |||
} | |||
/// <summary> | |||
/// Gets the input sampling rate of the encoder. | |||
/// </summary> | |||
public int InputSamplingRate { get; private set; } | |||
/// <summary> | |||
/// Gets the number of channels of the encoder. | |||
/// </summary> | |||
public int InputChannels { get; private set; } | |||
/// <summary> | |||
/// Gets the coding mode of the encoder. | |||
/// </summary> | |||
public Application Application { get; private set; } | |||
/// <summary> | |||
/// Gets or sets the size of memory allocated for reading encoded data. | |||
/// 4000 is recommended. | |||
/// </summary> | |||
public int MaxDataBytes { get; set; } | |||
/// <summary> | |||
/// Gets or sets the bitrate setting of the encoding. | |||
/// </summary> | |||
public int Bitrate | |||
{ | |||
get | |||
{ | |||
if (disposed) | |||
throw new ObjectDisposedException("OpusEncoder"); | |||
int bitrate; | |||
var ret = API.opus_encoder_ctl(_encoder, Ctl.GetBitrateRequest, out bitrate); | |||
if (ret < 0) | |||
throw new Exception("Encoder error - " + ((Errors)ret).ToString()); | |||
return bitrate; | |||
} | |||
set | |||
{ | |||
if (disposed) | |||
throw new ObjectDisposedException("OpusEncoder"); | |||
var ret = API.opus_encoder_ctl(_encoder, Ctl.SetBitrateRequest, value); | |||
if (ret < 0) | |||
throw new Exception("Encoder error - " + ((Errors)ret).ToString()); | |||
} | |||
} | |||
/// <summary> | |||
/// Gets or sets whether Forward Error Correction is enabled. | |||
/// </summary> | |||
public bool ForwardErrorCorrection | |||
{ | |||
get | |||
{ | |||
if (_encoder == IntPtr.Zero) | |||
throw new ObjectDisposedException("OpusEncoder"); | |||
int fec; | |||
int ret = API.opus_encoder_ctl(_encoder, Ctl.GetInbandFECRequest, out fec); | |||
if (ret < 0) | |||
throw new Exception("Encoder error - " + ((Errors)ret).ToString()); | |||
return fec > 0; | |||
} | |||
set | |||
{ | |||
if (_encoder == IntPtr.Zero) | |||
throw new ObjectDisposedException("OpusEncoder"); | |||
var ret = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0); | |||
if (ret < 0) | |||
throw new Exception("Encoder error - " + ((Errors)ret).ToString()); | |||
} | |||
} | |||
~OpusEncoder() | |||
{ | |||
Dispose(); | |||
} | |||
private bool disposed; | |||
public void Dispose() | |||
{ | |||
if (disposed) | |||
return; | |||
GC.SuppressFinalize(this); | |||
if (_encoder != IntPtr.Zero) | |||
{ | |||
API.opus_encoder_destroy(_encoder); | |||
_encoder = IntPtr.Zero; | |||
} | |||
disposed = true; | |||
} | |||
} | |||
} |