From 32565efa5c0bb86a97fa9448e5ebff3f32928ee7 Mon Sep 17 00:00:00 2001 From: Brandon Smith Date: Mon, 24 Aug 2015 02:44:42 -0300 Subject: [PATCH] Added Opus.Net, fixed public IP method, starting to add outgoing voice support. --- src/Discord.Net.Net45/Discord.Net.csproj | 6 + src/Discord.Net/DiscordClient.cs | 9 +- src/Discord.Net/DiscordVoiceSocket.cs | 67 +++++- src/Discord.Net/project.json | 102 ++++----- src/Opus.Net.Net45/Opus.Net.csproj | 79 +++++++ src/Opus.Net.Net45/Properties/AssemblyInfo.cs | 17 ++ src/Opus.Net.Net45/packages.config | 6 + src/Opus.Net/API.cs | 108 ++++++++++ src/Opus.Net/Opus.Net.xproj | 20 ++ src/Opus.Net/OpusDecoder.cs | 133 ++++++++++++ src/Opus.Net/OpusEncoder.cs | 198 ++++++++++++++++++ 11 files changed, 685 insertions(+), 60 deletions(-) create mode 100644 src/Opus.Net.Net45/Opus.Net.csproj create mode 100644 src/Opus.Net.Net45/Properties/AssemblyInfo.cs create mode 100644 src/Opus.Net.Net45/packages.config create mode 100644 src/Opus.Net/API.cs create mode 100644 src/Opus.Net/Opus.Net.xproj create mode 100644 src/Opus.Net/OpusDecoder.cs create mode 100644 src/Opus.Net/OpusEncoder.cs 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