| @@ -1,43 +1,19 @@ | |||||
| using System; | using System; | ||||
| using System.IO; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public abstract class AudioInStream : Stream | |||||
| public abstract class AudioInStream : AudioStream | |||||
| { | { | ||||
| public override bool CanRead => true; | |||||
| public override bool CanSeek => false; | |||||
| public override bool CanWrite => true; | |||||
| public abstract int AvailableFrames { get; } | public abstract int AvailableFrames { get; } | ||||
| public override bool CanRead => true; | |||||
| public override bool CanWrite => true; | |||||
| public abstract Task<RTPFrame> ReadFrameAsync(CancellationToken cancelToken); | public abstract Task<RTPFrame> ReadFrameAsync(CancellationToken cancelToken); | ||||
| public abstract bool TryReadFrame(CancellationToken cancelToken, out RTPFrame frame); | public abstract bool TryReadFrame(CancellationToken cancelToken, out RTPFrame frame); | ||||
| public RTPFrame ReadFrame() | |||||
| { | |||||
| return ReadFrameAsync(CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public override int Read(byte[] buffer, int offset, int count) | |||||
| { | |||||
| return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public override void Write(byte[] buffer, int offset, int count) | |||||
| { | |||||
| WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public override void Flush() { throw new NotSupportedException(); } | |||||
| public override long Length { get { throw new NotSupportedException(); } } | |||||
| public override long Position | |||||
| { | |||||
| get { throw new NotSupportedException(); } | |||||
| set { throw new NotSupportedException(); } | |||||
| } | |||||
| public override void SetLength(long value) { throw new NotSupportedException(); } | |||||
| public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |||||
| public override Task FlushAsync(CancellationToken cancelToken) { throw new NotSupportedException(); } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,39 +1,12 @@ | |||||
| using System; | using System; | ||||
| using System.IO; | using System.IO; | ||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| public abstract class AudioOutStream : Stream | |||||
| public abstract class AudioOutStream : AudioStream | |||||
| { | { | ||||
| public override bool CanRead => false; | |||||
| public override bool CanSeek => false; | |||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| public override void Write(byte[] buffer, int offset, int count) | |||||
| { | |||||
| WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public override void Flush() | |||||
| { | |||||
| FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public void Clear() | |||||
| { | |||||
| ClearAsync(CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); } | |||||
| //public virtual Task WriteSilenceAsync(CancellationToken cancellationToken) { return Task.Delay(0); } | |||||
| public override long Length { get { throw new NotSupportedException(); } } | |||||
| public override long Position | |||||
| { | |||||
| get { throw new NotSupportedException(); } | |||||
| set { throw new NotSupportedException(); } | |||||
| } | |||||
| public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | ||||
| public override void SetLength(long value) { throw new NotSupportedException(); } | public override void SetLength(long value) { throw new NotSupportedException(); } | ||||
| public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | ||||
| @@ -0,0 +1,40 @@ | |||||
| using System; | |||||
| using System.IO; | |||||
| using System.Threading; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Audio | |||||
| { | |||||
| public abstract class AudioStream : Stream | |||||
| { | |||||
| public override bool CanRead => false; | |||||
| public override bool CanSeek => false; | |||||
| public override bool CanWrite => false; | |||||
| public override void Write(byte[] buffer, int offset, int count) | |||||
| { | |||||
| WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public override void Flush() | |||||
| { | |||||
| FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public void Clear() | |||||
| { | |||||
| ClearAsync(CancellationToken.None).GetAwaiter().GetResult(); | |||||
| } | |||||
| public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); } | |||||
| public override long Length { get { throw new NotSupportedException(); } } | |||||
| public override long Position | |||||
| { | |||||
| get { throw new NotSupportedException(); } | |||||
| set { throw new NotSupportedException(); } | |||||
| } | |||||
| public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |||||
| public override void SetLength(long value) { throw new NotSupportedException(); } | |||||
| public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |||||
| } | |||||
| } | |||||
| @@ -42,12 +42,12 @@ namespace Discord.Audio | |||||
| private string _url, _sessionId, _token; | private string _url, _sessionId, _token; | ||||
| private ulong _userId; | private ulong _userId; | ||||
| private uint _ssrc; | private uint _ssrc; | ||||
| private byte[] _secretKey; | |||||
| public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
| public DiscordVoiceAPIClient ApiClient { get; private set; } | public DiscordVoiceAPIClient ApiClient { get; private set; } | ||||
| public int Latency { get; private set; } | public int Latency { get; private set; } | ||||
| public ulong ChannelId { get; internal set; } | public ulong ChannelId { get; internal set; } | ||||
| internal byte[] SecretKey { get; private set; } | |||||
| private DiscordSocketClient Discord => Guild.Discord; | private DiscordSocketClient Discord => Guild.Discord; | ||||
| public ConnectionState ConnectionState => _connection.State; | public ConnectionState ConnectionState => _connection.State; | ||||
| @@ -134,7 +134,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| CheckSamplesPerFrame(samplesPerFrame); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var outputStream = new OutputStream(ApiClient); | var outputStream = new OutputStream(ApiClient); | ||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey); | |||||
| var sodiumEncrypter = new SodiumEncryptStream( outputStream, this); | |||||
| var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | ||||
| return new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger); | return new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger); | ||||
| } | } | ||||
| @@ -142,14 +142,14 @@ namespace Discord.Audio | |||||
| { | { | ||||
| CheckSamplesPerFrame(samplesPerFrame); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var outputStream = new OutputStream(ApiClient); | var outputStream = new OutputStream(ApiClient); | ||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey); | |||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); | |||||
| return new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | return new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | ||||
| } | } | ||||
| public AudioOutStream CreatePCMStream(AudioApplication application, int samplesPerFrame, int channels, int? bitrate, int bufferMillis) | public AudioOutStream CreatePCMStream(AudioApplication application, int samplesPerFrame, int channels, int? bitrate, int bufferMillis) | ||||
| { | { | ||||
| CheckSamplesPerFrame(samplesPerFrame); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var outputStream = new OutputStream(ApiClient); | var outputStream = new OutputStream(ApiClient); | ||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey); | |||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); | |||||
| var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | ||||
| var bufferedStream = new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger); | var bufferedStream = new BufferedWriteStream(rtpWriter, this, samplesPerFrame, bufferMillis, _connection.CancelToken, _audioLogger); | ||||
| return new OpusEncodeStream(bufferedStream, channels, samplesPerFrame, bitrate ?? (96 * 1024), application); | return new OpusEncodeStream(bufferedStream, channels, samplesPerFrame, bitrate ?? (96 * 1024), application); | ||||
| @@ -158,7 +158,7 @@ namespace Discord.Audio | |||||
| { | { | ||||
| CheckSamplesPerFrame(samplesPerFrame); | CheckSamplesPerFrame(samplesPerFrame); | ||||
| var outputStream = new OutputStream(ApiClient); | var outputStream = new OutputStream(ApiClient); | ||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, _secretKey); | |||||
| var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); | |||||
| var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | var rtpWriter = new RTPWriteStream(sodiumEncrypter, samplesPerFrame, _ssrc); | ||||
| return new OpusEncodeStream(rtpWriter, channels, samplesPerFrame, bitrate ?? (96 * 1024), application); | return new OpusEncodeStream(rtpWriter, channels, samplesPerFrame, bitrate ?? (96 * 1024), application); | ||||
| } | } | ||||
| @@ -175,8 +175,10 @@ namespace Discord.Audio | |||||
| if (!_streams.ContainsKey(userId)) | if (!_streams.ContainsKey(userId)) | ||||
| { | { | ||||
| var readerStream = new InputStream(); | var readerStream = new InputStream(); | ||||
| var writerStream = new OpusDecodeStream(new RTPReadStream(readerStream, _secretKey)); | |||||
| _streams.TryAdd(userId, new StreamPair(readerStream, writerStream)); | |||||
| var opusDecoder = new OpusDecodeStream(readerStream); | |||||
| var rtpReader = new RTPReadStream(readerStream, opusDecoder); | |||||
| var decryptStream = new SodiumDecryptStream(rtpReader, this); | |||||
| _streams.TryAdd(userId, new StreamPair(readerStream, decryptStream)); | |||||
| await _streamCreatedEvent.InvokeAsync(userId, readerStream); | await _streamCreatedEvent.InvokeAsync(userId, readerStream); | ||||
| } | } | ||||
| } | } | ||||
| @@ -238,7 +240,7 @@ namespace Discord.Audio | |||||
| if (data.Mode != DiscordVoiceAPIClient.Mode) | if (data.Mode != DiscordVoiceAPIClient.Mode) | ||||
| throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | ||||
| _secretKey = data.SecretKey; | |||||
| SecretKey = data.SecretKey; | |||||
| await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); | await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); | ||||
| var _ = _connection.CompleteAsync(); | var _ = _connection.CompleteAsync(); | ||||
| @@ -335,7 +337,7 @@ namespace Discord.Audio | |||||
| await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); | await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); | ||||
| return; | return; | ||||
| } | } | ||||
| await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | |||||
| //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -26,7 +26,7 @@ namespace Discord.Audio.Streams | |||||
| private static readonly byte[] _silenceFrame = new byte[0]; | private static readonly byte[] _silenceFrame = new byte[0]; | ||||
| private readonly AudioClient _client; | private readonly AudioClient _client; | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly AudioStream _next; | |||||
| private readonly CancellationTokenSource _cancelTokenSource; | private readonly CancellationTokenSource _cancelTokenSource; | ||||
| private readonly CancellationToken _cancelToken; | private readonly CancellationToken _cancelToken; | ||||
| private readonly Task _task; | private readonly Task _task; | ||||
| @@ -38,9 +38,9 @@ namespace Discord.Audio.Streams | |||||
| private bool _isPreloaded; | private bool _isPreloaded; | ||||
| private int _silenceFrames; | private int _silenceFrames; | ||||
| public BufferedWriteStream(AudioOutStream next, IAudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500) | |||||
| public BufferedWriteStream(AudioStream next, IAudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500) | |||||
| : this(next, client as AudioClient, samplesPerFrame, bufferMillis, cancelToken, null, maxFrameSize) { } | : this(next, client as AudioClient, samplesPerFrame, bufferMillis, cancelToken, null, maxFrameSize) { } | ||||
| internal BufferedWriteStream(AudioOutStream next, AudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500) | |||||
| internal BufferedWriteStream(AudioStream next, AudioClient client, int samplesPerFrame, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500) | |||||
| { | { | ||||
| //maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms | //maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms | ||||
| _next = next; | _next = next; | ||||
| @@ -8,11 +8,11 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| public const int SampleRate = OpusEncodeStream.SampleRate; | public const int SampleRate = OpusEncodeStream.SampleRate; | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly AudioStream _next; | |||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly OpusDecoder _decoder; | private readonly OpusDecoder _decoder; | ||||
| public OpusDecodeStream(AudioOutStream next, int channels = OpusConverter.MaxChannels, int bufferSize = 4000) | |||||
| public OpusDecodeStream(AudioStream next, int channels = OpusConverter.MaxChannels, int bufferSize = 4000) | |||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _buffer = new byte[bufferSize]; | _buffer = new byte[bufferSize]; | ||||
| @@ -9,7 +9,7 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| public const int SampleRate = 48000; | public const int SampleRate = 48000; | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly AudioStream _next; | |||||
| private readonly OpusEncoder _encoder; | private readonly OpusEncoder _encoder; | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| @@ -17,7 +17,7 @@ namespace Discord.Audio.Streams | |||||
| private byte[] _partialFrameBuffer; | private byte[] _partialFrameBuffer; | ||||
| private int _partialFramePos; | private int _partialFramePos; | ||||
| public OpusEncodeStream(AudioOutStream next, int channels, int samplesPerFrame, int bitrate, AudioApplication application, int bufferSize = 4000) | |||||
| public OpusEncodeStream(AudioStream next, int channels, int samplesPerFrame, int bitrate, AudioApplication application, int bufferSize = 4000) | |||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _encoder = new OpusEncoder(SampleRate, channels, bitrate, application); | _encoder = new OpusEncoder(SampleRate, channels, bitrate, application); | ||||
| @@ -9,20 +9,19 @@ namespace Discord.Audio.Streams | |||||
| public class RTPReadStream : AudioOutStream | public class RTPReadStream : AudioOutStream | ||||
| { | { | ||||
| private readonly InputStream _queue; | private readonly InputStream _queue; | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly byte[] _buffer, _nonce, _secretKey; | |||||
| private readonly AudioStream _next; | |||||
| private readonly byte[] _buffer, _nonce; | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| public RTPReadStream(InputStream queue, byte[] secretKey, int bufferSize = 4000) | |||||
| : this(queue, null, secretKey, bufferSize) { } | |||||
| public RTPReadStream(InputStream queue, AudioOutStream next, byte[] secretKey, int bufferSize = 4000) | |||||
| public RTPReadStream(InputStream queue, int bufferSize = 4000) | |||||
| : this(queue, null, bufferSize) { } | |||||
| public RTPReadStream(InputStream queue, AudioStream next, int bufferSize = 4000) | |||||
| { | { | ||||
| _queue = queue; | _queue = queue; | ||||
| _next = next; | _next = next; | ||||
| _secretKey = secretKey; | |||||
| _buffer = new byte[bufferSize]; | _buffer = new byte[bufferSize]; | ||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| } | } | ||||
| @@ -7,14 +7,14 @@ namespace Discord.Audio.Streams | |||||
| ///<summary> Wraps data in an RTP frame </summary> | ///<summary> Wraps data in an RTP frame </summary> | ||||
| public class RTPWriteStream : AudioOutStream | public class RTPWriteStream : AudioOutStream | ||||
| { | { | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly AudioStream _next; | |||||
| private readonly byte[] _header; | private readonly byte[] _header; | ||||
| private int _samplesPerFrame; | private int _samplesPerFrame; | ||||
| private uint _ssrc, _timestamp = 0; | private uint _ssrc, _timestamp = 0; | ||||
| protected readonly byte[] _buffer; | protected readonly byte[] _buffer; | ||||
| public RTPWriteStream(AudioOutStream next, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | |||||
| public RTPWriteStream(AudioStream next, int samplesPerFrame, uint ssrc, int bufferSize = 4000) | |||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _samplesPerFrame = samplesPerFrame; | _samplesPerFrame = samplesPerFrame; | ||||
| @@ -7,18 +7,18 @@ namespace Discord.Audio.Streams | |||||
| ///<summary> Decrypts an RTP frame using libsodium </summary> | ///<summary> Decrypts an RTP frame using libsodium </summary> | ||||
| public class SodiumDecryptStream : AudioOutStream | public class SodiumDecryptStream : AudioOutStream | ||||
| { | { | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly byte[] _buffer, _nonce, _secretKey; | |||||
| private readonly AudioClient _client; | |||||
| private readonly AudioStream _next; | |||||
| private readonly byte[] _nonce; | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| public override bool CanWrite => true; | public override bool CanWrite => true; | ||||
| public SodiumDecryptStream(AudioOutStream next, byte[] secretKey, int bufferSize = 4000) | |||||
| public SodiumDecryptStream(AudioStream next, IAudioClient client) | |||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _secretKey = secretKey; | |||||
| _buffer = new byte[bufferSize]; | |||||
| _client = (AudioClient)client; | |||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| } | } | ||||
| @@ -26,11 +26,11 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| cancelToken.ThrowIfCancellationRequested(); | cancelToken.ThrowIfCancellationRequested(); | ||||
| Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce | |||||
| count = SecretBox.Decrypt(buffer, offset, count, _buffer, 0, _nonce, _secretKey); | |||||
| if (_client.SecretKey == null) | |||||
| return; | |||||
| var newBuffer = new byte[count]; | |||||
| Buffer.BlockCopy(_buffer, 0, newBuffer, 0, count); | |||||
| Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce | |||||
| count = SecretBox.Decrypt(buffer, offset + 12, count - 12, buffer, offset + 12, _nonce, _client.SecretKey); | |||||
| await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -7,16 +7,14 @@ namespace Discord.Audio.Streams | |||||
| ///<summary> Encrypts an RTP frame using libsodium </summary> | ///<summary> Encrypts an RTP frame using libsodium </summary> | ||||
| public class SodiumEncryptStream : AudioOutStream | public class SodiumEncryptStream : AudioOutStream | ||||
| { | { | ||||
| private readonly AudioOutStream _next; | |||||
| private readonly byte[] _nonce, _secretKey; | |||||
| private readonly AudioClient _client; | |||||
| private readonly AudioStream _next; | |||||
| private readonly byte[] _nonce; | |||||
| //protected readonly byte[] _buffer; | |||||
| public SodiumEncryptStream(AudioOutStream next, byte[] secretKey/*, int bufferSize = 4000*/) | |||||
| public SodiumEncryptStream(AudioStream next, IAudioClient client) | |||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _secretKey = secretKey; | |||||
| //_buffer = new byte[bufferSize]; //TODO: Can Sodium do an in-place encrypt? | |||||
| _client = (AudioClient)client; | |||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| } | } | ||||
| @@ -24,8 +22,11 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| cancelToken.ThrowIfCancellationRequested(); | cancelToken.ThrowIfCancellationRequested(); | ||||
| if (_client.SecretKey == null) | |||||
| return; | |||||
| Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header | Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header | ||||
| count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _secretKey); | |||||
| count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey); | |||||
| await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||