using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Concentus.Structs; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTrademark("")] [assembly: NeutralResourcesLanguage("en")] [assembly: TargetFramework(".NETFramework,Version=v4.5.2", FrameworkDisplayName = ".NET Framework 4.5.2")] [assembly: AssemblyCompany("Logan Stromberg")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © Logan Stromberg, Andrew Ward")] [assembly: AssemblyDescription("This package implements file streams which can be used to extract or encode Opus packets in an Ogg-formatted audio file (usually .opus), giving developers a very simple API to perform the task of reading or writing audio files that can be played universally. The Concentus library is used to encode/decode the opus packets automatically. The codec can optionally be accelerated by also referencing the Concentus.Native package.")] [assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyInformationalVersion("1.0.7.0+27c3125205ddcd891822a398284b246636fafb94")] [assembly: AssemblyProduct("Concentus.Oggfile")] [assembly: AssemblyTitle("Concentus.Oggfile")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/lostromb/concentus.oggfile")] [assembly: AssemblyVersion("1.0.7.0")] namespace Concentus.Oggfile; internal static class BinaryHelpers { internal static void Int16ToByteArrayLittleEndian(short val, byte[] target, int targetOffset) { UInt16ToByteArrayLittleEndian((ushort)val, target, targetOffset); } internal static void UInt16ToByteArrayLittleEndian(ushort val, byte[] target, int targetOffset) { target[targetOffset + 1] = (byte)((uint)(val >> 8) & 0xFFu); target[targetOffset] = (byte)(val & 0xFFu); } internal static void Int16ToByteSpanLittleEndian(short val, ref Span target) { UInt16ToByteArraySpanEndian((ushort)val, ref target); } internal static void UInt16ToByteArraySpanEndian(ushort val, ref Span target) { target[1] = (byte)((uint)(val >> 8) & 0xFFu); target[0] = (byte)(val & 0xFFu); } internal static void Int16ToByteArrayBigEndian(short val, byte[] target, int targetOffset) { UInt16ToByteArrayBigEndian((ushort)val, target, targetOffset); } internal static void UInt16ToByteArrayBigEndian(ushort val, byte[] target, int targetOffset) { target[targetOffset] = (byte)((uint)(val >> 8) & 0xFFu); target[targetOffset + 1] = (byte)(val & 0xFFu); } internal static void Int16ToByteSpanBigEndian(short val, ref Span target) { UInt16ToByteSpanBigEndian((ushort)val, ref target); } internal static void UInt16ToByteSpanBigEndian(ushort val, ref Span target) { target[0] = (byte)((uint)(val >> 8) & 0xFFu); target[1] = (byte)(val & 0xFFu); } internal static void Int32ToByteArrayLittleEndian(int val, byte[] target, int targetOffset) { UInt32ToByteArrayLittleEndian((uint)val, target, targetOffset); } internal static void UInt32ToByteArrayLittleEndian(uint val, byte[] target, int targetOffset) { target[targetOffset + 3] = (byte)((val >> 24) & 0xFFu); target[targetOffset + 2] = (byte)((val >> 16) & 0xFFu); target[targetOffset + 1] = (byte)((val >> 8) & 0xFFu); target[targetOffset] = (byte)(val & 0xFFu); } internal static void Int32ToByteSpanLittleEndian(int val, ref Span target) { UInt32ToByteSpanLittleEndian((uint)val, ref target); } internal static void UInt32ToByteSpanLittleEndian(uint val, ref Span target) { target[3] = (byte)((val >> 24) & 0xFFu); target[2] = (byte)((val >> 16) & 0xFFu); target[1] = (byte)((val >> 8) & 0xFFu); target[0] = (byte)(val & 0xFFu); } internal static void UInt24ToByteArrayBigEndian(uint val, byte[] target, int targetOffset) { target[targetOffset] = (byte)((val >> 16) & 0xFFu); target[targetOffset + 1] = (byte)((val >> 8) & 0xFFu); target[targetOffset + 2] = (byte)(val & 0xFFu); } internal static void UInt24ToByteSpanBigEndian(uint val, ref Span target) { target[0] = (byte)((val >> 16) & 0xFFu); target[1] = (byte)((val >> 8) & 0xFFu); target[2] = (byte)(val & 0xFFu); } internal static void Int32ToByteArrayBigEndian(int val, byte[] target, int targetOffset) { UInt32ToByteArrayBigEndian((uint)val, target, targetOffset); } internal static void UInt32ToByteArrayBigEndian(uint val, byte[] target, int targetOffset) { target[targetOffset] = (byte)((val >> 24) & 0xFFu); target[targetOffset + 1] = (byte)((val >> 16) & 0xFFu); target[targetOffset + 2] = (byte)((val >> 8) & 0xFFu); target[targetOffset + 3] = (byte)(val & 0xFFu); } internal static void Int32ToByteSpanBigEndian(int val, ref Span target) { UInt32ToByteSpanBigEndian((uint)val, ref target); } internal static void UInt32ToByteSpanBigEndian(uint val, ref Span target) { target[0] = (byte)((val >> 24) & 0xFFu); target[1] = (byte)((val >> 16) & 0xFFu); target[2] = (byte)((val >> 8) & 0xFFu); target[3] = (byte)(val & 0xFFu); } internal static void Int64ToByteArrayLittleEndian(long val, byte[] target, int targetOffset) { UInt64ToByteArrayLittleEndian((ulong)val, target, targetOffset); } internal static void UInt64ToByteArrayLittleEndian(ulong val, byte[] target, int targetOffset) { target[targetOffset + 7] = (byte)((val >> 56) & 0xFF); target[targetOffset + 6] = (byte)((val >> 48) & 0xFF); target[targetOffset + 5] = (byte)((val >> 40) & 0xFF); target[targetOffset + 4] = (byte)((val >> 32) & 0xFF); target[targetOffset + 3] = (byte)((val >> 24) & 0xFF); target[targetOffset + 2] = (byte)((val >> 16) & 0xFF); target[targetOffset + 1] = (byte)((val >> 8) & 0xFF); target[targetOffset] = (byte)(val & 0xFF); } internal static void Int64ToByteSpanLittleEndian(long val, ref Span target) { UInt64ToByteSpanLittleEndian((ulong)val, ref target); } internal static void UInt64ToByteSpanLittleEndian(ulong val, ref Span target) { target[7] = (byte)((val >> 56) & 0xFF); target[6] = (byte)((val >> 48) & 0xFF); target[5] = (byte)((val >> 40) & 0xFF); target[4] = (byte)((val >> 32) & 0xFF); target[3] = (byte)((val >> 24) & 0xFF); target[2] = (byte)((val >> 16) & 0xFF); target[1] = (byte)((val >> 8) & 0xFF); target[0] = (byte)(val & 0xFF); } internal static void Int64ToByteArrayBigEndian(long val, byte[] target, int targetOffset) { UInt64ToByteArrayBigEndian((ulong)val, target, targetOffset); } internal static void UInt64ToByteArrayBigEndian(ulong val, byte[] target, int targetOffset) { target[targetOffset] = (byte)((val >> 56) & 0xFF); target[targetOffset + 1] = (byte)((val >> 48) & 0xFF); target[targetOffset + 2] = (byte)((val >> 40) & 0xFF); target[targetOffset + 3] = (byte)((val >> 32) & 0xFF); target[targetOffset + 4] = (byte)((val >> 24) & 0xFF); target[targetOffset + 5] = (byte)((val >> 16) & 0xFF); target[targetOffset + 6] = (byte)((val >> 8) & 0xFF); target[targetOffset + 7] = (byte)(val & 0xFF); } internal static void Int64ToByteSpanBigEndian(long val, ref Span target) { UInt64ToByteSpanBigEndian((ulong)val, ref target); } internal static void UInt64ToByteSpanBigEndian(ulong val, ref Span target) { target[0] = (byte)((val >> 56) & 0xFF); target[1] = (byte)((val >> 48) & 0xFF); target[2] = (byte)((val >> 40) & 0xFF); target[3] = (byte)((val >> 32) & 0xFF); target[4] = (byte)((val >> 24) & 0xFF); target[5] = (byte)((val >> 16) & 0xFF); target[6] = (byte)((val >> 8) & 0xFF); target[7] = (byte)(val & 0xFF); } internal static short ByteArrayToInt16LittleEndian(byte[] source, int offset) { return (short)((short)(0 | (short)(source[offset + 1] << 8)) | (short)source[offset]); } internal static short ByteSpanToInt16LittleEndian(ref Span source) { return (short)((short)(0 | (short)(source[1] << 8)) | (short)source[0]); } internal static short ByteSpanToInt16LittleEndian(ref ReadOnlySpan source) { return (short)((short)(0 | (short)(source[1] << 8)) | (short)source[0]); } internal static ushort ByteArrayToUInt16LittleEndian(byte[] source, int offset) { return (ushort)((ushort)(0u | (ushort)(source[offset + 1] << 8)) | source[offset]); } internal static ushort ByteSpanToUInt16LittleEndian(ref Span source) { return (ushort)((ushort)(0u | (ushort)(source[1] << 8)) | source[0]); } internal static ushort ByteSpanToUInt16LittleEndian(ref ReadOnlySpan source) { return (ushort)((ushort)(0u | (ushort)(source[1] << 8)) | source[0]); } internal static short ByteArrayToInt16BigEndian(byte[] source, int offset) { return (short)((short)(0 | (short)(source[offset] << 8)) | (short)source[offset + 1]); } internal static short ByteSpanToInt16BigEndian(ref Span source) { return (short)((short)(0 | (short)(source[0] << 8)) | (short)source[1]); } internal static short ByteSpanToInt16BigEndian(ref ReadOnlySpan source) { return (short)((short)(0 | (short)(source[0] << 8)) | (short)source[1]); } internal static ushort ByteArrayToUInt16BigEndian(byte[] source, int offset) { return (ushort)((ushort)(0u | (ushort)(source[offset] << 8)) | source[offset + 1]); } internal static ushort ByteSpanToUInt16BigEndian(ref Span source) { return (ushort)((ushort)(0u | (ushort)(source[0] << 8)) | source[1]); } internal static ushort ByteSpanToUInt16BigEndian(ref ReadOnlySpan source) { return (ushort)((ushort)(0u | (ushort)(source[0] << 8)) | source[1]); } internal static int ByteArrayToInt32LittleEndian(byte[] source, int offset) { return 0 | (source[offset + 3] << 24) | (source[offset + 2] << 16) | (source[offset + 1] << 8) | source[offset]; } internal static int ByteSpanToInt32LittleEndian(ref Span source) { return 0 | (source[3] << 24) | (source[2] << 16) | (source[1] << 8) | source[0]; } internal static int ByteSpanToInt32LittleEndian(ref ReadOnlySpan source) { return 0 | (source[3] << 24) | (source[2] << 16) | (source[1] << 8) | source[0]; } internal static uint ByteArrayToUInt32LittleEndian(byte[] source, int offset) { return 0u | (uint)(source[offset + 3] << 24) | (uint)(source[offset + 2] << 16) | (uint)(source[offset + 1] << 8) | source[offset]; } internal static uint ByteSpanToUInt32LittleEndian(ref Span source) { return 0u | (uint)(source[3] << 24) | (uint)(source[2] << 16) | (uint)(source[1] << 8) | source[0]; } internal static uint ByteSpanToUInt32LittleEndian(ref ReadOnlySpan source) { return 0u | (uint)(source[3] << 24) | (uint)(source[2] << 16) | (uint)(source[1] << 8) | source[0]; } internal static uint ByteArrayToUInt24BigEndian(byte[] source, int offset) { return 0u | (uint)(source[offset] << 16) | (uint)(source[offset + 1] << 8) | source[offset + 2]; } internal static uint ByteSpanToUInt24BigEndian(ref Span source) { return 0u | (uint)(source[0] << 16) | (uint)(source[1] << 8) | source[2]; } internal static uint ByteSpanToUInt24BigEndian(ref ReadOnlySpan source) { return 0u | (uint)(source[0] << 16) | (uint)(source[1] << 8) | source[2]; } internal static int ByteArrayToInt32BigEndian(byte[] source, int offset) { return 0 | (source[offset] << 24) | (source[offset + 1] << 16) | (source[offset + 2] << 8) | source[offset + 3]; } internal static int ByteSpanToInt32BigEndian(ref Span source) { return 0 | (source[0] << 24) | (source[1] << 16) | (source[2] << 8) | source[3]; } internal static int ByteSpanToInt32BigEndian(ref ReadOnlySpan source) { return 0 | (source[0] << 24) | (source[1] << 16) | (source[2] << 8) | source[3]; } internal static uint ByteArrayToUInt32BigEndian(byte[] source, int offset) { return 0u | (uint)(source[offset] << 24) | (uint)(source[offset + 1] << 16) | (uint)(source[offset + 2] << 8) | source[offset + 3]; } internal static uint ByteSpanToUInt32BigEndian(ref Span source) { return 0u | (uint)(source[0] << 24) | (uint)(source[1] << 16) | (uint)(source[2] << 8) | source[3]; } internal static uint ByteSpanToUInt32BigEndian(ref ReadOnlySpan source) { return 0u | (uint)(source[0] << 24) | (uint)(source[1] << 16) | (uint)(source[2] << 8) | source[3]; } internal static long ByteArrayToInt64LittleEndian(byte[] source, int offset) { return (long)(0 | ((ulong)source[offset + 7] << 56) | ((ulong)source[offset + 6] << 48) | ((ulong)source[offset + 5] << 40) | ((ulong)source[offset + 4] << 32) | ((ulong)source[offset + 3] << 24) | ((ulong)source[offset + 2] << 16) | ((ulong)source[offset + 1] << 8) | source[offset]); } internal static long ByteSpanToInt64LittleEndian(ref Span source) { return (long)(0 | ((ulong)source[7] << 56) | ((ulong)source[6] << 48) | ((ulong)source[5] << 40) | ((ulong)source[4] << 32) | ((ulong)source[3] << 24) | ((ulong)source[2] << 16) | ((ulong)source[1] << 8) | source[0]); } internal static long ByteSpanToInt64LittleEndian(ref ReadOnlySpan source) { return (long)(0 | ((ulong)source[7] << 56) | ((ulong)source[6] << 48) | ((ulong)source[5] << 40) | ((ulong)source[4] << 32) | ((ulong)source[3] << 24) | ((ulong)source[2] << 16) | ((ulong)source[1] << 8) | source[0]); } internal static ulong ByteArrayToUInt64LittleEndian(byte[] source, int offset) { return 0 | ((ulong)source[offset + 7] << 56) | ((ulong)source[offset + 6] << 48) | ((ulong)source[offset + 5] << 40) | ((ulong)source[offset + 4] << 32) | ((ulong)source[offset + 3] << 24) | ((ulong)source[offset + 2] << 16) | ((ulong)source[offset + 1] << 8) | source[offset]; } internal static ulong ByteSpanToUInt64LittleEndian(ref Span source) { return 0 | ((ulong)source[7] << 56) | ((ulong)source[6] << 48) | ((ulong)source[5] << 40) | ((ulong)source[4] << 32) | ((ulong)source[3] << 24) | ((ulong)source[2] << 16) | ((ulong)source[1] << 8) | source[0]; } internal static ulong ByteSpanToUInt64LittleEndian(ref ReadOnlySpan source) { return 0 | ((ulong)source[7] << 56) | ((ulong)source[6] << 48) | ((ulong)source[5] << 40) | ((ulong)source[4] << 32) | ((ulong)source[3] << 24) | ((ulong)source[2] << 16) | ((ulong)source[1] << 8) | source[0]; } internal static long ByteArrayToInt64BigEndian(byte[] source, int offset) { return (long)(0 | ((ulong)source[offset] << 56) | ((ulong)source[offset + 1] << 48) | ((ulong)source[offset + 2] << 40) | ((ulong)source[offset + 3] << 32) | ((ulong)source[offset + 4] << 24) | ((ulong)source[offset + 5] << 16) | ((ulong)source[offset + 6] << 8) | source[offset + 7]); } internal static long ByteSpanToInt64BigEndian(ref Span source) { return (long)(0 | ((ulong)source[0] << 56) | ((ulong)source[1] << 48) | ((ulong)source[2] << 40) | ((ulong)source[3] << 32) | ((ulong)source[4] << 24) | ((ulong)source[5] << 16) | ((ulong)source[6] << 8) | source[7]); } internal static long ByteSpanToInt64BigEndian(ref ReadOnlySpan source) { return (long)(0 | ((ulong)source[0] << 56) | ((ulong)source[1] << 48) | ((ulong)source[2] << 40) | ((ulong)source[3] << 32) | ((ulong)source[4] << 24) | ((ulong)source[5] << 16) | ((ulong)source[6] << 8) | source[7]); } internal static ulong ByteArrayToUInt64BigEndian(byte[] source, int offset) { return 0 | ((ulong)source[offset] << 56) | ((ulong)source[offset + 1] << 48) | ((ulong)source[offset + 2] << 40) | ((ulong)source[offset + 3] << 32) | ((ulong)source[offset + 4] << 24) | ((ulong)source[offset + 5] << 16) | ((ulong)source[offset + 6] << 8) | source[offset + 7]; } internal static ulong ByteSpanToUInt64BigEndian(ref Span source) { return 0 | ((ulong)source[0] << 56) | ((ulong)source[1] << 48) | ((ulong)source[2] << 40) | ((ulong)source[3] << 32) | ((ulong)source[4] << 24) | ((ulong)source[5] << 16) | ((ulong)source[6] << 8) | source[7]; } internal static ulong ByteSpanToUInt64BigEndian(ref ReadOnlySpan source) { return 0 | ((ulong)source[0] << 56) | ((ulong)source[1] << 48) | ((ulong)source[2] << 40) | ((ulong)source[3] << 32) | ((ulong)source[4] << 24) | ((ulong)source[5] << 16) | ((ulong)source[6] << 8) | source[7]; } } internal class BufferedReadStream : Stream { private const int DEFAULT_INITIAL_SIZE = 32768; private const int DEFAULT_MAX_SIZE = 262144; private Stream _baseStream; private StreamReadBuffer _buffer; private long _readPosition; public bool CloseBaseStream { get; set; } public bool MinimalRead { get { return _buffer.MinimalRead; } set { _buffer.MinimalRead = value; } } public int MaxBufferSize { get { return _buffer.MaxSize; } set { CheckLock(); _buffer.MaxSize = value; } } public long BufferBaseOffset => _buffer.BaseOffset; public int BufferBytesFilled => _buffer.BytesFilled; public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => false; public override long Length => _baseStream.Length; public override long Position { get { return _readPosition; } set { Seek(value, SeekOrigin.Begin); } } public BufferedReadStream(Stream baseStream) : this(baseStream, 32768, 262144, minimalRead: false) { } public BufferedReadStream(Stream baseStream, bool minimalRead) : this(baseStream, 32768, 262144, minimalRead) { } public BufferedReadStream(Stream baseStream, int initialSize, int maxSize) : this(baseStream, initialSize, maxSize, minimalRead: false) { } public BufferedReadStream(Stream baseStream, int initialSize, int maxBufferSize, bool minimalRead) { if (baseStream == null) { throw new ArgumentNullException("baseStream"); } if (!baseStream.CanRead) { throw new ArgumentException("baseStream"); } if (maxBufferSize < 1) { maxBufferSize = 1; } if (initialSize < 1) { initialSize = 1; } if (initialSize > maxBufferSize) { initialSize = maxBufferSize; } _baseStream = baseStream; _buffer = new StreamReadBuffer(baseStream, initialSize, maxBufferSize, minimalRead); _buffer.MaxSize = maxBufferSize; _buffer.MinimalRead = minimalRead; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { if (_buffer != null) { _buffer.Dispose(); _buffer = null; } _ = CloseBaseStream; } } public void TakeLock() { } private void CheckLock() { } public void ReleaseLock() { } public void Discard(int bytes) { CheckLock(); _buffer.DiscardThrough(_buffer.BaseOffset + bytes); } public void DiscardThrough(long offset) { CheckLock(); _buffer.DiscardThrough(offset); } public override void Flush() { } public override int ReadByte() { CheckLock(); int num = _buffer.ReadByte(Position); if (num > -1) { Seek(1L, SeekOrigin.Current); } return num; } public override int Read(byte[] buffer, int offset, int count) { CheckLock(); int num = _buffer.Read(Position, buffer, offset, count); Seek(num, SeekOrigin.Current); return num; } public override long Seek(long offset, SeekOrigin origin) { CheckLock(); switch (origin) { case SeekOrigin.Current: offset += Position; break; case SeekOrigin.End: offset += _baseStream.Length; break; } if (!_baseStream.CanSeek) { if (offset < _buffer.BaseOffset) { throw new InvalidOperationException("Cannot seek to before the start of the buffer!"); } if (offset > _buffer.BufferEndOffset) { throw new InvalidOperationException("Cannot seek to beyond the end of the buffer! Discard some bytes."); } } return _readPosition = offset; } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } internal abstract class DataPacket { [Flags] protected enum PacketFlags : byte { IsResync = 1, IsEndOfStream = 2, IsShort = 4, HasGranuleCount = 8, User1 = 0x10, User2 = 0x20, User3 = 0x40, User4 = 0x80 } private ulong _bitBucket; private int _bitCount; private int _readBits; private byte _overflowBits; private PacketFlags _packetFlags; private long _granulePosition; private long _pageGranulePosition; private int _length; private int _granuleCount; private int _pageSequenceNumber; public bool IsResync { get { return GetFlag(PacketFlags.IsResync); } internal set { SetFlag(PacketFlags.IsResync, value); } } public long GranulePosition { get { return _granulePosition; } set { _granulePosition = value; } } public long PageGranulePosition { get { return _pageGranulePosition; } internal set { _pageGranulePosition = value; } } public int Length { get { return _length; } protected set { _length = value; } } public bool IsEndOfStream { get { return GetFlag(PacketFlags.IsEndOfStream); } internal set { SetFlag(PacketFlags.IsEndOfStream, value); } } public long BitsRead => _readBits; public int? GranuleCount { get { if (GetFlag(PacketFlags.HasGranuleCount)) { return _granuleCount; } return null; } set { if (value.HasValue) { _granuleCount = value.Value; SetFlag(PacketFlags.HasGranuleCount, value: true); } else { SetFlag(PacketFlags.HasGranuleCount, value: false); } } } internal int PageSequenceNumber { get { return _pageSequenceNumber; } set { _pageSequenceNumber = value; } } internal bool IsShort { get { return GetFlag(PacketFlags.IsShort); } private set { SetFlag(PacketFlags.IsShort, value); } } protected bool GetFlag(PacketFlags flag) { return (_packetFlags & flag) == flag; } protected void SetFlag(PacketFlags flag, bool value) { if (value) { _packetFlags |= flag; } else { _packetFlags &= (PacketFlags)(byte)(~(int)flag); } } protected DataPacket(int length) { Length = length; } protected abstract int ReadNextByte(); public virtual void Done() { } public ulong TryPeekBits(int count, out int bitsRead) { ulong num = 0uL; switch (count) { default: throw new ArgumentOutOfRangeException("count"); case 0: bitsRead = 0; return 0uL; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: break; } while (_bitCount < count) { int num2 = ReadNextByte(); if (num2 == -1) { bitsRead = _bitCount; num = _bitBucket; _bitBucket = 0uL; _bitCount = 0; IsShort = true; return num; } _bitBucket = (ulong)((long)(num2 & 0xFF) << _bitCount) | _bitBucket; _bitCount += 8; if (_bitCount > 64) { _overflowBits = (byte)(num2 >> 72 - _bitCount); } } num = _bitBucket; if (count < 64) { num &= (ulong)((1L << count) - 1); } bitsRead = count; return num; } public void SkipBits(int count) { if (count == 0) { return; } if (_bitCount > count) { if (count > 63) { _bitBucket = 0uL; } else { _bitBucket >>= count; } if (_bitCount > 64) { int num = _bitCount - 64; _bitBucket |= (ulong)_overflowBits << _bitCount - count - num; if (num > count) { _overflowBits = (byte)(_overflowBits >> count); } } _bitCount -= count; _readBits += count; return; } if (_bitCount == count) { _bitBucket = 0uL; _bitCount = 0; _readBits += count; return; } count -= _bitCount; _readBits += _bitCount; _bitCount = 0; _bitBucket = 0uL; while (count > 8) { if (ReadNextByte() == -1) { count = 0; IsShort = true; break; } count -= 8; _readBits += 8; } if (count > 0) { int num2 = ReadNextByte(); if (num2 == -1) { IsShort = true; return; } _bitBucket = (ulong)(num2 >> count); _bitCount = 8 - count; _readBits += count; } } protected void ResetBitReader() { _bitBucket = 0uL; _bitCount = 0; _readBits = 0; IsShort = false; } public ulong ReadBits(int count) { if (count == 0) { return 0uL; } int bitsRead; ulong result = TryPeekBits(count, out bitsRead); SkipBits(count); return result; } public byte PeekByte() { int bitsRead; return (byte)TryPeekBits(8, out bitsRead); } public byte ReadByte() { return (byte)ReadBits(8); } public byte[] ReadBytes(int count) { List list = new List(count); while (list.Count < count) { list.Add(ReadByte()); } return list.ToArray(); } public int Read(byte[] buffer, int index, int count) { if (index < 0 || index + count > buffer.Length) { throw new ArgumentOutOfRangeException("index"); } for (int i = 0; i < count; i++) { int bitsRead; byte b = (byte)TryPeekBits(8, out bitsRead); if (bitsRead == 0) { return i; } buffer[index++] = b; SkipBits(8); } return count; } public bool ReadBit() { return ReadBits(1) == 1; } public short ReadInt16() { return (short)ReadBits(16); } public int ReadInt32() { return (int)ReadBits(32); } public long ReadInt64() { return (long)ReadBits(64); } public ushort ReadUInt16() { return (ushort)ReadBits(16); } public uint ReadUInt32() { return (uint)ReadBits(32); } public ulong ReadUInt64() { return ReadBits(64); } public void SkipBytes(int count) { SkipBits(count * 8); } } internal interface IContainerReader : IDisposable { int[] StreamSerials { get; } bool CanSeek { get; } long WasteBits { get; } int PagesRead { get; } event EventHandler NewStream; bool Init(); bool FindNextStream(); int GetTotalPageCount(); } internal interface IPacketProvider : IDisposable { int StreamSerial { get; } bool CanSeek { get; } long ContainerBits { get; } event EventHandler ParameterChange; int GetTotalPageCount(); DataPacket GetNextPacket(); DataPacket PeekNextPacket(); DataPacket GetPacket(int packetIndex); long GetGranuleCount(); DataPacket FindPacket(long granulePos, Func packetGranuleCountCallback); void SeekToPacket(DataPacket packet, int preRoll); } internal class NewStreamEventArgs : EventArgs { public IPacketProvider PacketProvider { get; private set; } public bool IgnoreStream { get; set; } public NewStreamEventArgs(IPacketProvider packetProvider) { if (packetProvider == null) { throw new ArgumentNullException("packetProvider"); } PacketProvider = packetProvider; } } internal class OggContainerReader : IContainerReader, IDisposable { private class PageHeader { public int StreamSerial { get; set; } public PageFlags Flags { get; set; } public long GranulePosition { get; set; } public int SequenceNumber { get; set; } public long DataOffset { get; set; } public int[] PacketSizes { get; set; } public bool LastPacketContinues { get; set; } public bool IsResync { get; set; } } private Crc _crc = new Crc(); private BufferedReadStream _stream; private Dictionary _packetReaders; private List _disposedStreamSerials; private long _nextPageOffset; private int _pageCount; private byte[] _readBuffer = new byte[65025]; private long _containerBits; private long _wasteBits; public int[] StreamSerials => _packetReaders.Keys.ToArray(); public int PagesRead => _pageCount; public bool CanSeek => _stream.CanSeek; public long WasteBits => _wasteBits; public event EventHandler NewStream; public OggContainerReader(Stream stream, bool closeOnDispose) { _packetReaders = new Dictionary(); _disposedStreamSerials = new List(); _stream = (stream as BufferedReadStream) ?? new BufferedReadStream(stream) { CloseBaseStream = closeOnDispose }; } public bool Init() { _stream.TakeLock(); try { return GatherNextPage() != -1; } finally { _stream.ReleaseLock(); } } public void Dispose() { int[] streamSerials = StreamSerials; foreach (int key in streamSerials) { _packetReaders[key].Dispose(); } _nextPageOffset = 0L; _containerBits = 0L; _wasteBits = 0L; _stream.Dispose(); } public IPacketProvider GetStream(int streamSerial) { if (!_packetReaders.TryGetValue(streamSerial, out var value)) { throw new ArgumentOutOfRangeException("streamSerial"); } return value; } public bool FindNextStream() { if (!CanSeek) { throw new InvalidOperationException(); } int count = _packetReaders.Count; while (_packetReaders.Count == count) { _stream.TakeLock(); try { if (GatherNextPage() == -1) { break; } } finally { _stream.ReleaseLock(); } } return count > _packetReaders.Count; } public int GetTotalPageCount() { if (!CanSeek) { throw new InvalidOperationException(); } while (true) { _stream.TakeLock(); try { if (GatherNextPage() == -1) { break; } } finally { _stream.ReleaseLock(); } } return _pageCount; } private PageHeader ReadPageHeader(long position) { _stream.Seek(position, SeekOrigin.Begin); if (_stream.Read(_readBuffer, 0, 27) != 27) { return null; } if (_readBuffer[0] != 79 || _readBuffer[1] != 103 || _readBuffer[2] != 103 || _readBuffer[3] != 83) { return null; } if (_readBuffer[4] != 0) { return null; } PageHeader pageHeader = new PageHeader(); pageHeader.Flags = (PageFlags)_readBuffer[5]; pageHeader.GranulePosition = BitConverter.ToInt64(_readBuffer, 6); pageHeader.StreamSerial = BitConverter.ToInt32(_readBuffer, 14); pageHeader.SequenceNumber = BitConverter.ToInt32(_readBuffer, 18); uint checkCrc = BitConverter.ToUInt32(_readBuffer, 22); _crc.Reset(); for (int i = 0; i < 22; i++) { _crc.Update(_readBuffer[i]); } _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(0); _crc.Update(_readBuffer[26]); int num = _readBuffer[26]; if (_stream.Read(_readBuffer, 0, num) != num) { return null; } List list = new List(num); int num2 = 0; int num3 = 0; for (int j = 0; j < num; j++) { byte b = _readBuffer[j]; _crc.Update(b); if (num3 == list.Count) { list.Add(0); } list[num3] += b; if (b < byte.MaxValue) { num3++; pageHeader.LastPacketContinues = false; } else { pageHeader.LastPacketContinues = true; } num2 += b; } pageHeader.PacketSizes = list.ToArray(); pageHeader.DataOffset = position + 27 + num; if (_stream.Read(_readBuffer, 0, num2) != num2) { return null; } for (int k = 0; k < num2; k++) { _crc.Update(_readBuffer[k]); } if (_crc.Test(checkCrc)) { _containerBits += 8 * (27 + num); _pageCount++; return pageHeader; } return null; } private PageHeader FindNextPageHeader() { long num = _nextPageOffset; bool isResync = false; PageHeader pageHeader; while ((pageHeader = ReadPageHeader(num)) == null) { isResync = true; _wasteBits += 8L; num = (_stream.Position = num + 1); int num3 = 0; do { switch (_stream.ReadByte()) { case 79: if (_stream.ReadByte() == 103 && _stream.ReadByte() == 103 && _stream.ReadByte() == 83) { num += num3; goto end_IL_0032; } _stream.Seek(-3L, SeekOrigin.Current); break; case -1: return null; } _wasteBits += 8L; continue; end_IL_0032: break; } while (++num3 < 65536); if (num3 == 65536) { return null; } } pageHeader.IsResync = isResync; _nextPageOffset = pageHeader.DataOffset; for (int i = 0; i < pageHeader.PacketSizes.Length; i++) { _nextPageOffset += pageHeader.PacketSizes[i]; } return pageHeader; } private bool AddPage(PageHeader hdr) { if (!_packetReaders.TryGetValue(hdr.StreamSerial, out var value)) { value = new PacketReader(this, hdr.StreamSerial); } value.ContainerBits += _containerBits; _containerBits = 0L; bool isContinued = hdr.PacketSizes.Length == 1 && hdr.LastPacketContinues; bool isContinuation = (hdr.Flags & PageFlags.ContinuesPacket) == PageFlags.ContinuesPacket; bool isEndOfStream = false; bool isResync = hdr.IsResync; long num = hdr.DataOffset; int num2 = hdr.PacketSizes.Length; int[] packetSizes = hdr.PacketSizes; foreach (int num3 in packetSizes) { Packet packet = new Packet(this, num, num3) { PageGranulePosition = hdr.GranulePosition, IsEndOfStream = isEndOfStream, PageSequenceNumber = hdr.SequenceNumber, IsContinued = isContinued, IsContinuation = isContinuation, IsResync = isResync }; value.AddPacket(packet); num += num3; isContinuation = false; isResync = false; if (--num2 == 1) { isContinued = hdr.LastPacketContinues; isEndOfStream = (hdr.Flags & PageFlags.EndOfStream) == PageFlags.EndOfStream; } } if (!_packetReaders.ContainsKey(hdr.StreamSerial)) { _packetReaders.Add(hdr.StreamSerial, value); return true; } return false; } private int GatherNextPage() { PageHeader pageHeader; while (true) { pageHeader = FindNextPageHeader(); if (pageHeader == null) { return -1; } if (!_disposedStreamSerials.Contains(pageHeader.StreamSerial)) { if (!AddPage(pageHeader)) { break; } EventHandler newStream = this.NewStream; if (newStream == null) { break; } NewStreamEventArgs newStreamEventArgs = new NewStreamEventArgs(_packetReaders[pageHeader.StreamSerial]); newStream(this, newStreamEventArgs); if (!newStreamEventArgs.IgnoreStream) { break; } _packetReaders[pageHeader.StreamSerial].Dispose(); } } return pageHeader.StreamSerial; } internal void DisposePacketReader(PacketReader packetReader) { _disposedStreamSerials.Add(packetReader.StreamSerial); _packetReaders.Remove(packetReader.StreamSerial); } internal int PacketReadByte(long offset) { _stream.TakeLock(); try { _stream.Position = offset; return _stream.ReadByte(); } finally { _stream.ReleaseLock(); } } internal void PacketDiscardThrough(long offset) { _stream.TakeLock(); try { _stream.DiscardThrough(offset); } finally { _stream.ReleaseLock(); } } internal void GatherNextPage(int streamSerial) { if (!_packetReaders.ContainsKey(streamSerial)) { throw new ArgumentOutOfRangeException("streamSerial"); } int num; do { _stream.TakeLock(); try { if (_packetReaders[streamSerial].HasEndOfStream) { break; } num = GatherNextPage(); if (num != -1) { continue; } foreach (KeyValuePair packetReader in _packetReaders) { if (!packetReader.Value.HasEndOfStream) { packetReader.Value.SetEndOfStream(); } } break; } finally { _stream.ReleaseLock(); } } while (num != streamSerial); } } internal class Crc { private const uint CRC32_POLY = 79764919u; private static uint[] crcTable; private uint _crc; public uint Value => _crc; static Crc() { crcTable = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num << 24; for (int i = 0; i < 8; i++) { num2 = (num2 << 1) ^ ((num2 >= 2147483648u) ? 79764919u : 0u); } crcTable[num] = num2; } } public Crc() { Reset(); } public void Reset() { _crc = 0u; } public void Update(int nextVal) { _crc = (_crc << 8) ^ crcTable[nextVal ^ (_crc >> 24)]; } public bool Test(uint checkCrc) { return _crc == checkCrc; } } internal class Packet : DataPacket { private long _offset; private int _length; private int _curOfs; private Packet _mergedPacket; private Packet _next; private Packet _prev; private OggContainerReader _containerReader; internal Packet Next { get { return _next; } set { _next = value; } } internal Packet Prev { get { return _prev; } set { _prev = value; } } internal bool IsContinued { get { return GetFlag(PacketFlags.User1); } set { SetFlag(PacketFlags.User1, value); } } internal bool IsContinuation { get { return GetFlag(PacketFlags.User2); } set { SetFlag(PacketFlags.User2, value); } } internal Packet(OggContainerReader containerReader, long streamOffset, int length) : base(length) { _containerReader = containerReader; _offset = streamOffset; _length = length; _curOfs = 0; } internal void MergeWith(DataPacket continuation) { if (!(continuation is Packet mergedPacket)) { throw new ArgumentException("Incorrect packet type!"); } base.Length += continuation.Length; if (_mergedPacket == null) { _mergedPacket = mergedPacket; } else { _mergedPacket.MergeWith(continuation); } base.PageGranulePosition = continuation.PageGranulePosition; base.PageSequenceNumber = continuation.PageSequenceNumber; } internal void Reset() { _curOfs = 0; ResetBitReader(); if (_mergedPacket != null) { _mergedPacket.Reset(); } } protected override int ReadNextByte() { if (_curOfs == _length) { if (_mergedPacket == null) { return -1; } return _mergedPacket.ReadNextByte(); } int num = _containerReader.PacketReadByte(_offset + _curOfs); if (num != -1) { _curOfs++; } return num; } public override void Done() { if (_mergedPacket != null) { _mergedPacket.Done(); } else { _containerReader.PacketDiscardThrough(_offset + _length); } } } [DebuggerTypeProxy(typeof(DebugView))] internal class PacketReader : IPacketProvider, IDisposable { internal class DebugView { private PacketReader _reader; private Packet _last; private Packet _first; private Packet[] _packetList = new Packet[0]; public OggContainerReader Container => _reader._container; public int StreamSerial => _reader._streamSerial; public bool EndOfStreamFound => _reader._eosFound; public int CurrentPacketIndex { get { if (_reader._current == null) { return -1; } return Array.IndexOf(Packets, _reader._current); } } public Packet[] Packets { get { if (_reader._last == _last && _reader._first == _first) { return _packetList; } _last = _reader._last; _first = _reader._first; List list = new List(); for (Packet packet = _first; packet != null; packet = packet.Next) { list.Add(packet); } _packetList = list.ToArray(); return _packetList; } } public DebugView(PacketReader reader) { if (reader == null) { throw new ArgumentNullException("reader"); } _reader = reader; } } private OggContainerReader _container; private int _streamSerial; private bool _eosFound; private Packet _first; private Packet _current; private Packet _last; private object _packetLock = new object(); internal bool HasEndOfStream => _eosFound; public int StreamSerial => _streamSerial; public long ContainerBits { get; set; } public bool CanSeek => _container.CanSeek; public event EventHandler ParameterChange; internal PacketReader(OggContainerReader container, int streamSerial) { _container = container; _streamSerial = streamSerial; } public void Dispose() { _eosFound = true; _container.DisposePacketReader(this); _container = null; _current = null; if (_first != null) { Packet packet = _first; _first = null; while (packet.Next != null) { Packet next = packet.Next; packet.Next = null; packet = next; packet.Prev = null; } packet = null; } _last = null; } internal void AddPacket(Packet packet) { lock (_packetLock) { if (_eosFound) { return; } if (packet.IsResync) { packet.IsContinuation = false; if (_last != null) { _last.IsContinued = false; } } if (packet.IsContinuation) { if (_last == null) { throw new InvalidDataException(); } if (!_last.IsContinued) { throw new InvalidDataException(); } _last.MergeWith(packet); _last.IsContinued = packet.IsContinued; } else { if (packet == null) { throw new ArgumentException("Wrong packet datatype", "packet"); } if (_first == null) { _first = packet; _last = packet; } else { Packet packet2 = (packet.Prev = _last); Packet last2 = (packet2.Next = packet); _last = last2; } } if (packet.IsEndOfStream) { SetEndOfStream(); } } } internal void SetEndOfStream() { lock (_packetLock) { _eosFound = true; if (_last.IsContinued) { _last = _last.Prev; _last.Next.Prev = null; _last.Next = null; } } } public DataPacket GetNextPacket() { return _current = PeekNextPacketInternal(); } public DataPacket PeekNextPacket() { return PeekNextPacketInternal(); } private Packet PeekNextPacketInternal() { Packet packet; if (_current == null) { packet = _first; } else { while (true) { lock (_packetLock) { packet = _current.Next; if ((packet != null && !packet.IsContinued) || _eosFound) { break; } goto IL_004f; } IL_004f: _container.GatherNextPage(_streamSerial); } } if (packet != null) { if (packet.IsContinued) { throw new InvalidDataException("Packet is incomplete!"); } packet.Reset(); } return packet; } internal void ReadAllPages() { if (!CanSeek) { throw new InvalidOperationException(); } while (!_eosFound) { _container.GatherNextPage(_streamSerial); } } internal DataPacket GetLastPacket() { ReadAllPages(); return _last; } public int GetTotalPageCount() { ReadAllPages(); int num = 0; int num2 = 0; for (Packet packet = _first; packet != null; packet = packet.Next) { if (packet.PageSequenceNumber != num2) { num++; num2 = packet.PageSequenceNumber; } } return num; } public DataPacket GetPacket(int packetIndex) { if (!CanSeek) { throw new InvalidOperationException(); } if (packetIndex < 0) { throw new ArgumentOutOfRangeException("index"); } if (_first == null) { throw new InvalidOperationException("Packet reader has no packets!"); } Packet packet = _first; while (--packetIndex >= 0) { while (packet.Next == null) { if (_eosFound) { throw new ArgumentOutOfRangeException("index"); } _container.GatherNextPage(_streamSerial); } packet = packet.Next; } packet.Reset(); return packet; } private Packet GetLastPacketInPage(Packet packet) { if (packet != null) { int pageSequenceNumber = packet.PageSequenceNumber; while (packet.Next != null && packet.Next.PageSequenceNumber == pageSequenceNumber) { packet = packet.Next; } if (packet != null && packet.IsContinued) { packet = packet.Prev; } } return packet; } private Packet FindPacketInPage(Packet pagePacket, long targetGranulePos, Func packetGranuleCountCallback) { Packet lastPacketInPage = GetLastPacketInPage(pagePacket); if (lastPacketInPage == null) { return null; } Packet packet = lastPacketInPage; do { if (!packet.GranuleCount.HasValue) { if (packet == lastPacketInPage) { packet.GranulePosition = packet.PageGranulePosition; } else { packet.GranulePosition = packet.Next.GranulePosition - packet.Next.GranuleCount.Value; } if (packet == _last && _eosFound && packet.Prev.PageSequenceNumber < packet.PageSequenceNumber) { packet.GranuleCount = (int)(packet.GranulePosition - packet.Prev.PageGranulePosition); } else if (packet.Prev != null) { packet.Prev.Reset(); packet.Reset(); packet.GranuleCount = packetGranuleCountCallback(packet, packet.Prev); } else { if (packet.GranulePosition > packet.Next.GranulePosition - packet.Next.GranuleCount) { throw new InvalidOperationException("First data packet size mismatch"); } packet.GranuleCount = (int)packet.GranulePosition; } } if (targetGranulePos <= packet.GranulePosition && targetGranulePos > packet.GranulePosition - packet.GranuleCount) { if (packet.Prev != null && !packet.Prev.GranuleCount.HasValue) { packet.Prev.GranulePosition = packet.GranulePosition - packet.GranuleCount.Value; } return packet; } packet = packet.Prev; } while (packet != null && packet.PageSequenceNumber == lastPacketInPage.PageSequenceNumber); if (packet != null && packet.PageGranulePosition < targetGranulePos) { packet.GranulePosition = packet.PageGranulePosition; return packet.Next; } return null; } public DataPacket FindPacket(long granulePos, Func packetGranuleCountCallback) { if (granulePos < 0) { throw new ArgumentOutOfRangeException("granulePos"); } Packet packet = null; Packet packet2 = _current ?? _first; if (granulePos > packet2.PageGranulePosition) { while (granulePos > packet2.PageGranulePosition) { if ((packet2.Next == null || packet2.IsContinued) && !_eosFound) { _container.GatherNextPage(_streamSerial); if (_eosFound) { packet2 = null; break; } } packet2 = packet2.Next; } return FindPacketInPage(packet2, granulePos, packetGranuleCountCallback); } while (packet2.Prev != null && (granulePos <= packet2.Prev.PageGranulePosition || packet2.Prev.PageGranulePosition == -1)) { packet2 = packet2.Prev; } return FindPacketInPage(packet2, granulePos, packetGranuleCountCallback); } public void SeekToPacket(DataPacket packet, int preRoll) { if (preRoll < 0) { throw new ArgumentOutOfRangeException("preRoll"); } if (packet == null) { throw new ArgumentNullException("granulePos"); } Packet packet2 = packet as Packet; if (packet2 == null) { throw new ArgumentException("Incorrect packet type!", "packet"); } while (--preRoll >= 0) { packet2 = packet2.Prev; if (packet2 == null) { throw new ArgumentOutOfRangeException("preRoll"); } } _current = packet2.Prev; } public long GetGranuleCount() { return GetLastPacket().PageGranulePosition; } } [Flags] internal enum PageFlags { None = 0, ContinuesPacket = 1, BeginningOfStream = 2, EndOfStream = 4 } internal class OpusHeader { private byte version; private byte channel_count; private ushort pre_skip; private uint input_sample_rate; private short output_gain; private byte mapping_family; private byte stream_count; private byte coupled_count; } public class OpusOggReadStream { private const double GranuleSampleRate = 48000.0; private readonly Stream _stream; private readonly IOpusDecoder _decoder; private byte[] _nextDataPacket; private IPacketProvider _packetProvider; private bool _endOfStream; private OggContainerReader _containerReader; public bool CanSeek => _stream.CanSeek; public OpusTags Tags { get; private set; } public bool HasNextPacket => !_endOfStream; public string LastError { get; private set; } public long PageGranulePosition { get; private set; } public TimeSpan CurrentTime => TimeSpan.FromSeconds((double)PageGranulePosition / 48000.0); public long GranuleCount { get; private set; } public TimeSpan TotalTime => TimeSpan.FromSeconds((double)GranuleCount / 48000.0); public long PagePosition { get; private set; } public long PageCount { get; private set; } public OpusOggReadStream(IOpusDecoder decoder, Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } _stream = stream; _decoder = decoder; _endOfStream = !Initialize(); } public short[] DecodeNextPacket() { //IL_0091: Expected O, but got Unknown if (_decoder == null) { throw new InvalidOperationException("Cannot decode opus packets as a decoder was never provided"); } if (_nextDataPacket == null || _nextDataPacket.Length == 0) { _endOfStream = true; return null; } try { int numSamples = OpusPacketInfo.GetNumSamples((ReadOnlySpan)_nextDataPacket.AsSpan(), _decoder.SampleRate); short[] array = new short[numSamples * _decoder.NumChannels]; _decoder.Decode((ReadOnlySpan)_nextDataPacket.AsSpan(), array.AsSpan(), numSamples, false); QueueNextPacket(); return array; } catch (OpusException val) { OpusException val2 = val; LastError = "Opus decoder threw exception: " + ((Exception)(object)val2).Message; return null; } } public byte[] ReadNextRawPacket() { if (_nextDataPacket == null || _nextDataPacket.Length == 0) { _endOfStream = true; return null; } byte[] nextDataPacket = _nextDataPacket; QueueNextPacket(); return nextDataPacket; } private bool Initialize() { try { OggContainerReader oggContainerReader = new OggContainerReader(_stream, closeOnDispose: true); if (!oggContainerReader.Init()) { LastError = "Could not initialize stream"; oggContainerReader.Dispose(); return false; } if (oggContainerReader.StreamSerials.Length == 0) { LastError = "Initialization failed: No elementary streams found in input file"; oggContainerReader.Dispose(); return false; } _containerReader = oggContainerReader; int streamSerial = oggContainerReader.StreamSerials[0]; _packetProvider = oggContainerReader.GetStream(streamSerial); if (CanSeek) { GranuleCount = _packetProvider.GetGranuleCount(); PageCount = _packetProvider.GetTotalPageCount(); } QueueNextPacket(); return true; } catch (Exception ex) { LastError = "Unknown initialization error: " + ex.Message; return false; } } public void SeekTo(TimeSpan playbackTime) { if (!CanSeek) { throw new InvalidOperationException("Stream is not seekable."); } if (playbackTime < TimeSpan.Zero || playbackTime > TotalTime) { throw new ArgumentOutOfRangeException("playbackTime"); } long granulePosition = Convert.ToInt64(playbackTime.TotalSeconds * 48000.0); SeekToGranulePosition(granulePosition); } private void SeekToGranulePosition(long granulePosition) { if (!CanSeek) { throw new InvalidOperationException("Stream is not seekable."); } if (granulePosition < 0 || granulePosition > GranuleCount) { throw new ArgumentOutOfRangeException("granulePosition"); } DataPacket dataPacket = _packetProvider.FindPacket(granulePosition, GetPacketLength); if (dataPacket == null || dataPacket.IsEndOfStream) { _endOfStream = true; _nextDataPacket = null; return; } _packetProvider.SeekToPacket(dataPacket, 1); PageGranulePosition = _packetProvider.PeekNextPacket().PageGranulePosition; if (_decoder != null) { _decoder.ResetState(); } } private int GetPacketLength(DataPacket curPacket, DataPacket lastPacket) { if (lastPacket == null || curPacket.IsResync) { return 0; } if (curPacket.ReadBit()) { return 0; } if (lastPacket.ReadBit()) { return 0; } return 1; } private void QueueNextPacket() { if (_endOfStream) { return; } DataPacket nextPacket = _packetProvider.GetNextPacket(); if (nextPacket == null) { _endOfStream = true; _nextDataPacket = null; return; } PageGranulePosition = nextPacket.PageGranulePosition; PagePosition = nextPacket.PageSequenceNumber; byte[] array = new byte[nextPacket.Length]; nextPacket.Read(array, 0, nextPacket.Length); nextPacket.Done(); if (array.Length > 8 && "OpusHead".Equals(Encoding.UTF8.GetString(array, 0, 8))) { QueueNextPacket(); } else if (array.Length > 8 && "OpusTags".Equals(Encoding.UTF8.GetString(array, 0, 8))) { Tags = OpusTags.ParsePacket(array, array.Length); QueueNextPacket(); } else { _nextDataPacket = array; } } public void Close() { _containerReader?.Dispose(); } } public class OpusOggWriteStream { private const int FRAME_SIZE_MS = 20; private const byte EARLY_FINALIZE_SEGMENT_LIMIT = 248; private IOpusEncoder _encoder; private Stream _outputStream; private Crc _crc; private int _inputChannels; private IResampler _resampler; private int _inputSampleRate; private readonly bool _leaveOpen; private int _encoderSampleRate; private short[] _opusFrame; private int _opusFrameSamples; private int _opusFrameIndex; private byte[] _currentHeader = new byte[400]; private byte[] _currentPayload = new byte[65536]; private int _headerIndex; private int _payloadIndex; private int _pageCounter; private int _logicalStreamId; private long _granulePosition; private byte _packetsInPage; private byte _lacingTableCount; private byte _maxPacketsPerPage = 248; private const int PAGE_FLAGS_POS = 5; private const int GRANULE_COUNT_POS = 6; private const int CHECKSUM_HEADER_POS = 22; private const int SEGMENT_COUNT_POS = 26; private bool _finalized; public TimeSpan MaxAudioLengthPerPage { get { return TimeSpan.FromMilliseconds(20 * _maxPacketsPerPage); } set { if (value <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException("MaxAudioLengthPerPage"); } _maxPacketsPerPage = (byte)Math.Min(248, Math.Max(1, (int)(value.TotalMilliseconds / 20.0))); } } public OpusOggWriteStream(IOpusEncoder encoder, Stream outputStream, OpusTags fileTags = null, int inputSampleRate = 0, int resamplerQuality = 5, bool leaveOpen = false) { _encoder = encoder; if (_encoder.UseDTX) { throw new ArgumentException("DTX is not currently supported in Ogg streams"); } _inputSampleRate = inputSampleRate; _leaveOpen = leaveOpen; if (_inputSampleRate == 0) { _inputSampleRate = _encoder.SampleRate; } _logicalStreamId = new Random().Next(); _encoderSampleRate = encoder.SampleRate; _inputChannels = encoder.NumChannels; _outputStream = outputStream; _opusFrameIndex = 0; _granulePosition = 0L; _opusFrameSamples = (int)((long)_encoderSampleRate * 20L / 1000); _opusFrame = new short[_opusFrameSamples * _inputChannels]; _crc = new Crc(); _resampler = ResamplerFactory.CreateResampler(_inputChannels, _inputSampleRate, _encoderSampleRate, resamplerQuality, (TextWriter)null); BeginNewPage(); WriteOpusHeadPage(); WriteOpusTagsPage(fileTags); } public void WriteSamples(short[] data, int offset, int count) { if (_finalized) { throw new InvalidOperationException("Cannot write new samples to Ogg file, the output stream is already closed!"); } if (data.Length - offset < count) { throw new ArgumentOutOfRangeException("The given audio buffer claims to have " + count + " samples, but it actually only has " + (data.Length - offset)); } int num = 0; for (int num2 = Math.Min(_opusFrame.Length - _opusFrameIndex, count - num); num2 > 0; num2 = Math.Min(_opusFrame.Length - _opusFrameIndex, count - num)) { if (_inputSampleRate != _encoderSampleRate) { int num3 = (count - num) / _inputChannels; int num4 = num2 / _inputChannels; _resampler.ProcessInterleaved(data.AsSpan(offset + num), ref num3, _opusFrame.AsSpan(_opusFrameIndex), ref num4); num += num3 * _inputChannels; _opusFrameIndex += num4 * _inputChannels; } else { data.AsSpan(offset + num, num2).CopyTo(_opusFrame.AsSpan(_opusFrameIndex, num2)); _opusFrameIndex += num2; num += num2; } if (_opusFrameIndex == _opusFrame.Length) { int num5 = _encoder.Encode((ReadOnlySpan)_opusFrame.AsSpan(), _opusFrameSamples, _currentPayload.AsSpan(_payloadIndex), _currentPayload.Length - _payloadIndex); _payloadIndex += num5; _granulePosition += 960L; int num6 = num5; while (num6 >= 255) { num6 -= 255; _currentHeader[_headerIndex++] = byte.MaxValue; _lacingTableCount++; } _currentHeader[_headerIndex++] = (byte)num6; _lacingTableCount++; _packetsInPage++; if (_packetsInPage >= _maxPacketsPerPage || _lacingTableCount > 248) { FinalizePage(); } _opusFrameIndex = 0; } } } public void WriteSamples(float[] data, int offset, int count) { if (_finalized) { throw new InvalidOperationException("Cannot write new samples to Ogg file, the output stream is already closed!"); } short[] array = new short[count]; for (int i = 0; i < count; i++) { array[i] = (short)(data[i + offset] * 32767f); } WriteSamples(array, 0, count); } public void Finish() { int num = _opusFrame.Length - _opusFrameIndex; short[] data = new short[num]; WriteSamples(data, 0, num); FinalizePage(); WriteStreamFinishedPage(); _outputStream.Flush(); if (!_leaveOpen) { _outputStream.Dispose(); } _finalized = true; } private void WriteStreamFinishedPage() { _currentHeader[_headerIndex++] = 0; _lacingTableCount++; _currentHeader[5] = 4; FinalizePage(); } private void WriteOpusHeadPage() { if (_payloadIndex != 0) { throw new InvalidOperationException("Must begin writing OpusHead on a new page!"); } _payloadIndex += WriteValueToByteBuffer("OpusHead", _currentPayload, _payloadIndex); _currentPayload[_payloadIndex++] = 1; _currentPayload[_payloadIndex++] = (byte)_inputChannels; short val = 0; _payloadIndex += WriteValueToByteBuffer(val, _currentPayload, _payloadIndex); _payloadIndex += WriteValueToByteBuffer(_encoderSampleRate, _currentPayload, _payloadIndex); short val2 = 0; _payloadIndex += WriteValueToByteBuffer(val2, _currentPayload, _payloadIndex); _currentPayload[_payloadIndex++] = 0; _currentHeader[_headerIndex++] = (byte)_payloadIndex; _lacingTableCount++; _currentHeader[5] = 2; FinalizePage(); } private void WriteOpusTagsPage(OpusTags tags = null) { if (tags == null) { tags = new OpusTags(); } if (string.IsNullOrEmpty(tags.Comment)) { tags.Comment = _encoder.GetVersionString(); } if (_payloadIndex != 0) { throw new InvalidOperationException("Must begin writing OpusTags on a new page!"); } _payloadIndex += WriteValueToByteBuffer("OpusTags", _currentPayload, _payloadIndex); int num = WriteValueToByteBuffer(tags.Comment, _currentPayload, _payloadIndex + 4); _payloadIndex += WriteValueToByteBuffer(num, _currentPayload, _payloadIndex); _payloadIndex += num; int payloadIndex = _payloadIndex; _payloadIndex += 4; int num2 = 0; foreach (KeyValuePair field in tags.Fields) { if (!string.IsNullOrEmpty(field.Key) && !string.IsNullOrEmpty(field.Value)) { num = WriteValueToByteBuffer(field.Key + "=" + field.Value, _currentPayload, _payloadIndex + 4); _payloadIndex += WriteValueToByteBuffer(num, _currentPayload, _payloadIndex); _payloadIndex += num; num2++; } } WriteValueToByteBuffer(num2, _currentPayload, payloadIndex); int num3; for (num3 = _payloadIndex; num3 >= 255; num3 -= 255) { _currentHeader[_headerIndex++] = byte.MaxValue; _lacingTableCount++; } _currentHeader[_headerIndex++] = (byte)num3; _lacingTableCount++; FinalizePage(); } private void BeginNewPage() { _headerIndex = 0; _payloadIndex = 0; _packetsInPage = 0; _lacingTableCount = 0; _headerIndex += WriteValueToByteBuffer("OggS", _currentHeader, _headerIndex); _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _headerIndex += WriteValueToByteBuffer(_granulePosition, _currentHeader, _headerIndex); _headerIndex += WriteValueToByteBuffer(_logicalStreamId, _currentHeader, _headerIndex); _headerIndex += WriteValueToByteBuffer(_pageCounter, _currentHeader, _headerIndex); _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = _lacingTableCount; _pageCounter++; } private void FinalizePage() { if (_finalized) { throw new InvalidOperationException("Cannot finalize page, the output stream is already closed!"); } if (_lacingTableCount != 0) { _currentHeader[26] = _lacingTableCount; WriteValueToByteBuffer(_granulePosition, _currentHeader, 6); _crc.Reset(); for (int i = 0; i < _headerIndex; i++) { _crc.Update(_currentHeader[i]); } for (int j = 0; j < _payloadIndex; j++) { _crc.Update(_currentPayload[j]); } WriteValueToByteBuffer(_crc.Value, _currentHeader, 22); _outputStream.Write(_currentHeader, 0, _headerIndex); _outputStream.Write(_currentPayload, 0, _payloadIndex); BeginNewPage(); } } private static int WriteValueToByteBuffer(int val, byte[] target, int targetOffset) { BinaryHelpers.Int32ToByteArrayLittleEndian(val, target, targetOffset); return 4; } private static int WriteValueToByteBuffer(long val, byte[] target, int targetOffset) { BinaryHelpers.Int64ToByteArrayLittleEndian(val, target, targetOffset); return 8; } private static int WriteValueToByteBuffer(uint val, byte[] target, int targetOffset) { BinaryHelpers.UInt32ToByteArrayLittleEndian(val, target, targetOffset); return 4; } private static int WriteValueToByteBuffer(short val, byte[] target, int targetOffset) { BinaryHelpers.Int16ToByteArrayLittleEndian(val, target, targetOffset); return 2; } private static int WriteValueToByteBuffer(string val, byte[] target, int targetOffset) { if (string.IsNullOrEmpty(val)) { return 0; } return Encoding.UTF8.GetBytes(val, 0, val.Length, target, targetOffset); } } public class OpusRawWriteStream { private Stream _outputStream; private Crc _crc; private int _inputChannels; private readonly bool _leaveOpen; private int _inputSampleRate; private byte[] _currentHeader = new byte[400]; private byte[] _currentPayload = new byte[65536]; private int _headerIndex; private int _payloadIndex; private int _pageCounter; private int _logicalStreamId; private long _granulePosition; private byte _lacingTableCount; private const int PAGE_FLAGS_POS = 5; private const int GRANULE_COUNT_POS = 6; private const int CHECKSUM_HEADER_POS = 22; private const int SEGMENT_COUNT_POS = 26; private bool _finalized; public OpusRawWriteStream(Stream outputStream, OpusTags fileTags, int inputSampleRate, int inputNumChannels, bool leaveOpen = false) { _inputSampleRate = inputSampleRate; _logicalStreamId = new Random().Next(); _inputChannels = inputNumChannels; _leaveOpen = leaveOpen; _outputStream = outputStream; _granulePosition = 0L; _crc = new Crc(); BeginNewPage(); WriteOpusHeadPage(); WriteOpusTagsPage(fileTags); } public void WriteSinglePacket(byte[] opusPacket, int offset, int packetSize) { if (_finalized) { throw new InvalidOperationException("Cannot write new samples to Ogg file, the output stream is already closed!"); } if (opusPacket.Length - offset < packetSize) { throw new ArgumentOutOfRangeException("The given audio buffer claims to have " + packetSize + " samples, but it actually only has " + (opusPacket.Length - offset)); } Array.Copy(opusPacket, offset, _currentPayload, _payloadIndex, packetSize); _payloadIndex += packetSize; int numSamplesPerFrame = OpusPacketInfo.GetNumSamplesPerFrame((ReadOnlySpan)opusPacket.AsSpan(offset), 48000); _granulePosition += numSamplesPerFrame; int num = packetSize; while (num >= 255) { num -= 255; _currentHeader[_headerIndex++] = byte.MaxValue; _lacingTableCount++; } _currentHeader[_headerIndex++] = (byte)num; _lacingTableCount++; if (_lacingTableCount > 248) { FinalizePage(); } } public void Finish() { FinalizePage(); WriteStreamFinishedPage(); _outputStream.Flush(); if (!_leaveOpen) { _outputStream.Dispose(); } _finalized = true; } private void WriteStreamFinishedPage() { _currentHeader[_headerIndex++] = 0; _lacingTableCount++; _currentHeader[5] = 4; FinalizePage(); } private void WriteOpusHeadPage() { if (_payloadIndex != 0) { throw new InvalidOperationException("Must begin writing OpusHead on a new page!"); } _payloadIndex += WriteValueToByteBuffer("OpusHead", _currentPayload, _payloadIndex); _currentPayload[_payloadIndex++] = 1; _currentPayload[_payloadIndex++] = (byte)_inputChannels; short val = 0; _payloadIndex += WriteValueToByteBuffer(val, _currentPayload, _payloadIndex); _payloadIndex += WriteValueToByteBuffer(_inputSampleRate, _currentPayload, _payloadIndex); short val2 = 0; _payloadIndex += WriteValueToByteBuffer(val2, _currentPayload, _payloadIndex); _currentPayload[_payloadIndex++] = 0; _currentHeader[_headerIndex++] = (byte)_payloadIndex; _lacingTableCount++; _currentHeader[5] = 2; FinalizePage(); } private void WriteOpusTagsPage(OpusTags tags = null) { if (tags == null) { tags = new OpusTags(); } if (string.IsNullOrEmpty(tags.Comment)) { tags.Comment = "Concentus.OggFile"; } if (_payloadIndex != 0) { throw new InvalidOperationException("Must begin writing OpusTags on a new page!"); } _payloadIndex += WriteValueToByteBuffer("OpusTags", _currentPayload, _payloadIndex); int num = WriteValueToByteBuffer(tags.Comment, _currentPayload, _payloadIndex + 4); _payloadIndex += WriteValueToByteBuffer(num, _currentPayload, _payloadIndex); _payloadIndex += num; int payloadIndex = _payloadIndex; _payloadIndex += 4; int num2 = 0; foreach (KeyValuePair field in tags.Fields) { if (!string.IsNullOrEmpty(field.Key) && !string.IsNullOrEmpty(field.Value)) { num = WriteValueToByteBuffer(field.Key + "=" + field.Value, _currentPayload, _payloadIndex + 4); _payloadIndex += WriteValueToByteBuffer(num, _currentPayload, _payloadIndex); _payloadIndex += num; num2++; } } WriteValueToByteBuffer(num2, _currentPayload, payloadIndex); int num3; for (num3 = _payloadIndex; num3 >= 255; num3 -= 255) { _currentHeader[_headerIndex++] = byte.MaxValue; _lacingTableCount++; } _currentHeader[_headerIndex++] = (byte)num3; _lacingTableCount++; FinalizePage(); } private void BeginNewPage() { _headerIndex = 0; _payloadIndex = 0; _lacingTableCount = 0; _headerIndex += WriteValueToByteBuffer("OggS", _currentHeader, _headerIndex); _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _headerIndex += WriteValueToByteBuffer(_granulePosition, _currentHeader, _headerIndex); _headerIndex += WriteValueToByteBuffer(_logicalStreamId, _currentHeader, _headerIndex); _headerIndex += WriteValueToByteBuffer(_pageCounter, _currentHeader, _headerIndex); _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = 0; _currentHeader[_headerIndex++] = _lacingTableCount; _pageCounter++; } private void FinalizePage() { if (_finalized) { throw new InvalidOperationException("Cannot finalize page, the output stream is already closed!"); } if (_lacingTableCount != 0) { _currentHeader[26] = _lacingTableCount; WriteValueToByteBuffer(_granulePosition, _currentHeader, 6); _crc.Reset(); for (int i = 0; i < _headerIndex; i++) { _crc.Update(_currentHeader[i]); } for (int j = 0; j < _payloadIndex; j++) { _crc.Update(_currentPayload[j]); } WriteValueToByteBuffer(_crc.Value, _currentHeader, 22); _outputStream.Write(_currentHeader, 0, _headerIndex); _outputStream.Write(_currentPayload, 0, _payloadIndex); BeginNewPage(); } } private static int WriteValueToByteBuffer(int val, byte[] target, int targetOffset) { Array.Copy(BitConverter.GetBytes(val), 0, target, targetOffset, 4); return 4; } private static int WriteValueToByteBuffer(long val, byte[] target, int targetOffset) { Array.Copy(BitConverter.GetBytes(val), 0, target, targetOffset, 8); return 8; } private static int WriteValueToByteBuffer(uint val, byte[] target, int targetOffset) { Array.Copy(BitConverter.GetBytes(val), 0, target, targetOffset, 4); return 4; } private static int WriteValueToByteBuffer(short val, byte[] target, int targetOffset) { Array.Copy(BitConverter.GetBytes(val), 0, target, targetOffset, 2); return 2; } private static int WriteValueToByteBuffer(string val, byte[] target, int targetOffset) { if (string.IsNullOrEmpty(val)) { return 0; } byte[] bytes = Encoding.UTF8.GetBytes(val); Array.Copy(bytes, 0, target, targetOffset, bytes.Length); return bytes.Length; } } public class OpusTagName { public const string Title = "title"; public const string Artist = "artist"; public const string Album = "album"; } public class OpusTags { private string _comment = string.Empty; private IDictionary _fields = new Dictionary(); public string Comment { get { return _comment; } set { _comment = value; } } public IDictionary Fields => _fields; internal static OpusTags ParsePacket(byte[] packet, int packetLength) { if (packetLength < 8) { return null; } if (!"OpusTags".Equals(Encoding.UTF8.GetString(packet, 0, 8))) { return null; } OpusTags opusTags = new OpusTags(); int num = 8; int num2 = BitConverter.ToInt32(packet, num); num += 4; if (num2 > 0) { opusTags._comment = Encoding.UTF8.GetString(packet, num, num2); num += num2; } int num3 = BitConverter.ToInt32(packet, num); num += 4; for (int i = 0; i < num3; i++) { num2 = BitConverter.ToInt32(packet, num); num += 4; if (num2 > 0) { string @string = Encoding.UTF8.GetString(packet, num, num2); num += num2; int num4 = @string.IndexOf('='); if (num4 > 0) { string key = @string.Substring(0, num4); string value = @string.Substring(num4 + 1); opusTags._fields[key] = value; } } } return opusTags; } } internal class ParameterChangeEventArgs : EventArgs { public DataPacket FirstPacket { get; private set; } public ParameterChangeEventArgs(DataPacket firstPacket) { FirstPacket = firstPacket; } } internal class StreamReadBuffer : IDisposable { internal class StreamWrapper { internal Stream Source; internal object LockObject = new object(); internal long EofOffset = long.MaxValue; internal int RefCount = 1; } private class SavedBuffer { public byte[] Buffer; public long BaseOffset; public int End; public int DiscardCount; public long VersionSaved; } private static Dictionary _lockObjects = new Dictionary(); private StreamWrapper _wrapper; private int _maxSize; private byte[] _data; private long _baseOffset; private int _end; private int _discardCount; private bool _minimalRead; private long _versionCounter; private List _savedBuffers; public bool MinimalRead { get { return _minimalRead; } set { _minimalRead = value; } } public int MaxSize { get { return _maxSize; } set { if (value < 1) { throw new ArgumentOutOfRangeException("Must be greater than zero."); } int num = 1 << (int)Math.Ceiling(Math.Log(value, 2.0)); if (num < _end) { if (num < _end - _discardCount) { throw new ArgumentOutOfRangeException("Must be greater than or equal to the number of bytes currently buffered."); } CommitDiscard(); byte[] array = new byte[num]; Buffer.BlockCopy(_data, 0, array, 0, _end); _data = array; } _maxSize = num; } } public long BaseOffset => _baseOffset + _discardCount; public int BytesFilled => _end - _discardCount; public int Length => _data.Length; internal long BufferEndOffset { get { if (_end - _discardCount > 0) { return _baseOffset + _discardCount + _maxSize; } if (_wrapper.Source.CanSeek) { return _wrapper.Source.Length; } return _baseOffset + Length; } } internal StreamReadBuffer(Stream source, int initialSize, int maxSize, bool minimalRead) { StreamWrapper value; lock (_lockObjects) { if (!_lockObjects.TryGetValue(source, out value)) { _lockObjects.Add(source, new StreamWrapper { Source = source }); value = _lockObjects[source]; if (source.CanSeek) { value.EofOffset = source.Length; } } else { value.RefCount++; } } initialSize = 2 << (int)Math.Log(initialSize - 1, 2.0); maxSize = 1 << (int)Math.Log(maxSize, 2.0); _wrapper = value; _data = new byte[initialSize]; _maxSize = maxSize; _minimalRead = minimalRead; _savedBuffers = new List(); } public void Dispose() { lock (_lockObjects) { if (--_wrapper.RefCount == 0) { _lockObjects.Remove(_wrapper.Source); } } } public int Read(long offset, byte[] buffer, int index, int count) { if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } if (index < 0 || index + count > buffer.Length) { throw new ArgumentOutOfRangeException("index"); } if (count < 0) { throw new ArgumentOutOfRangeException("count"); } if (offset >= _wrapper.EofOffset) { return 0; } int srcOffset = EnsureAvailable(offset, ref count, isRecursion: false); Buffer.BlockCopy(_data, srcOffset, buffer, index, count); return count; } internal int ReadByte(long offset) { if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if (offset >= _wrapper.EofOffset) { return -1; } int count = 1; int num = EnsureAvailable(offset, ref count, isRecursion: false); if (count == 1) { return _data[num]; } return -1; } private int EnsureAvailable(long offset, ref int count, bool isRecursion) { if (offset >= _baseOffset && offset + count < _baseOffset + _end) { return (int)(offset - _baseOffset); } if (count > _maxSize) { throw new InvalidOperationException("Not enough room in the buffer! Increase the maximum size and try again."); } _versionCounter++; if (!isRecursion) { for (int i = 0; i < _savedBuffers.Count; i++) { long num = _savedBuffers[i].BaseOffset - offset; if ((num < 0 && _savedBuffers[i].End + num > 0) || (num > 0 && count - num > 0)) { SwapBuffers(_savedBuffers[i]); return EnsureAvailable(offset, ref count, isRecursion: true); } } } while (_savedBuffers.Count > 0 && _savedBuffers[0].VersionSaved + 25 < _versionCounter) { _savedBuffers[0].Buffer = null; _savedBuffers.RemoveAt(0); } if (offset < _baseOffset && !_wrapper.Source.CanSeek) { throw new InvalidOperationException("Cannot seek before buffer on forward-only streams!"); } CalcBuffer(offset, count, out var readStart, out var readEnd); count = FillBuffer(offset, count, readStart, readEnd); return (int)(offset - _baseOffset); } private void SaveBuffer() { _savedBuffers.Add(new SavedBuffer { Buffer = _data, BaseOffset = _baseOffset, End = _end, DiscardCount = _discardCount, VersionSaved = _versionCounter }); _data = null; _end = 0; _discardCount = 0; } private void CreateNewBuffer(long offset, int count) { SaveBuffer(); _data = new byte[Math.Min(2 << (int)Math.Log(count - 1, 2.0), _maxSize)]; _baseOffset = offset; } private void SwapBuffers(SavedBuffer savedBuffer) { _savedBuffers.Remove(savedBuffer); SaveBuffer(); _data = savedBuffer.Buffer; _baseOffset = savedBuffer.BaseOffset; _end = savedBuffer.End; _discardCount = savedBuffer.DiscardCount; } private void CalcBuffer(long offset, int count, out int readStart, out int readEnd) { readStart = 0; readEnd = 0; if (offset < _baseOffset) { if (offset + _maxSize <= _baseOffset) { if (_baseOffset - (offset + _maxSize) > _maxSize) { CreateNewBuffer(offset, count); } else { EnsureBufferSize(count, copyContents: false, 0); } _baseOffset = offset; readEnd = count; } else { readEnd = (int)(offset - _baseOffset); EnsureBufferSize(Math.Min((int)(offset + _maxSize - _baseOffset), _end) - readEnd, copyContents: true, readEnd); readEnd = (int)(offset - _baseOffset) - readEnd; } } else if (offset >= _baseOffset + _maxSize) { if (offset - (_baseOffset + _maxSize) > _maxSize) { CreateNewBuffer(offset, count); } else { EnsureBufferSize(count, copyContents: false, 0); } _baseOffset = offset; readEnd = count; } else { readEnd = (int)(offset + count - _baseOffset); int num = Math.Max(readEnd - _maxSize, 0); EnsureBufferSize(readEnd - num, copyContents: true, num); readStart = _end; readEnd = (int)(offset + count - _baseOffset); } } private void EnsureBufferSize(int reqSize, bool copyContents, int copyOffset) { byte[] array = _data; if (reqSize > _data.Length) { if (reqSize > _maxSize) { if (!_wrapper.Source.CanSeek && reqSize - _discardCount > _maxSize) { throw new InvalidOperationException("Not enough room in the buffer! Increase the maximum size and try again."); } int num = reqSize - _maxSize; copyOffset += num; reqSize = _maxSize; } else { int num2; for (num2 = _data.Length; num2 < reqSize; num2 *= 2) { } reqSize = num2; } if (reqSize > _data.Length) { array = new byte[reqSize]; } } if (copyContents) { if ((copyOffset > 0 && copyOffset < _end) || (copyOffset == 0 && array != _data)) { Buffer.BlockCopy(_data, copyOffset, array, 0, _end - copyOffset); if ((_discardCount -= copyOffset) < 0) { _discardCount = 0; } } else if (copyOffset < 0 && -copyOffset < _end) { if (array != _data || _end <= -copyOffset) { Buffer.BlockCopy(_data, 0, array, -copyOffset, Math.Max(_end, Math.Min(_end, _data.Length + copyOffset))); } else { _end = copyOffset; } _discardCount = 0; } else { _end = copyOffset; _discardCount = 0; } _baseOffset += copyOffset; _end -= copyOffset; if (_end > array.Length) { _end = array.Length; } } else { _discardCount = 0; _end = 0; } _data = array; } private int FillBuffer(long offset, int count, int readStart, int readEnd) { long readOffset = _baseOffset + readStart; int readCount = readEnd - readStart; lock (_wrapper.LockObject) { readCount = PrepareStreamForRead(readCount, readOffset); ReadStream(readStart, readCount, readOffset); if (_end < readStart + readCount) { count = Math.Max(0, (int)(_baseOffset + _end - offset)); } else if (!_minimalRead && _end < _data.Length) { readCount = _data.Length - _end; readCount = PrepareStreamForRead(readCount, _baseOffset + _end); _end += _wrapper.Source.Read(_data, _end, readCount); } } return count; } private int PrepareStreamForRead(int readCount, long readOffset) { if (readCount > 0 && _wrapper.Source.Position != readOffset) { if (readOffset < _wrapper.EofOffset) { if (_wrapper.Source.CanSeek) { _wrapper.Source.Position = readOffset; } else { long num = readOffset - _wrapper.Source.Position; if (num < 0) { readCount = 0; } else { while (--num >= 0) { if (_wrapper.Source.ReadByte() == -1) { _wrapper.EofOffset = _wrapper.Source.Position; readCount = 0; break; } } } } } else { readCount = 0; } } return readCount; } private void ReadStream(int readStart, int readCount, long readOffset) { while (readCount > 0 && readOffset < _wrapper.EofOffset) { int num = _wrapper.Source.Read(_data, readStart, readCount); if (num == 0) { break; } readStart += num; readOffset += num; readCount -= num; } if (readStart > _end) { _end = readStart; } } public void DiscardThrough(long offset) { int val = (int)(offset - _baseOffset); _discardCount = Math.Max(val, _discardCount); if (_discardCount >= _data.Length) { CommitDiscard(); } } private void CommitDiscard() { if (_discardCount >= _data.Length || _discardCount >= _end) { _baseOffset += _discardCount; _end = 0; } else { Buffer.BlockCopy(_data, _discardCount, _data, 0, _end - _discardCount); _baseOffset += _discardCount; _end -= _discardCount; } _discardCount = 0; } }