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