Added support for partial PCM frames
This commit is contained in:
@@ -21,31 +21,27 @@ namespace Discord.Audio
|
|||||||
/// Creates a new outgoing stream accepting Opus-encoded data.
|
/// Creates a new outgoing stream accepting Opus-encoded 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="bufferSize">The size of the internal buffer used for encryption.</param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000);
|
Stream CreateOpusStream(int samplesPerFrame);
|
||||||
/// <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>
|
||||||
/// <param name="bufferSize">The size of the internal buffer used for encryption.</param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000);
|
Stream 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>
|
||||||
/// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000);
|
Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null);
|
||||||
/// <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>
|
||||||
/// <param name="bufferSize">The size of the internal buffer used for encoding and encryption.</param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Stream CreateDirectPCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000);
|
Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,29 +169,29 @@ namespace Discord.Audio
|
|||||||
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000)
|
public Stream CreateOpusStream(int samplesPerFrame)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token);
|
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token);
|
||||||
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000);
|
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
||||||
}
|
}
|
||||||
public Stream CreateDirectOpusStream(int samplesPerFrame, int bufferSize = 4000)
|
public Stream CreateDirectOpusStream(int samplesPerFrame)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new DirectAudioTarget(ApiClient);
|
var target = new DirectAudioTarget(ApiClient);
|
||||||
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc, bufferSize = 4000);
|
return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc);
|
||||||
}
|
}
|
||||||
public Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000)
|
public Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token);
|
var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, _cancelTokenSource.Token);
|
||||||
return new OpusEncodeStream(target, _secretKey, samplesPerFrame, _ssrc, bitrate, bufferSize);
|
return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate);
|
||||||
}
|
}
|
||||||
public Stream CreateDirectPCMStream(int samplesPerFrame, int? bitrate = null, int bufferSize = 4000)
|
public Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null)
|
||||||
{
|
{
|
||||||
CheckSamplesPerFrame(samplesPerFrame);
|
CheckSamplesPerFrame(samplesPerFrame);
|
||||||
var target = new DirectAudioTarget(ApiClient);
|
var target = new DirectAudioTarget(ApiClient);
|
||||||
return new OpusEncodeStream(target, _secretKey, samplesPerFrame, _ssrc, bitrate, bufferSize);
|
return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate);
|
||||||
}
|
}
|
||||||
private void CheckSamplesPerFrame(int samplesPerFrame)
|
private void CheckSamplesPerFrame(int samplesPerFrame)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
namespace Discord.Audio
|
using System;
|
||||||
|
|
||||||
|
namespace Discord.Audio
|
||||||
{
|
{
|
||||||
internal class OpusEncodeStream : RTPWriteStream
|
internal class OpusEncodeStream : RTPWriteStream
|
||||||
{
|
{
|
||||||
public int SampleRate = 48000;
|
public const int SampleRate = 48000;
|
||||||
public int Channels = 2;
|
private int _frameSize;
|
||||||
|
private byte[] _partialFrameBuffer;
|
||||||
|
private int _partialFramePos;
|
||||||
|
|
||||||
private readonly OpusEncoder _encoder;
|
private readonly OpusEncoder _encoder;
|
||||||
|
|
||||||
internal OpusEncodeStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc, int? bitrate = null, int bufferSize = 4000)
|
internal OpusEncodeStream(IAudioTarget target, byte[] secretKey, int channels, int samplesPerFrame, uint ssrc, int? bitrate = null)
|
||||||
: base(target, secretKey, samplesPerFrame, ssrc, bufferSize)
|
: base(target, secretKey, samplesPerFrame, ssrc)
|
||||||
{
|
{
|
||||||
_encoder = new OpusEncoder(SampleRate, Channels);
|
_encoder = new OpusEncoder(SampleRate, channels);
|
||||||
|
_frameSize = samplesPerFrame * channels * 2;
|
||||||
|
_partialFrameBuffer = new byte[_frameSize];
|
||||||
|
|
||||||
_encoder.SetForwardErrorCorrection(true);
|
_encoder.SetForwardErrorCorrection(true);
|
||||||
if (bitrate != null)
|
if (bitrate != null)
|
||||||
@@ -19,8 +25,27 @@
|
|||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
count = _encoder.EncodeFrame(buffer, offset, count, _buffer, 0);
|
//Assume threadsafe
|
||||||
base.Write(_buffer, 0, count);
|
while (count > 0)
|
||||||
|
{
|
||||||
|
if (_partialFramePos + count >= _frameSize)
|
||||||
|
{
|
||||||
|
int partialSize = _frameSize - _partialFramePos;
|
||||||
|
Buffer.BlockCopy(buffer, offset, _partialFrameBuffer, _partialFramePos, partialSize);
|
||||||
|
offset += partialSize;
|
||||||
|
count -= partialSize;
|
||||||
|
_partialFramePos = 0;
|
||||||
|
|
||||||
|
int encFrameSize = _encoder.EncodeFrame(_partialFrameBuffer, 0, _frameSize, _buffer, 0);
|
||||||
|
base.Write(_buffer, 0, encFrameSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(buffer, offset, _partialFrameBuffer, _partialFramePos, count);
|
||||||
|
_partialFramePos += count;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ namespace Discord.Audio
|
|||||||
public override bool CanSeek => false;
|
public override bool CanSeek => false;
|
||||||
public override bool CanWrite => true;
|
public override bool CanWrite => true;
|
||||||
|
|
||||||
internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc, int bufferSize = 4000)
|
internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc)
|
||||||
{
|
{
|
||||||
_target = target;
|
_target = target;
|
||||||
_secretKey = secretKey;
|
_secretKey = secretKey;
|
||||||
_samplesPerFrame = samplesPerFrame;
|
_samplesPerFrame = samplesPerFrame;
|
||||||
_ssrc = ssrc;
|
_ssrc = ssrc;
|
||||||
_buffer = new byte[bufferSize];
|
_buffer = new byte[4000];
|
||||||
_nonce = new byte[24];
|
_nonce = new byte[24];
|
||||||
_nonce[0] = 0x80;
|
_nonce[0] = 0x80;
|
||||||
_nonce[1] = 0x78;
|
_nonce[1] = 0x78;
|
||||||
|
|||||||
@@ -75,4 +75,4 @@ namespace Discord.Audio
|
|||||||
Dispose(true);
|
Dispose(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user