Browse Source

Added buffer length to outgoing audio

tags/docs-0.9
Brandon Smith 9 years ago
parent
commit
144fe7b570
3 changed files with 45 additions and 39 deletions
  1. +6
    -5
      src/Discord.Net/DiscordClient.cs
  2. +3
    -0
      src/Discord.Net/DiscordClientConfig.cs
  3. +36
    -34
      src/Discord.Net/DiscordVoiceSocket.cs

+ 6
- 5
src/Discord.Net/DiscordClient.cs View File

@@ -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
}

/// <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>
public Task SendVoicePCM(byte[] data, int count)
/// <remarks>Will block until</remarks>
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);
}

/// <summary> Clears the PCM buffer. </summary>


+ 3
- 0
src/Discord.Net/DiscordClientConfig.cs View File

@@ -24,6 +24,9 @@
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>
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() { }
}


+ 36
- 34
src/Discord.Net/DiscordVoiceSocket.cs View File

@@ -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<Packet> _sendQueue;
private ConcurrentQueue<byte[]> _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<Packet>();
_sendQueue = new ConcurrentQueue<byte[]>();
_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;
}


Loading…
Cancel
Save