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;