unit AudioMP4;

interface

uses KOL, ATLCommon, TagMP4;

type TMP4AudioInfo = PAudioInfo;

function NewAudioMP4: TMP4AudioInfo;

implementation

uses ATLLib;

{$IFDEF USE_OtherAudioProps}
const CompatibleBrands: array [1..12, 1..2] of String = (
            ('isom', 'ISO 14496-1 Base Media'),
            ('iso2', 'ISO 14496-12 Base Media'),
            ('mp41', 'ISO 14496-1 v.1'),
            ('mp42', 'ISO 14496-1 v.2'),
            ('qt  ', 'Quicktime Movie'),
            ('avc1', 'JVT AVC'),
            ('3gp4', '3G MP4 profile'),
            ('mmp4', '3G Mobile MP4'),
            ('M4A ', 'Apple AAC audio'),
            ('M4P ', 'AES encrypted audio'),
            ('M4B ', 'Apple audio'),
            ('mp71', 'ISO 14496-12 MPEG-7')
            );
{$ENDIF}

const
  TrackOD = 'odsm';
  TrackScene = 'sdsm';
  TrackAudio = 'soun';
  TrackVideo = 'vide';
  TrackHint = 'hint';
  TrackCNTL = 'cntl';

{$IFDEF USE_OtherAudioProps}
  ObjectTypeCount = 32;
  ObjectTypeIDs: array [1..ObjectTypeCount] of Byte =
     ($40, $C0, $E0, $E1, $E2, $E3, $E4, $E5, $E6, $66, $67, $68, $69, $6B,
      $20, $60, $61, $62, $63, $64, $65, $6A, $6C, $D0, $F0, $F1, $F2, $F3, $21, $22,
      $01, $02);

  ObjectTypeNames: array [1..ObjectTypeCount] of String =
     ('MPEG-4 audio', 'private audio', '16-bit PCM Little Endian audio', 'vorbis audio',
      'Dolby v3 (AC3) audio', 'aLaw audio', 'uLaw audio', 'G723 ADPCM audio',
      '16-bit PCM Big Endian audio', 'MPEG-4 ADTS main', 'MPEG-4 ADTS Low Complexity',
      'MPEG-4 ADTS Scalable Sampling Rate', 'MPEG-2 ADTS', 'MPEG-1 ADTS',

      'MPEG-4 video', 'MPEG-2 simple video', 'MPEG-2 main video', 'MPEG-2 SNR video',
      'MPEG-2 spatial video', 'MPEG-2 high video', 'MPEG-2 4:2:2 video', 'MPEG-1 video',
      'JPEG video', 'private video', 'Y''''CbCr 4:2:0 (YV12) video', 'H264 video',
      'H263 video', 'H261 video', 'MPEG-4 AVC SPS', 'MPEG-4 AVC PPS',

      'System v1', 'System v2');

  MPEG_4Names: array [1..27] of String =
     ('AAC main', 'AAC LC', 'AAC SSR', 'AAC LTP',
      'AAC HE', 'AAC Scalable', 'TwinVQ', 'CELP',
      'HVXC', '', '', 'TTSI', 'Main Synthetic',
      'Wavetable Syn', 'General MIDI', 'Algo Syn and Audio FX',
      'ER AAC LC', '', 'ER AAC LTP', 'ER AAC Scalable',
      'ER TwinVQ', 'ER BSAC', 'ER ACC LD', 'ER CELP',
      'ER HVXC', 'ER HILN', 'ER Parametric');

{$ENDIF}

type
  PAudioSampleEntry = ^TAudioSampleEntry;
  TAudioSampleEntry = packed record
    Format: Cardinal;
    CodingName: array[1..4] of Char;
    Reserved: array[1..6] of Byte;
    Data_reference_index: Word;

    Reserved_: array[1..2] of Cardinal;
    ChannelCount: Word;
    SampleSize: Word;
    Pre_defined: Word;
    Reserved___: Word;
    Samplerate: Cardinal;
  end;

  PEsdsAtom = ^TEsdsAtom;
  TEsdsAtom = packed record
    Size: Cardinal;
    AtomType: TAtomType;
    Version: Cardinal;
    ES_Descr: Byte;      //03
    ES_Descr_Ext: array[1..3] of Byte;
    ES_Len: Byte;
    ES_ID: Word;
    Stream_Priority: Byte;

    Decoder_Config_Descr: Byte;   //4
    Decoder_Config_Ext: array[1..3] of Byte;
    Decoder_Config_Len: Byte;

    Object_Type_ID: Byte;
    Stream_Type: Byte;
    Buffer_Size: array[1..3] of Byte;
    Max_Bitrate: Cardinal;
    Avg_Bitrate: Cardinal;
    Decoder_Specific_Descr: Byte;   //5
    Decoder_Specific_Ext: array[1..3] of Byte;
    Decoder_Specific_Len: Byte;

    ES_Header: Byte;   //Реально буфер больше, но нас интересует 1-й байт
    SL_Config_Descr: Byte;           //6
    SL_Config_Ext: array[1..3] of Byte;
    SL_Config_Len: Byte;
    SL_Value: Byte;                  //2
  end;

  PMediaHeader_1 = ^TMediaHeader_1;
  TMediaHeader_1 = packed record
    Version: Byte;
    Flags: array[1..3] of Byte;
    CreationTime: Int64;
    ModificationTime: Int64;
    TimeScale: Cardinal;
    Duration: Int64;
    Language: Word; //bit(1) pad = 0;
    Pre_defined: Word;
  end;

  PMediaHeader_0 = ^TMediaHeader_0;
  TMediaHeader_0 = packed record
//    Size: Cardinal;
//    AtomType: TAtomType;
    Version: Byte;
    Flags: array[1..3] of Byte;
    CreationTime: Cardinal;
    ModificationTime: Cardinal;
    TimeScale: Cardinal;
    Duration: Cardinal;
    Language: Word; //bit(1) pad = 0;
    Pre_defined: Word;
  end;


type
  TAudioMP4 = object(TAudioInfo)
  private
  end;

function AudioMP4Reader(const AFileName: KOLstring; AudioInfo: TMP4AudioInfo): Boolean; forward;

function NewAudioMP4: TMP4AudioInfo;
begin
    New(Result, Create);
    with TAudioMP4(Result^) do begin
      FAudioVendor:= aMP4;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio, apBitrateType];
      TagAdd(tMp4, NewTagMp4);
      FReader:= AudioMP4Reader;
    end;
end;

function AudioMP4Reader(const AFileName: KOLstring; AudioInfo: TMP4AudioInfo): Boolean;
function ReadEsds(_EsdsAtom: PEsdsAtom; var EsdsAtom: TEsdsAtom): Boolean;
begin
    Result:= False;
    FillChar(EsdsAtom, SizeOf(TEsdsAtom), 0);
    Move(_EsdsAtom^, EsdsAtom, 12);

    if _EsdsAtom.ES_Descr = $03 then
      Move(_EsdsAtom^.ES_Descr, EsdsAtom.ES_Descr, 8)
    else begin
      PChar(_EsdsAtom):= PChar(_EsdsAtom) - 5;
      Move(_EsdsAtom^.ES_Descr, EsdsAtom.ES_Descr, 3);
    end;

    if _EsdsAtom.Decoder_Config_Descr <> $04 then Exit;
    if _EsdsAtom.Decoder_Config_Len < 15 then Exit;
    Move(_EsdsAtom^.Decoder_Config_Descr, EsdsAtom.Decoder_Config_Descr, 18);

    if _EsdsAtom.Decoder_Specific_Descr <> $05 then Exit;

    Move(_EsdsAtom^.Decoder_Specific_Descr, EsdsAtom.Decoder_Specific_Descr, 5);
    if EsdsAtom.Decoder_Specific_Len >= 2 then EsdsAtom.ES_Header:= _EsdsAtom.ES_Header;
    Result:= True;
end;

var SourceFile: PStream;
    AtomList: PList;
    SmplSize: Double;
    i, Sz: Integer;
    _Buff_: Pointer;
    S, SS: String;
    Atom, TrackAtom: PAtomMp4;

    AudioSampleEntry: PAudioSampleEntry;
    EsdsAtom: PEsdsAtom;
    Esds: TEsdsAtom;
begin
    with TAudioMP4(AudioInfo^) do begin
      Result:= False;
      try
        ResetData;
        SourceFile:= OpenFileRead(AFileName);
        AtomList:= NewList;
        try
          FFileSize:= SourceFile.Size;
          TagInfo[tMP4].ReadFromFile(AFileName);
          ReadTree(SourceFile, AtomList, SourceFile.Size, nil);

          FValid:= (AtomList.Count>0) and (PAtomMp4(AtomList.Items[0]).AtomType = 'ftyp');
          if not FValid then Exit;

          {$IFDEF USE_OtherAudioProps}
          _Buff_:= ReadPath(SourceFile, AtomList,  ['ftyp'], nil, Atom, Sz);
          S:= Copy(PChar(_Buff_), 1, 4) + ' rev.' + Int2Str(PWord(PChar(_Buff_)+4)^);
          SS:= Copy(PChar(_Buff_)+8, 1, min(4, Sz-8));
          for i:= 1 to 12 do
            if Pos(CompatibleBrands[i, 1], SS)>0 then begin
              S:= S + ' (' + CompatibleBrands[i, 2] + ')';
              Break;
            end;
          OtherProps.Add('Version=' + S);
          DisposeMem(_Buff_);
          {$ENDIF}

          repeat
            _Buff_:= ReadPath(SourceFile, AtomList, [Containers[ct_moov], Containers[ct_trak], Containers[ct_mdia], 'hdlr'], nil, Atom, Sz);
            if Atom <> nil then begin
              SS:= Copy(PChar(_Buff_)+8, 1, 4);
              DisposeMem(_Buff_);
              TrackAtom:= Atom.Parent.Parent;
              Atom.AtomType:= #0#0#0#0;


              if SS = TrackAudio then begin
                _Buff_:= ReadPath(SourceFile, AtomList, [Containers[ct_mdia], Containers[ct_minf], Containers[ct_stbl], 'stsd'], TrackAtom, Atom, Sz);
                FValid:= Atom <> nil;
                if not FValid then Exit;
                FValid:= SwapOrder32(PCardinal(PChar(_Buff_)+4)^) = 1;
                if not FValid then Exit;

                AudioSampleEntry:= Pointer(PChar(_Buff_)+8);

                FBits:= System.Swap(AudioSampleEntry.SampleSize);
                FSampleRate:= SwapOrder32(AudioSampleEntry.Samplerate) shr 16;
                FChannels:= System.Swap(AudioSampleEntry.ChannelCount);

                {$IFDEF USE_OtherAudioProps}
                S:= AudioSampleEntry.CodingName;
                if AudioSampleEntry.CodingName = '' then S:= 'Unknown - no media data name';
                if AudioSampleEntry.CodingName = 'samr' then S:= 'ARM';
	        if AudioSampleEntry.CodingName = 'sawb' then S:= 'AMR-WB';

	        if AudioSampleEntry.CodingName = 'mp4a' then begin
                  EsdsAtom:= nil;
                  for i:= 8+SizeOf(TAudioSampleEntry) to Sz - 12 do
                    if PEsdsAtom(PChar(_Buff_)+i).AtomType = 'esds' then begin
                      EsdsAtom:= Pointer(PChar(_Buff_)+i);
                      Break;
                    end;
                  if EsdsAtom <> nil then begin
                    S:= 'AAC from .mov';
                    FBitrate:= Round(SwapOrder32(EsdsAtom.Avg_Bitrate)/1000);
                    if (EsdsAtom.Object_Type_ID = $40) then begin
                      if ReadEsds(EsdsAtom, Esds) then begin
                        FBitrate:= Round(SwapOrder32(Esds.Avg_Bitrate)/1000);
                        i:= (Esds.ES_Header shr 3) and $1F;
	      	        if (i = 0) or (i = 10) or (i = 11) or (i = 18) or (i >= 28) then
                          S:= 'MPEG-4 Unknown Profile'
                        else
                          S:= 'MPEG-4 ' + MPEG_4Names[i];
                      end
                      else
	                S:= 'MPEG-4 (no GAConfig)';
                    end
                    else
                      for i:= 1 to ObjectTypeCount do
                        if EsdsAtom.Object_Type_ID = ObjectTypeIds[i] then begin
                          S:= ObjectTypeNames[i];
                          Break;
                        end;
                  end;
                end;
                OtherProps.Add('Type=' + S);
                DisposeMem(_Buff_);
                {$ENDIF}


                _Buff_:= ReadPath(SourceFile, AtomList, [Containers[ct_mdia], 'mdhd'], TrackAtom, Atom, Sz);
                if Atom <> nil then begin
                  if PMediaHeader_1(_Buff_).Version = 1 then begin
                    FDuration:= (SwapOrder64(PMediaHeader_1(_Buff_).Duration))/FSampleRate;
                  end
                  else begin
                    FDuration:= (SwapOrder32(PMediaHeader_0(_Buff_).Duration))/FSampleRate;
                  end;
                end;
                DisposeMem(_Buff_);

                SmplSize:= 0;
                _Buff_:= ReadPath(SourceFile, AtomList, [Containers[ct_mdia], Containers[ct_minf], Containers[ct_stbl], 'stsz'{'stz2'-???}], TrackAtom, Atom, Sz);
                if Atom <> nil then begin
                  SmplSize:= SwapOrder32(PCardinal(PChar(_Buff_)+4)^);
                  FSamples:= SwapOrder32(PCardinal(PChar(_Buff_)+8)^);

                  if SmplSize = 0 then begin
                    if FBitrate = 0 then for i:= 0 to FSamples-1 do SmplSize:= SmplSize + SwapOrder32(PCardinal(PChar(_Buff_)+12+i*4)^);
                    FBitrateType:= btVBR;
                  end
                  else begin
                    SmplSize:= SmplSize * FSamples;
                    FBitrateType:= btCBR;
                  end;
                end;
                DisposeMem(_Buff_);

                if FBitrate = 0 then FBitrate:= Round(8*SmplSize / FDuration/1000);
                FRatio:= (FBitrate*1000) / (FSampleRate  * FChannels * FBits) * 100;
                {ExpectChildAtom("enca", Optional, Many);}
              end;


              if SS = TrackVideo then begin
{
	ExpectChildAtom("avc1", Optional, Many);
	ExpectChildAtom("encv", Optional, Many);
	ExpectChildAtom("mp4v", Optional, Many);
 	ExpectChildAtom("s263", Optional, Many); // For H.263
}
              end;
              if SS = TrackHint then begin

              end;
              if SS = TrackScene then begin

              end;
              if SS = TrackOD then begin

              end;
              if SS = TrackCNTL then begin

              end;
{????????????????
ExpectChildAtom("mp4s", Optional, Many);
ExpectChildAtom("rtp ", Optional, Many);
}


              TrackAtom.AtomType:= #0#0#0#0;
            end;
          until Atom = nil;

          Result:= True;
        finally
          AtomList.Release;
          SourceFile.Free;
        end;
      except end;
    end;
end;

end.
