| @@ -141,6 +141,12 @@ | |||||
| </Compile> | </Compile> | ||||
| <Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
| </ItemGroup> | </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="$(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')" /> | <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"> | <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | ||||
| @@ -1320,6 +1320,13 @@ namespace Discord | |||||
| return DiscordAPI.Undeafen(serverId, userId); | return DiscordAPI.Undeafen(serverId, userId); | ||||
| } | } | ||||
| #if !DNXCORE50 | |||||
| public void SendVoiceWAV(byte[] buffer, int count) | |||||
| { | |||||
| _voiceWebSocket.SendWAV(buffer, count); | |||||
| } | |||||
| #endif | |||||
| //Profile | //Profile | ||||
| /// <summary> Changes your username to newName. </summary> | /// <summary> Changes your username to newName. </summary> | ||||
| public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) | public async Task ChangeUsername(string newName, string currentEmail, string currentPassword) | ||||
| @@ -1360,7 +1367,7 @@ namespace Discord | |||||
| private string GenerateNonce() | private string GenerateNonce() | ||||
| { | { | ||||
| lock (_rand) | 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> | /// <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; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; | using WebSocketMessage = Discord.API.Models.VoiceWebSocketCommands.WebSocketMessage; | ||||
| using System.Text; | |||||
| #if !DNXCORE50 | |||||
| using Opus.Net; | |||||
| #endif | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| @@ -23,13 +27,24 @@ namespace Discord | |||||
| private byte[] _secretKey; | private byte[] _secretKey; | ||||
| private string _mode; | private string _mode; | ||||
| private bool _isFirst; | 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) | public DiscordVoiceSocket(int timeout, int interval) | ||||
| : base(timeout, interval) | : base(timeout, interval) | ||||
| { | { | ||||
| _connectWaitOnLogin = new ManualResetEventSlim(false); | _connectWaitOnLogin = new ManualResetEventSlim(false); | ||||
| _sendQueue = new ConcurrentQueue<byte[]>(); | _sendQueue = new ConcurrentQueue<byte[]>(); | ||||
| } | |||||
| #if !DNXCORE50 | |||||
| _encoder = OpusEncoder.Create(24000, 1, Application.Voip); | |||||
| #endif | |||||
| } | |||||
| protected override void OnConnect() | protected override void OnConnect() | ||||
| { | { | ||||
| @@ -58,7 +73,7 @@ namespace Discord | |||||
| _connectWaitOnLogin.Reset(); | _connectWaitOnLogin.Reset(); | ||||
| _myIp = (await Http.Get("http://ipinfo.io/ip")).Trim(); | |||||
| _sequence = 0; | |||||
| VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | VoiceWebSocketCommands.Login msg = new VoiceWebSocketCommands.Login(); | ||||
| msg.Payload.ServerId = serverId; | msg.Payload.ServerId = serverId; | ||||
| @@ -138,13 +153,18 @@ namespace Discord | |||||
| //_mode = payload.Modes.LastOrDefault(); | //_mode = payload.Modes.LastOrDefault(); | ||||
| _mode = "plain"; | _mode = "plain"; | ||||
| _udp.Connect(_endpoint); | _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] { | _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, 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; | int port = buffer[68] | buffer[69] << 8; | ||||
| _myIp = Encoding.ASCII.GetString(buffer, 4, 70 - 6).TrimEnd('\0'); | |||||
| var login2 = new VoiceWebSocketCommands.Login2(); | var login2 = new VoiceWebSocketCommands.Login2(); | ||||
| login2.Payload.Protocol = "udp"; | login2.Payload.Protocol = "udp"; | ||||
| login2.Payload.SocketData.Address = _myIp; | login2.Payload.SocketData.Address = _myIp; | ||||
| @@ -189,7 +211,7 @@ namespace Discord | |||||
| else | else | ||||
| { | { | ||||
| //Parse RTP Data | //Parse RTP Data | ||||
| if (length < 12) | |||||
| /*if (length < 12) | |||||
| throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | ||||
| byte flags = buffer[0]; | byte flags = buffer[0]; | ||||
| @@ -227,11 +249,38 @@ namespace Discord | |||||
| byte[] newBuffer = new byte[buffer.Length - 12]; | byte[] newBuffer = new byte[buffer.Length - 12]; | ||||
| Buffer.BlockCopy(buffer, 12, newBuffer, 0, newBuffer.Length); | Buffer.BlockCopy(buffer, 12, newBuffer, 0, newBuffer.Length); | ||||
| buffer = newBuffer; | 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() | protected override object GetKeepAlive() | ||||
| { | { | ||||
| return new VoiceWebSocketCommands.KeepAlive(); | 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; | |||||
| } | |||||
| } | |||||
| } | |||||