@@ -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); | |||||
} | } | ||||
} | } |