Browse Source

Fixed several audio stream issues

tags/1.0.0-rc2
RogueException 8 years ago
parent
commit
c49118e25f
11 changed files with 89 additions and 98 deletions
  1. +5
    -29
      src/Discord.Net.Core/Audio/AudioInStream.cs
  2. +1
    -28
      src/Discord.Net.Core/Audio/AudioOutStream.cs
  3. +40
    -0
      src/Discord.Net.Core/Audio/AudioStream.cs
  4. +11
    -9
      src/Discord.Net.WebSocket/Audio/AudioClient.cs
  5. +3
    -3
      src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs
  6. +2
    -2
      src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
  7. +2
    -2
      src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
  8. +5
    -6
      src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs
  9. +2
    -2
      src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs
  10. +9
    -9
      src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs
  11. +9
    -8
      src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs

+ 5
- 29
src/Discord.Net.Core/Audio/AudioInStream.cs View File

@@ -1,43 +1,19 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
public abstract class AudioInStream : Stream
public abstract class AudioInStream : AudioStream
{
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public abstract int AvailableFrames { get; }

public override bool CanRead => true;
public override bool CanWrite => true;

public abstract Task<RTPFrame> ReadFrameAsync(CancellationToken cancelToken);
public abstract bool TryReadFrame(CancellationToken cancelToken, out RTPFrame frame);

public RTPFrame ReadFrame()
{
return ReadFrameAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}

public override void Flush() { throw new NotSupportedException(); }

public override long Length { get { throw new NotSupportedException(); } }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}

public override void SetLength(long value) { throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
public override Task FlushAsync(CancellationToken cancelToken) { throw new NotSupportedException(); }
}
}

+ 1
- 28
src/Discord.Net.Core/Audio/AudioOutStream.cs View File

@@ -1,39 +1,12 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
public abstract class AudioOutStream : Stream
public abstract class AudioOutStream : AudioStream
{
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;

public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public void Clear()
{
ClearAsync(CancellationToken.None).GetAwaiter().GetResult();
}

public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); }
//public virtual Task WriteSilenceAsync(CancellationToken cancellationToken) { return Task.Delay(0); }

public override long Length { get { throw new NotSupportedException(); } }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}

public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
public override void SetLength(long value) { throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }


+ 40
- 0
src/Discord.Net.Core/Audio/AudioStream.cs View File

@@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Discord.Audio
{
public abstract class AudioStream : Stream
{
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => false;

public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
}
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public void Clear()
{
ClearAsync(CancellationToken.None).GetAwaiter().GetResult();
}

public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); }

public override long Length { get { throw new NotSupportedException(); } }
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}

public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
public override void SetLength(long value) { throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
}
}

+ 11
- 9
src/Discord.Net.WebSocket/Audio/AudioClient.cs View File

@@ -42,12 +42,12 @@ namespace Discord.Audio
private string _url, _sessionId, _token;
private ulong _userId;
private uint _ssrc;
private byte[] _secretKey;

public SocketGuild Guild { get; }
public DiscordVoiceAPIClient ApiClient { get; private set; }
public int Latency { get; private set; }
public ulong ChannelId { get; internal set; }
internal byte[] SecretKey { get; private set; }

private DiscordSocketClient Discord => Guild.Discord;
public ConnectionState ConnectionState => _connection.State;
@@ -134,7 +134,7 @@ namespace Discord.Audio
{
CheckSamplesPerFrame(samplesPerFrame);
var outputStream = new OutputStream(ApiClient);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey);
var sodiumEncrypter = new SodiumEncryptStream( outputStream, this);
var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc);
return new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger);
}
@@ -142,14 +142,14 @@ namespace Discord.Audio
{
CheckSamplesPerFrame(samplesPerFrame);
var outputStream = new OutputStream(ApiClient);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this);
return new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc);
}
public AudioOutStream CreatePCMStream(AudioApplication application, int samplesPerFrame, int channels, int? bitrate, int bufferMillis)
{
CheckSamplesPerFrame(samplesPerFrame);
var outputStream = new OutputStream(ApiClient);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this);
var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc);
var bufferedStream = new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger);
return new OpusEncodeStream(bufferedStream, channels, samplesPerFrame, bitrate ?? (96 * 1024), application);
@@ -158,7 +158,7 @@ namespace Discord.Audio
{
CheckSamplesPerFrame(samplesPerFrame);
var outputStream = new OutputStream(ApiClient);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey);
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this);
var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc);
return new OpusEncodeStream(rtpWriter, channels, samplesPerFrame, bitrate ?? (96 * 1024), application);
}
@@ -175,8 +175,10 @@ namespace Discord.Audio
if (!_streams.ContainsKey(userId))
{
var readerStream = new InputStream();
var writerStream = new OpusDecodeStream(new RTPReadStream(readerStream, _secretKey));
_streams.TryAdd(userId, new StreamPair(readerStream, writerStream));
var opusDecoder = new OpusDecodeStream(readerStream);
var rtpReader = new RTPReadStream(readerStream, opusDecoder);
var decryptStream = new SodiumDecryptStream(rtpReader, this);
_streams.TryAdd(userId, new StreamPair(readerStream, decryptStream));
await _streamCreatedEvent.InvokeAsync(userId, readerStream);
}
}
@@ -238,7 +240,7 @@ namespace Discord.Audio
if (data.Mode != DiscordVoiceAPIClient.Mode)
throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}");

_secretKey = data.SecretKey;
SecretKey = data.SecretKey;
await ApiClient.SendSetSpeaking(false).ConfigureAwait(false);

var _ = _connection.CompleteAsync();
@@ -335,7 +337,7 @@ namespace Discord.Audio
await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false);
return;
}
await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false);
//await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false);
}
}



+ 3
- 3
src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs View File

@@ -26,7 +26,7 @@ namespace Discord.Audio.Streams
private static readonly byte[] _silenceFrame = new byte[0];

private readonly AudioClient _client;
private readonly AudioOutStream _next;
private readonly AudioStream _next;
private readonly CancellationTokenSource _cancelTokenSource;
private readonly CancellationToken _cancelToken;
private readonly Task _task;
@@ -38,9 +38,9 @@ namespace Discord.Audio.Streams
private bool _isPreloaded;
private int _silenceFrames;

public BufferedWriteStream(AudioOutStream next, IAudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500)
public BufferedWriteStream(AudioStream next, IAudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500)
: this(next, client as AudioClient, samplesPerFrame, bufferMillis, cancelToken, null, maxFrameSize) { }
internal BufferedWriteStream(AudioOutStream next, AudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500)
internal BufferedWriteStream(AudioStream next, AudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500)
{
//maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms
_next = next;


+ 2
- 2
src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs View File

@@ -8,11 +8,11 @@ namespace Discord.Audio.Streams
{
public const int SampleRate = OpusEncodeStream.SampleRate;

private readonly AudioOutStream _next;
private readonly AudioStream _next;
private readonly byte[] _buffer;
private readonly OpusDecoder _decoder;

public OpusDecodeStream(AudioOutStream next, int channels = OpusConverter.MaxChannels, int bufferSize = 4000)
public OpusDecodeStream(AudioStream next, int channels = OpusConverter.MaxChannels, int bufferSize = 4000)
{
_next = next;
_buffer = new byte[bufferSize];


+ 2
- 2
src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Audio.Streams
{
public const int SampleRate = 48000;
private readonly AudioOutStream _next;
private readonly AudioStream _next;
private readonly OpusEncoder _encoder;
private readonly byte[] _buffer;

@@ -17,7 +17,7 @@ namespace Discord.Audio.Streams
private byte[] _partialFrameBuffer;
private int _partialFramePos;

public OpusEncodeStream(AudioOutStream next, int channels, int samplesPerFrame, int bitrate, AudioApplication application, int bufferSize = 4000)
public OpusEncodeStream(AudioStream next, int channels, int samplesPerFrame, int bitrate, AudioApplication application, int bufferSize = 4000)
{
_next = next;
_encoder = new OpusEncoder(SampleRate, channels, bitrate, application);


+ 5
- 6
src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs View File

@@ -9,20 +9,19 @@ namespace Discord.Audio.Streams
public class RTPReadStream : AudioOutStream
{
private readonly InputStream _queue;
private readonly AudioOutStream _next;
private readonly byte[] _buffer, _nonce, _secretKey;
private readonly AudioStream _next;
private readonly byte[] _buffer, _nonce;

public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;

public RTPReadStream(InputStream queue, byte[] secretKey, int bufferSize = 4000)
: this(queue, null, secretKey, bufferSize) { }
public RTPReadStream(InputStream queue, AudioOutStream next, byte[] secretKey, int bufferSize = 4000)
public RTPReadStream(InputStream queue, int bufferSize = 4000)
: this(queue, null, bufferSize) { }
public RTPReadStream(InputStream queue, AudioStream next, int bufferSize = 4000)
{
_queue = queue;
_next = next;
_secretKey = secretKey;
_buffer = new byte[bufferSize];
_nonce = new byte[24];
}


+ 2
- 2
src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs View File

@@ -7,14 +7,14 @@ namespace Discord.Audio.Streams
///<summary> Wraps data in an RTP frame </summary>
public class RTPWriteStream : AudioOutStream
{
private readonly AudioOutStream _next;
private readonly AudioStream _next;
private readonly byte[] _header;
private int _samplesPerFrame;
private uint _ssrc, _timestamp = 0;

protected readonly byte[] _buffer;

public RTPWriteStream(AudioOutStream next, int samplesPerFrame, uint ssrc, int bufferSize = 4000)
public RTPWriteStream(AudioStream next, int samplesPerFrame, uint ssrc, int bufferSize = 4000)
{
_next = next;
_samplesPerFrame = samplesPerFrame;


+ 9
- 9
src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs View File

@@ -7,18 +7,18 @@ namespace Discord.Audio.Streams
///<summary> Decrypts an RTP frame using libsodium </summary>
public class SodiumDecryptStream : AudioOutStream
{
private readonly AudioOutStream _next;
private readonly byte[] _buffer, _nonce, _secretKey;
private readonly AudioClient _client;
private readonly AudioStream _next;
private readonly byte[] _nonce;

public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;

public SodiumDecryptStream(AudioOutStream next, byte[] secretKey, int bufferSize = 4000)
public SodiumDecryptStream(AudioStream next, IAudioClient client)
{
_next = next;
_secretKey = secretKey;
_buffer = new byte[bufferSize];
_client = (AudioClient)client;
_nonce = new byte[24];
}

@@ -26,11 +26,11 @@ namespace Discord.Audio.Streams
{
cancelToken.ThrowIfCancellationRequested();

Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce
count = SecretBox.Decrypt(buffer, offset, count, _buffer, 0, _nonce, _secretKey);
if (_client.SecretKey == null)
return;

var newBuffer = new byte[count];
Buffer.BlockCopy(_buffer, 0, newBuffer, 0, count);
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);
await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false);
}



+ 9
- 8
src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs View File

@@ -7,16 +7,14 @@ namespace Discord.Audio.Streams
///<summary> Encrypts an RTP frame using libsodium </summary>
public class SodiumEncryptStream : AudioOutStream
{
private readonly AudioOutStream _next;
private readonly byte[] _nonce, _secretKey;
private readonly AudioClient _client;
private readonly AudioStream _next;
private readonly byte[] _nonce;

//protected readonly byte[] _buffer;

public SodiumEncryptStream(AudioOutStream next, byte[] secretKey/*, int bufferSize = 4000*/)
public SodiumEncryptStream(AudioStream next, IAudioClient client)
{
_next = next;
_secretKey = secretKey;
//_buffer = new byte[bufferSize]; //TODO: Can Sodium do an in-place encrypt?
_client = (AudioClient)client;
_nonce = new byte[24];
}

@@ -24,8 +22,11 @@ namespace Discord.Audio.Streams
{
cancelToken.ThrowIfCancellationRequested();

if (_client.SecretKey == 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, _secretKey);
count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey);
await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false);
}



Loading…
Cancel
Save