using System; using System.Threading; using System.Threading.Tasks; namespace Discord.Audio { internal class OpusEncodeStream : RTPWriteStream { public const int SampleRate = 48000; private int _frameSize; private byte[] _partialFrameBuffer; private int _partialFramePos; private readonly OpusEncoder _encoder; internal OpusEncodeStream(IAudioTarget target, byte[] secretKey, int channels, int samplesPerFrame, uint ssrc, int? bitrate = null) : base(target, secretKey, samplesPerFrame, ssrc) { _encoder = new OpusEncoder(SampleRate, channels); _frameSize = samplesPerFrame * channels * 2; _partialFrameBuffer = new byte[_frameSize]; _encoder.SetForwardErrorCorrection(true); if (bitrate != null) _encoder.SetBitrate(bitrate.Value); } public override void Write(byte[] buffer, int offset, int count) { WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { //Assume threadsafe 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); await base.WriteAsync(_buffer, 0, encFrameSize, cancellationToken).ConfigureAwait(false); } else { Buffer.BlockCopy(buffer, offset, _partialFrameBuffer, _partialFramePos, count); _partialFramePos += count; break; } } } public override void Flush() { FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); } public override async Task FlushAsync(CancellationToken cancellationToken) { try { int encFrameSize = _encoder.EncodeFrame(_partialFrameBuffer, 0, _partialFramePos, _buffer, 0); base.Write(_buffer, 0, encFrameSize); } catch (Exception) { } //Incomplete frame _partialFramePos = 0; await base.FlushAsync(cancellationToken).ConfigureAwait(false); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) _encoder.Dispose(); } } }