//Bits - ?
unit AudioOgg;

interface

uses Windows, KOL, ATLCommon, TagID3v2, TagVorbis;

type TOggAudioInfo = PAudioInfo;

function NewAudioOgg: TOggAudioInfo;

implementation

uses ATLLib;

type
  TOggHeader = packed record
    ID: array [1..4] of Char;                                 { Always "OggS" }
    StreamVersion: Byte;                           { Stream structure version }
    TypeFlag: Byte;                                        { Header type flag }
    AbsolutePosition: Int64;                      { Absolute granule position }
    Serial: Integer;                                   { Stream serial number }
    PageNumber: Integer;                               { Page sequence number }
    Checksum: Integer;                                        { Page checksum }
    Segments: Byte;                                 { Number of page segments }
  end;

  TVorbisHeader = packed record
    ID: array [1..7] of Char;                          { Always #1 + "vorbis" }
    BitstreamVersion: array [1..4] of Byte;        { Bitstream version number }
    ChannelMode: Byte;                                   { Number of channels }
    SampleRate: Integer;                                   { Sample rate (hz) }
    BitRateMaximal: Integer;                           { Bit rate upper limit }
    BitRateNominal: Integer;                               { Nominal bit rate }
    BitRateMinimal: Integer;                           { Bit rate lower limit }
    BlockSize: Byte;                   { Coded size for small and long blocks }
    StopFlag: Byte;                                                { Always 1 }
  end;

  TSpeexHeader = packed record
    speex_string: array [1..8] of Char;
    speex_version: array [1..20] of Char;
    speex_version_id: Integer;
    header_size: Integer;
    rate: Integer;
    mode: Integer;
    mode_bitstream_version: Integer;
    nb_channels: Integer;
    bitrate: Integer;
    frame_size: Integer;
    vbr: Integer;
    frames_per_packet: Integer;
    extra_headers: Integer;
    reserved1: Integer;
    reserved2: Integer;
  end;

type
  TAudioOgg = object(TAudioInfo)
  end;

function AudioOggReader(const AFileName: KOLstring; AudioInfo: TOggAudioInfo): Boolean; forward;

function NewAudioOgg: TOggAudioInfo;
begin
    New(Result, Create);
    with TAudioOgg(Result^) do begin
      TagAdd(tID3v2, NewTagID3v2);
      TagAdd(tVorbis, NewTagVorbis);
      FAudioVendor:= aOGG;
      FExistsProps:= [apSampleRate, apBitrate, apChannels, apBits, apSamples, apDuration, apRatio, apBitrateType];
      FReader:= AudioOggReader;
    end;
end;

function AudioOggReader(const AFileName: KOLstring; AudioInfo: TOggAudioInfo): Boolean;
var SourceFile: PStream;
    i, j, DataIndex, BitRateNominal: Cardinal;
    Data250: array [0..250] of Char;
    Header: TOggHeader;
    Buff: Pointer;
    FirstPage: TOggHeader;
begin
    with TAudioOgg(AudioInfo^) do begin
      Result:= false;
      try
        ResetData; BitRateNominal:= 0; FBitrateType:= btCBR;
        SourceFile:= OpenFileRead(AFileName);
        Buff:= AllocMem(SizeOf(TSpeexHeader));

        FFileSize:= SourceFile.Size;

        TagInfo[tVorbis].ReadFromFile(AFileName);
        TagInfo[tID3v2].ReadFromFile(AFileName);
        SourceFile.Seek(TagInfo[tID3v2].TagSize, spBegin);

        { ÏÅÐÂÀß ÑÒÐÀÍÈÖÀ }
        SourceFile.Read(FirstPage, OggHeaderSize);  //×èòàåì Ogg çàãîëîâîê
        if FirstPage.ID <> OGG_PAGE_ID then Exit;

        SourceFile.Seek(FirstPage.Segments, spCurrent);
        SourceFile.Read(Buff^, SizeOf(TSpeexHeader));

        { Read Vorbis or SPEEX parameter header }
        if  TVorbisHeader(Buff^).ID = VORBIS_PARAMETERS_ID then begin
          {$IFDEF USE_OtherAudioProps}
          OtherProps.Add('Encoder=Vorbis');
          {$ENDIF}
          FChannels:= TVorbisHeader(Buff^).ChannelMode;
          FSampleRate:= TVorbisHeader(Buff^).SampleRate;
          BitRateNominal:= TVorbisHeader(Buff^).BitRateNominal;
        end
        else
          if TSpeexHeader(Buff^).speex_string = SPEEX_PARAMETERS_ID then begin
            {$IFDEF USE_OtherAudioProps}
            OtherProps.Add('Encoder=Speex');
            OtherProps.Add('SpeexVersion='+Trim(TSpeexHeader(Buff^).speex_version));
            {$ENDIF}
            FChannels:= TSpeexHeader(Buff^).nb_channels;
            FSampleRate:= TSpeexHeader(Buff^).rate;
            if TSpeexHeader(Buff^).vbr > 0 then FBitrateType:= btVBR;
          end
          else Exit;

        FSamples:= 0;
        for i:= 1 to 50 do begin
          DataIndex:= SourceFile.Size - (SizeOf(Data250) - 10) * i - 10;
          SourceFile.Seek(DataIndex, spBegin);
          SourceFile.Read(Data250, SizeOf(Data250));
          { Get number of PCM samples from last Ogg packet header }
          for j:= SizeOf(Data250) - 10 downto 0 do
            if Data250[j] + Data250[j + 1] + Data250[j + 2] + Data250[j + 3] = OGG_PAGE_ID then begin
              SourceFile.Seek(DataIndex + j, spBegin);
              SourceFile.Read(Header, SizeOf(Header));
              FSamples:= Header.AbsolutePosition;
              Break;
            end;
        end;

        BitRateNominal:= BitRateNominal div 1000;

        {$IFDEF USE_OtherAudioProps}
        OtherProps.Add('StreamVersion='+Int2Str(FirstPage.StreamVersion));
        OtherProps.Add('Serial='+Int2Str(FirstPage.Serial));
        if BitRateNominal > 0 then
          OtherProps.Add('BitRateNominal='+Int2Str(BitRateNominal));
        {$ENDIF}

        if FSamples > 0 then begin
          if FSampleRate > 0 then FDuration:= FSamples / FSampleRate;
        end
        else begin
          if (BitRateNominal > 0) and (FChannels > 0) then
            FDuration:= 8 * (FFileSize - TagInfo[tID3v2].TagSize - TagInfo[tVorbis].TagSize) / BitRateNominal / FChannels / 1000 * 2;
        end;
        if FDuration > 0 then FBitrate:= Round(8*(FFileSize - TagInfo[tID3v2].TagSize - TagInfo[tVorbis].TagSize) / FDuration / 1000);
        FBits:= 16;
        FRatio:= (FFileSize - TagInfo[tID3v2].TagSize - TagInfo[tVorbis].TagSize) / (FSamples * FChannels * FBits / 8 + 44) * 100;
        FValid:= (FChannels > 0) and (FSampleRate > 0) and (FDuration > 0.1) and (FBitRate > 0);

        Result:= true;
      finally
        SourceFile.Free;
        DisposeMem(Buff);
      end;
    end;
end;

end.
