Files
ichni_Creator_Studio/Assets/NLayer/Decoder/MpegStreamReader.cs
2025-07-12 18:27:10 +08:00

719 lines
26 KiB
C#

using System;
using System.IO;
namespace NLayer.Decoder
{
class MpegStreamReader
{
ID3Frame _id3Frame, _id3v1Frame;
RiffHeaderFrame _riffHeaderFrame;
VBRInfo _vbrInfo;
MpegFrame _first, _current, _last, _lastFree;
long _readOffset, _eofOffset;
Stream _source;
bool _canSeek, _endFound, _mixedFrameSize;
object _readLock = new object();
object _frameLock = new object();
internal MpegStreamReader(Stream source)
{
_source = source;
_canSeek = source.CanSeek;
_readOffset = 0L;
_eofOffset = long.MaxValue;
// find the first Mpeg frame
var frame = FindNextFrame();
while (frame != null && !(frame is MpegFrame))
{
frame = FindNextFrame();
}
// if we still don't have a frame, we never sync'ed
if (frame == null) throw new InvalidDataException("Not a valid MPEG file!");
// the very next frame "should be" an mpeg frame
frame = FindNextFrame();
// if not, it's not a valid file
if (frame == null || !(frame is MpegFrame)) throw new InvalidDataException("Not a valid MPEG file!");
// seek to the first frame
_current = _first;
}
FrameBase FindNextFrame()
{
// if we've found the end, don't bother looking for anything else
if (_endFound) return null;
var freeFrame = _lastFree;
var lastFrameStart = _readOffset;
lock (_frameLock)
{
// read 3 bytes
var syncBuf = new byte[4];
try
{
if (Read(_readOffset, syncBuf, 0, 4) == 4)
{
// now loop until a frame is found
do
{
var sync = (uint)(syncBuf[0] << 24 | syncBuf[1] << 16 | syncBuf[2] << 8 | syncBuf[3]);
lastFrameStart = _readOffset;
// try ID3 first (for v2 frames)
if (_id3Frame == null)
{
var f = ID3Frame.TrySync(sync);
if (f != null)
{
if (f.Validate(_readOffset, this))
{
if (!_canSeek) f.SaveBuffer();
_readOffset += f.Length;
DiscardThrough(_readOffset, true);
return _id3Frame = f;
}
}
}
// now look for a RIFF header
if (_first == null && _riffHeaderFrame == null)
{
var f = RiffHeaderFrame.TrySync(sync);
if (f != null)
{
if (f.Validate(_readOffset, this))
{
_readOffset += f.Length;
DiscardThrough(_readOffset, true);
return _riffHeaderFrame = f;
}
}
}
// finally, just try for an MPEG frame
var frame = MpegFrame.TrySync(sync);
if (frame != null)
{
if (frame.Validate(_readOffset, this)
&& !(freeFrame != null
&& (frame.Layer != freeFrame.Layer
|| frame.Version != freeFrame.Version
|| frame.SampleRate != freeFrame.SampleRate
|| frame.BitRateIndex > 0
)
)
)
{
if (!_canSeek)
{
frame.SaveBuffer();
DiscardThrough(_readOffset + frame.FrameLength, true);
}
_readOffset += frame.FrameLength;
if (_first == null)
{
if (_vbrInfo == null && (_vbrInfo = frame.ParseVBR()) != null)
{
return FindNextFrame();
}
else
{
frame.Number = 0;
_first = _last = frame;
}
}
else
{
if (frame.SampleCount != _first.SampleCount)
{
_mixedFrameSize = true;
}
frame.SampleOffset = _last.SampleCount + _last.SampleOffset;
frame.Number = _last.Number + 1;
_last = (_last.Next = frame);
}
if (frame.BitRateIndex == 0)
{
_lastFree = frame;
}
return frame;
}
}
// if we've read MPEG frames and can't figure out what frame type we have, try looking for a new ID3 tag
if (_last != null)
{
var f = ID3Frame.TrySync(sync);
if (f != null)
{
if (f.Validate(_readOffset, this))
{
if (!_canSeek) f.SaveBuffer();
// if it's a v1 tag, go ahead and parse it
if (f.Version == 1)
{
_id3v1Frame = f;
}
else
{
// grrr... the ID3 2.4 spec says tags can be anywhere in the file and that later tags can override earlier ones... boo
_id3Frame.Merge(f);
}
_readOffset += f.Length;
DiscardThrough(_readOffset, true);
return f;
}
}
}
// well, we didn't find anything, so rinse and repeat with the next byte
++_readOffset;
if (_first == null || !_canSeek) DiscardThrough(_readOffset, true);
Buffer.BlockCopy(syncBuf, 1, syncBuf, 0, 3);
} while (Read(_readOffset + 3, syncBuf, 3, 1) == 1);
}
// move the "end of frame" marker for the last free format frame (in case we have one)
// this is because we don't include the last four bytes otherwise
lastFrameStart += 4;
_endFound = true;
return null;
}
finally
{
if (freeFrame != null)
{
freeFrame.Length = (int)(lastFrameStart - freeFrame.Offset);
if (!_canSeek)
{
// gotta finish filling the buffer!!
throw new InvalidOperationException("Free frames cannot be read properly from forward-only streams!");
}
// if _lastFree hasn't changed (we got a non-MPEG frame), clear it out
if (_lastFree == freeFrame)
{
_lastFree = null;
}
}
}
}
}
class ReadBuffer
{
public byte[] Data;
public long BaseOffset;
public int End;
public int DiscardCount;
object _localLock = new object();
public ReadBuffer(int initialSize)
{
initialSize = 2 << (int)Math.Log(initialSize, 2);
Data = new byte[initialSize];
}
public int Read(MpegStreamReader reader, long offset, byte[] buffer, int index, int count)
{
lock (_localLock)
{
var startIdx = EnsureFilled(reader, offset, ref count);
Buffer.BlockCopy(Data, startIdx, buffer, index, count);
}
return count;
}
public int ReadByte(MpegStreamReader reader, long offset)
{
lock (_localLock)
{
var count = 1;
var startIdx = EnsureFilled(reader, offset, ref count);
if (count == 1)
{
return Data[startIdx];
}
}
return -1;
}
int EnsureFilled(MpegStreamReader reader, long offset, ref int count)
{
// if the offset & count are inside our buffer's range, just return the appropriate index
var startIdx = (int)(offset - BaseOffset);
int endIdx = startIdx + count;
if (startIdx < 0 || endIdx > End)
{
int readStart = 0, readCount = 0, moveCount = 0;
long readOffset = 0;
#region Decision-Making
if (startIdx < 0)
{
// if we can't seek, there's nothing we can do
if (!reader._source.CanSeek) throw new InvalidOperationException("Cannot seek backwards on a forward-only stream!");
// if there's data in the buffer, try to keep it (up to doubling the buffer size)
if (End > 0)
{
// if doubling the buffer would push it past the max size, don't check it
if ((startIdx + Data.Length > 0) || (Data.Length * 2 <= 16384 && startIdx + Data.Length * 2 > 0))
{
endIdx = End;
}
}
// we know we'll have to start reading here
readOffset = offset;
// if the end of the request is before the start of our buffer...
if (endIdx < 0)
{
// ... just truncate and move on
Truncate();
// set up our read parameters
BaseOffset = offset;
startIdx = 0;
endIdx = count;
// how much do we need to read?
readCount = count;
}
else // i.e., endIdx >= 0
{
// we have overlap with existing data... save as much as possible
moveCount = -endIdx;
readCount = -startIdx;
}
}
else // i.e., startIdx >= 0
{
// we only get to here if at least one byte of the request is past the end of the read data
// start with the simplest scenario and work our way up
// 1) We just need to fill the buffer a bit more
if (endIdx < Data.Length)
{
readCount = endIdx - End;
readStart = End;
readOffset = BaseOffset + readStart;
}
// 2) We need to discard some bytes, then fill the buffer
else if (endIdx - DiscardCount < Data.Length)
{
moveCount = DiscardCount;
readStart = End;
readCount = endIdx - readStart;
readOffset = BaseOffset + readStart;
}
// 3) We need to expand the buffer to hold all the existing & requested data
else if (Data.Length * 2 <= 16384)
{
// by definition, we discard
moveCount = DiscardCount;
readStart = End;
readCount = endIdx - End;
readOffset = BaseOffset + readStart;
}
// 4) We have to throw away some data that hasn't been discarded
else
{
// just truncate
Truncate();
// set up our read parameters
BaseOffset = offset;
readOffset = offset;
startIdx = 0;
endIdx = count;
// how much do we have to read?
readCount = count;
}
}
#endregion
#region Buffer Resizing & Data Moving
if (endIdx - moveCount > Data.Length || readStart + readCount - moveCount > Data.Length)
{
var newSize = Data.Length * 2;
while (newSize < endIdx - moveCount)
{
newSize *= 2;
}
var newBuf = new byte[newSize];
if (moveCount < 0)
{
// reverse copy
Buffer.BlockCopy(Data, 0, newBuf, -moveCount, End + moveCount);
DiscardCount = 0;
}
else
{
// forward or neutral copy
Buffer.BlockCopy(Data, moveCount, newBuf, 0, End - moveCount);
DiscardCount -= moveCount;
}
Data = newBuf;
}
else if (moveCount != 0)
{
if (moveCount > 0)
{
// forward move
Buffer.BlockCopy(Data, moveCount, Data, 0, End - moveCount);
DiscardCount -= moveCount;
}
else
{
// backward move
for (int i = 0, srcIdx = Data.Length - 1, destIdx = Data.Length - 1 - moveCount; i < moveCount; i++, srcIdx--, destIdx--)
{
Data[destIdx] = Data[srcIdx];
}
DiscardCount = 0;
}
}
BaseOffset += moveCount;
readStart -= moveCount;
startIdx -= moveCount;
endIdx -= moveCount;
End -= moveCount;
#endregion
#region Buffer Filling
lock (reader._readLock)
{
if (readCount > 0 && reader._source.Position != readOffset && readOffset < reader._eofOffset)
{
if (reader._canSeek)
{
try
{
reader._source.Position = readOffset;
}
catch (EndOfStreamException)
{
reader._eofOffset = reader._source.Length;
readCount = 0;
}
}
else
{
// ugh, gotta read bytes until we've reached the desired offset
var seekCount = readOffset - reader._source.Position;
while (--seekCount >= 0)
{
if (reader._source.ReadByte() == -1)
{
reader._eofOffset = reader._source.Position;
readCount = 0;
break;
}
}
}
}
while (readCount > 0 && readOffset < reader._eofOffset)
{
var temp = reader._source.Read(Data, readStart, readCount);
if (temp == 0)
{
break;
}
readStart += temp;
readOffset += temp;
readCount -= temp;
}
if (readStart > End)
{
End = readStart;
}
if (End < endIdx)
{
// we didn't get a full read...
count = Math.Max(0, End - startIdx);
}
// NB: if desired, switch to "minimal reads" by commenting-out this clause
else if (End < Data.Length)
{
// try to finish filling the buffer
var temp = reader._source.Read(Data, End, Data.Length - End);
End += temp;
}
}
#endregion
}
return startIdx;
}
public void DiscardThrough(long offset)
{
lock (_localLock)
{
var count = (int)(offset - BaseOffset);
DiscardCount = Math.Max(count, DiscardCount);
if (DiscardCount >= Data.Length) CommitDiscard();
}
}
void Truncate()
{
End = 0;
DiscardCount = 0;
}
void CommitDiscard()
{
if (DiscardCount >= Data.Length || DiscardCount >= End)
{
// we have been told to discard the entire buffer
BaseOffset += DiscardCount;
End = 0;
}
else
{
// just discard the first part...
//Array.Copy(_readBuf, _readBufDiscardCount, _readBuf, 0, _readBufEnd - _readBufDiscardCount);
Buffer.BlockCopy(Data, DiscardCount, Data, 0, End - DiscardCount);
BaseOffset += DiscardCount;
End -= DiscardCount;
}
DiscardCount = 0;
}
}
ReadBuffer _readBuf = new ReadBuffer(2048);
internal int Read(long offset, byte[] buffer, int index, int count)
{
// make sure the offset is at least positive
if (offset < 0L) throw new ArgumentOutOfRangeException("offset");
// make sure the buffer is valid
if (index < 0 || index + count > buffer.Length) throw new ArgumentOutOfRangeException("index");
return _readBuf.Read(this, offset, buffer, index, count);
}
internal int ReadByte(long offset)
{
if (offset < 0L) throw new ArgumentOutOfRangeException("offset");
return _readBuf.ReadByte(this, offset);
}
internal void DiscardThrough(long offset, bool minimalRead)
{
_readBuf.DiscardThrough(offset);
}
internal void ReadToEnd()
{
try
{
var maxAllocation = 40000;
if (_id3Frame != null)
{
maxAllocation += _id3Frame.Length;
}
while (!_endFound)
{
FindNextFrame();
while (!_canSeek && FrameBase.TotalAllocation >= maxAllocation)
{
#if NET35
System.Threading.Thread.Sleep(500);
#else
System.Threading.Tasks.Task.Delay(500).Wait(); //
#endif
}
}
}
catch (ObjectDisposedException)
{
// in case the stream was disposed before we finished...
}
}
internal bool CanSeek
{
get { return _canSeek; }
}
internal long SampleCount
{
get
{
if (_vbrInfo != null) return _vbrInfo.VBRStreamSampleCount;
if (!_canSeek) return -1;
ReadToEnd();
return _last.SampleCount + _last.SampleOffset;
}
}
internal int SampleRate
{
get
{
if (_vbrInfo != null) return _vbrInfo.SampleRate;
return _first.SampleRate;
}
}
internal int Channels
{
get
{
if (_vbrInfo != null) return _vbrInfo.Channels;
return _first.Channels;
}
}
internal int FirstFrameSampleCount
{
get { return (_first != null ? _first.SampleCount : 0); }
}
internal long SeekTo(long sampleNumber)
{
if (!_canSeek) throw new InvalidOperationException("Cannot seek!");
// first try to "seek" by calculating the frame number
var cnt = (int)(sampleNumber / _first.SampleCount);
var frame = _first;
if (_current != null && _current.Number <= cnt && _current.SampleOffset <= sampleNumber)
{
// if this fires, we can short-circuit things a bit...
frame = _current;
cnt -= frame.Number;
}
while (!_mixedFrameSize && --cnt >= 0 && frame != null)
{
// make sure we have more frames to look at
if (frame == _last && !_endFound)
{
do
{
FindNextFrame();
} while (frame == _last && !_endFound);
}
// if we've found a different frame size, fall through...
if (_mixedFrameSize)
{
break;
}
frame = frame.Next;
}
// this should not run unless we found mixed frames...
while (frame != null && frame.SampleOffset + frame.SampleCount < sampleNumber)
{
if (frame == _last && !_endFound)
{
do
{
FindNextFrame();
} while (frame == _last && !_endFound);
}
frame = frame.Next;
}
if (frame == null) return -1;
return (_current = frame).SampleOffset;
}
internal MpegFrame NextFrame()
{
// if _current is null, we've returned the last frame already
var frame = _current;
if (frame != null)
{
if (_canSeek)
{
frame.SaveBuffer();
DiscardThrough(frame.Offset + frame.FrameLength, false);
}
if (frame == _last && !_endFound)
{
do
{
FindNextFrame();
} while (frame == _last && !_endFound);
}
_current = frame.Next;
if (!_canSeek)
{
// if we're in a forward-only stream, don't bother keeping the frames that have already been processed
lock (_frameLock)
{
var temp = _first;
_first = temp.Next;
temp.Next = null;
}
}
}
return frame;
}
internal MpegFrame GetCurrentFrame()
{
return _current;
}
}
}