Added AudioOutStream, exposed Clear/ClearAsync methods. Added support for cancelTokens.
This commit is contained in:
16
src/Discord.Net.Core/Audio/AudioOutStream.cs
Normal file
16
src/Discord.Net.Core/Audio/AudioOutStream.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Discord.Audio
|
||||||
|
{
|
||||||
|
public abstract class AudioOutStream : Stream
|
||||||
|
{
|
||||||
|
public override bool CanRead => false;
|
||||||
|
public override bool CanSeek => false;
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
|
||||||
|
public virtual void Clear() { }
|
||||||
|
public virtual Task ClearAsync(CancellationToken cancelToken) { return Task.Delay(0); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,26 +22,26 @@ namespace Discord.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateOpusStream(int samplesPerFrame, int bufferMillis = 1000);
|
AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis = 1000);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer.
|
/// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateDirectOpusStream(int samplesPerFrame);
|
AudioOutStream CreateDirectOpusStream(int samplesPerFrame);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new outgoing stream accepting PCM (raw) data.
|
/// Creates a new outgoing stream accepting PCM (raw) data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
||||||
/// <param name="bitrate"></param>
|
/// <param name="bitrate"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null, int bufferMillis = 1000);
|
AudioOutStream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null, int bufferMillis = 1000);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer.
|
/// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
/// <param name="samplesPerFrame">Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively.</param>
|
||||||
/// <param name="bitrate"></param>
|
/// <param name="bitrate"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null);
|
AudioOutStream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,25 +170,25 @@ namespace Discord.Audio
|
|||||||
await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false);
|
await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream CreateOpusStream(int samplesPerFrame, int bufferMillis)
|
public AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token);
|
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token);
|
||||||
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
||||||
}
|
}
|
||||||
public Stream CreateDirectOpusStream(int samplesPerFrame)
|
public AudioOutStream CreateDirectOpusStream(int samplesPerFrame)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new DirectAudioTarget(ApiClient);
|
var target = new DirectAudioTarget(ApiClient);
|
||||||
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
||||||
}
|
}
|
||||||
public Stream CreatePCMStream(int samplesPerFrame, int channels, int? bitrate, int bufferMillis)
|
public AudioOutStream CreatePCMStream(int samplesPerFrame, int channels, int? bitrate, int bufferMillis)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token);
|
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token);
|
||||||
return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate);
|
return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate);
|
||||||
}
|
}
|
||||||
public Stream CreateDirectPCMStream(int samplesPerFrame, int channels, int? bitrate)
|
public AudioOutStream CreateDirectPCMStream(int samplesPerFrame, int channels, int? bitrate)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new DirectAudioTarget(ApiClient);
|
var target = new DirectAudioTarget(ApiClient);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal class RTPWriteStream : Stream
|
internal class RTPWriteStream : AudioOutStream
|
||||||
{
|
{
|
||||||
private readonly IAudioTarget _target;
|
private readonly IAudioTarget _target;
|
||||||
private readonly byte[] _nonce, _secretKey;
|
private readonly byte[] _nonce, _secretKey;
|
||||||
@@ -14,10 +14,6 @@ namespace Discord.Audio
|
|||||||
|
|
||||||
protected readonly byte[] _buffer;
|
protected readonly byte[] _buffer;
|
||||||
|
|
||||||
public override bool CanRead => false;
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
public override bool CanWrite => true;
|
|
||||||
|
|
||||||
internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc)
|
internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc)
|
||||||
{
|
{
|
||||||
_target = target;
|
_target = target;
|
||||||
@@ -40,6 +36,7 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
unchecked
|
unchecked
|
||||||
{
|
{
|
||||||
if (_nonce[3]++ == byte.MaxValue)
|
if (_nonce[3]++ == byte.MaxValue)
|
||||||
@@ -63,7 +60,16 @@ namespace Discord.Audio
|
|||||||
}
|
}
|
||||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
public override async Task FlushAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _target.FlushAsync().ConfigureAwait(false);
|
await _target.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear()
|
||||||
|
{
|
||||||
|
ClearAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
public override async Task ClearAsync(CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
await _target.ClearAsync(cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override long Length { get { throw new NotSupportedException(); } }
|
public override long Length { get { throw new NotSupportedException(); } }
|
||||||
|
|||||||
@@ -87,17 +87,25 @@ namespace Discord.Audio
|
|||||||
Buffer.BlockCopy(data, 0, buffer, 0, count);
|
Buffer.BlockCopy(data, 0, buffer, 0, count);
|
||||||
_queuedFrames.Enqueue(new Frame(buffer, count));
|
_queuedFrames.Enqueue(new Frame(buffer, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FlushAsync()
|
public async Task FlushAsync(CancellationToken cancelToken)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
cancelToken.ThrowIfCancellationRequested();
|
||||||
if (_queuedFrames.Count == 0)
|
if (_queuedFrames.Count == 0)
|
||||||
return;
|
return;
|
||||||
await Task.Delay(250).ConfigureAwait(false);
|
await Task.Delay(250, cancelToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public Task ClearAsync(CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
Frame ignored;
|
||||||
|
do
|
||||||
|
cancelToken.ThrowIfCancellationRequested();
|
||||||
|
while (_queuedFrames.TryDequeue(out ignored));
|
||||||
|
return Task.Delay(0);
|
||||||
|
}
|
||||||
protected void Dispose(bool disposing)
|
protected void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,9 @@ namespace Discord.Audio
|
|||||||
public Task SendAsync(byte[] buffer, int count)
|
public Task SendAsync(byte[] buffer, int count)
|
||||||
=> _client.SendAsync(buffer, count);
|
=> _client.SendAsync(buffer, count);
|
||||||
|
|
||||||
public Task FlushAsync()
|
public Task FlushAsync(CancellationToken cancelToken)
|
||||||
|
=> Task.Delay(0);
|
||||||
|
public Task ClearAsync(CancellationToken cancelToken)
|
||||||
=> Task.Delay(0);
|
=> Task.Delay(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Discord.Audio
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal interface IAudioTarget
|
internal interface IAudioTarget
|
||||||
{
|
{
|
||||||
Task SendAsync(byte[] buffer, int count);
|
Task SendAsync(byte[] buffer, int count);
|
||||||
Task FlushAsync();
|
Task FlushAsync(CancellationToken cancelToken);
|
||||||
|
Task ClearAsync(CancellationToken cancelToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user