Added support for partial PCM frames

This commit is contained in:
RogueException
2016-12-30 16:02:01 -04:00
parent c38a786039
commit 3b72d48950
5 changed files with 49 additions and 28 deletions

View File

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

View File

@@ -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)
{ {

View File

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

View File

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

View File

@@ -75,4 +75,4 @@ namespace Discord.Audio
Dispose(true); Dispose(true);
} }
} }
} }