@@ -28,8 +28,8 @@ namespace Discord.Audio | |||
/// <summary>Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer.</summary> | |||
AudioOutStream CreateDirectOpusStream(); | |||
/// <summary>Creates a new outgoing stream accepting PCM (raw) data.</summary> | |||
AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000); | |||
AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000, int packetLoss = 30); | |||
/// <summary>Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer.</summary> | |||
AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate = null); | |||
AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate = null, int packetLoss = 30); | |||
} | |||
} |
@@ -153,20 +153,20 @@ namespace Discord.Audio | |||
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header | |||
return new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header (external input), passes | |||
} | |||
public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis) | |||
public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis, int packetLoss) | |||
{ | |||
var outputStream = new OutputStream(ApiClient); //Ignores header | |||
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header | |||
var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes | |||
var bufferedStream = new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Ignores header, generates header | |||
return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application); //Generates header | |||
return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application, packetLoss); //Generates header | |||
} | |||
public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate) | |||
public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate, int packetLoss) | |||
{ | |||
var outputStream = new OutputStream(ApiClient); //Ignores header | |||
var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header | |||
var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes | |||
return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application); //Generates header | |||
return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application, packetLoss); //Generates header | |||
} | |||
internal async Task CreateInputStreamAsync(ulong userId) | |||
@@ -17,7 +17,7 @@ namespace Discord.Audio | |||
public AudioApplication Application { get; } | |||
public int BitRate { get;} | |||
public OpusEncoder(int bitrate, AudioApplication application) | |||
public OpusEncoder(int bitrate, AudioApplication application, int packetLoss) | |||
{ | |||
if (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate) | |||
throw new ArgumentOutOfRangeException(nameof(bitrate)); | |||
@@ -48,7 +48,7 @@ namespace Discord.Audio | |||
_ptr = CreateEncoder(SamplingRate, Channels, (int)opusApplication, out var error); | |||
CheckError(error); | |||
CheckError(EncoderCtl(_ptr, OpusCtl.SetSignal, (int)opusSignal)); | |||
CheckError(EncoderCtl(_ptr, OpusCtl.SetPacketLossPercent, 30)); //% | |||
CheckError(EncoderCtl(_ptr, OpusCtl.SetPacketLossPercent, packetLoss)); //% | |||
CheckError(EncoderCtl(_ptr, OpusCtl.SetInbandFEC, 1)); //True | |||
CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate)); | |||
} | |||
@@ -8,18 +8,18 @@ namespace Discord.Audio.Streams | |||
public class OpusEncodeStream : AudioOutStream | |||
{ | |||
public const int SampleRate = 48000; | |||
private readonly AudioStream _next; | |||
private readonly OpusEncoder _encoder; | |||
private readonly byte[] _buffer; | |||
private int _partialFramePos; | |||
private ushort _seq; | |||
private uint _timestamp; | |||
public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application) | |||
public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss) | |||
{ | |||
_next = next; | |||
_encoder = new OpusEncoder(bitrate, application); | |||
_encoder = new OpusEncoder(bitrate, application, packetLoss); | |||
_buffer = new byte[OpusConverter.FrameBytes]; | |||
} | |||
@@ -38,7 +38,7 @@ namespace Discord.Audio.Streams | |||
offset += OpusConverter.FrameBytes; | |||
count -= OpusConverter.FrameBytes; | |||
_seq++; | |||
_timestamp += OpusConverter.FrameBytes; | |||
_timestamp += OpusConverter.FrameSamplesPerChannel; | |||
} | |||
else if (_partialFramePos + count >= OpusConverter.FrameBytes) | |||
{ | |||
@@ -53,7 +53,7 @@ namespace Discord.Audio.Streams | |||
count -= partialSize; | |||
_partialFramePos = 0; | |||
_seq++; | |||
_timestamp += OpusConverter.FrameBytes; | |||
_timestamp += OpusConverter.FrameSamplesPerChannel; | |||
} | |||
else | |||
{ | |||