Allow SendVoicePCM to accept multiple frames.
This commit is contained in:
@@ -615,7 +615,7 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Sends a PCM frame to the voice server. </summary>
|
/// <summary> Sends a PCM frame to the voice server. </summary>
|
||||||
/// <param name="data">PCM frame to send. This must be an uncompressed 48Kz monochannel 20ms PCM frame. </param>
|
/// <param name="data">PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. </param>
|
||||||
/// <param name="count">Number of bytes in this frame. </param>
|
/// <param name="count">Number of bytes in this frame. </param>
|
||||||
/// <remarks>Will block until</remarks>
|
/// <remarks>Will block until</remarks>
|
||||||
public void SendVoicePCM(byte[] data, int count)
|
public void SendVoicePCM(byte[] data, int count)
|
||||||
@@ -626,7 +626,7 @@ namespace Discord
|
|||||||
if (_isDebugMode)
|
if (_isDebugMode)
|
||||||
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output.");
|
RaiseOnDebugMessage(DebugMessageType.VoiceOutput, $"Queued {count} bytes for voice output.");
|
||||||
#if !DNXCORE50
|
#if !DNXCORE50
|
||||||
_voiceWebSocket.SendPCMFrame(data, count);
|
_voiceWebSocket.SendPCMFrames(data, count);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,33 +409,70 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendPCMFrame(byte[] data, int count)
|
public void SendPCMFrames(byte[] data, int bytes)
|
||||||
{
|
{
|
||||||
if (!_isReady)
|
var cancelToken = _disconnectToken.Token;
|
||||||
|
if (!_isReady || cancelToken == null)
|
||||||
|
throw new InvalidOperationException("Not connected to a voice server.");
|
||||||
|
if (bytes == 0)
|
||||||
return;
|
return;
|
||||||
if (count != _encoder.FrameSize)
|
|
||||||
throw new InvalidOperationException($"Invalid frame size. Got {count}, expected {_encoder.FrameSize}.");
|
int frameSize = _encoder.FrameSize;
|
||||||
|
int frames = bytes / frameSize;
|
||||||
|
int expectedBytes = frames * frameSize;
|
||||||
|
int lastFrameSize = expectedBytes - bytes;
|
||||||
|
|
||||||
|
//If this only consists of a partial frame and the buffer is too small to pad the end, make a new one
|
||||||
|
if (data.Length < frameSize)
|
||||||
|
{
|
||||||
|
byte[] newData = new byte[frameSize];
|
||||||
|
Buffer.BlockCopy(data, 0, newData, 0, bytes);
|
||||||
|
data = newData;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] payload;
|
byte[] payload;
|
||||||
|
//Opus encoder requires packets be queued in the same order they were generated, so all of this must still be locked.
|
||||||
lock (_encoder)
|
lock (_encoder)
|
||||||
{
|
{
|
||||||
int encodedLength = _encoder.EncodeFrame(data, _encodingBuffer);
|
for (int i = 0, pos = 0; i <= frames; i++, pos += frameSize)
|
||||||
|
{
|
||||||
|
if (i == frames)
|
||||||
|
{
|
||||||
|
//If there are no partial frames, skip this step
|
||||||
|
if (lastFrameSize == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
//Take the partial frame from the end of the buffer and put it at the start
|
||||||
|
Buffer.BlockCopy(data, pos, data, 0, lastFrameSize);
|
||||||
|
pos = 0;
|
||||||
|
|
||||||
|
//Wipe the end of the buffer
|
||||||
|
for (int j = lastFrameSize; j < frameSize; j++)
|
||||||
|
data[j] = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Encode the frame
|
||||||
|
int encodedLength = _encoder.EncodeFrame(data, pos, _encodingBuffer);
|
||||||
|
|
||||||
|
//TODO: Handle Encryption
|
||||||
if (_mode == "xsalsa20_poly1305")
|
if (_mode == "xsalsa20_poly1305")
|
||||||
{
|
{
|
||||||
//TODO: Encode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Copy result to the queue
|
||||||
payload = new byte[encodedLength];
|
payload = new byte[encodedLength];
|
||||||
Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength);
|
Buffer.BlockCopy(_encodingBuffer, 0, payload, 0, encodedLength);
|
||||||
}
|
|
||||||
|
|
||||||
|
//Wait until the queue has a spot open
|
||||||
_sendQueueWait.Wait(_disconnectToken.Token);
|
_sendQueueWait.Wait(_disconnectToken.Token);
|
||||||
_sendQueue.Enqueue(payload);
|
_sendQueue.Enqueue(payload);
|
||||||
if (_sendQueue.Count >= _targetAudioBufferLength)
|
if (_sendQueue.Count >= _targetAudioBufferLength)
|
||||||
_sendQueueWait.Reset();
|
_sendQueueWait.Reset();
|
||||||
_sendQueueEmptyWait.Reset();
|
_sendQueueEmptyWait.Reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public void ClearPCMFrames()
|
public void ClearPCMFrames()
|
||||||
{
|
{
|
||||||
_isClearing = true;
|
_isClearing = true;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Discord.Opus
|
namespace Discord.Opus
|
||||||
{
|
{
|
||||||
internal class API
|
internal unsafe class API
|
||||||
{
|
{
|
||||||
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern IntPtr opus_encoder_create(int Fs, int channels, int application, out Error error);
|
public static extern IntPtr opus_encoder_create(int Fs, int channels, int application, out Error error);
|
||||||
@@ -12,7 +12,7 @@ namespace Discord.Opus
|
|||||||
public static extern void opus_encoder_destroy(IntPtr encoder);
|
public static extern void opus_encoder_destroy(IntPtr encoder);
|
||||||
|
|
||||||
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern int opus_encode(IntPtr st, byte[] pcm, int frame_size, IntPtr data, int max_data_bytes);
|
public static extern int opus_encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes);
|
||||||
|
|
||||||
/*[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
/*[DllImport("lib/opus", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern IntPtr opus_decoder_create(int Fs, int channels, out Errors error);
|
public static extern IntPtr opus_decoder_create(int Fs, int channels, out Errors error);
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ namespace Discord.Opus
|
|||||||
if (samplingRate != 8000 && samplingRate != 12000 &&
|
if (samplingRate != 8000 && samplingRate != 12000 &&
|
||||||
samplingRate != 16000 && samplingRate != 24000 &&
|
samplingRate != 16000 && samplingRate != 24000 &&
|
||||||
samplingRate != 48000)
|
samplingRate != 48000)
|
||||||
throw new ArgumentOutOfRangeException("inputSamplingRate");
|
throw new ArgumentOutOfRangeException(nameof(samplingRate));
|
||||||
if (channels != 1 && channels != 2)
|
if (channels != 1 && channels != 2)
|
||||||
throw new ArgumentOutOfRangeException("inputChannels");
|
throw new ArgumentOutOfRangeException(nameof(channels));
|
||||||
|
|
||||||
InputSamplingRate = samplingRate;
|
InputSamplingRate = samplingRate;
|
||||||
InputChannels = channels;
|
InputChannels = channels;
|
||||||
@@ -50,31 +50,29 @@ namespace Discord.Opus
|
|||||||
Error error;
|
Error error;
|
||||||
_encoder = API.opus_encoder_create(samplingRate, channels, (int)application, out error);
|
_encoder = API.opus_encoder_create(samplingRate, channels, (int)application, out error);
|
||||||
if (error != Error.OK)
|
if (error != Error.OK)
|
||||||
throw new InvalidOperationException("Error occured while creating encoder: " + error.ToString());
|
throw new InvalidOperationException($"Error occured while creating encoder: {error}");
|
||||||
|
|
||||||
SetForwardErrorCorrection(true);
|
SetForwardErrorCorrection(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Produces Opus encoded audio from PCM samples. </summary>
|
/// <summary> Produces Opus encoded audio from PCM samples. </summary>
|
||||||
/// <param name="pcmSamples">PCM samples to encode.</param>
|
/// <param name="pcmSamples">PCM samples to encode.</param>
|
||||||
/// <param name="encodedLength">Length of encoded audio.</param>
|
/// <param name="offset">Offset of the frame in pcmSamples.</param>
|
||||||
/// <returns>Opus encoded audio buffer.</returns>
|
/// <param name="outputBuffer">Buffer to store the encoded frame.</param>
|
||||||
public unsafe int EncodeFrame(byte[] pcmSamples, byte[] outputBuffer)
|
/// <returns>Length of the frame contained in outputBuffer.</returns>
|
||||||
|
public unsafe int EncodeFrame(byte[] pcmSamples, int offset, byte[] outputBuffer)
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
throw new ObjectDisposedException("OpusEncoder");
|
throw new ObjectDisposedException("OpusEncoder");
|
||||||
|
|
||||||
IntPtr encodedPtr;
|
int result = 0;
|
||||||
int length = 0;
|
fixed (byte* inPtr = pcmSamples)
|
||||||
fixed (byte* bPtr = outputBuffer)
|
fixed (byte* outPtr = outputBuffer)
|
||||||
{
|
result = API.opus_encode(_encoder, inPtr + offset, SamplesPerFrame, outPtr, outputBuffer.Length);
|
||||||
encodedPtr = new IntPtr((void*)bPtr);
|
|
||||||
length = API.opus_encode(_encoder, pcmSamples, SamplesPerFrame, encodedPtr, outputBuffer.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length < 0)
|
if (result < 0)
|
||||||
throw new Exception("Encoding failed: " + ((Error)length).ToString());
|
throw new Exception("Encoding failed: " + ((Error)result).ToString());
|
||||||
return length;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
|
/// <summary> Gets or sets whether Forward Error Correction is enabled. </summary>
|
||||||
@@ -83,9 +81,9 @@ namespace Discord.Opus
|
|||||||
if (_encoder == IntPtr.Zero)
|
if (_encoder == IntPtr.Zero)
|
||||||
throw new ObjectDisposedException("OpusEncoder");
|
throw new ObjectDisposedException("OpusEncoder");
|
||||||
|
|
||||||
var ret = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0);
|
var result = API.opus_encoder_ctl(_encoder, Ctl.SetInbandFECRequest, value ? 1 : 0);
|
||||||
if (ret < 0)
|
if (result < 0)
|
||||||
throw new Exception("Encoder error - " + ((Error)ret).ToString());
|
throw new Exception("Encoder error: " + ((Error)result).ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
|
|||||||
Reference in New Issue
Block a user