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