diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs
index c08bef3ae..0d9f82ab4 100644
--- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs
+++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs
@@ -12,6 +12,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
namespace Discord.Audio
{
@@ -45,14 +46,17 @@ namespace Discord.Audio
private string _url, _sessionId, _token;
private ulong _userId;
private uint _ssrc;
+ private byte[] _secretKey;
+ private GCHandle _secretKeyHandle;
private bool _isSpeaking;
+ private bool _isDisposed;
public SocketGuild Guild { get; }
public DiscordVoiceAPIClient ApiClient { get; private set; }
public int Latency { get; private set; }
public int UdpLatency { get; private set; }
public ulong ChannelId { get; internal set; }
- internal byte[] SecretKey { get; private set; }
+ internal IntPtr SecretKeyPtr { get; private set; }
private DiscordSocketClient Discord => Guild.Discord;
public ConnectionState ConnectionState => _connection.State;
@@ -93,6 +97,20 @@ namespace Discord.Audio
UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false);
}
+ internal void Dispose(bool disposing)
+ {
+ if (disposing && !_isDisposed)
+ {
+ StopAsync().GetAwaiter().GetResult();
+ ApiClient.Dispose();
+ if (_secretKeyHandle.IsAllocated)
+ _secretKeyHandle.Free();
+ _isDisposed = true;
+ }
+ }
+ ///
+ public void Dispose() => Dispose(true);
+
internal async Task StartAsync(string url, ulong userId, string sessionId, string token)
{
_url = url;
@@ -242,7 +260,12 @@ namespace Discord.Audio
if (data.Mode != DiscordVoiceAPIClient.Mode)
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}");
- SecretKey = data.SecretKey;
+ _secretKey = data.SecretKey;
+ if (_secretKeyHandle != null)
+ _secretKeyHandle.Free();
+ _secretKeyHandle = GCHandle.Alloc(data.SecretKey, GCHandleType.Pinned);
+ SecretKeyPtr = _secretKeyHandle.AddrOfPinnedObject();
+
_isSpeaking = false;
await ApiClient.SendSetSpeaking(false).ConfigureAwait(false);
_keepaliveTask = RunKeepaliveAsync(5000, _connection.CancelToken);
@@ -386,7 +409,7 @@ namespace Discord.Audio
await _audioLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
- var now = Environment.TickCount;
+ int now = Environment.TickCount;
//Did server respond to our last heartbeat?
if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis &&
@@ -427,7 +450,7 @@ namespace Discord.Audio
await _audioLogger.DebugAsync("Keepalive Started").ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
- var now = Environment.TickCount;
+ int now = Environment.TickCount;
try
{
@@ -474,16 +497,5 @@ namespace Discord.Audio
return buffer;
return new byte[OpusConverter.FrameBytes];
}
-
- internal void Dispose(bool disposing)
- {
- if (disposing)
- {
- StopAsync().GetAwaiter().GetResult();
- ApiClient.Dispose();
- }
- }
- ///
- public void Dispose() => Dispose(true);
}
}
diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs
index 41c48e1ac..bd63ed989 100644
--- a/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs
+++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs
@@ -20,12 +20,9 @@ namespace Discord.Audio
CheckError(error);
}
- public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset, bool decodeFEC)
+ public unsafe int DecodeFrame(byte* inPtr, int inputOffset, int inputCount, byte* outPtr, int outputOffset, bool decodeFEC)
{
- int result = 0;
- fixed (byte* inPtr = input)
- fixed (byte* outPtr = output)
- result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, decodeFEC ? 1 : 0);
+ int result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, decodeFEC ? 1 : 0);
CheckError(result);
return result * SampleBytes;
}
diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs
index 1ff5a5d9a..fc0113be9 100644
--- a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs
+++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs
@@ -53,12 +53,9 @@ namespace Discord.Audio
CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate));
}
- public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output, int outputOffset)
+ public unsafe int EncodeFrame(byte* inPtr, int inputOffset, byte* outPtr, int outputOffset, int count)
{
- int result = 0;
- fixed (byte* inPtr = input)
- fixed (byte* outPtr = output)
- result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, output.Length - outputOffset);
+ int result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, count);
CheckError(result);
return result;
}
diff --git a/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs b/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs
index 4187c9f08..b8a77c681 100644
--- a/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs
+++ b/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs
@@ -6,31 +6,23 @@ namespace Discord.Audio
public unsafe static class SecretBox
{
[DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)]
- private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret);
+ private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte* nonce, byte* secret);
[DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)]
- private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret);
+ private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte* nonce, byte* secret);
- public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret)
+ public static int Encrypt(byte* inPtr, int inputOffset, int inputLength, byte* outPtr, int outputOffset, byte* nonce, byte* secret)
{
- fixed (byte* inPtr = input)
- fixed (byte* outPtr = output)
- {
- int error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret);
- if (error != 0)
- throw new Exception($"Sodium Error: {error}");
- return inputLength + 16;
- }
+ int error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret);
+ if (error != 0)
+ throw new Exception($"Sodium Error: {error}");
+ return inputLength + 16;
}
- public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret)
+ public static int Decrypt(byte* inPtr, int inputOffset, int inputLength, byte* outPtr, int outputOffset, byte* nonce, byte* secret)
{
- fixed (byte* inPtr = input)
- fixed (byte* outPtr = output)
- {
- int error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret);
- if (error != 0)
- throw new Exception($"Sodium Error: {error}");
- return inputLength - 16;
- }
+ int error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret);
+ if (error != 0)
+ throw new Exception($"Sodium Error: {error}");
+ return inputLength - 16;
}
}
}
diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
index 58c4f4c70..55d923147 100644
--- a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
+++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -12,15 +13,31 @@ namespace Discord.Audio.Streams
private readonly AudioStream _next;
private readonly OpusDecoder _decoder;
private readonly byte[] _buffer;
+ private readonly GCHandle _bufferHandle;
+ private readonly IntPtr _bufferPtr;
+
private bool _nextMissed;
private bool _hasHeader;
+ private bool _isDisposed;
public OpusDecodeStream(AudioStream next)
{
_next = next;
_buffer = new byte[OpusConverter.FrameBytes];
+ _bufferHandle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
+ _bufferPtr = _bufferHandle.AddrOfPinnedObject();
_decoder = new OpusDecoder();
}
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (disposing && !_isDisposed)
+ {
+ _decoder.Dispose();
+ _bufferHandle.Free();
+ _isDisposed = true;
+ }
+ }
public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{
@@ -39,17 +56,28 @@ namespace Discord.Audio.Streams
if (!_nextMissed)
{
- count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, false);
+ unsafe
+ {
+ fixed (byte* inPtr = buffer)
+ count = _decoder.DecodeFrame(inPtr, offset, count, (byte*)_bufferPtr, 0, false);
+ }
await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false);
}
else if (count > 0)
{
- count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, true);
+ unsafe
+ {
+ fixed(byte* inPtr = buffer)
+ count = _decoder.DecodeFrame(inPtr, offset, count, (byte*)_bufferPtr, 0, true);
+ }
await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false);
}
else
{
- count = _decoder.DecodeFrame(null, 0, 0, _buffer, 0, true);
+ unsafe
+ {
+ count = _decoder.DecodeFrame(null, 0, 0, (byte*)_bufferPtr, 0, true);
+ }
await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false);
}
}
@@ -62,13 +90,5 @@ namespace Discord.Audio.Streams
{
await _next.ClearAsync(cancelToken).ConfigureAwait(false);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- _decoder.Dispose();
- }
}
}
diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
index f5883ad4b..4531d252c 100644
--- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
+++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -12,26 +13,48 @@ namespace Discord.Audio.Streams
private readonly AudioStream _next;
private readonly OpusEncoder _encoder;
private readonly byte[] _buffer;
+ private readonly GCHandle _bufferHandle;
+ private readonly IntPtr _bufferPtr;
private int _partialFramePos;
private ushort _seq;
private uint _timestamp;
-
+ private bool _isDisposed;
+
public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss)
{
_next = next;
_encoder = new OpusEncoder(bitrate, application, packetLoss);
_buffer = new byte[OpusConverter.FrameBytes];
+ _bufferHandle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
+ _bufferPtr = _bufferHandle.AddrOfPinnedObject();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing && !_isDisposed)
+ {
+ _encoder.Dispose();
+ _bufferHandle.Free();
+ _isDisposed = true;
+ }
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{
//Assume threadsafe
+ int encFrameSize = 0;
while (count > 0)
{
if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes)
{
//We have enough data and no partial frames. Pass the buffer directly to the encoder
- int encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0);
+ unsafe
+ {
+ fixed (byte* inPtr = buffer)
+ encFrameSize = _encoder.EncodeFrame(inPtr, offset, (byte*)_bufferPtr, 0, OpusConverter.FrameBytes);
+ }
_next.WriteHeader(_seq, _timestamp, false);
await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false);
@@ -45,7 +68,10 @@ namespace Discord.Audio.Streams
//We have enough data to complete a previous partial frame.
int partialSize = OpusConverter.FrameBytes - _partialFramePos;
Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize);
- int encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0);
+ unsafe
+ {
+ encFrameSize = _encoder.EncodeFrame((byte*)_bufferPtr, 0, (byte*)_bufferPtr, 0, OpusConverter.FrameBytes);
+ }
_next.WriteHeader(_seq, _timestamp, false);
await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false);
@@ -86,13 +112,5 @@ namespace Discord.Audio.Streams
{
await _next.ClearAsync(cancelToken).ConfigureAwait(false);
}
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- _encoder.Dispose();
- }
}
}
diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs
index 9ed849a5e..669a96c7b 100644
--- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs
+++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,10 @@ namespace Discord.Audio.Streams
private readonly AudioClient _client;
private readonly AudioStream _next;
private readonly byte[] _nonce;
+ private readonly GCHandle _nonceHandle;
+ private readonly IntPtr _noncePtr;
+
+ private bool _isDisposed;
public override bool CanRead => true;
public override bool CanSeek => false;
@@ -20,17 +25,32 @@ namespace Discord.Audio.Streams
_next = next;
_client = (AudioClient)client;
_nonce = new byte[24];
+ _nonceHandle = GCHandle.Alloc(_nonce, GCHandleType.Pinned);
+ _noncePtr = _nonceHandle.AddrOfPinnedObject();
+ }
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (disposing && !_isDisposed)
+ {
+ _nonceHandle.Free();
+ _isDisposed = true;
+ }
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
{
cancelToken.ThrowIfCancellationRequested();
- if (_client.SecretKey == null)
+ if (_client.SecretKeyPtr == null)
return;
Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce
- count = SecretBox.Decrypt(buffer, offset + 12, count - 12, buffer, offset + 12, _nonce, _client.SecretKey);
+ unsafe
+ {
+ fixed (byte* ptr = buffer)
+ count = SecretBox.Decrypt(ptr, offset + 12, count - 12, ptr, offset + 12, (byte*)_noncePtr, (byte*)_client.SecretKeyPtr);
+ }
await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false);
}
diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs
index bacc9be47..526406022 100644
--- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs
+++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -10,17 +11,32 @@ namespace Discord.Audio.Streams
private readonly AudioClient _client;
private readonly AudioStream _next;
private readonly byte[] _nonce;
+ private readonly GCHandle _nonceHandle;
+ private readonly IntPtr _noncePtr;
+
private bool _hasHeader;
private ushort _nextSeq;
private uint _nextTimestamp;
+ private bool _isDisposed;
public SodiumEncryptStream(AudioStream next, IAudioClient client)
{
_next = next;
_client = (AudioClient)client;
_nonce = new byte[24];
+ _nonceHandle = GCHandle.Alloc(_nonce, GCHandleType.Pinned);
+ _noncePtr = _nonceHandle.AddrOfPinnedObject();
+ }
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (disposing && !_isDisposed)
+ {
+ _nonceHandle.Free();
+ _isDisposed = true;
+ }
}
-
+
public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{
if (_hasHeader)
@@ -37,11 +53,15 @@ namespace Discord.Audio.Streams
throw new InvalidOperationException("Received payload without an RTP header");
_hasHeader = false;
- if (_client.SecretKey == null)
+ if (_client.SecretKeyPtr == null)
return;
Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header
- count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey);
+ unsafe
+ {
+ fixed (byte* ptr = buffer)
+ count = SecretBox.Encrypt(ptr, offset + 12, count - 12, ptr, 12, (byte*)_noncePtr, (byte*)_client.SecretKeyPtr);
+ }
_next.WriteHeader(_nextSeq, _nextTimestamp, false);
await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false);
}