719 lines
26 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|