(*
  this file is a part of audio components suite v 2.3.
  copyright (c) 2002-2005 andrei borovsky. all rights reserved.
  see the license file for more details.
  you can contact me at mail@z0m3ie.de
*)

{
$Log: acs_flac.pas,v $
Revision 1.7  2006/07/09 16:40:35  z0m3ie
*** empty log message ***

Revision 1.6  2006/07/04 17:12:45  z0m3ie
ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...)

Revision 1.2  2005/12/30 11:10:57  z0m3ie
some corrections to lazarus-linux depending things

Revision 1.1  2005/12/19 18:36:38  z0m3ie
*** empty log message ***

Revision 1.4  2005/12/04 16:54:34  z0m3ie
All classes are renamed, Style TACS... than T... to avoid conflicts with other components (eg TMixer is TACSMixer now)

Revision 1.3  2005/11/28 21:57:24  z0m3ie
mostly FileOut fixes
moved PBuffer to PBuffer8
set all to dynamically Buffering

Revision 1.2  2005/09/13 04:04:50  z0m3ie
First release without Components for Fileformats
only TFileIn and TFileOut are Visible

Revision 1.1  2005/09/12 22:04:52  z0m3ie
modified structure again, fileformats are now in an sperat folder.
all File In/Out classes are capsulated from TFileIn and TFileOut

Revision 1.2  2005/08/22 20:17:01  z0m3ie
changed Headers to log
changed mail adress

}

unit acs_flac;

{$ifdef fpc}
{$mode delphi}
{$endif}

interface

uses

 ACS_File,Classes, SysUtils, ACS_Types, ACS_Classes, FLAC,
{$IFDEF LINUX}
  libc;
{$ENDIF}

{$IFDEF WIN32}
  Windows;
{$ENDIF}

type

  TFLACOut = class(TACSCustomFileOut)
  private
    _encoder : PFLAC__SeekableStreamEncoder;
    FBufSize : Integer;
    FVerify : Boolean;
    FBlockSize : Word;
    FBestModelSearch : Boolean;
    FEnableMidSideStereo : Boolean;
    FMaxLPCOrder : Word;
    EndOfInput : Boolean;
    FEnableLooseMidSideStereo : Boolean;
    FQLPCoeffPrecision : Word;
    FQLPCoeffPrecisionSearch : Boolean;
    FMaxResidualPartitionOrder : Word;
    FMinResidualPartitionOrder : Word;
    procedure SetEnableLooseMidSideStereo(val : Boolean);
    procedure SetBestModelSearch(val : Boolean);
  protected
    procedure Done; override;
    function DoOutput(Abort : Boolean):Boolean; override;
    procedure Prepare; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property BestModelSearch : Boolean read FBestModelSearch write SetBestModelSearch;
    property BlockSize : Word read FBlockSize write FBlockSize;
    property EnableMidSideStereo : Boolean read FEnableMidSideStereo write FEnableMidSideStereo;
    property EnableLooseMidSideStereo : Boolean read FEnableLooseMidSideStereo write SetEnableLooseMidSideStereo;
    property MaxLPCOrder : Word read FMaxLPCOrder write FMaxLPCOrder;
    property MaxResidualPartitionOrder : Word read FMaxResidualPartitionOrder write FMaxResidualPartitionOrder;
    property MinResidualPartitionOrder : Word read FMinResidualPartitionOrder write FMinResidualPartitionOrder;
    property QLPCoeffPrecision : Word read FQLPCoeffPrecision write FQLPCoeffPrecision;
    property QLPCoeffPrecisionSearch : Boolean read FQLPCoeffPrecisionSearch write FQLPCoeffPrecisionSearch;
    property Verify : Boolean read FVerify write FVerify;
  end;

  TFLACIn = class(TACSCustomFileIn)
  private
    Buff : PACSBuffer8;
    _decoder : PFLAC__SeekableStreamDecoder;
    FBlockSize: Integer;
    BytesPerBlock : Integer;
    EndOfStream : Boolean;
    MinFrameSize : Integer;
  protected
    procedure OpenFile; override;
    procedure CloseFile; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetData(Buffer : Pointer; BufferSize : Integer): Integer; override;
    function Seek(SampleNum : Integer) : Boolean; override;
  end;


implementation

type

  FLACBuf = array[0..0] of FLAC__int32;
  PFLACBuf = ^FLACBuf;

  function EncWriteCBFunc(encoder : PFLAC__SeekableStreamEncoder;
                                buffer : PFLAC__byte;
                                bytes, samples, current_frame : LongWord;
                                client_data : Pointer) : Integer; cdecl;
  var
    FLACOut : TFLACOut;
  begin
    FLACOut := TFLACOut(client_data);
    Result := FLAC__SEEKABLE_STREAM_ENCODER_OK;
    try
      FLACOut.FStream.Write(buffer^, bytes);
    except
      Result := FLAC__SEEKABLE_STREAM_ENCODER_WRITE_ERROR;
    end;
  end;

  function EncSeekCBFunc(encoder : PFLAC__SeekableStreamEncoder;
                      absolute_byte_offset : FLAC__uint64;
                      client_data : Pointer) : Integer; cdecl;
  var
    FLACOut : TFLACOut;
  begin
    FLACOut := TFLACOut(client_data);
    Result := FLAC__SEEKABLE_STREAM_ENCODER_SEEK_STATUS_OK;
    try
      FLACOut.FStream.Seek(absolute_byte_offset, soFromBeginning);
    except
      Result := FLAC__SEEKABLE_STREAM_ENCODER_SEEK_ERROR;
    end;
  end;

  function DecReadCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                         buffer : PFLAC__byte;
                         var bytes : LongWord;
                         client_data : Pointer) : Integer; cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    Result := FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
    if FLACIn.FStream.Position >= FLACIn.FStream.Size then
    begin
      Result := FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM;
      Exit;
    end;
    try
      bytes := FLACIn.FStream.Read(buffer^, bytes);
    except
      Result := FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR;
    end;
  end;

  function DecSeekCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                         absolute_byte_offset : FLAC__uint64;
                         client_data : Pointer) : Integer; cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    Result := FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
    try
      FLACIn.FStream.Seek(absolute_byte_offset, soFromBeginning);
    except
      Result := FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
    end;
  end;

  function DecTellCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                         var absolute_byte_offset : FLAC__uint64;
                         client_data : Pointer) : Integer; cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    Result := FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
    try
      absolute_byte_offset := FLACIn.FStream.Position;
    except
      Result := FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR;
    end;
  end;

  function DecLengthCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                           var stream_length : FLAC__uint64;
                           client_data : Pointer) : Integer; cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    Result := FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
    try
      stream_length := FLACIn.FStream.Size;
    except
      Result := FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR;
    end;
  end;

  function DecEOFCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                        client_data : Pointer) : Boolean; cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    if FLACIn.FStream.Position >= FLACIn.FStream.Size then Result := True
    else Result := False;
  end;

  function DecWriteCBFunc(decoder : PFLAC__SeekableStreamDecoder;
                          frame : PFLAC__Frame;
                          buffer : PFLACChannels;
                          client_data : Pointer) : Integer; cdecl;
  var
    FLACIn : TFLACIn;
    Header : PFLAC__FrameHeader;
    buffer1 : PFLACIntBuf;
    buffer2 : PFLACIntBuf;
    B16 : PACSBuffer16;
    i : Integer;
  begin
    FLACIn := TFLACIn(client_data);
    Header := PFLAC__FrameHeader(frame);
    FLACIn.FBlockSize := Header.blocksize;
    FLACIn.BytesPerBlock := FLACIn.FBlockSize*(FLACIn.FBPS shr 3)*FLACIn.FChan;
    GetMem(FLACIn.Buff, FLACIn.BytesPerBlock);
//    FillChar(FLACIn.Buff[0], FLACIn.BytesPerBlock, 255);
    if FLACIn.FBPS = 16 then
    begin
      B16 := PACSBuffer16(FLACIn.Buff);
      if FLACIn.FChan = 1 then
      begin
       buffer1 := buffer[0];
       for i := 0 to FLACIn.FBlockSize-1 do B16[i] := buffer1[i]
      end else
      begin
        buffer1 := buffer[0];
        buffer2 := buffer[1];
        for i := 0 to FLACIn.FBlockSize-1 do
        begin
          B16[i shl 1] := buffer1[i];
          B16[(i shl 1)+1] := buffer2[i];
        end;
      end;
    end else
    begin
      if FLACIn.FChan = 1 then
      begin
        buffer1 := buffer[0];
        for i := 0 to FLACIn.FBlockSize-1 do FLACIn.Buff[i] := buffer1[i];
      end else
      begin
        buffer1 := buffer[0];
        buffer2 := buffer[1];
        for i := 0 to FLACIn.FBlockSize-1 do
        begin
          FLACIn.Buff[i shl 1] := buffer1[i];
          FLACIn.Buff[(i shl 1)+1] := buffer2[i];
        end;
      end;
    end;
    Result := FLAC__SEEKABLE_STREAM_ENCODER_OK;
  end;

  procedure DecMetadataCBProc(decoder : PFLAC__SeekableStreamDecoder;
                              metadata : PFLAC__StreamMetadata;
                              client_data : Pointer); cdecl;
  var
    FLACIn : TFLACIn;
    P : Pointer;
    FI : PFLACInfo;
  begin
    if LongWord(metadata^) <> 0 then Exit;
    P := metadata;
    (*
     STREAMINFO block format differs in different
     FLAC codec versions, so we are trying to be flexible here.
    *)
    while LongWord(P^) = 0 do Inc(Integer(P), 4);
    Inc(Integer(P), 4);
    if LongWord(P^) = 0 then Inc(Integer(P), 4);
    FI := PFLACInfo(P);
    FLACIn := TFLACIn(client_data);
    FLACIn.FSR := FI.sample_rate;
    FLACIn.FChan := FI.channels;
    if FLACIn.FChan > 2 then FLACIn.FValid := False;
    FLACIn.FBPS := FI.bits_per_sample;
    if FLACIn.FChan > 16 then FLACIn.FValid := False;
    FLACIn.FTotalSamples := FI.total_samples1;
    if FLACIn.FTotalSamples = 0 then
    FLACIn.FTotalSamples := FI.total_samples2;
    FLACIn.FSize := FLACIn.FTotalSamples*(FLACIn.FBPS shr 3)*FLACIn.FChan;
    FLACIn.MinFrameSize := FI.min_framesize;
  end;

  procedure DecErrorCBProc(decoder : PFLAC__SeekableStreamDecoder;
                           status : Integer;
                           client_data : Pointer); cdecl;
  var
    FLACIn : TFLACIn;
  begin
    FLACIn := TFLACIn(client_data);
    FLACIn.FValid := False;
  end;

  constructor TFLACOut.Create;
  begin
    inherited Create(AOwner);
    FVerify := False;
    FBufferSize := $6000;
    FBlockSize := 4608;
    FBestModelSearch := False;
    FEnableMidSideStereo := True;
    if not (csDesigning	in ComponentState) then
    if not LibFLACLoaded then
    raise EACSException.Create(LibFLACPath + ' library could not be loaded.');
  end;

  destructor TFLACOut.Destroy;
  begin
    inherited Destroy;
  end;

  procedure TFLACOut.Prepare;
  begin
    if not FStreamAssigned then
    begin
      if FFileName = '' then raise EACSException.Create('File name is not assigned.');
      if (not FileExists(FFileName)) or (FFileMode = foRewrite) then
      FStream := TFileStream.Create(FFileName, fmCreate or fmShareExclusive, FAccessMask)
      else FStream := TFileStream.Create(FFileName, fmOpenReadWrite or fmShareExclusive, FAccessMask);
    end;
    EndOfInput := False;
    _encoder := FLAC__seekable_stream_encoder_new;
    if _encoder = nil then
    raise EACSException.Create('Failed to initialize FLAC encoder.');
    FInput.Init;
    FLAC__seekable_stream_encoder_set_verify(_encoder, FVerify);
    FLAC__seekable_stream_encoder_set_channels(_encoder, FInput.Channels);
    FLAC__seekable_stream_encoder_set_bits_per_sample(_encoder, FInput.BitsPerSample);
    FLAC__seekable_stream_encoder_set_sample_rate(_encoder, FInput.SampleRate);
    if FInput.Channels = 2 then
    begin
      FLAC__seekable_stream_encoder_set_do_mid_side_stereo(_encoder, FEnableMidSideStereo);
      FLAC__seekable_stream_encoder_set_loose_mid_side_stereo(_encoder, FEnableLooseMidSideStereo);
    end;
    FLAC__seekable_stream_encoder_set_blocksize(_encoder, FBlockSize);
    FLAC__seekable_stream_encoder_set_max_lpc_order(_encoder, FMaxLPCOrder);
    if FQLPCoeffPrecision + FInput.BitsPerSample > 31 then FQLPCoeffPrecision := 31 - FInput.BitsPerSample;
    FLAC__seekable_stream_encoder_set_qlp_coeff_precision(_encoder, FQLPCoeffPrecision);
    FLAC__seekable_stream_encoder_set_do_qlp_coeff_prec_search(_encoder, FQLPCoeffPrecisionSearch);
    FLAC__seekable_stream_encoder_set_min_residual_partition_order(_encoder, FMinResidualPartitionOrder);
    FLAC__seekable_stream_encoder_set_max_residual_partition_order(_encoder, FMaxResidualPartitionOrder);
    FLAC__seekable_stream_encoder_set_do_exhaustive_model_search(_encoder, FBestModelSearch);
    if FInput.Size > 0 then
    FLAC__seekable_stream_encoder_set_total_samples_estimate(_encoder, Round(FInput.Size/(FInput.BitsPerSample shr 3)/FInput.Channels));
    FLAC__seekable_stream_encoder_set_seek_callback(_encoder, EncSeekCBFunc);
    FLAC__seekable_stream_encoder_set_write_callback(_encoder, EncWriteCBFunc);
    FLAC__seekable_stream_encoder_set_client_data(_encoder, Self);
    if FLAC__seekable_stream_encoder_init(_encoder) <>
    FLAC__SEEKABLE_STREAM_ENCODER_OK then
    begin
      FInput.Flush;
      raise EACSException.Create('Failed to initialize FLAC encoder.');
    end;
    //TODO: Recreate this stuff with uffersize more equal to FBufferSize
    FBufSize := FBufferSize div FBlockSize;
    FBufSize := FBufSize * (FInput.BitsPerSample shr 3) * FInput.Channels;
    GetMem(FBuffer, FBufSize);
  end;

  procedure TFLACOut.Done;
  begin
    if not FStreamAssigned then
    FLAC__seekable_stream_encoder_finish(_encoder);
    FLAC__seekable_stream_encoder_delete(_encoder);
    if FBuffer <> nil then
      FreeMem(FBuffer);
    FBuffer := nil;
    FStream.Free;
    FInput.Flush;
  end;

  function TFLACOut.DoOutput(Abort : Boolean):Boolean;
  var
    Len, i, l, samples : Integer;
    FB : PFLACBuf;
    B16 : PACSBuffer16;
  begin
    Result := True;
    if not CanOutput then Exit;
    if Abort or EndOfInput then
    begin
      Result := False;
      Exit;
    end;
    while InputLock do;
    InputLock := True;
    Len := 0;
    while Len < FBufSize do
    begin
      l := Finput.GetData(@FBuffer[Len], FBufSize-Len);
      Inc(Len, l);
      if l = 0 then
      begin
        EndOfInput := True;
        Break;
      end;
    end;
    InputLock := False;
    if Len = 0 then
    begin
      Result := False;
      Exit;
    end;
    samples := (Len shl 3) div Finput.BitsPerSample;
    GetMem(FB, samples*SizeOF(FLAC__int32));
    if FInput.BitsPerSample = 16 then
    begin
      B16 := @FBuffer[0];
      for i := 0 to samples - 1 do FB[i] := B16[i];
    end else
    for i := 0 to samples - 1 do FB[i] := FBuffer[i];
    if not FLAC__seekable_stream_encoder_process_interleaved(_encoder, @FB[0], samples div FInput.Channels) then
    raise EACSException.Create('Failed to encode data.');
    FreeMem(FB);
  end;

  procedure TFLACOut.SetEnableLooseMidSideStereo;
  begin
    if Val then FEnableMidSideStereo := True;
    FEnableLooseMidSideStereo := Val;
  end;

  procedure TFLACOut.SetBestModelSearch;
  begin
    if Val then
    begin
      FEnableMidSideStereo := True;
      FEnableLooseMidSideStereo := False;
    end;
    FBestModelSearch := Val;
  end;

  constructor TFLACIn.Create;
  begin
    inherited Create(AOwner);
    if not (csDesigning	in ComponentState) then
    if not LibFLACLoaded then
    raise EACSException.Create(LibFLACPath + ' library could not be loaded.');
  end;

  destructor TFLACIn.Destroy;
  begin
    CloseFile;
    inherited Destroy;
  end;

  procedure TFLACIn.OpenFile;
  begin
    Inc(FOpened);
    if FOpened = 1 then
    begin
      if (not FStreamAssigned) and (FFileName = '') then
      raise EACSException.Create('File name is not assigned');
      if not FStreamAssigned then FStream := TFileStream.Create(FFileName, fmOpenRead, fmShareDenyNone);
      FValid := True;
      _decoder := FLAC__seekable_stream_decoder_new;
      if _decoder = nil then
      raise EACSException.Create('Failed to initialize FLAC decoder.');
//      FLAC__seekable_stream_decoder_set_metadata_ignore_all(_decoder);
      FLAC__seekable_stream_decoder_set_read_callback(_decoder, DecReadCBFunc);
      FLAC__seekable_stream_decoder_set_seek_callback(_decoder, DecSeekCBFunc);
      FLAC__seekable_stream_decoder_set_tell_callback(_decoder, DecTellCBFunc);
      FLAC__seekable_stream_decoder_set_length_callback(_decoder, DecLengthCBFunc);
      FLAC__seekable_stream_decoder_set_eof_callback(_decoder, DecEOFCBFunc);
      FLAC__seekable_stream_decoder_set_write_callback(_decoder, DecWriteCBFunc);
      FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, DecMetadataCBProc);
      FLAC__seekable_stream_decoder_set_error_callback(_decoder, DecErrorCBProc);
      FLAC__seekable_stream_decoder_set_client_data(_decoder, Self);
      if FLAC__seekable_stream_decoder_init(_decoder) <> FLAC__SEEKABLE_STREAM_DECODER_OK then
      raise EACSException.Create('Failed to initialize FLAC decoder.');
      if not FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder) then
      FValid := False;
      EndOfStream := False;
    end;
  end;

  procedure TFlacIn.CloseFile;
  begin
    if FOpened = 1 then
    begin
      if _decoder <> nil then
      begin
        FLAC__seekable_stream_decoder_flush(_decoder);
        FLAC__seekable_stream_decoder_finish(_decoder);
        FLAC__seekable_stream_decoder_delete(_decoder);
        _decoder := nil;
      end;
      if Buff <> nil then FreeMem(Buff);
      Buff := nil;
      if not FStreamAssigned then FStream.Free
      else FStream.Seek(0, soFromBeginning);
    end;
    if FOpened > 0 then Dec(FOpened);
  end;

  function TFLACIn.GetData(Buffer : Pointer; BufferSize : Integer): Integer;
  var
    dec_state, offs : Integer;
  begin
    if not Busy then  raise EACSException.Create('The Stream is not opened');
    if BufStart >= BufEnd then
    begin
      if FOffset <> 0 then
      begin
        offs := Round((FOffset/100)*Self.FTotalSamples);
        FPosition := FPosition + offs*(FBPS shr 3)*FChan;
        if FPosition < 0 then FPosition := 0
        else if FPosition > FSize then FPosition := FSize;
        Seek((FPosition div (FBPS shr 3)) div FChan);
        FOffset := 0;
      end;
      BufStart := 0;
      BufEnd := 0;
      if FPosition+MinFrameSize > FSize then EndOfStream := True;
      if EndOfStream then
      begin
        if FLoop then
        begin
          Flush;
          Init;
        end else
        begin
          Result := 0;
          Exit;
        end;
      end;
      if Buff <> nil then FreeMem(Buff);
      Buff := nil;
      if not FLAC__seekable_stream_decoder_process_single(_decoder) then
      begin
        dec_state := FLAC__seekable_stream_decoder_get_state(_decoder);
        if dec_state = FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM then
        begin
          EndOfStream := True;
          Result := 0;
          Exit;
        end  
        else raise EACSException.Create('Error reading FLAC file');
      end else BufEnd := Self.BytesPerBlock;
    end;
    if BufferSize < (BufEnd - BufStart)
    then Result := BufferSize
    else Result := BufEnd - BufStart;
    Move(Buff[BufStart], Buffer^, Result);
    Inc(BufStart, Result);
    Inc(FPosition, Result);
  end;

  function TFLACIn.Seek(SampleNum : Integer) : Boolean;
  begin
    Result := FLAC__seekable_stream_decoder_seek_absolute(_decoder, Samplenum);
  end;

initialization
  FileFormats.Add('flac','Free Lossless Audio Codec',TFLACIn);
  FileFormats.Add('flac','Free Lossless Audio Codec',TFLACOut);

end.
