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