diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs
index b0ed0f8e0..2229d35bc 100644
--- a/src/Discord.Net/DiscordClient.cs
+++ b/src/Discord.Net/DiscordClient.cs
@@ -615,7 +615,7 @@ namespace Discord
}
/// Sends a PCM frame to the voice server.
- /// PCM frame to send. This must be an uncompressed 48Kz monochannel 20ms PCM frame.
+ /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames.
/// Number of bytes in this frame.
/// Will block until
public void SendVoicePCM(byte[] data, int count)
@@ -626,7 +626,7 @@ namespace Discord
if (_isDebugMode)
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output.");
#if !DNXCORE50
- _voiceWebSocket.SendPCMFrame(data, count);
+ _voiceWebSocket.SendPCMFrames(data, count);
#endif
}
diff --git a/src/Discord.Net/DiscordVoiceSocket.cs b/src/Discord.Net/DiscordVoiceSocket.cs
index 306db336c..6c3954066 100644
--- a/src/Discord.Net/DiscordVoiceSocket.cs
+++ b/src/Discord.Net/DiscordVoiceSocket.cs
@@ -409,33 +409,70 @@ namespace Discord
}
}
- public void SendPCMFrame(byte[] data, int count)
+ public void SendPCMFrames(byte[] data, int bytes)
{
- if (!_isReady)
+ var cancelToken = _disconnectToken.Token;
+ if (!_isReady || cancelToken == null)
+ throw new InvalidOperationException("Not connected to a voice server.");
+ if (bytes == 0)
return;
- if (count != _encoder.FrameSize)
- throw new InvalidOperationException($"Invalid frame size. Got {count}, expected {_encoder.FrameSize}.");
- byte[] payload;
- lock (_encoder)
+ int frameSize = _encoder.FrameSize;
+ int frames = bytes / frameSize;
+ int expectedBytes = frames * frameSize;
+ int lastFrameSize = expectedBytes - bytes;
+
+ //If this only consists of a partial frame and the buffer is too small to pad the end, make a new one
+ if (data.Length < frameSize)
{
- int encodedLength = _encoder.EncodeFrame(data, _encodingBuffer);
+ byte[] newData = new byte[frameSize];
+ Buffer.BlockCopy(data, 0, newData, 0, bytes);
+ data = newData;
+ }
- if (_mode == "xsalsa20_poly1305")
+ byte[] payload;
+ //Opus encoder requires packets be queued in the same order they were generated, so all of this must still be locked.
+ lock (_encoder)
+ {
+ for (int i = 0, pos = 0; i <= frames; i++, pos += frameSize)
{
- //TODO: Encode
- }
+ if (i == frames)
+ {
+ //If there are no partial frames, skip this step
+ if (lastFrameSize == 0)
+ break;
- payload = new byte[encodedLength];
- Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength);
- }
+ //Take the partial frame from the end of the buffer and put it at the start
+ Buffer.BlockCopy(data, pos, data, 0, lastFrameSize);
+ pos = 0;
- _sendQueueWait.Wait(_disconnectToken.Token);
- _sendQueue.Enqueue(payload);
- if (_sendQueue.Count >= _targetAudioBufferLength)
- _sendQueueWait.Reset();
- _sendQueueEmptyWait.Reset();
- }
+ //Wipe the end of the buffer
+ for (int j = lastFrameSize; j < frameSize; j++)
+ data[j] = 0;
+
+ }
+
+ //Encode the frame
+ int encodedLength = _encoder.EncodeFrame(data, pos, _encodingBuffer);
+
+ //TODO: Handle Encryption
+ if (_mode == "xsalsa20_poly1305")
+ {
+ }
+
+ //Copy result to the queue
+ payload = new byte[encodedLength];
+ Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength);
+
+ //Wait until the queue has a spot open
+ _sendQueueWait.Wait(_disconnectToken.Token);
+ _sendQueue.Enqueue(payload);
+ if (_sendQueue.Count >= _targetAudioBufferLength)
+ _sendQueueWait.Reset();
+ _sendQueueEmptyWait.Reset();
+ }
+ }
+ }
public void ClearPCMFrames()
{
_isClearing = true;
diff --git a/src/Discord.Net/lib/Opus/API.cs b/src/Discord.Net/lib/Opus/API.cs
index 553bf9eb2..afc5060ea 100644
--- a/src/Discord.Net/lib/Opus/API.cs
+++ b/src/Discord.Net/lib/Opus/API.cs
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
namespace Discord.Opus
{
- internal class API
+ internal unsafe class API
{
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr opus_encoder_create(int Fs, int channels, int application, out Error error);
@@ -12,7 +12,7 @@ namespace Discord.Opus
public static extern void opus_encoder_destroy(IntPtr encoder);
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
- public static extern int opus_encode(IntPtr st, byte[] pcm, int frame_size, IntPtr data, int max_data_bytes);
+ public static extern int opus_encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes);
/*[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr opus_decoder_create(int Fs, int channels, out Errors error);
diff --git a/src/Discord.Net/lib/Opus/OpusEncoder.cs b/src/Discord.Net/lib/Opus/OpusEncoder.cs
index f9e5b9b78..06db38e49 100644
--- a/src/Discord.Net/lib/Opus/OpusEncoder.cs
+++ b/src/Discord.Net/lib/Opus/OpusEncoder.cs
@@ -35,9 +35,9 @@ namespace Discord.Opus
if (samplingRate != 8000 && samplingRate != 12000 &&
samplingRate != 16000 && samplingRate != 24000 &&
samplingRate != 48000)
- throw new ArgumentOutOfRangeException("inputSamplingRate");
+ throw new ArgumentOutOfRangeException(nameof(samplingRate));
if (channels != 1 && channels != 2)
- throw new ArgumentOutOfRangeException("inputChannels");
+ throw new ArgumentOutOfRangeException(nameof(channels));
InputSamplingRate = samplingRate;
InputChannels = channels;
@@ -50,31 +50,29 @@ namespace Discord.Opus
Error error;
_encoder = API.opus_encoder_create(samplingRate, channels, (int)application, out error);
if (error != Error.OK)
- throw new InvalidOperationException("Error occured while creating encoder: " + error.ToString());
+ throw new InvalidOperationException($"Error occured while creating encoder: {error}");
SetForwardErrorCorrection(true);
}
/// Produces Opus encoded audio from PCM samples.
/// PCM samples to encode.
- /// Length of encoded audio.
- /// Opus encoded audio buffer.
- public unsafe int EncodeFrame(byte[] pcmSamples, byte[] outputBuffer)
+ /// Offset of the frame in pcmSamples.
+ /// Buffer to store the encoded frame.
+ /// Length of the frame contained in outputBuffer.
+ public unsafe int EncodeFrame(byte[] pcmSamples, int offset, byte[] outputBuffer)
{
if (disposed)
throw new ObjectDisposedException("OpusEncoder");
- IntPtr encodedPtr;
- int length = 0;
- fixed (byte* bPtr = outputBuffer)
- {
- encodedPtr = new IntPtr((void*)bPtr);
- length = API.opus_encode(_encoder, pcmSamples, SamplesPerFrame, encodedPtr, outputBuffer.Length);
- }
+ int result = 0;
+ fixed (byte* inPtr = pcmSamples)
+ fixed (byte* outPtr = outputBuffer)
+ result = API.opus_encode(_encoder, inPtr + offset, SamplesPerFrame, outPtr, outputBuffer.Length);
- if (length < 0)
- throw new Exception("Encoding failed: " + ((Error)length).ToString());
- return length;
+ if (result < 0)
+ throw new Exception("Encoding failed: " + ((Error)result).ToString());
+ return result;
}
/// Gets or sets whether Forward Error Correction is enabled.
@@ -83,9 +81,9 @@ namespace Discord.Opus
if (_encoder == IntPtr.Zero)
throw new ObjectDisposedException("OpusEncoder");
- var ret = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0);
- if (ret < 0)
- throw new Exception("Encoder error - " + ((Error)ret).ToString());
+ var result = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0);
+ if (result < 0)
+ throw new Exception("Encoder error: " + ((Error)result).ToString());
}
private bool disposed;