631 lines
19 KiB
C#
631 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace NLayer.Decoder
|
|
{
|
|
class MpegFrame : FrameBase, IMpegFrame
|
|
{
|
|
static readonly int[][][] _bitRateTable =
|
|
{
|
|
new int[][]
|
|
{
|
|
new int[] { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 },
|
|
new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 },
|
|
new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }
|
|
},
|
|
new int[][]
|
|
{
|
|
new int[] { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 },
|
|
new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 },
|
|
new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }
|
|
},
|
|
};
|
|
|
|
internal static MpegFrame TrySync(uint syncMark)
|
|
{
|
|
if ((syncMark & 0xFFE00000) == 0xFFE00000 // frame sync
|
|
&& (syncMark & 0x00180000) != 0x00080000 // MPEG version != reserved
|
|
&& (syncMark & 0x00060000) != 0x00000000 // layer version != reserved
|
|
&& (syncMark & 0x0000F000) != 0x0000F000 // bitrate != bad
|
|
&& (syncMark & 0x00000C00) != 0x00000C00 // sample rate != reserved
|
|
)
|
|
{
|
|
// now check the stereo modes
|
|
switch ((syncMark >> 4) & 0xF)
|
|
{
|
|
case 0x0: // stereo
|
|
case 0x4: // joint stereo
|
|
case 0x5: // "
|
|
case 0x6: // "
|
|
case 0x7: // "
|
|
case 0x8: // dual channel
|
|
case 0xC: // mono
|
|
return new MpegFrame { _syncBits = (int)syncMark };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// base: 2xIntPtr, 1x8, 1x4
|
|
// total: 3xIntPtr, 4x8, 4x4
|
|
// Long Mode: 72 bytes per instance
|
|
// Prot Mode: 60 bytes per instance
|
|
|
|
// IntPtr
|
|
internal MpegFrame Next;
|
|
// 4
|
|
internal int Number;
|
|
|
|
// 4
|
|
int _syncBits;
|
|
|
|
// 8
|
|
int _readOffset, _bitsRead;
|
|
// 8
|
|
ulong _bitBucket = 0UL;
|
|
// 8
|
|
long _offset;
|
|
// 4
|
|
bool _isMuted;
|
|
|
|
MpegFrame()
|
|
{
|
|
|
|
}
|
|
|
|
protected override int Validate()
|
|
{
|
|
// TrySync has already validated version, layer, bitrate, and samplerate
|
|
|
|
// if layer2, we have to verify the channel mode is valid for the bitrate selected
|
|
if (Layer == MpegLayer.LayerII)
|
|
{
|
|
switch (BitRate)
|
|
{
|
|
case 32000:
|
|
case 48000:
|
|
case 56000:
|
|
case 80000:
|
|
// don't allow anything except mono
|
|
if (ChannelMode != MpegChannelMode.Mono) return -1;
|
|
break;
|
|
case 224000:
|
|
case 256000:
|
|
case 320000:
|
|
case 384000:
|
|
// don't allow mono
|
|
if (ChannelMode == MpegChannelMode.Mono) return -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// calculate the frame's length
|
|
int frameSize;
|
|
if (BitRateIndex > 0)
|
|
{
|
|
if (Layer == MpegLayer.LayerI)
|
|
{
|
|
frameSize = (12 * BitRate / SampleRate + Padding) * 4;
|
|
}
|
|
else
|
|
{
|
|
frameSize = 144 * BitRate / SampleRate + Padding;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// "free" frame... we have to calculate it later
|
|
frameSize = _readOffset + GetSideDataSize() + Padding; // we know the frame will be at least this big...
|
|
}
|
|
|
|
// now check the crc if one is present
|
|
if (HasCrc)
|
|
{
|
|
// prep for CRC reading
|
|
_readOffset = 4 + (HasCrc ? 2 : 0);
|
|
|
|
// check the CRC
|
|
if (!ValidateCRC())
|
|
{
|
|
// mute this frame
|
|
_isMuted = true;
|
|
return 6; // header + crc... force the reader to re-sync
|
|
}
|
|
}
|
|
|
|
// prep for reading
|
|
Reset();
|
|
|
|
// finally, let our caller know how big this frame is (including the sync header)
|
|
return frameSize;
|
|
}
|
|
|
|
internal int GetSideDataSize()
|
|
{
|
|
switch (Layer)
|
|
{
|
|
case MpegLayer.LayerI:
|
|
if (ChannelMode == MpegChannelMode.Mono)
|
|
{
|
|
// mono
|
|
return 16;
|
|
}
|
|
else if (ChannelMode == MpegChannelMode.Stereo || ChannelMode == MpegChannelMode.DualChannel)
|
|
{
|
|
// full stereo / dual channel
|
|
return 32;
|
|
}
|
|
else
|
|
{
|
|
// joint stereo... ugh...
|
|
switch (ChannelModeExtension)
|
|
{
|
|
case 0:
|
|
return 18;
|
|
case 1:
|
|
return 20;
|
|
case 2:
|
|
return 22;
|
|
case 3:
|
|
return 24;
|
|
}
|
|
}
|
|
break;
|
|
case MpegLayer.LayerII:
|
|
return 0;
|
|
case MpegLayer.LayerIII:
|
|
if (ChannelMode == MpegChannelMode.Mono && Version >= MpegVersion.Version2)
|
|
{
|
|
return 9;
|
|
}
|
|
else if (ChannelMode != MpegChannelMode.Mono && Version < MpegVersion.Version2)
|
|
{
|
|
return 32;
|
|
}
|
|
else
|
|
{
|
|
return 17;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ValidateCRC()
|
|
{
|
|
var crc = 0xFFFFU;
|
|
|
|
// process the common bits...
|
|
UpdateCRC(_syncBits, 16, ref crc);
|
|
|
|
var apply = false;
|
|
switch (Layer)
|
|
{
|
|
case MpegLayer.LayerI:
|
|
apply = LayerIDecoder.GetCRC(this, ref crc);
|
|
break;
|
|
case MpegLayer.LayerII:
|
|
apply = LayerIIDecoder.GetCRC(this, ref crc);
|
|
break;
|
|
case MpegLayer.LayerIII:
|
|
apply = LayerIIIDecoder.GetCRC(this, ref crc);
|
|
break;
|
|
}
|
|
|
|
if (apply)
|
|
{
|
|
var checkCrc = ReadByte(4) << 8 | ReadByte(5);
|
|
return checkCrc == crc;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static internal void UpdateCRC(int data, int length, ref uint crc)
|
|
{
|
|
var masking = 1U << length;
|
|
while ((masking >>= 1) != 0)
|
|
{
|
|
var carry = crc & 0x8000;
|
|
crc <<= 1;
|
|
if ((carry == 0) ^ ((data & masking) == 0))
|
|
{
|
|
crc ^= 0x8005;
|
|
}
|
|
}
|
|
crc &= 0xFFFF;
|
|
}
|
|
|
|
internal VBRInfo ParseVBR()
|
|
{
|
|
var buf = new byte[4];
|
|
|
|
// Xing first
|
|
int offset;
|
|
if (Version == MpegVersion.Version1 && ChannelMode != MpegChannelMode.Mono)
|
|
{
|
|
offset = 32 + 4;
|
|
}
|
|
else if (Version > MpegVersion.Version1 && ChannelMode == MpegChannelMode.Mono)
|
|
{
|
|
offset = 9 + 4;
|
|
}
|
|
else
|
|
{
|
|
offset = 17 + 4;
|
|
}
|
|
|
|
if (Read(offset, buf) != 4) return null;
|
|
if (buf[0] == 'X' && buf[1] == 'i' && buf[2] == 'n' && buf[3] == 'g'
|
|
|| buf[0] == 'I' && buf[1] == 'n' && buf[2] == 'f' && buf[3] == 'o')
|
|
{
|
|
return ParseXing(offset + 4);
|
|
}
|
|
|
|
// then VBRI (kinda rare)
|
|
if (Read(36, buf) != 4) return null;
|
|
if (buf[0] == 'V' && buf[1] == 'B' && buf[2] == 'R' && buf[3] == 'I')
|
|
{
|
|
return ParseVBRI();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
VBRInfo ParseXing(int offset)
|
|
{
|
|
VBRInfo info = new VBRInfo();
|
|
info.Channels = Channels;
|
|
info.SampleRate = SampleRate;
|
|
info.SampleCount = SampleCount;
|
|
|
|
var buf = new byte[100];
|
|
if (Read(offset, buf, 0, 4) != 4) return null;
|
|
offset += 4;
|
|
|
|
var flags = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
|
|
|
// frame count
|
|
if ((flags & 0x1) != 0)
|
|
{
|
|
if (Read(offset, buf, 0, 4) != 4) return null;
|
|
offset += 4;
|
|
info.VBRFrames = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
|
}
|
|
|
|
// byte count
|
|
if ((flags & 0x2) != 0)
|
|
{
|
|
if (Read(offset, buf, 0, 4) != 4) return null;
|
|
offset += 4;
|
|
info.VBRBytes = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
|
}
|
|
|
|
// TOC
|
|
if ((flags & 0x4) != 0)
|
|
{
|
|
// we're not using the TOC, so just discard it
|
|
if (Read(offset, buf) != 100) return null;
|
|
offset += 100;
|
|
}
|
|
|
|
// scale
|
|
if ((flags & 0x8) != 0)
|
|
{
|
|
if (Read(offset, buf, 0, 4) != 4) return null;
|
|
offset += 4;
|
|
info.VBRQuality = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
|
}
|
|
|
|
//// now look for a LAME header (note: if it isn't found, it doesn't fail the VBR parse)
|
|
//do
|
|
//{
|
|
// if (Read(offset, buf, 0, 20) != 20) break;
|
|
// offset += 20;
|
|
|
|
// // LAME tag revision: only 0 and 1 are valid
|
|
// if ((buf[9] & 0xF0) > 0x10) break;
|
|
|
|
// // VBR mode: 0-6, 8 & 9 are valid
|
|
// var mode = buf[9] & 0xF;
|
|
// if (mode == 7 || mode > 9) break;
|
|
|
|
// // Lowpass filter value
|
|
// var lowpass = buf[10] / 100.0;
|
|
|
|
// // Replay Gain
|
|
// var rgPeak = BitConverter.ToSingle(buf, 11);
|
|
// var rgGainRadio = buf[15] << 8 | buf[16];
|
|
// var rgGain = buf[17] << 8 | buf[18];
|
|
//} while (false);
|
|
|
|
return info;
|
|
}
|
|
|
|
VBRInfo ParseVBRI()
|
|
{
|
|
VBRInfo info = new VBRInfo();
|
|
info.Channels = Channels;
|
|
info.SampleRate = SampleRate;
|
|
info.SampleCount = SampleCount;
|
|
|
|
// VBRI is "fixed" size... Yay. :)
|
|
var buf = new byte[26];
|
|
if (Read(36, buf) != 26) return null;
|
|
|
|
// Version
|
|
var version = buf[4] << 8 | buf[5];
|
|
|
|
// Delay
|
|
info.VBRDelay = buf[6] << 8 | buf[7];
|
|
|
|
// Quality
|
|
info.VBRQuality = buf[8] << 8 | buf[9];
|
|
|
|
// Bytes
|
|
info.VBRBytes = buf[10] << 24 | buf[11] << 16 | buf[12] << 8 | buf[13];
|
|
|
|
// Frames
|
|
info.VBRFrames = buf[14] << 24 | buf[15] << 16 | buf[16] << 8 | buf[17];
|
|
|
|
// TOC
|
|
// entries
|
|
var tocEntries = buf[18] << 8 | buf[19];
|
|
var tocScale = buf[20] << 8 | buf[21];
|
|
var tocEntrySize = buf[22] << 8 | buf[23];
|
|
var tocFramesPerEntry = buf[24] << 8 | buf[25];
|
|
var tocSize = tocEntries * tocEntrySize;
|
|
|
|
var toc = new byte[tocSize];
|
|
if (Read(62, toc) != tocSize) return null;
|
|
|
|
return info;
|
|
}
|
|
|
|
public int FrameLength
|
|
{
|
|
get { return base.Length; }
|
|
}
|
|
|
|
public MpegVersion Version
|
|
{
|
|
get
|
|
{
|
|
switch ((_syncBits >> 19) & 3)
|
|
{
|
|
case 0:
|
|
return MpegVersion.Version25;
|
|
case 2:
|
|
return MpegVersion.Version2;
|
|
case 3:
|
|
return MpegVersion.Version1;
|
|
}
|
|
return MpegVersion.Unknown;
|
|
}
|
|
}
|
|
public MpegLayer Layer
|
|
{
|
|
get
|
|
{
|
|
// the order is backwards, and "0" is invalid
|
|
return (MpegLayer)((4 - ((_syncBits >> 17) & 3)) % 4);
|
|
}
|
|
}
|
|
public bool HasCrc
|
|
{
|
|
get { return (_syncBits & 0x10000) == 0; }
|
|
}
|
|
public int BitRate
|
|
{
|
|
get
|
|
{
|
|
if (BitRateIndex > 0)
|
|
{
|
|
return _bitRateTable[(int)Version / 10 - 1][(int)Layer - 1][BitRateIndex] * 1000;
|
|
}
|
|
else
|
|
{
|
|
// bitrate is always an even multiple of 1000, so round
|
|
return ((((FrameLength * 8) * SampleRate) / SampleCount + 499) + 500) / 1000 * 1000;
|
|
}
|
|
}
|
|
}
|
|
public int BitRateIndex
|
|
{
|
|
get { return (_syncBits >> 12) & 0xF; }
|
|
}
|
|
public int SampleRate
|
|
{
|
|
get
|
|
{
|
|
int sr;
|
|
switch (SampleRateIndex)
|
|
{
|
|
case 0: sr = 44100; break;
|
|
case 1: sr = 48000; break;
|
|
case 2: sr = 32000; break;
|
|
default: sr = 0; break;
|
|
}
|
|
if (Version > MpegVersion.Version1)
|
|
{
|
|
if (Version == MpegVersion.Version25)
|
|
{
|
|
sr /= 4;
|
|
}
|
|
else
|
|
{
|
|
sr /= 2;
|
|
}
|
|
}
|
|
return sr;
|
|
}
|
|
}
|
|
public int SampleRateIndex
|
|
{
|
|
get { return (_syncBits >> 10) & 0x3; }
|
|
}
|
|
private int Padding
|
|
{
|
|
get { return (_syncBits >> 9) & 0x1; }
|
|
}
|
|
public MpegChannelMode ChannelMode
|
|
{
|
|
get { return (MpegChannelMode)((_syncBits >> 6) & 0x3); }
|
|
}
|
|
public int ChannelModeExtension
|
|
{
|
|
get { return (_syncBits >> 4) & 0x3; }
|
|
}
|
|
internal int Channels
|
|
{
|
|
get { return (ChannelMode == MpegChannelMode.Mono ? 1 : 2); }
|
|
}
|
|
public bool IsCopyrighted
|
|
{
|
|
get { return (_syncBits & 0x8) == 0x8; }
|
|
}
|
|
internal bool IsOriginal
|
|
{
|
|
get { return (_syncBits & 0x4) == 0x4; }
|
|
}
|
|
internal int EmphasisMode
|
|
{
|
|
get { return (_syncBits & 0x3); }
|
|
}
|
|
public bool IsCorrupted
|
|
{
|
|
get { return _isMuted; }
|
|
}
|
|
|
|
public int SampleCount
|
|
{
|
|
get
|
|
{
|
|
if (Layer == MpegLayer.LayerI) return 384;
|
|
if (Layer == MpegLayer.LayerIII && Version > MpegVersion.Version1) return 576;
|
|
return 1152;
|
|
}
|
|
}
|
|
internal long SampleOffset
|
|
{
|
|
get { return _offset; }
|
|
set { _offset = value; }
|
|
}
|
|
|
|
|
|
public void Reset()
|
|
{
|
|
_readOffset = 4 + (HasCrc ? 2 : 0);
|
|
_bitBucket = 0UL;
|
|
_bitsRead = 0;
|
|
}
|
|
|
|
public int ReadBits(int bitCount)
|
|
{
|
|
if (bitCount < 1 || bitCount > 32) throw new ArgumentOutOfRangeException("bitCount");
|
|
if (_isMuted) return 0;
|
|
|
|
while (_bitsRead < bitCount)
|
|
{
|
|
var b = ReadByte(_readOffset);
|
|
if (b == -1) throw new System.IO.EndOfStreamException();
|
|
|
|
++_readOffset;
|
|
|
|
_bitBucket <<= 8;
|
|
_bitBucket |= (byte)(b & 0xFF);
|
|
_bitsRead += 8;
|
|
}
|
|
|
|
var temp = (int)((_bitBucket >> (_bitsRead - bitCount)) & ((1UL << bitCount) - 1));
|
|
_bitsRead -= bitCount;
|
|
return temp;
|
|
}
|
|
|
|
#if DEBUG
|
|
public override string ToString()
|
|
{
|
|
// version
|
|
var sb = new StringBuilder("MPEG");
|
|
switch (Version)
|
|
{
|
|
case MpegVersion.Version1: sb.Append("1"); break;
|
|
case MpegVersion.Version2: sb.Append("2"); break;
|
|
case MpegVersion.Version25: sb.Append("2.5"); break;
|
|
}
|
|
|
|
// layer
|
|
sb.Append(" Layer ");
|
|
switch (Layer)
|
|
{
|
|
case MpegLayer.LayerI: sb.Append("I"); break;
|
|
case MpegLayer.LayerII: sb.Append("II"); break;
|
|
case MpegLayer.LayerIII: sb.Append("III"); break;
|
|
}
|
|
|
|
// bitrate
|
|
sb.AppendFormat(" {0} kbps ", BitRate / 1000);
|
|
|
|
// channel mode
|
|
switch (ChannelMode)
|
|
{
|
|
case MpegChannelMode.Stereo:
|
|
sb.Append("Stereo");
|
|
break;
|
|
case MpegChannelMode.JointStereo:
|
|
sb.Append("Joint Stereo");
|
|
switch (ChannelModeExtension)
|
|
{
|
|
case 1: sb.Append(" (I)"); break;
|
|
case 2: sb.Append(" (M/S)"); break;
|
|
case 3: sb.Append(" (M/S,I)"); break;
|
|
}
|
|
break;
|
|
case MpegChannelMode.DualChannel:
|
|
sb.Append("Dual Channel");
|
|
break;
|
|
case MpegChannelMode.Mono:
|
|
sb.Append("Mono");
|
|
break;
|
|
}
|
|
|
|
// sample rate
|
|
sb.AppendFormat(" {0} KHz", (float)SampleRate / 1000);
|
|
|
|
var flagList = new List<string>();
|
|
// protection
|
|
if (HasCrc) flagList.Add("CRC");
|
|
|
|
// copyright
|
|
if (IsCopyrighted) flagList.Add("Copyright");
|
|
|
|
// original
|
|
if (IsOriginal) flagList.Add("Original");
|
|
|
|
// emphasis
|
|
switch (EmphasisMode)
|
|
{
|
|
case 1:
|
|
flagList.Add("50/15 ms");
|
|
break;
|
|
case 2:
|
|
flagList.Add("Invalid Emphasis");
|
|
break;
|
|
case 3:
|
|
flagList.Add("CCIT J.17");
|
|
break;
|
|
}
|
|
|
|
if (flagList.Count > 0)
|
|
{
|
|
sb.AppendFormat(" ({0})", string.Join(",", flagList.ToArray()));
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
#endif
|
|
}
|
|
}
|