You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

99 lines
3.8 KiB

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