From 3b72d489506501fedad9cb0b2df9cb225c851d12 Mon Sep 17 00:00:00 2001 From: RogueException Date: Fri, 30 Dec 2016 16:02:01 -0400 Subject: [PATCH] Added support for partial PCM frames --- src/Discord.Net.Core/Audio/IAudioClient.cs | 12 ++---- src/Discord.Net.WebSocket/Audio/AudioClient.cs | 16 ++++---- .../Audio/Streams/OpusEncodeStream.cs | 43 +++++++++++++++++----- .../Audio/Streams/RTPWriteStream.cs | 4 +- .../Audio/Targets/BufferedAudioTarget.cs | 2 +- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/Discord.Net.Core/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs index 746515fa7..c8e8de747 100644 --- a/src/Discord.Net.Core/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -21,31 +21,27 @@ namespace Discord.Audio /// Creates a new outgoing stream accepting Opus-encoded data. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. - /// The size of the internal buffer used for encryption. /// - Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); + Stream CreateOpusStream(int samplesPerFrame); /// /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. - /// The size of the internal buffer used for encryption. /// - Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000); + Stream CreateDirectOpusStream(int samplesPerFrame); /// /// Creates a new outgoing stream accepting PCM (raw) data. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// - /// The size of the internal buffer used for encoding and encryption. /// - Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000); + Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); /// /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// - /// The size of the internal buffer used for encoding and encryption. /// - Stream CreateDirectPCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000); + Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index 2d11baaec..0f5079caa 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -169,29 +169,29 @@ namespace Discord.Audio await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); } - public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) + public Stream CreateOpusStream(int samplesPerFrame) { CheckSamplesPerFrame(samplesPerFrame); var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token); - return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); + return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc); } - public Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000) + public Stream CreateDirectOpusStream(int samplesPerFrame) { CheckSamplesPerFrame(samplesPerFrame); var target = new DirectAudioTarget(ApiClient); - return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000); + return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc); } - public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000) + public Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null) { CheckSamplesPerFrame(samplesPerFrame); var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token); - return new OpusEncodeStream(target, _secretKey, samplesPerFrame, _ssrc, bitrate, bufferSize); + return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate); } - public Stream CreateDirectPCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000) + public Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null) { CheckSamplesPerFrame(samplesPerFrame); var target = new DirectAudioTarget(ApiClient); - return new OpusEncodeStream(target, _secretKey, samplesPerFrame, _ssrc, bitrate, bufferSize); + return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate); } private void CheckSamplesPerFrame(int samplesPerFrame) { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index f86901ef1..d207e3cf7 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -1,16 +1,22 @@ -namespace Discord.Audio +using System; + +namespace Discord.Audio { internal class OpusEncodeStream : RTPWriteStream { - public int SampleRate = 48000; - public int Channels = 2; - + public const int SampleRate = 48000; + private int _frameSize; + private byte[] _partialFrameBuffer; + private int _partialFramePos; + private readonly OpusEncoder _encoder; - internal OpusEncodeStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc, int? bitrate = null, int bufferSize = 4000) - : base(target, secretKey, samplesPerFrame, ssrc, bufferSize) + internal OpusEncodeStream(IAudioTarget target, byte[] secretKey, int channels, int samplesPerFrame, uint ssrc, int? bitrate = null) + : base(target, secretKey, samplesPerFrame, ssrc) { - _encoder = new OpusEncoder(SampleRate, Channels); + _encoder = new OpusEncoder(SampleRate, channels); + _frameSize = samplesPerFrame * channels * 2; + _partialFrameBuffer = new byte[_frameSize]; _encoder.SetForwardErrorCorrection(true); if (bitrate != null) @@ -19,8 +25,27 @@ public override void Write(byte[] buffer, int offset, int count) { - count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0); - base.Write(_buffer, 0, count); + //Assume threadsafe + while (count > 0) + { + if (_partialFramePos + count >= _frameSize) + { + int partialSize = _frameSize - _partialFramePos; + Buffer.BlockCopy(buffer, offset, _partialFrameBuffer, _partialFramePos, partialSize); + offset += partialSize; + count -= partialSize; + _partialFramePos = 0; + + int encFrameSize = _encoder.EncodeFrame(_partialFrameBuffer, 0, _frameSize, _buffer, 0); + base.Write(_buffer, 0, encFrameSize); + } + else + { + Buffer.BlockCopy(buffer, offset, _partialFrameBuffer, _partialFramePos, count); + _partialFramePos += count; + break; + } + } } protected override void Dispose(bool disposing) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index 5ea0d2473..217555052 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -16,13 +16,13 @@ namespace Discord.Audio public override bool CanSeek => false; public override bool CanWrite => true; - internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000) + internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc) { _target = target; _secretKey = secretKey; _samplesPerFrame = samplesPerFrame; _ssrc = ssrc; - _buffer = new byte[bufferSize]; + _buffer = new byte[4000]; _nonce = new byte[24]; _nonce[0] = 0x80; _nonce[1] = 0x78; diff --git a/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs b/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs index 19bdee245..848a77131 100644 --- a/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs +++ b/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs @@ -75,4 +75,4 @@ namespace Discord.Audio Dispose(true); } } -} +} \ No newline at end of file