@@ -21,31 +21,27 @@ namespace Discord.Audio | |||
/// Creates a new outgoing stream accepting Opus-encoded data. | |||
/// </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="bufferSize">The size of the internal buffer used for encryption.</param> | |||
/// <returns></returns> | |||
Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||
Stream CreateOpusStream(int samplesPerFrame); | |||
/// <summary> | |||
/// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. | |||
/// </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="bufferSize">The size of the internal buffer used for encryption.</param> | |||
/// <returns></returns> | |||
Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||
Stream CreateDirectOpusStream(int samplesPerFrame); | |||
/// <summary> | |||
/// Creates a new outgoing stream accepting PCM (raw) data. | |||
/// </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="bitrate"></param> | |||
/// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param> | |||
/// <returns></returns> | |||
Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000); | |||
Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); | |||
/// <summary> | |||
/// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. | |||
/// </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="bitrate"></param> | |||
/// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param> | |||
/// <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); | |||
} | |||
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) | |||
{ | |||
@@ -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) | |||
@@ -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; | |||
@@ -75,4 +75,4 @@ namespace Discord.Audio | |||
Dispose(true); | |||
} | |||
} | |||
} | |||
} |