|
|
|
|
using System;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Discord.Audio.Streams
|
|
|
|
|
{
|
|
|
|
|
///<summary> Converts PCM to Opus </summary>
|
|
|
|
|
public class OpusEncodeStream : AudioOutStream
|
|
|
|
|
{
|
|
|
|
|
public const int SampleRate = 48000;
|
|
|
|
|
|
|
|
|
|
private readonly AudioStream _next;
|
|
|
|
|
private readonly OpusEncoder _encoder;
|
|
|
|
|
private readonly byte[] _buffer;
|
|
|
|
|
private int _partialFramePos;
|
|
|
|
|
private ushort _seq;
|
|
|
|
|
private uint _timestamp;
|
|
|
|
|
|
|
|
|
|
public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss)
|
|
|
|
|
{
|
|
|
|
|
_next = next;
|
|
|
|
|
_encoder = new OpusEncoder(bitrate, application, packetLoss);
|
|
|
|
|
_buffer = new byte[OpusConverter.FrameBytes];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken)
|
|
|
|
|
{
|
|
|
|
|
//Assume thread-safe
|
|
|
|
|
while (count > 0)
|
|
|
|
|
{
|
|
|
|
|
if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes)
|
|
|
|
|
{
|
|
|
|
|
//We have enough data and no partial frames. Pass the buffer directly to the encoder
|
|
|
|
|
int encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0);
|
|
|
|
|
_next.WriteHeader(_seq, _timestamp, false);
|
|
|
|
|
await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
offset += OpusConverter.FrameBytes;
|
|
|
|
|
count -= OpusConverter.FrameBytes;
|
|
|
|
|
_seq++;
|
|
|
|
|
_timestamp += OpusConverter.FrameSamplesPerChannel;
|
|
|
|
|
}
|
|
|
|
|
else if (_partialFramePos + count >= OpusConverter.FrameBytes)
|
|
|
|
|
{
|
|
|
|
|
//We have enough data to complete a previous partial frame.
|
|
|
|
|
int partialSize = OpusConverter.FrameBytes - _partialFramePos;
|
|
|
|
|
Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize);
|
|
|
|
|
int encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0);
|
|
|
|
|
_next.WriteHeader(_seq, _timestamp, false);
|
|
|
|
|
await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
offset += partialSize;
|
|
|
|
|
count -= partialSize;
|
|
|
|
|
_partialFramePos = 0;
|
|
|
|
|
_seq++;
|
|
|
|
|
_timestamp += OpusConverter.FrameSamplesPerChannel;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//Not enough data to build a complete frame, store this part for later
|
|
|
|
|
Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, count);
|
|
|
|
|
_partialFramePos += count;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* //Opus throws memory errors on bad frames
|
|
|
|
|
public override async Task FlushAsync(CancellationToken cancelToken)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
int encFrameSize = _encoder.EncodeFrame(_partialFrameBuffer, 0, _partialFramePos, _buffer, 0);
|
|
|
|
|
base.Write(_buffer, 0, encFrameSize);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception) { } //Incomplete frame
|
|
|
|
|
_partialFramePos = 0;
|
|
|
|
|
await base.FlushAsync(cancelToken).ConfigureAwait(false);
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
public override async Task FlushAsync(CancellationToken cancelToken)
|
|
|
|
|
{
|
|
|
|
|
await _next.FlushAsync(cancelToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
public override async Task ClearAsync(CancellationToken cancelToken)
|
|
|
|
|
{
|
|
|
|
|
await _next.ClearAsync(cancelToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
|
|
|
|
|
if (disposing)
|
|
|
|
|
_encoder.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|