| @@ -21,31 +21,27 @@ namespace Discord.Audio | |||||
| /// Creates a new outgoing stream accepting Opus-encoded data. | /// Creates a new outgoing stream accepting Opus-encoded data. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | ||||
| /// <param name="bufferSize">The size of the internal buffer used for encryption.</param> | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| Stream CreateOpusStream(int samplesPerFrame); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. | /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | ||||
| /// <param name="bufferSize">The size of the internal buffer used for encryption.</param> | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| Stream CreateDirectOpusStream(int samplesPerFrame); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new outgoing stream accepting PCM (raw) data. | /// Creates a new outgoing stream accepting PCM (raw) data. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | ||||
| /// <param name="bitrate"></param> | /// <param name="bitrate"></param> | ||||
| /// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param> | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000); | |||||
| Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. | /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | /// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param> | ||||
| /// <param name="bitrate"></param> | /// <param name="bitrate"></param> | ||||
| /// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param> | |||||
| /// <returns></returns> | /// <returns></returns> | ||||
| Stream CreateDirectPCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000); | |||||
| Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); | |||||
| } | } | ||||
| } | } | ||||
| @@ -169,29 +169,29 @@ namespace Discord.Audio | |||||
| await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); | ||||
| } | } | ||||
| public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000) | |||||
| public Stream CreateOpusStream(int samplesPerFrame) | |||||
| { | { | ||||
| CheckSamplesPerFrame(samplesPerFrame); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token); | 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); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var target = new DirectAudioTarget(ApiClient); | 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); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token); | 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); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var target = new DirectAudioTarget(ApiClient); | 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) | private void CheckSamplesPerFrame(int samplesPerFrame) | ||||
| { | { | ||||
| @@ -1,16 +1,22 @@ | |||||
| namespace Discord.Audio | |||||
| using System; | |||||
| namespace Discord.Audio | |||||
| { | { | ||||
| internal class OpusEncodeStream : RTPWriteStream | 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; | 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); | _encoder.SetForwardErrorCorrection(true); | ||||
| if (bitrate != null) | if (bitrate != null) | ||||
| @@ -19,8 +25,27 @@ | |||||
| 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); | |||||
| 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) | protected override void Dispose(bool disposing) | ||||
| @@ -16,13 +16,13 @@ namespace Discord.Audio | |||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| public override bool CanWrite => true; | 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; | _target = target; | ||||
| _secretKey = secretKey; | _secretKey = secretKey; | ||||
| _samplesPerFrame = samplesPerFrame; | _samplesPerFrame = samplesPerFrame; | ||||
| _ssrc = ssrc; | _ssrc = ssrc; | ||||
| _buffer = new byte[bufferSize]; | |||||
| _buffer = new byte[4000]; | |||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| _nonce[0] = 0x80; | _nonce[0] = 0x80; | ||||
| _nonce[1] = 0x78; | _nonce[1] = 0x78; | ||||
| @@ -75,4 +75,4 @@ namespace Discord.Audio | |||||
| Dispose(true); | Dispose(true); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||