diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index 4e62571e5..34255168a 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -133,7 +133,7 @@ namespace Discord
#if !DNXCORE50
if (_config.EnableVoice)
{
- _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, config.EnableDebug);
+ _voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, _config.VoiceBufferLength, _config.EnableDebug);
_voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected();
_voiceWebSocket.Disconnected += async (s, e) =>
{
@@ -643,17 +643,18 @@ namespace Discord
}
/// Sends a PCM frame to the voice server.
- /// PCM frame to send.
+ /// PCM frame to send. This must be a 48Kz 20ms
/// Number of bytes in this frame.
- public Task SendVoicePCM(byte[] data, int count)
+ /// Will block until
+ public void SendVoicePCM(byte[] data, int count)
{
CheckReady();
if (!_config.EnableVoice) throw new InvalidOperationException("Voice is not enabled for this client.");
- if (count == 0) return TaskHelper.CompletedTask;
+ if (count == 0) return;
if (_isDebugMode)
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output.");
- return _voiceWebSocket.SendPCMFrame(data, count);
+ _voiceWebSocket.SendPCMFrame(data, count);
}
/// Clears the PCM buffer.
diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs
index 698e6a3bd..6e92d0e7b 100644
--- a/src/Discord.Net/DiscordClientConfig.cs
+++ b/src/Discord.Net/DiscordClientConfig.cs
@@ -24,6 +24,9 @@
public bool UseMessageQueue { get; set; } = false;
/// Gets or sets the time (in milliseconds) to wait when the message queue is empty before checking again.
public int MessageQueueInterval { get; set; } = 100;
+ /// Gets or sets the max buffer length (in milliseconds) for outgoing voice packets.
+ /// This value is the target maximum but is not guaranteed. The buffer will often go a bit above this value.
+ public int VoiceBufferLength { get; set; } = 1000;
public DiscordClientConfig() { }
}
diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs
index b439afbb5..8acb58b51 100644
--- a/src/Discord.Net/DiscordVoiceSocket.cs
+++ b/src/Discord.Net/DiscordVoiceSocket.cs
@@ -19,23 +19,14 @@ namespace Discord
{
internal sealed partial class DiscordVoiceSocket : DiscordWebSocket
{
- private struct Packet
- {
- public byte[] Data;
- public int Count;
- public Packet(byte[] data, int count)
- {
- Data = data;
- Count = count;
- }
- }
-
- private ManualResetEventSlim _connectWaitOnLogin;
+ private readonly int _targetAudioBufferLength;
+ private ManualResetEventSlim _connectWaitOnLogin;
private uint _ssrc;
- private readonly Random _rand = new Random();
+ private readonly Random _rand = new Random();
private OpusEncoder _encoder;
- private ConcurrentQueue _sendQueue;
+ private ConcurrentQueue _sendQueue;
+ private ManualResetEventSlim _sendQueueWait;
private UdpClient _udp;
private IPEndPoint _endpoint;
private bool _isReady, _isClearing;
@@ -43,17 +34,21 @@ namespace Discord
private string _myIp;
private ushort _sequence;
private string _mode;
+ private byte[] _encodingBuffer;
#if USE_THREAD
private Thread _sendThread;
#endif
- public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, bool isDebug)
+ public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, int audioBufferLength, bool isDebug)
: base(client, timeout, interval, isDebug)
{
_connectWaitOnLogin = new ManualResetEventSlim(false);
- _sendQueue = new ConcurrentQueue();
+ _sendQueue = new ConcurrentQueue();
+ _sendQueueWait = new ManualResetEventSlim(true);
_encoder = new OpusEncoder(48000, 1, 20, Application.Audio);
- }
+ _encodingBuffer = new byte[4000];
+ _targetAudioBufferLength = audioBufferLength / 20;
+ }
protected override void OnConnect()
{
@@ -81,9 +76,9 @@ namespace Discord
#endif
return new Task[]
{
- ReceiveAsync(),
+ ReceiveVoiceAsync(),
#if !USE_THREAD
- SendAsync(),
+ SendVoiceAsync(),
#endif
WatcherAsync()
}.Concat(base.CreateTasks()).ToArray();
@@ -118,7 +113,7 @@ namespace Discord
SetConnected();
}
- private async Task ReceiveAsync()
+ private async Task ReceiveVoiceAsync()
{
var cancelSource = _disconnectToken;
var cancelToken = cancelSource.Token;
@@ -138,17 +133,17 @@ namespace Discord
}
#if USE_THREAD
- private void SendAsync(CancellationTokenSource cancelSource)
+ private void SendVoiceAsync(CancellationTokenSource cancelSource)
{
var cancelToken = cancelSource.Token;
#else
- private async Task SendAsync()
+ private async Task SendVoiceAsync()
{
var cancelSource = _disconnectToken;
var cancelToken = cancelSource.Token;
await Task.Yield();
- Packet packet;
+ byte[] packet;
try
{
while (!cancelToken.IsCancellationRequested && !_isReady)
@@ -165,7 +160,7 @@ namespace Discord
uint samplesPerFrame = (uint)_encoder.SamplesPerFrame;
Stopwatch sw = Stopwatch.StartNew();
- byte[] rtpPacket = new byte[4012];
+ byte[] rtpPacket = new byte[_encodingBuffer.Length + 12];
rtpPacket[0] = 0x80; //Flags;
rtpPacket[1] = 0x78; //Payload Type
rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF);
@@ -175,6 +170,10 @@ namespace Discord
while (!cancelToken.IsCancellationRequested)
{
+ //If we have less than our target data buffered, request more
+ if (_sendQueue.Count < _targetAudioBufferLength)
+ _sendQueueWait.Set();
+
double ticksToNextFrame = nextTicks - sw.ElapsedTicks;
if (ticksToNextFrame <= 0.0)
{
@@ -191,11 +190,11 @@ namespace Discord
rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF);
rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF);
rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF);
- Buffer.BlockCopy(packet.Data, 0, rtpPacket, 12, packet.Count);
+ Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length);
#if USE_THREAD
_udp.Send(rtpPacket, packet.Count + 12);
#else
- await _udp.SendAsync(rtpPacket, packet.Count + 12);
+ await _udp.SendAsync(rtpPacket, packet.Length + 12);
#endif
}
timestamp = unchecked(timestamp + samplesPerFrame);
@@ -369,31 +368,34 @@ namespace Discord
}
}
- public Task SendPCMFrame(byte[] data, int count)
+ public void SendPCMFrame(byte[] data, int count)
{
if (count != _encoder.FrameSize)
throw new InvalidOperationException($"Invalid frame size. Got {count}, expected {_encoder.FrameSize}.");
byte[] payload;
- int encodedLength;
lock (_encoder)
{
- payload = new byte[4000];
- encodedLength = _encoder.EncodeFrame(data, payload);
+ int encodedLength = _encoder.EncodeFrame(data, _encodingBuffer);
if (_mode == "xsalsa20_poly1305")
{
//TODO: Encode
}
- }
- _sendQueue.Enqueue(new Packet(payload, encodedLength));
- return Task.Delay(0);
+ payload = new byte[encodedLength];
+ Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength);
+ }
+
+ _sendQueueWait.Wait(_disconnectToken.Token);
+ _sendQueue.Enqueue(payload);
+ if (_sendQueue.Count >= _targetAudioBufferLength)
+ _sendQueueWait.Reset();
}
public void ClearPCMFrames()
{
_isClearing = true;
- Packet ignored;
+ byte[] ignored;
while (_sendQueue.TryDequeue(out ignored)) { }
_isClearing = false;
}