diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj
index f0c978fd3..46ac504d3 100644
--- a/src/Discord.Net.Net45/Discord.Net.csproj
+++ b/src/Discord.Net.Net45/Discord.Net.csproj
@@ -141,6 +141,12 @@
+
+
+ {114c8c10-7354-4ec3-819a-33e83aa57768}
+ Opus.Net
+
+
diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index a6a99528e..6096c61ad 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -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
/// Changes your username to newName.
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();
}
/// Blocking call that will not return until client has been stopped. This is mainly intended for use in console applications.
diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs
index fde7208a1..f7f10c986 100644
--- a/src/Discord.Net/DiscordVoiceSocket.cs
+++ b/src/Discord.Net/DiscordVoiceSocket.cs
@@ -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();
- }
+#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();
diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json
index 9861125a1..939bb334e 100644
--- a/src/Discord.Net/project.json
+++ b/src/Discord.Net/project.json
@@ -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"
+ }
+ }
+ }
}
diff --git a/src/Opus.Net.Net45/Opus.Net.csproj b/src/Opus.Net.Net45/Opus.Net.csproj
new file mode 100644
index 000000000..521c46c40
--- /dev/null
+++ b/src/Opus.Net.Net45/Opus.Net.csproj
@@ -0,0 +1,79 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {114C8C10-7354-4EC3-819A-33E83AA57768}
+ Library
+ Properties
+ Discord.Net
+ Discord.Net
+ 512
+ v4.5
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 2
+ false
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+
+ ..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll
+ True
+
+
+ ..\..\packages\libsodium-net.0.8.0\lib\Net40\Sodium.dll
+ True
+
+
+
+
+
+
+
+
+
+ API.cs
+
+
+ OpusDecoder.cs
+
+
+ OpusEncoder.cs
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Opus.Net.Net45/Properties/AssemblyInfo.cs b/src/Opus.Net.Net45/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..1e97e3ca9
--- /dev/null
+++ b/src/Opus.Net.Net45/Properties/AssemblyInfo.cs
@@ -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")]
diff --git a/src/Opus.Net.Net45/packages.config b/src/Opus.Net.Net45/packages.config
new file mode 100644
index 000000000..46251e3d3
--- /dev/null
+++ b/src/Opus.Net.Net45/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Opus.Net/API.cs b/src/Opus.Net/API.cs
new file mode 100644
index 000000000..62c2b3839
--- /dev/null
+++ b/src/Opus.Net/API.cs
@@ -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
+ }
+
+ ///
+ /// Supported coding modes.
+ ///
+ public enum Application
+ {
+ ///
+ /// Best for most VoIP/videoconference applications where listening quality and intelligibility matter most.
+ ///
+ Voip = 2048,
+ ///
+ /// Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to input.
+ ///
+ Audio = 2049,
+ ///
+ /// Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
+ ///
+ Restricted_LowLatency = 2051
+ }
+
+ public enum Errors
+ {
+ ///
+ /// No error.
+ ///
+ OK = 0,
+ ///
+ /// One or more invalid/out of range arguments.
+ ///
+ BadArg = -1,
+ ///
+ /// The mode struct passed is invalid.
+ ///
+ BufferToSmall = -2,
+ ///
+ /// An internal error was detected.
+ ///
+ InternalError = -3,
+ ///
+ /// The compressed data passed is corrupted.
+ ///
+ InvalidPacket = -4,
+ ///
+ /// Invalid/unsupported request number.
+ ///
+ Unimplemented = -5,
+ ///
+ /// An encoder or decoder structure is invalid or already freed.
+ ///
+ InvalidState = -6,
+ ///
+ /// Memory allocation has failed.
+ ///
+ AllocFail = -7
+ }
+}
diff --git a/src/Opus.Net/Opus.Net.xproj b/src/Opus.Net/Opus.Net.xproj
new file mode 100644
index 000000000..8e1675b53
--- /dev/null
+++ b/src/Opus.Net/Opus.Net.xproj
@@ -0,0 +1,20 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+ 42ab6a2d-2f2c-4003-80ef-33b5b5b0ed8e
+ Opus.Net
+ ..\artifacts\obj\$(MSBuildProjectName)
+ ..\artifacts\bin\$(MSBuildProjectName)\
+
+
+
+ 2.0
+
+
+
diff --git a/src/Opus.Net/OpusDecoder.cs b/src/Opus.Net/OpusDecoder.cs
new file mode 100644
index 000000000..cea5f6c59
--- /dev/null
+++ b/src/Opus.Net/OpusDecoder.cs
@@ -0,0 +1,133 @@
+using System;
+
+namespace Opus.Net
+{
+ ///
+ /// Opus audio decoder.
+ ///
+ public class OpusDecoder : IDisposable
+ {
+ ///
+ /// Creates a new Opus decoder.
+ ///
+ /// Sample rate to decode at (Hz). This must be one of 8000, 12000, 16000, 24000, or 48000.
+ /// Number of channels to decode.
+ /// A new OpusDecoder.
+ 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;
+ }
+
+ ///
+ /// Produces PCM samples from Opus encoded data.
+ ///
+ /// Opus encoded data to decode, null for dropped packet.
+ /// Length of data to decode.
+ /// Set to the length of the decoded sample data.
+ /// PCM audio samples.
+ 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;
+ }
+
+ ///
+ /// Determines the number of frames that can fit into a buffer of the given size.
+ ///
+ ///
+ ///
+ public int FrameCount(int bufferSize)
+ {
+ // seems like bitrate should be required
+ int bitrate = 16;
+ int bytesPerSample = (bitrate / 8) * OutputChannels;
+ return bufferSize / bytesPerSample;
+ }
+
+ ///
+ /// Gets the output sampling rate of the decoder.
+ ///
+ public int OutputSamplingRate { get; private set; }
+
+ ///
+ /// Gets the number of channels of the decoder.
+ ///
+ public int OutputChannels { get; private set; }
+
+ ///
+ /// Gets or sets the size of memory allocated for decoding data.
+ ///
+ public int MaxDataBytes { get; set; }
+
+ ///
+ /// Gets or sets whether forward error correction is enabled or not.
+ ///
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Opus.Net/OpusEncoder.cs b/src/Opus.Net/OpusEncoder.cs
new file mode 100644
index 000000000..f835680a6
--- /dev/null
+++ b/src/Opus.Net/OpusEncoder.cs
@@ -0,0 +1,198 @@
+using System;
+
+namespace Opus.Net
+{
+ ///
+ /// Opus codec wrapper.
+ ///
+ public class OpusEncoder : IDisposable
+ {
+ ///
+ /// Creates a new Opus encoder.
+ ///
+ /// Sampling rate of the input signal (Hz). This must be one of 8000, 12000, 16000, 24000, or 48000.
+ /// Number of channels (1 or 2) in input signal.
+ /// Coding mode.
+ /// A new OpusEncoder
+ 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;
+ }
+
+ ///
+ /// Produces Opus encoded audio from PCM samples.
+ ///
+ /// PCM samples to encode.
+ /// How many bytes to encode.
+ /// Set to length of encoded audio.
+ /// Opus encoded audio buffer.
+ 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;
+ }
+
+ ///
+ /// Determines the number of frames in the PCM samples.
+ ///
+ ///
+ ///
+ public int FrameCount(byte[] pcmSamples)
+ {
+ // seems like bitrate should be required
+ int bitrate = 16;
+ int bytesPerSample = (bitrate / 8) * InputChannels;
+ return pcmSamples.Length / bytesPerSample;
+ }
+
+ ///
+ /// Helper method to determine how many bytes are required for encoding to work.
+ ///
+ /// Target frame size.
+ ///
+ public int FrameByteCount(int frameCount)
+ {
+ int bitrate = 16;
+ int bytesPerSample = (bitrate / 8) * InputChannels;
+ return frameCount * bytesPerSample;
+ }
+
+ ///
+ /// Gets the input sampling rate of the encoder.
+ ///
+ public int InputSamplingRate { get; private set; }
+
+ ///
+ /// Gets the number of channels of the encoder.
+ ///
+ public int InputChannels { get; private set; }
+
+ ///
+ /// Gets the coding mode of the encoder.
+ ///
+ public Application Application { get; private set; }
+
+ ///
+ /// Gets or sets the size of memory allocated for reading encoded data.
+ /// 4000 is recommended.
+ ///
+ public int MaxDataBytes { get; set; }
+
+ ///
+ /// Gets or sets the bitrate setting of the encoding.
+ ///
+ 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());
+ }
+ }
+
+ ///
+ /// Gets or sets whether Forward Error Correction is enabled.
+ ///
+ 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;
+ }
+ }
+}
\ No newline at end of file