diff --git a/src/Discord.Net/Audio/VoiceBuffer.cs b/src/Discord.Net/Audio/VoiceBuffer.cs index eaff4e5fd..9df05b2fb 100644 --- a/src/Discord.Net/Audio/VoiceBuffer.cs +++ b/src/Discord.Net/Audio/VoiceBuffer.cs @@ -9,7 +9,7 @@ namespace Discord.Audio private readonly byte[] _buffer; private readonly byte[] _blankFrame; private ushort _readCursor, _writeCursor; - private ManualResetEventSlim _underflowEvent, _notOverflowEvent; + private ManualResetEventSlim _notOverflowEvent; private bool _isClearing; public int FrameSize => _frameSize; @@ -26,7 +26,6 @@ namespace Discord.Audio _writeCursor = 0; _buffer = new byte[_bufferSize]; _blankFrame = new byte[_frameSize]; - _underflowEvent = new ManualResetEventSlim(); //Notifies when an underflow has occurred _notOverflowEvent = new ManualResetEventSlim(); //Notifies when an overflow is solved } @@ -79,7 +78,6 @@ namespace Discord.Audio //Advance the write cursor to the next position AdvanceCursorPos(ref _writeCursor); - _underflowEvent.Set(); } } } @@ -88,7 +86,6 @@ namespace Discord.Audio { if (_writeCursor == _readCursor) { - _underflowEvent.Set(); _notOverflowEvent.Set(); return false; } @@ -112,8 +109,8 @@ namespace Discord.Audio Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++); try { - _underflowEvent.Wait(cancelToken); - } + Wait(cancelToken); + } catch (OperationCanceledException) { } _writeCursor = 0; _readCursor = 0; @@ -123,7 +120,12 @@ namespace Discord.Audio public void Wait(CancellationToken cancelToken) { - _underflowEvent.Wait(cancelToken); + while (true) + { + _notOverflowEvent.Wait(cancelToken); + if (_writeCursor == _readCursor) + break; + } } private void AdvanceCursorPos(ref ushort pos) diff --git a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs index 394be9842..2d92f6592 100644 --- a/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs @@ -21,7 +21,7 @@ namespace Discord.Net.WebSockets private const string EncryptedMode = "xsalsa20_poly1305"; private const string UnencryptedMode = "plain"; - private readonly Random _rand; + //private readonly Random _rand; private readonly int _targetAudioBufferLength; private OpusEncoder _encoder; private readonly ConcurrentDictionary _decoders; @@ -48,7 +48,7 @@ namespace Discord.Net.WebSockets public VoiceWebSocket(DiscordWSClient client) : base(client) { - _rand = new Random(); + //_rand = new Random(); _decoders = new ConcurrentDictionary(); _targetAudioBufferLength = client.Config.VoiceBufferLength / 20; //20 ms frames _encodingBuffer = new byte[MaxOpusSize]; @@ -301,7 +301,8 @@ namespace Discord.Net.WebSockets byte[] voicePacket, pingPacket, nonce = null; uint timestamp = 0; double nextTicks = 0.0, nextPingTicks = 0.0; - double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; + long ticksPerSeconds = Stopwatch.Frequency; + double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; double spinLockThreshold = 3 * ticksPerMillisecond; uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; @@ -346,57 +347,69 @@ namespace Discord.Net.WebSockets if (_isEncrypted) Buffer.BlockCopy(voicePacket, 0, nonce, 0, 12); + bool hasFrame = false; while (!cancelToken.IsCancellationRequested) { - double ticksToNextFrame = nextTicks - sw.ElapsedTicks; + if (!hasFrame && _sendBuffer.Pop(frame)) + { + ushort sequence = unchecked(_sequence++); + voicePacket[2] = (byte)((sequence >> 8) & 0xFF); + voicePacket[3] = (byte)((sequence >> 0) & 0xFF); + voicePacket[4] = (byte)((timestamp >> 24) & 0xFF); + voicePacket[5] = (byte)((timestamp >> 16) & 0xFF); + voicePacket[6] = (byte)((timestamp >> 8) & 0xFF); + voicePacket[7] = (byte)((timestamp >> 0) & 0xFF); + + //Encode + int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame); + + //Encrypt + if (_isEncrypted) + { + Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce + int ret = Sodium.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey); + if (ret != 0) + continue; + rtpPacketLength = encodedLength + 12 + 16; + } + else + { + Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength); + rtpPacketLength = encodedLength + 12; + } + + timestamp = unchecked(timestamp + samplesPerFrame); + hasFrame = true; + } + + long currentTicks = sw.ElapsedTicks; + double ticksToNextFrame = nextTicks - currentTicks; if (ticksToNextFrame <= 0.0) { - long currentTicks = sw.ElapsedTicks; - while (currentTicks > nextTicks) + if (hasFrame) { - if (_sendBuffer.Pop(frame)) - { - ushort sequence = unchecked(_sequence++); - voicePacket[2] = (byte)((sequence >> 8) & 0xFF); - voicePacket[3] = (byte)((sequence >> 0) & 0xFF); - voicePacket[4] = (byte)((timestamp >> 24) & 0xFF); - voicePacket[5] = (byte)((timestamp >> 16) & 0xFF); - voicePacket[6] = (byte)((timestamp >> 8) & 0xFF); - voicePacket[7] = (byte)((timestamp >> 0) & 0xFF); - - //Encode - int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame); - - //Encrypt - if (_isEncrypted) - { - Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce - int ret = Sodium.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey); - if (ret != 0) - continue; - rtpPacketLength = encodedLength + 12 + 16; - } - else - { - Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength); - rtpPacketLength = encodedLength + 12; - } - _udp.Send(voicePacket, rtpPacketLength); - } - timestamp = unchecked(timestamp + samplesPerFrame); - nextTicks += ticksPerFrame; + _udp.Send(voicePacket, rtpPacketLength); + hasFrame = false; } + nextTicks += ticksPerFrame; + + //Is it time to send out another ping? if (currentTicks > nextPingTicks) { - //_udp.Send(pingPacket, pingPacket.Length); - nextPingTicks = currentTicks + 5 * Stopwatch.Frequency; + _udp.Send(pingPacket, pingPacket.Length); + nextPingTicks = currentTicks + 5 * ticksPerSeconds; } } - //Dont sleep if we need to output audio in the next spinLockThreshold - else if (ticksToNextFrame > spinLockThreshold) + else { - int time = (int)Math.Ceiling((ticksToNextFrame - spinLockThreshold) / ticksPerMillisecond); - Thread.Sleep(1); + if (hasFrame) + { + int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); + if (time > 0) + Thread.Sleep(time); + } + else + Thread.Sleep(1); //Give as much time to the encrypter as possible } } } @@ -445,7 +458,7 @@ namespace Discord.Net.WebSockets } _udp.Connect(_endpoint); - _sequence = (ushort)_rand.Next(0, ushort.MaxValue); + _sequence = 0;// (ushort)_rand.Next(0, ushort.MaxValue); //No thread issue here because SendAsync doesn't start until _isReady is true byte[] packet = new byte[70]; packet[0] = (byte)((_ssrc >> 24) & 0xFF);