| @@ -2,12 +2,17 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> Specifies an audio mode for Discord. </summary> | |||||
| [Flags] | [Flags] | ||||
| public enum AudioMode : byte | public enum AudioMode : byte | ||||
| { | { | ||||
| /// <summary> Audio send/receive is disabled. </summary> | |||||
| Disabled = 0, | Disabled = 0, | ||||
| /// <summary> Audio can only be broadcasted by the client. </summary> | |||||
| Outgoing = 1, | Outgoing = 1, | ||||
| /// <summary> Audio can only be received by the client. </summary> | |||||
| Incoming = 2, | Incoming = 2, | ||||
| /// <summary> Audio can be sent and received by the client. </summary> | |||||
| Both = Outgoing | Incoming | Both = Outgoing | Incoming | ||||
| } | } | ||||
| } | } | ||||
| @@ -5,19 +5,26 @@ namespace Discord.Audio | |||||
| { | { | ||||
| public interface IAudioClient | public interface IAudioClient | ||||
| { | { | ||||
| /// <summary> Fired when the client connects to Discord. </summary> | |||||
| event Func<Task> Connected; | event Func<Task> Connected; | ||||
| /// <summary> Fired when the client disconnects from Discord. </summary> | |||||
| event Func<Exception, Task> Disconnected; | event Func<Exception, Task> Disconnected; | ||||
| /// <summary> Fired in response to a heartbeat, providing the old and new latency. </summary> | |||||
| event Func<int, int, Task> LatencyUpdated; | event Func<int, int, Task> LatencyUpdated; | ||||
| /// <summary> Gets the API client used for communicating with Discord. </summary> | |||||
| DiscordVoiceAPIClient ApiClient { get; } | DiscordVoiceAPIClient ApiClient { get; } | ||||
| /// <summary> Gets the current connection state of this client. </summary> | /// <summary> Gets the current connection state of this client. </summary> | ||||
| ConnectionState ConnectionState { get; } | ConnectionState ConnectionState { get; } | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
| int Latency { get; } | int Latency { get; } | ||||
| /// <summary> Disconnects the current client from Discord. </summary> | |||||
| Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
| /// <summary> Creates an Opus stream for sending raw Opus-encoded data. </summary> | |||||
| RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | ||||
| /// <summary> Creates a PCM stream for sending unencoded PCM data. </summary> | |||||
| OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,9 +1,16 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> The types of encoding which Opus supports during encoding. </summary> | |||||
| public enum OpusApplication : int | public enum OpusApplication : int | ||||
| { | { | ||||
| /// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
| /// encoding to improve the quality of voice communication. </summary> | |||||
| Voice = 2048, | Voice = 2048, | ||||
| /// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
| /// encoding to improve the overall quality of mixed-media audio transmission. </summary> | |||||
| MusicOrMixed = 2049, | MusicOrMixed = 2049, | ||||
| /// <summary> Specifies that the <see cref="Discord.Audio.IAudioClient"/> uses | |||||
| /// encoding to reduce overall latency. </summary> | |||||
| LowLatency = 2051 | LowLatency = 2051 | ||||
| } | } | ||||
| } | } | ||||
| @@ -24,7 +24,9 @@ namespace Discord.Audio | |||||
| /// <summary> Produces PCM samples from Opus-encoded audio. </summary> | /// <summary> Produces PCM samples from Opus-encoded audio. </summary> | ||||
| /// <param name="input">PCM samples to decode.</param> | /// <param name="input">PCM samples to decode.</param> | ||||
| /// <param name="inputOffset">Offset of the frame in input.</param> | /// <param name="inputOffset">Offset of the frame in input.</param> | ||||
| /// <param name="inputCount">Number of bytes of the frame in input.</param> | |||||
| /// <param name="output">Buffer to store the decoded frame.</param> | /// <param name="output">Buffer to store the decoded frame.</param> | ||||
| /// <param name="outputOffset">Zero-based offset for the output.</param> | |||||
| public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | ||||
| { | { | ||||
| int result = 0; | int result = 0; | ||||
| @@ -31,6 +31,9 @@ namespace Discord.Audio | |||||
| /// <summary> Produces Opus encoded audio from PCM samples. </summary> | /// <summary> Produces Opus encoded audio from PCM samples. </summary> | ||||
| /// <param name="input">PCM samples to encode.</param> | /// <param name="input">PCM samples to encode.</param> | ||||
| /// <param name="output">Buffer to store the encoded frame.</param> | /// <param name="output">Buffer to store the encoded frame.</param> | ||||
| /// <param name="inputOffset">Offset of the frame in input.</param> | |||||
| /// <param name="inputCount">Number of bytes of the frame in input.</param> | |||||
| /// <param name="outputOffset">Zero-based offset for the output.</param> | |||||
| /// <returns>Length of the frame contained in outputBuffer.</returns> | /// <returns>Length of the frame contained in outputBuffer.</returns> | ||||
| public unsafe int EncodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | public unsafe int EncodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset) | ||||
| { | { | ||||
| @@ -3,13 +3,14 @@ using System.Runtime.InteropServices; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public unsafe static class SecretBox | |||||
| public unsafe static class SecretBox // TODO: should this be public? | |||||
| { | { | ||||
| [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
| private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | ||||
| [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
| private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | ||||
| /// <summary> Encrypts a payload with the given nonce and secret. </summary> | |||||
| public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | ||||
| { | { | ||||
| fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
| @@ -21,6 +22,7 @@ namespace Discord.Audio | |||||
| return inputLength + 16; | return inputLength + 16; | ||||
| } | } | ||||
| } | } | ||||
| /// <summary> Decrypts a payload with the given nonce and secret. </summary> | |||||
| public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | ||||
| { | { | ||||
| fixed (byte* inPtr = input) | fixed (byte* inPtr = input) | ||||
| @@ -1,5 +1,6 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> A stream which decodes Opus frames as they are read. </summary> | |||||
| public class OpusDecodeStream : RTPReadStream | public class OpusDecodeStream : RTPReadStream | ||||
| { | { | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| @@ -13,12 +14,14 @@ | |||||
| _decoder = new OpusDecoder(samplingRate, channels); | _decoder = new OpusDecoder(samplingRate, channels); | ||||
| } | } | ||||
| /// <summary> Reads Opus-encoded frame from the stream, filling the buffer with PCM data </summary> | |||||
| public override int Read(byte[] buffer, int offset, int count) | public override int Read(byte[] buffer, int offset, int count) | ||||
| { | { | ||||
| count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0); | count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0); | ||||
| return base.Read(_buffer, 0, count); | return base.Read(_buffer, 0, count); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| protected override void Dispose(bool disposing) | protected override void Dispose(bool disposing) | ||||
| { | { | ||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| @@ -1,8 +1,11 @@ | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> A stream which encodes Opus frames as raw PCM data is written. </summary> | |||||
| public class OpusEncodeStream : RTPWriteStream | public class OpusEncodeStream : RTPWriteStream | ||||
| { | { | ||||
| public int SampleRate = 48000; | |||||
| /// <summary> The sample rate of the Opus stream. </summary> | |||||
| public int SampleRate = 48000; // TODO: shouldn't these be readonly? | |||||
| /// <summary> The number of channels of the Opus stream. </summary> | |||||
| public int Channels = 2; | public int Channels = 2; | ||||
| private readonly OpusEncoder _encoder; | private readonly OpusEncoder _encoder; | ||||
| @@ -18,12 +21,14 @@ | |||||
| _encoder.SetBitrate(bitrate.Value); | _encoder.SetBitrate(bitrate.Value); | ||||
| } | } | ||||
| /// <summary> Writes Opus-encoded PCM data to the stream. </summary> | |||||
| public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
| { | { | ||||
| count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0); | count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0); | ||||
| base.Write(_buffer, 0, count); | base.Write(_buffer, 0, count); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| protected override void Dispose(bool disposing) | protected override void Dispose(bool disposing) | ||||
| { | { | ||||
| base.Dispose(disposing); | base.Dispose(disposing); | ||||
| @@ -4,14 +4,18 @@ using System.IO; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> A stream used for reading raw audio data from Discord. </summary> | |||||
| public class RTPReadStream : Stream | public class RTPReadStream : Stream | ||||
| { | { | ||||
| private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer | ||||
| private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
| private readonly byte[] _buffer, _nonce, _secretKey; | private readonly byte[] _buffer, _nonce, _secretKey; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| internal RTPReadStream(AudioClient audioClient, byte[] secretKey, int bufferSize = 4000) | internal RTPReadStream(AudioClient audioClient, byte[] secretKey, int bufferSize = 4000) | ||||
| @@ -23,12 +27,14 @@ namespace Discord.Audio | |||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override int Read(byte[] buffer, int offset, int count) | public override int Read(byte[] buffer, int offset, int count) | ||||
| { | { | ||||
| var queuedData = _queuedData.Take(); | var queuedData = _queuedData.Take(); | ||||
| Buffer.BlockCopy(queuedData, 0, buffer, offset, Math.Min(queuedData.Length, count)); | Buffer.BlockCopy(queuedData, 0, buffer, offset, Math.Min(queuedData.Length, count)); | ||||
| return queuedData.Length; | return queuedData.Length; | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
| { | { | ||||
| Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); | Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); | ||||
| @@ -38,16 +44,21 @@ namespace Discord.Audio | |||||
| _queuedData.Add(newBuffer); | _queuedData.Add(newBuffer); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override void Flush() { throw new NotSupportedException(); } | public override void Flush() { throw new NotSupportedException(); } | ||||
| /// <inheritdoc/> | |||||
| public override long Length { get { throw new NotSupportedException(); } } | public override long Length { get { throw new NotSupportedException(); } } | ||||
| /// <inheritdoc/> | |||||
| public override long Position | public override long Position | ||||
| { | { | ||||
| get { throw new NotSupportedException(); } | get { throw new NotSupportedException(); } | ||||
| set { throw new NotSupportedException(); } | set { throw new NotSupportedException(); } | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override void SetLength(long value) { throw new NotSupportedException(); } | public override void SetLength(long value) { throw new NotSupportedException(); } | ||||
| /// <inheritdoc/> | |||||
| public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ using System.IO; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| /// <summary> A stream used for writing raw audio data to Discord. </summary> | |||||
| public class RTPWriteStream : Stream | public class RTPWriteStream : Stream | ||||
| { | { | ||||
| private readonly AudioClient _audioClient; | private readonly AudioClient _audioClient; | ||||
| @@ -10,10 +11,14 @@ namespace Discord.Audio | |||||
| private int _samplesPerFrame; | private int _samplesPerFrame; | ||||
| private uint _ssrc, _timestamp = 0; | private uint _ssrc, _timestamp = 0; | ||||
| /// <summary> The current output buffer. </summary> | |||||
| protected readonly byte[] _buffer; | protected readonly byte[] _buffer; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanRead => false; | public override bool CanRead => false; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| /// <inheritdoc/> | |||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| internal RTPWriteStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | internal RTPWriteStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | ||||
| @@ -32,6 +37,7 @@ namespace Discord.Audio | |||||
| _nonce[11] = (byte)(_ssrc >> 0); | _nonce[11] = (byte)(_ssrc >> 0); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override void Write(byte[] buffer, int offset, int count) | public override void Write(byte[] buffer, int offset, int count) | ||||
| { | { | ||||
| unchecked | unchecked | ||||
| @@ -51,17 +57,23 @@ namespace Discord.Audio | |||||
| _audioClient.Send(_buffer, count + 12); | _audioClient.Send(_buffer, count + 12); | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override void Flush() { } | public override void Flush() { } | ||||
| /// <inheritdoc/> | |||||
| public override long Length { get { throw new NotSupportedException(); } } | public override long Length { get { throw new NotSupportedException(); } } | ||||
| /// <inheritdoc/> | |||||
| public override long Position | public override long Position | ||||
| { | { | ||||
| get { throw new NotSupportedException(); } | get { throw new NotSupportedException(); } | ||||
| set { throw new NotSupportedException(); } | set { throw new NotSupportedException(); } | ||||
| } | } | ||||
| /// <inheritdoc/> | |||||
| public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | ||||
| /// <inheritdoc/> | |||||
| public override void SetLength(long value) { throw new NotSupportedException(); } | public override void SetLength(long value) { throw new NotSupportedException(); } | ||||
| /// <inheritdoc/> | |||||
| public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,10 +1,15 @@ | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| /// <summary> Specifies the type of channel a message was sent to or eceived from. </summary> | |||||
| public enum ChannelType | public enum ChannelType | ||||
| { | { | ||||
| ///<summary> A text channel </summary> | |||||
| Text = 0, | Text = 0, | ||||
| ///<summary> A direct-message text channel </summary> | |||||
| DM = 1, | DM = 1, | ||||
| ///<summary> A voice channel channel </summary> | |||||
| Voice = 2, | Voice = 2, | ||||
| ///<summary> A group channel </summary> | |||||
| Group = 3 | Group = 3 | ||||
| } | } | ||||
| } | } | ||||