| @@ -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 | |||
| { | |||