//Bits - ?
unit AudioAAC;

interface

uses KOL, ATLCommon, TagID3v1, TagID3v2, TagAPE;

type TAACAudioInfo = PAudioInfo;

function NewAudioAAC: TAACAudioInfo;

implementation

uses ATLLib;

const
  _SAMPLE_RATE: array [0..15] of Integer = (96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 0, 0, 0, 0);
  _TypeAAC: array [0..2] of string = ('Unknown type', 'ADIF', 'ADTS');
  _VersionMPEG: array [0..2] of string = ('Unknown', 'MPEG-2', 'MPEG-4');
  _ProfileACC: array [0..4] of string = ('Unknown Profile', 'AAC Main', 'AAC_LC', 'AAC_SSR', 'AAC_LTP');
  _accUnknown = 0;
  _accADIF = 1;
  _accADTS = 2;
  _mpegUnknown = 0;
  _mpegMPEG_2 = 1;
  _mpegMPEG_4 = 2;

type
  TAudioAAC = object(TAudioInfo)
  private
  end;

function AudioACCReader(const AFileName: KOLstring; AudioInfo: TAACAudioInfo): Boolean; forward;

function NewAudioAAC: TAACAudioInfo;
begin
    New(Result, Create);
    with TAudioAAC(Result^) do begin
      FAudioVendor:= aAAC;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio, apBitrateType];
      TagAdd(tID3v1, NewTagID3v1);
      TagAdd(tID3v2, NewTagID3v2);
      TagAdd(tAPE, NewTagAPE);
      FReader:= AudioACCReader;
    end;
end;

function AudioACCReader(const AFileName: KOLstring; AudioInfo: TAACAudioInfo): Boolean;
  function ReadBits(Source: PStream; Position, Count: Integer): Integer;
  var Buffer: array [1..4] of Byte;
  begin
      Source.Seek(Position div 8, spBegin);
      Source.Read(Buffer, SizeOf(Buffer));
      Result:= Buffer[1] * $1000000 + Buffer[2] * $10000 + Buffer[3] * $100 + Buffer[4];
      Result:= (Result shl (Position mod 8)) shr (32 - Count);
  end;
  function RecognizeHeaderType(Source: PStream): Byte;
  var Header: array [1..4] of Char;
  begin
      Result:= _accUnknown;
      Source.Seek(TAudioAAC(AudioInfo^).TagInfo[tID3v2].TagSize, spBegin);
      Source.Read(Header, SizeOf(Header));
      if Header[1] + Header[2] + Header[3] + Header[4] = 'ADIF' then Result:= _accADIF
      else
        if (Byte(Header[1]) = $FF) and (Byte(Header[1]) and $F0 = $F0) then Result:= _accADTS;
  end;
  procedure ReadADIF(Source: PStream);
  var Position: Integer;
  begin
      with TAudioAAC(AudioInfo^) do begin
        Position:= TagInfo[tID3v2].TagSize * 8 + 32;
        if ReadBits(Source, Position, 1) = 0 then Inc(Position, 3)
        else Inc(Position, 75);
        if ReadBits(Source, Position, 1) = 0 then FBitrateType:= btCBR
        else FBitrateType:= btVBR;

        Inc(Position, 1);
        FBitRate:= ReadBits(Source, Position, 23);

        if FBitrateType = btCBR then Inc(Position, 51)
        else Inc(Position, 31);
        {$IFDEF USE_OtherAudioProps}
        OtherProps.Add('ProfileACC='+_ProfileACC[ReadBits(Source, Position, 2) + 1]);
        {$ENDIF}
        Inc(Position, 2);
        FSampleRate:= _SAMPLE_RATE[ReadBits(Source, Position, 4)];
        Inc(Position, 4);
        Inc(FChannels, ReadBits(Source, Position, 4));
        Inc(Position, 4);
        Inc(FChannels, ReadBits(Source, Position, 4));
        Inc(Position, 4);
        Inc(FChannels, ReadBits(Source, Position, 4));
        Inc(Position, 4);
        Inc(FChannels, ReadBits(Source, Position, 2));
        {$IFDEF USE_OtherAudioProps}
        OtherProps.Add('VersionMPEG='+_VersionMPEG[_mpegMPEG_4]);
        {$ENDIF}
      end;
  end;

  procedure ReadADTS(Source: PStream);
  var VersionMPEG, ProfileACC, Position, Frames, TotalSize, P: Cardinal;
  begin
      with TAudioAAC(AudioInfo^) do begin
        Frames:= 0; TotalSize:= 0; VersionMPEG:= _mpegUnknown; ProfileACC:= 0;
        repeat
          Inc(Frames);
          Position:= (TagInfo[tID3v2].TagSize + TotalSize) * 8;
          if ReadBits(Source, Position, 12) <> $FFF then break;

          Inc(Position, 12);
          if ReadBits(Source, Position, 1) = 0 then VersionMPEG:= _mpegMPEG_4
          else VersionMPEG:= _mpegMPEG_2;

          Inc(Position, 4);
          ProfileACC:= ReadBits(Source, Position, 2) + 1;

          Inc(Position, 2);
          FSampleRate:= _SAMPLE_RATE[ReadBits(Source, Position, 4)];

          Inc(Position, 5);
          FChannels:= ReadBits(Source, Position, 3);

          if VersionMPEG = _mpegMPEG_4 then Inc(Position, 9)
          else Inc(Position, 7);

          P:= ReadBits(Source, Position, 13);
          if TotalSize = TotalSize + P then Break;
          Inc(TotalSize, P);

          Inc(Position, 13);
          if ReadBits(Source, Position, 11) = $7FF then FBitrateType:= btVBR
          else FBitrateType:= btCBR;

    //      if FBitrateType = btCBR then break;
        until (Source.Size <= TotalSize + TagInfo[tID3v2].TagSize + TagInfo[tID3v1].TagSize + PTagID3v1(TagInfo[tID3v1]).Lyrics.TagSize + TagInfo[tAPE].TagSize);
        FSamples:= Frames;

        FBitRate:= 8 * Round(TotalSize / 1024 / Frames * FSampleRate/1000);

        {$IFDEF USE_OtherAudioProps}
        OtherProps.Add('ProfileACC='+_ProfileACC[ProfileACC]);
        OtherProps.Add('VersionMPEG='+_VersionMPEG[VersionMPEG]);
        {$ENDIF}
      end;
  end;

var SourceFile: PStream;
    TagSize: Cardinal;
    TypeAAC: Byte;
begin
    with TAudioAAC(AudioInfo^) do begin
      Result:= false;
      try
        ResetData;
        SourceFile:= OpenFileRead(AFileName);
        try
          TagInfo[tID3v2].ReadFromFile(AFileName);
          TagInfo[tID3v1].ReadFromFile(AFileName);
          TagInfo[tAPE].ReadFromFile(AFileName);
          TagSize:= TagInfo[tID3v2].TagSize + TagInfo[tID3v1].TagSize + PTagID3v1(TagInfo[tID3v1]).Lyrics.TagSize + TagInfo[tAPE].TagSize;

          FFileSize:= SourceFile.Size;
          TypeAAC:= RecognizeHeaderType(SourceFile);
          case TypeAAC of
            _accUnknown: Exit;
            _accADIF: ReadADIF(SourceFile);
            _accADTS: ReadADTS(SourceFile);
          end;

          FValid:= (FChannels > 0) and (FSampleRate > 0) and (FBitRate > 0);
          if FValid then begin
            FBits:= 16;
            FDuration:= 8 * (FFileSize - TagSize) / (1000 * FBitRate);
            FRatio:= (FFileSize - TagSize) / ((FSamples * 1024) * (FChannels * FBits / 8) + 44) * 100;

            {$IFDEF USE_OtherAudioProps}
            OtherProps.Add('TypeAAC='+_TypeAAC[TypeAAC]);
            {$ENDIF}
          end;
          Result:= true;
        finally
          SourceFile.Free;
        end;
      except end;
    end;
end;

end.
