Browse Source

Added Opus.Net, fixed public IP method, starting to add outgoing voice support.

tags/docs-0.9
Brandon Smith 9 years ago
parent
commit
32565efa5c
11 changed files with 685 additions and 60 deletions
  1. +6
    -0
      src/Discord.Net.Net45/Discord.Net.csproj
  2. +8
    -1
      src/Discord.Net/DiscordClient.cs
  3. +58
    -9
      src/Discord.Net/DiscordVoiceSocket.cs
  4. +52
    -50
      src/Discord.Net/project.json
  5. +79
    -0
      src/Opus.Net.Net45/Opus.Net.csproj
  6. +17
    -0
      src/Opus.Net.Net45/Properties/AssemblyInfo.cs
  7. +6
    -0
      src/Opus.Net.Net45/packages.config
  8. +108
    -0
      src/Opus.Net/API.cs
  9. +20
    -0
      src/Opus.Net/Opus.Net.xproj
  10. +133
    -0
      src/Opus.Net/OpusDecoder.cs
  11. +198
    -0
      src/Opus.Net/OpusEncoder.cs

+ 6
- 0
src/Discord.Net.Net45/Discord.Net.csproj View File

@@ -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">


+ 8
- 1
src/Discord.Net/DiscordClient.cs View File

@@ -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>


+ 58
- 9
src/Discord.Net/DiscordVoiceSocket.cs View File

@@ -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();


+ 52
- 50
src/Discord.Net/project.json View File

@@ -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"
}
}
}
}

+ 79
- 0
src/Opus.Net.Net45/Opus.Net.csproj View File

@@ -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>

+ 17
- 0
src/Opus.Net.Net45/Properties/AssemblyInfo.cs View File

@@ -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")]

+ 6
- 0
src/Opus.Net.Net45/packages.config View File

@@ -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>

+ 108
- 0
src/Opus.Net/API.cs View File

@@ -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
}
}

+ 20
- 0
src/Opus.Net/Opus.Net.xproj View File

@@ -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>

+ 133
- 0
src/Opus.Net/OpusDecoder.cs View File

@@ -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;
}
}
}

+ 198
- 0
src/Opus.Net/OpusEncoder.cs View File

@@ -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;
}
}
}

Loading…
Cancel
Save