Concrete class prototype
This commit is contained in:
30
src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
Normal file
30
src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class OpusDecodeStream : RTPReadStream
|
||||
{
|
||||
private readonly byte[] _buffer;
|
||||
private readonly OpusDecoder _decoder;
|
||||
|
||||
internal OpusDecodeStream(AudioClient audioClient, byte[] secretKey, int samplingRate,
|
||||
int channels = OpusConverter.MaxChannels, int bufferSize = 4000)
|
||||
: base(audioClient, secretKey)
|
||||
{
|
||||
_buffer = new byte[bufferSize];
|
||||
_decoder = new OpusDecoder(samplingRate, channels);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0);
|
||||
return base.Read(_buffer, 0, count);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_decoder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
Normal file
35
src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class OpusEncodeStream : RTPWriteStream
|
||||
{
|
||||
public int SampleRate = 48000;
|
||||
public int Channels = 2;
|
||||
|
||||
private readonly OpusEncoder _encoder;
|
||||
|
||||
internal OpusEncodeStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int? bitrate = null,
|
||||
OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000)
|
||||
: base(audioClient, secretKey, samplesPerFrame, ssrc, bufferSize)
|
||||
{
|
||||
_encoder = new OpusEncoder(SampleRate, Channels);
|
||||
|
||||
_encoder.SetForwardErrorCorrection(true);
|
||||
if (bitrate != null)
|
||||
_encoder.SetBitrate(bitrate.Value);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0);
|
||||
base.Write(_buffer, 0, count);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
_encoder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs
Normal file
53
src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class RTPReadStream : Stream
|
||||
{
|
||||
private readonly BlockingCollection<byte[]> _queuedData; //TODO: Replace with max-length ring buffer
|
||||
private readonly AudioClient _audioClient;
|
||||
private readonly byte[] _buffer, _nonce, _secretKey;
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
internal RTPReadStream(AudioClient audioClient, byte[] secretKey, int bufferSize = 4000)
|
||||
{
|
||||
_audioClient = audioClient;
|
||||
_secretKey = secretKey;
|
||||
_buffer = new byte[bufferSize];
|
||||
_queuedData = new BlockingCollection<byte[]>(100);
|
||||
_nonce = new byte[24];
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var queuedData = _queuedData.Take();
|
||||
Buffer.BlockCopy(queuedData, 0, buffer, offset, Math.Min(queuedData.Length, count));
|
||||
return queuedData.Length;
|
||||
}
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, 0, _nonce, 0, 12);
|
||||
count = SecretBox.Decrypt(buffer, offset, count, _buffer, 0, _nonce, _secretKey);
|
||||
var newBuffer = new byte[count];
|
||||
Buffer.BlockCopy(_buffer, 0, newBuffer, 0, count);
|
||||
_queuedData.Add(newBuffer);
|
||||
}
|
||||
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
67
src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs
Normal file
67
src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Discord.Audio
|
||||
{
|
||||
public class RTPWriteStream : Stream
|
||||
{
|
||||
private readonly AudioClient _audioClient;
|
||||
private readonly byte[] _nonce, _secretKey;
|
||||
private int _samplesPerFrame;
|
||||
private uint _ssrc, _timestamp = 0;
|
||||
|
||||
protected readonly byte[] _buffer;
|
||||
|
||||
public override bool CanRead => false;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
internal RTPWriteStream(AudioClient audioClient, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000)
|
||||
{
|
||||
_audioClient = audioClient;
|
||||
_secretKey = secretKey;
|
||||
_samplesPerFrame = samplesPerFrame;
|
||||
_ssrc = ssrc;
|
||||
_buffer = new byte[bufferSize];
|
||||
_nonce = new byte[24];
|
||||
_nonce[0] = 0x80;
|
||||
_nonce[1] = 0x78;
|
||||
_nonce[8] = (byte)(_ssrc >> 24);
|
||||
_nonce[9] = (byte)(_ssrc >> 16);
|
||||
_nonce[10] = (byte)(_ssrc >> 8);
|
||||
_nonce[11] = (byte)(_ssrc >> 0);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
if (_nonce[3]++ == byte.MaxValue)
|
||||
_nonce[2]++;
|
||||
|
||||
_timestamp += (uint)_samplesPerFrame;
|
||||
_nonce[4] = (byte)(_timestamp >> 24);
|
||||
_nonce[5] = (byte)(_timestamp >> 16);
|
||||
_nonce[6] = (byte)(_timestamp >> 8);
|
||||
_nonce[7] = (byte)(_timestamp >> 0);
|
||||
}
|
||||
|
||||
count = SecretBox.Encrypt(buffer, offset, count, _buffer, 12, _nonce, _secretKey);
|
||||
Buffer.BlockCopy(_nonce, 0, _buffer, 0, 12); //Copy the RTP header from nonce to buffer
|
||||
_audioClient.Send(_buffer, count + 12);
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user