unit MP3Utils;

interface
uses Classes,Windows,Sysutils;

type TMP3RawHeader = record
b1,b2,b3,b4 : byte;
end;

type TMP3FrameHeader = record
 IsValidMP3 : boolean;
 framesync : boolean; // 31-21 Good or bad - bits compute 11 bits all set
 version : byte; //20-19  Version returns 0 = bad (01), 1 = MPEG v1 (11), 2 = MPEG v2 (10), 3=MPEG version 2.5
 layer : byte; //18-17  Layver returns 0 = bad (00), 3 = Layer III (01), 2 = Layer II (10), 1 = Layer I (11)
 crcoff : boolean; //16 Protection bit returns 1 = Not protected (bitset), (0) Protected but CRC (16 bit crc follows header) (1)
 BitRate : integer; //15-12 Assume Layer 3 and return  bitrate  // 0 is free -1 is bad
 SampleRate : integer;// 11,10
 Padding : boolean;//9 0 not set - 1 Frame padded with extrabit;
 Prvate : boolean; // 8
 ChannelMode : byte; // 7,6 0= Stereo, 01 = Joint Stereo, 10 - Dual Channel Stereo, 11 - Single Channel (Mono)
 IntensityStereo : boolean;  //Used for joint Stereo
 MSStereo : boolean; // Used for joint Stereo
 Copyright : boolean; // 3 = 0 Not Copyright 1, 1 = Copyright
 Original : boolean;  // 3 = 0 Copy 1, 1 = Original
 Emphasis : byte; // 00 none 01 50/15ms 10 reserved - 11 CCIT J.17
end;


type TMP3FileInfo = class(TObject)
 private
  fheader : TMP3FrameHeader;
  fs : TFileStream;
  raw : TMP3RawHeader;
  fFrameLength : dword;
  fDuration : real;
  fstartframe : dword;
  fFileSize : dword;
  fChannels : byte;
  p : dword;
  function findfirstframe(sp : dword) :  dword;
  procedure GetFileStats;
  function readheader(sp : dword) : boolean;
 public
  constructor Create(fn : string);
  destructor Destroy;override;
  property header : TMP3FrameHeader read fheader write fheader;
  property FrameLength : dword read fFrameLength;
  property Duration : real read fDuration;
  function CalculatePositionFromFileSize(fp : dword) : real;
  function CalculateFilePositionFromTime(cp : dword) : dword;
  property StartFrameIsAt : dword read fstartframe;
  property Channels : byte read fChannels;
 end;



implementation

Constructor TMP3FileInfo.Create(fn : string);
var
n : byte;
begin
try
  fs:=TFileStream.Create(fn,fmOpenRead);
except
destroy;
exit;
end;
// Try and read first frame. If it bails for any reason try up to 10 other frames and continue when a valid frame is found
if readheader(0)=false then for n := 0 to 10 do if p+4<fs.size then begin
  if readheader(p+2)=true then break;
end;



if fheader.IsValidMP3=true then getfilestats;
fs.free;
end;

Destructor TMP3FileInfo.Destroy;
begin
  fs.Free;
end;

function TMP3FileInfo.findfirstframe(sp : dword) :  dword;
var
found : boolean;
b1,b2 : byte;
begin
found:=false;
fs.position:=sp;
while (found=false) and (fs.Position<fs.Size) do begin
fs.Read(b1,1);
if b1=$ff then begin
  fs.Read(b2,1);
  if (b2 and $e0) = $e0 then begin
  result:=fs.Position-2;
  exit;
  end;
end;
end;
result:=fs.size;
end;

function TMP3FileInfo.readheader(sp : dword) : boolean;
var
v,o : byte;
begin
p:=findfirstframe(sp);
if p+4>fs.size then begin
  fheader.IsValidMP3:=false;
  exit;
end;
fstartframe:=p;
fs.position:=p;
fs.read(raw,4);
result:=true;
fheader.IsValidMP3:=true;

//Framesync
if (raw.b1=$ff) and ((raw.b2 and $e0) = $e0) then  fheader.framesync:=true else begin
    fheader.framesync:=false;
    fheader.IsValidMP3:=false;
    result:=false;
    exit;
end;

//Version
asm
  mov o,00011000b;
end;
v:=raw.b2 AND o;
v:=v shr 3;
case v of
  0 : fheader.version:=3;
  1 : fheader.version:=0;
  2 : fheader.version:=2;
  3 : fheader.version:=1;
end;

//Layer
asm
  mov o,00000110b;
end;
v:=raw.b2 AND o;
v:=v shr 1;
case v of
  0 : fheader.layer:=0;
  1 : fheader.layer:=3;
  2 : fheader.layer:=2;
  3 : fheader.layer:=1;
end;

if fheader.layer<>3 then begin
//Only interested in layer 3, if incorrect bail out
result:=false;
exit;
end;

//CRC
asm
  mov o,00000001b;
end;
v:=raw.b2 AND o;
case v of
  0 : fheader.crcoff:=false;
  1 : fheader.crcoff:=true;
end;

//BitRate
asm
  mov o,11110000b;
end;
//v:=raw.b3 AND o;
v:=raw.b3 shr 4;

fheader.BitRate:=-1;
 case v of
  0: fheader.BitRate:=0;
  1: fheader.BitRate := 32;
  2: fheader.BitRate := 40;
  3: fheader.BitRate := 48;
  4: fheader.BitRate := 56;
  5: fheader.BitRate := 64;
  6: fheader.BitRate := 80;
  7: fheader.BitRate := 96;
  8: fheader.BitRate := 112;
  9: fheader.BitRate := 128;
  10: fheader.BitRate := 160;
  11: fheader.BitRate := 192;
  12: fheader.BitRate := 224;
  13: fheader.BitRate := 256;
  14: fheader.BitRate := 320;
  15: fheader.BitRate := -1;
 end;

if fheader.BitRate=-1 then begin
  fheader.IsValidMP3:=false;
  result:=false;
  exit;
end;

if fheader.bitrate=0 then fheader.bitrate:=320; // VBR


//Sample Frequency
asm
  mov o,00001100b;
end;
v:=raw.b3 AND o;
v:=v shr 2;
fheader.SampleRate:=-1;
case v of
  0: fheader.SampleRate:=44100;
  1: fheader.SampleRate:=48000;
  2: fheader.SampleRate:=32000;
end;

if fheader.SampleRate=-1 then begin
  fheader.IsValidMP3:=false;
  result:=false;
  exit;
end;


//Private bit
asm
  mov o,00000010b;
end;
v:=raw.b3 AND o;
v:=v shr 1;
case v of
  0: fheader.Prvate:=false;
  1: fheader.Prvate:=true;
end;

//ChannelMode

asm
  mov o,11000000b;
end;
v:=raw.b3 shr 6;
fheader.ChannelMode:=v;

case v of
0 : fChannels:=2;
1 : fChannels:=2;
2 : fChannels:=2;
3 : fChannels:=1;
end;


//Mode Extension for joint Stereo
asm
  mov o,00110000b;
end;
v:=raw.b4 AND o;
v:=v shr 4;
case v of
  0 : begin
   fheader.IntensityStereo:=false;
   fheader.MSStereo:=false;
  end;
  1 : begin
   fheader.IntensityStereo:=true;
   fheader.MSStereo:=false;
  end;
  2 : begin
   fheader.IntensityStereo:=false;
   fheader.MSStereo:=true;
  end;
  3 : begin
   fheader.IntensityStereo:=true;
   fheader.MSStereo:=true;
  end;
end;

//Copyright
asm
  mov o,00001000b;
end;
v:=raw.b4 AND o;
v:=v shr 3;
if v<>0 then fheader.Copyright:=true else fheader.Copyright:=false;

//Original
asm
  mov o,00000100b;
end;
v:=raw.b4 AND o;
v:=v shr 2;
if v<>0 then fheader.Original:=true else fheader.Original:=false;


//Emphasis
asm
  mov o,00000011b;
end;
v:=raw.b4 AND o;
fheader.Emphasis:=v;

end;

procedure TMP3FileInfo.GetFileStats;
const
SamplesPerFrame=1152;
var
padding : byte;
begin
padding:=0;
if fheader.Padding then padding:=1;
FFrameLength:=round((SamplesPerFrame / 8 * (fheader.Bitrate*1000))/fheader.SampleRate)+padding;
FDuration:=((fs.Size - fstartframe)/1024) / fheader.BitRate * 8;
FFileSize:=fs.Size;
end;

function TMP3FileInfo.CalculatePositionFromFileSize(fp : dword) : real;
begin
result:=((fp - fstartframe)/1024) / fheader.BitRate * 8;
end;

function TMP3FileInfo.CalculateFilePositionFromTime(cp : dword) : dword;
begin
result:=round((cp)*((fheader.BitRate/8)*1024));

//result:=((fp - fstartframe)/1024) / fheader.BitRate * 8;
end;



end.