unit UAudio_FFMpeg_Pa;

(*******************************************************************************

This unit is primarily based upon -
    http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
    
    and tutorial03.c

    http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html

*******************************************************************************)

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}


uses Classes,
     {$IFDEF win32}
     windows,
     {$ENDIF}
     Messages,
     SysUtils,
     {$IFNDEF FPC}
     Forms,
     {$ENDIF}
     SDL,       // Used for Audio output Interface
     avcodec,   // FFMpeg Audio file decoding
     avformat,
     avutil,
     ULog,
     UMusic;

implementation

uses
     {$IFDEF LAZARUS}
     lclintf,
       {$ifndef win32}
       libc, // not available in win32
       {$endif}
     {$ENDIF}
     portaudio,
     UIni,
     UMain,
     UThemes;


type
  PPacketQueue = ^TPacketQueue;
  TPacketQueue = class
    private
      firstPkt,
      lastPkt    : PAVPacketList;
      nbPackets  : integer;
      size       : integer;
      mutex      : PSDL_Mutex;
      cond       : PSDL_Cond;
      quit       : boolean;

    public
      constructor Create();

      function Put(pkt : PAVPacket): integer;
      function Get(var pkt: TAVPacket; block: boolean): integer;
  end;

type
  TStreamStatus = (sNotReady, sStopped, sPlaying, sSeeking, sPaused, sOpen);

const
  StreamStatusStr:  array[TStreamStatus] of string = ('Not ready', 'Stopped', 'Playing', 'Seeking', 'Paused', 'Open');

type
  PAudioBuffer = ^TAudioBuffer;
  TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;

const
  SDL_AUDIO_BUFFER_SIZE = 1024;

type
  TFFMpegOutputStream = class(TAudioOutputStream)
    private
      parseThread: PSDL_Thread;
      packetQueue: TPacketQueue;

      status: TStreamStatus;

      // FFMpeg internal data
      pFormatCtx     : PAVFormatContext;
      pCodecCtx      : PAVCodecContext;
      pCodec         : PAVCodec;
      ffmpegStreamID : Integer;
      ffmpegStream   : PAVStream;

      // static vars for AudioDecodeFrame
      pkt             : TAVPacket;
      audio_pkt_data  : PChar;
      audio_pkt_size  : integer;

      // static vars for AudioCallback
      audio_buf_index : cardinal;
      audio_buf_size  : cardinal;
      audio_buf       : TAudioBuffer;

      channel: PPaStream;
    public
      constructor Create(); overload;
      constructor Create(pFormatCtx: PAVFormatContext;
                         pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
                         ffmpegStreamID : Integer; ffmpegStream: PAVStream); overload;

      procedure Play();
      procedure Pause();
      procedure Stop();
      procedure Close();

      function AudioDecodeFrame(buffer : PUInt8; bufSize: integer): integer;
  end;

type
  TAudio_FFMpeg = class( TInterfacedObject, IAudioPlayback )
    private
      MusicStream:        TFFMpegOutputStream;

      StartSoundStream:   TFFMpegOutputStream;
      BackSoundStream:    TFFMpegOutputStream;
      SwooshSoundStream:  TFFMpegOutputStream;
      ChangeSoundStream:  TFFMpegOutputStream;
      OptionSoundStream:  TFFMpegOutputStream;
      ClickSoundStream:   TFFMpegOutputStream;
      DrumSoundStream:    TFFMpegOutputStream;
      HihatSoundStream:   TFFMpegOutputStream;
      ClapSoundStream:    TFFMpegOutputStream;
      ShuffleSoundStream: TFFMpegOutputStream;

      //Custom Sounds
      CustomSounds: array of TCustomSoundEntry;
      Loaded:   boolean;
      Loop:     boolean;

      function FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
    public
      function  GetName: String;
      procedure InitializePlayback;
      procedure SetVolume(Volume: integer);
      procedure SetMusicVolume(Volume: integer);
      procedure SetLoop(Enabled: boolean);
      function Open(Name: string): boolean; // true if succeed
      procedure Rewind;
      procedure MoveTo(Time: real);
      procedure Play;
      procedure Pause; //Pause Mod
      procedure Stop;
      procedure Close;
      function Finished: boolean;
      function Length: real;
      function getPosition: real;
      procedure PlayStart;
      procedure PlayBack;
      procedure PlaySwoosh;
      procedure PlayChange;
      procedure PlayOption;
      procedure PlayClick;
      procedure PlayDrum;
      procedure PlayHihat;
      procedure PlayClap;
      procedure PlayShuffle;
      procedure StopShuffle;

      function LoadSoundFromFile(Name: string): TFFMpegOutputStream;

      //Equalizer
      function GetFFTData: TFFTData;

      // Interface for Visualizer
      function GetPCMData(var data: TPCMData): Cardinal;

      //Custom Sounds
      function LoadCustomSound(const Filename: String): Cardinal;
      procedure PlayCustomSound(const Index: Cardinal );
  end;

var
  test: TFFMpegOutputStream;
  it: integer;

var
  singleton_MusicFFMpeg : IAudioPlayback = nil;


function ParseAudio(data: Pointer): integer; cdecl; forward;
procedure SDL_AudioCallback( userdata: Pointer; stream: PUInt8; len: Integer ); cdecl; forward;
function Pa_AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
      userData: Pointer): Integer; cdecl; forward;

constructor TFFMpegOutputStream.Create();
begin
  inherited;

  packetQueue := TPacketQueue.Create();

  FillChar(pkt, sizeof(TAVPacket), #0);

  status := sStopped;

  audio_pkt_data := nil;
  audio_pkt_size := 0;

  audio_buf_index := 0;
  audio_buf_size  := 0;
end;

constructor TFFMpegOutputStream.Create(pFormatCtx: PAVFormatContext;
                   pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
                   ffmpegStreamID : Integer; ffmpegStream: PAVStream);
begin
  Create();

  Self.pFormatCtx := pFormatCtx;
  Self.pCodecCtx := pCodecCtx;
  Self.pCodec    := pCodec;
  Self.ffmpegStreamID := ffmpegStreamID;
  Self.ffmpegStream   := ffmpegStream;

  test := Self;
  it:=0;
end;

procedure TFFMpegOutputStream.Play();
var
  err: TPaError;
begin
  writeln('Play request');
  if(status = sStopped) then
  begin
    writeln('Play ok');
    status := sPlaying;
    Self.parseThread := SDL_CreateThread(@ParseAudio, Self);
    //SDL_PauseAudio(0);

    err := Pa_StartStream(Self.channel);
  end;
end;

procedure TFFMpegOutputStream.Pause();
begin
end;

procedure TFFMpegOutputStream.Stop();
begin
end;

procedure TFFMpegOutputStream.Close();
begin
  // Close the codec
  avcodec_close(pCodecCtx);

  // Close the video file
  av_close_input_file(pFormatCtx);
end;


function TAudio_FFMpeg.GetName: String;
begin
  result := 'FFMpeg';
end;

procedure TAudio_FFMpeg.InitializePlayback;
begin
  Log.LogStatus('InitializePlayback', 'UAudio_FFMpeg');

  Loaded := false;
  Loop   := false;

  av_register_all();
  //SDL_Init(SDL_INIT_AUDIO);
  Pa_Initialize();

  StartSoundStream   := LoadSoundFromFile(SoundPath + 'Common start.mp3');
  {
  BackSoundStream    := LoadSoundFromFile(SoundPath + 'Common Back.mp3');
  SwooshSoundStream  := LoadSoundFromFile(SoundPath + 'menu swoosh.mp3');
  ChangeSoundStream  := LoadSoundFromFile(SoundPath + 'select music change music 50.mp3');
  OptionSoundStream  := LoadSoundFromFile(SoundPath + 'option change col.mp3');
  ClickSoundStream   := LoadSoundFromFile(SoundPath + 'rimshot022b.mp3');
  }
//  DrumSoundStream  := LoadSoundFromFile(SoundPath + 'bassdrumhard076b.mp3');
//  HihatSoundStream := LoadSoundFromFile(SoundPath + 'hihatclosed068b.mp3');
//  ClapSoundStream  := LoadSoundFromFile(SoundPath + 'claps050b.mp3');

//  ShuffleSoundStream := LoadSoundFromFile(SoundPath + 'Shuffle.mp3');
end;


procedure TAudio_FFMpeg.SetVolume(Volume: integer);
begin
  //New: Sets Volume only for this Application
(*
  BASS_SetConfig(BASS_CONFIG_GVOL_SAMPLE, Volume);
  BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, Volume);
  BASS_SetConfig(BASS_CONFIG_GVOL_MUSIC, Volume);
*)
end;

procedure TAudio_FFMpeg.SetMusicVolume(Volume: Integer);
begin
  //Max Volume Prevention
  if Volume > 100 then
    Volume := 100;

  if Volume < 0 then
    Volume := 0;


  //Set Volume
//  BASS_ChannelSetAttributes (Bass, -1, Volume, -101);
end;

procedure TAudio_FFMpeg.SetLoop(Enabled: boolean);
begin
  Loop := Enabled;
end;

function TAudio_FFMpeg.Open(Name: string): boolean;
begin
  Loaded := false;
  if FileExists(Name) then
  begin
//    Bass := Bass_StreamCreateFile(false, pchar(Name), 0, 0, 0);

    Loaded := true;
    //Set Max Volume
    SetMusicVolume (100);
  end;

  Result := Loaded;
end;

procedure TAudio_FFMpeg.Rewind;
begin
  if Loaded then
  begin
  end;
end;

procedure TAudio_FFMpeg.MoveTo(Time: real);
var
  bytes:    integer;
begin
//  bytes := BASS_ChannelSeconds2Bytes(Bass, Time);
//  BASS_ChannelSetPosition(Bass, bytes);
end;

procedure TAudio_FFMpeg.Play;
begin
  if MusicStream <> nil then
  if Loaded then
  begin
    if Loop then
    begin
    end;
    // start from beginning...
    // actually bass itself does not loop, nor does this TAudio_FFMpeg Class
    MusicStream.Play();
  end;
end;

procedure TAudio_FFMpeg.Pause; //Pause Mod
begin
  if MusicStream <> nil then
  if Loaded then begin
    MusicStream.Pause(); // Pauses Song
  end;
end;

procedure TAudio_FFMpeg.Stop;
begin
  if MusicStream <> nil then
  MusicStream.Stop();
end;

procedure TAudio_FFMpeg.Close;
begin
  if MusicStream <> nil then
  MusicStream.Close();
end;

function TAudio_FFMpeg.Length: real;
var
  bytes: integer;
begin
  Result := 0;
  // Todo : why is Music stream always nil !?

  if assigned( MusicStream ) then
  begin
    Result := MusicStream.pFormatCtx^.duration / AV_TIME_BASE;
  end;
end;

function TAudio_FFMpeg.getPosition: real;
var
  bytes: integer;
begin
  Result := 0;

(*
  bytes  := BASS_ChannelGetPosition(BASS);
  Result := BASS_ChannelBytes2Seconds(BASS, bytes);
*)
end;

function TAudio_FFMpeg.Finished: boolean;
begin
  Result := false;

(*
  if BASS_ChannelIsActive(BASS) = BASS_ACTIVE_STOPPED then
  begin
    Result := true;
  end;
*)
end;

procedure TAudio_FFMpeg.PlayStart;
begin
  if StartSoundStream <> nil then
  StartSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayBack;
begin
  if BackSoundStream <> nil then
  BackSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlaySwoosh;
begin
  if SwooshSoundStream <> nil then
  SwooshSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayChange;
begin
  if ChangeSoundStream <> nil then
  ChangeSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayOption;
begin
  if OptionSoundStream <> nil then
  OptionSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayClick;
begin
  if ClickSoundStream <> nil then
  ClickSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayDrum;
begin
  if DrumSoundStream <> nil then
  DrumSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayHihat;
begin
  if HihatSoundStream <> nil then
  HihatSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayClap;
begin
  if ClapSoundStream <> nil then
  ClapSoundStream.Play();
end;

procedure TAudio_FFMpeg.PlayShuffle;
begin
  if ShuffleSoundStream <> nil then
  ShuffleSoundStream.Play();
end;

procedure TAudio_FFMpeg.StopShuffle;
begin
  if ShuffleSoundStream <> nil then
  ShuffleSoundStream.Stop();
end;


function TFFMpegOutputStream.AudioDecodeFrame(buffer : PUInt8; bufSize: integer): integer;
var
  len1,
  data_size: integer;
begin
  result := -1;

  if (buffer = nil)  then
    exit;

  while true do
  begin
    while (audio_pkt_size > 0) do
    begin
//      writeln( 'got audio packet' );
      data_size := bufSize;

      if(pCodecCtx = nil) then begin
//        writeln('Das wars');
        exit;
      end;

      // TODO: should be avcodec_decode_audio2 but this wont link on my ubuntu box.
      len1 := avcodec_decode_audio(pCodecCtx, Pointer(buffer),
                  data_size, audio_pkt_data, audio_pkt_size);

      //writeln('avcodec_decode_audio : ' + inttostr( len1 ));

      if(len1 < 0) then
      begin
	      // if error, skip frame
//       	writeln( 'Skip audio frame' );
        audio_pkt_size := 0;
       	break;
      end;

      audio_pkt_data := audio_pkt_data + len1;
      audio_pkt_size := audio_pkt_size + len1;

      if (data_size <= 0) then
      begin
    	  // No data yet, get more frames
     	  continue;
      end;

      // We have data, return it and come back for more later
      result := data_size;
      exit;
    end;

    inc(it);
    if (pkt.data <> nil) then
    begin
      av_free_packet(pkt);
    end;

    if (packetQueue.quit) then
    begin
      result := -1;
      exit;
    end;
//    writeln(it);
    if (packetQueue.Get(pkt, true) < 0) then
    begin
      result := -1;
      exit;
    end;

    audio_pkt_data := PChar(pkt.data);
    audio_pkt_size := pkt.size;
//    writeln( 'Audio Packet Size - ' + inttostr(audio_pkt_size) );
  end;
end;

procedure SDL_AudioCallback(userdata: Pointer; stream: PUInt8; len: Integer); cdecl;
var
  outStream       : TFFMpegOutputStream;
  len1,
  audio_size      : integer;
  pSrc            : Pointer;
begin
  outStream := TFFMpegOutputStream(userdata);

  while (len > 0) do
  with outStream do begin
    if (audio_buf_index >= audio_buf_size) then
    begin
      // We have already sent all our data; get more
      audio_size := AudioDecodeFrame(@audio_buf, sizeof(TAudioBuffer));
      //writeln('audio_decode_frame : '+ inttostr(audio_size));

      if(audio_size < 0) then
      begin
      	// If error, output silence
        audio_buf_size := 1024;
        FillChar(audio_buf, audio_buf_size, #0);
        //writeln( 'Silence' );
      end
      else
      begin
      	audio_buf_size := audio_size;
      end;
      audio_buf_index := 0;  // Todo : jb - SegFault ?
    end;

    len1 := audio_buf_size - audio_buf_index;
    if (len1 > len) then
      len1 := len;

    pSrc := PChar(@audio_buf) + audio_buf_index;
    {$ifdef WIN32}
      CopyMemory(stream, pSrc , len1);
    {$else}
      memcpy(stream, pSrc , len1);
    {$endif}

    Dec(len, len1);
    Inc(stream, len1);
    Inc(audio_buf_index, len1);
  end;
end;

function Pa_AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
      timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
      userData: Pointer): Integer; cdecl;
var
  outStream       : TFFMpegOutputStream;
  len1,
  audio_size      : integer;
  pSrc            : Pointer;
  len             : integer;
begin
  outStream := TFFMpegOutputStream(userData);
  len := frameCount * 2;  // use *2 for mono-files

  while (len > 0) do
  with outStream do begin
    if (audio_buf_index >= audio_buf_size) then
    begin
      // We have already sent all our data; get more
      audio_size := AudioDecodeFrame(@audio_buf, sizeof(TAudioBuffer));
      //writeln('audio_decode_frame : '+ inttostr(audio_size));

      if(audio_size < 0) then
      begin
      	// If error, output silence
        audio_buf_size := 1024;
        FillChar(audio_buf, audio_buf_size, #0);
        //writeln( 'Silence' );
      end
      else
      begin
      	audio_buf_size := audio_size;
      end;
      audio_buf_index := 0;  // Todo : jb - SegFault ?
    end;

    len1 := audio_buf_size - audio_buf_index;
    if (len1 > len) then
      len1 := len;

    pSrc := PChar(@audio_buf) + audio_buf_index;
    {$ifdef WIN32}
      CopyMemory(output, pSrc , len1);
    {$else}
      memcpy(output, pSrc , len1);
    {$endif}

    Dec(len, len1);
    Inc(PChar(output), len1);
    Inc(audio_buf_index, len1);
  end;

  result := paContinue;
end;

function TAudio_FFMpeg.FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
var
  i : integer;
  streamID: integer;
  stream : PAVStream;
begin
  // Find the first audio stream
  streamID := -1;

  for i := 0 to pFormatCtx^.nb_streams-1 do
  begin
    //Log.LogStatus('aFormatCtx.streams[i] : ' + inttostr(i), 'UAudio_FFMpeg');
    stream := pFormatCtx^.streams[i];

    if ( stream.codec^.codec_type = CODEC_TYPE_AUDIO ) then
    begin
      Log.LogStatus('Found Audio Stream', 'UAudio_FFMpeg');
      streamID := i;
      break;
    end;
  end;

  result := streamID;
end;

function ParseAudio(data: Pointer): integer; cdecl;
var
  packet: TAVPacket;
  stream: TFFMpegOutputStream;
begin
  stream := TFFMpegOutputStream(data);

  while (av_read_frame(stream.pFormatCtx, packet) >= 0) do
  begin
    //writeln( 'ffmpeg - av_read_frame' );

    if (packet.stream_index = stream.ffmpegStreamID) then
    begin
      //writeln( 'packet_queue_put' );
      stream.packetQueue.put(@packet);
    end
    else
    begin
      av_free_packet(packet);
    end;
  end;

  //Writeln('Done: ' + inttostr(stream.packetQueue.nbPackets));

  result := 0;
end;


function TAudio_FFMpeg.LoadSoundFromFile(Name: string): TFFMpegOutputStream;
var
  pFormatCtx     : PAVFormatContext;
  pCodecCtx      : PAVCodecContext;
  pCodec         : PAVCodec;
  ffmpegStreamID : Integer;
  ffmpegStream   : PAVStream;
  wanted_spec,
  spec           : TSDL_AudioSpec;
  csIndex        : integer;
  stream         : TFFMpegOutputStream;
  err            : TPaError;
begin
  result := nil;

  if (not FileExists(Name)) then
  begin
    Log.LogStatus('LoadSoundFromFile: Sound not found "' + Name + '"', 'UAudio_FFMpeg');
    writeln('ERROR : LoadSoundFromFile: Sound not found "' + Name + '"', 'UAudio_FFMpeg');
    exit;
  end;

  // Open audio file
  if (av_open_input_file(pFormatCtx, PChar(Name), nil, 0, nil) > 0) then
    exit;

  // Retrieve stream information
  if (av_find_stream_info(pFormatCtx) < 0) then
    exit;

  dump_format(pFormatCtx, 0, pchar(Name), 0);

  ffmpegStreamID := FindAudioStreamID(pFormatCtx);
  if (ffmpegStreamID < 0) then
    exit;

  //Log.LogStatus('Audio Stream ID is : '+ inttostr(ffmpegStreamID), 'UAudio_FFMpeg');

  ffmpegStream := pFormatCtx.streams[ffmpegStreamID];
  pCodecCtx := ffmpegStream^.codec;

  pCodec := avcodec_find_decoder(pCodecCtx^.codec_id);
  if (pCodec = nil) then
  begin
    Log.LogStatus('Unsupported codec!', 'UAudio_FFMpeg');
    exit;
  end;

  avcodec_open(pCodecCtx, pCodec);
  //writeln( 'Opened the codec' );

  stream := TFFMpegOutputStream.Create(pFormatCtx, pCodecCtx, pCodec,
              ffmpegStreamID, ffmpegStream);

  {
  // Set SDL audio settings from codec info
  wanted_spec.freq     := pCodecCtx^.sample_rate;
  wanted_spec.format   := AUDIO_S16SYS;
  wanted_spec.channels := pCodecCtx^.channels;
  wanted_spec.silence  := 0;
  wanted_spec.samples  := SDL_AUDIO_BUFFER_SIZE;
  wanted_spec.callback := AudioCallback;
  wanted_spec.userdata := stream;

  // TODO: this works only one time (?)
  if (SDL_OpenAudio(@wanted_spec, @spec) < 0) then
  begin
    Log.LogStatus('SDL_OpenAudio: '+SDL_GetError(), 'UAudio_FFMpeg');
    stream.Free();
    exit;
  end;
  }

  err := Pa_OpenDefaultStream(stream.channel, 0, pCodecCtx^.channels, paInt16,
          pCodecCtx^.sample_rate, SDL_AUDIO_BUFFER_SIZE div 2, //paFramesPerBufferUnspecified,
          @PA_AudioCallback, stream);
  if(err <> paNoError) then begin
    Log.LogStatus('Pa_OpenDefaultStream: '+Pa_GetErrorText(err), 'UAudio_FFMpeg');
    stream.Free();
    exit;
  end;

  Log.LogStatus('SDL opened audio device', 'UAudio_FFMpeg');

  //Add CustomSound
  csIndex := High(CustomSounds) + 1;
  SetLength (CustomSounds, csIndex + 1);
  CustomSounds[csIndex].Filename := Name;
  CustomSounds[csIndex].Stream := stream;

  result := stream;
end;

//Equalizer
function TAudio_FFMpeg.GetFFTData: TFFTData;
var
  data: TFFTData;
begin
  //Get Channel Data Mono and 256 Values
//  BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
  result := data;
end;

// Interface for Visualizer
function TAudio_FFMpeg.GetPCMData(var data: TPCMData): Cardinal;
begin
  result := 0;
end;

function TAudio_FFMpeg.LoadCustomSound(const Filename: String): Cardinal;
var
  S: TFFMpegOutputStream;
  I: Integer;
  F: String;
begin
  //Search for Sound in already loaded Sounds
  F := UpperCase(SoundPath + FileName);
  For I := 0 to High(CustomSounds) do
  begin
    if (UpperCase(CustomSounds[I].Filename) = F) then
    begin
      Result := I;
      Exit;
    end;
  end;

  S := LoadSoundFromFile(SoundPath + Filename);
  if (S <> nil) then
    Result := High(CustomSounds)
  else
    Result := 0;
end;

procedure TAudio_FFMpeg.PlayCustomSound(const Index: Cardinal );
begin
  if Index <= High(CustomSounds) then
    (CustomSounds[Index].Stream as TFFMpegOutputStream).Play();
end;


constructor TPacketQueue.Create();
begin
  inherited;

  firstPkt := nil;
  lastPkt  := nil;
  nbPackets := 0;
  size := 0;

  mutex := SDL_CreateMutex();
  cond  := SDL_CreateCond();
end;

function TPacketQueue.Put(pkt : PAVPacket): integer;
var
  pkt1 : PAVPacketList;
begin
  result := -1;

  if (av_dup_packet(pkt) < 0) then
    exit;
    
  pkt1 := av_malloc(sizeof(TAVPacketList));
  if (pkt1 = nil) then
    exit;

  pkt1^.pkt  := pkt^;
  pkt1^.next := nil;


  SDL_LockMutex(Self.mutex);
  try

    if (Self.lastPkt = nil) then
      Self.firstPkt := pkt1
    else
      Self.lastPkt^.next := pkt1;
      
    Self.lastPkt := pkt1;
    inc(Self.nbPackets);

//    Writeln('Put: ' + inttostr(nbPackets));

    Self.size := Self.size + pkt1^.pkt.size;
    SDL_CondSignal(Self.cond);

  finally
    SDL_UnlockMutex(Self.mutex);
  end;

  result := 0;
end;

function TPacketQueue.Get(var pkt: TAVPacket; block: boolean): integer;
var
  pkt1 : PAVPacketList;
begin
  result := -1;

  SDL_LockMutex(Self.mutex);
  try
    while true do
    begin
      if (quit) then
        exit;

      pkt1 := Self.firstPkt;

      if (pkt1 <> nil) then
      begin
        Self.firstPkt := pkt1.next;
        if (Self.firstPkt = nil) then
      	  Self.lastPkt := nil;
        dec(Self.nbPackets);

//        Writeln('Get: ' + inttostr(nbPackets));

        Self.size := Self.size - pkt1^.pkt.size;
        pkt := pkt1^.pkt;
        av_free(pkt1);

        result := 1;
        break;
      end
      else
      if (not block) then
      begin
        result := 0;
        break;
      end
      else
      begin
        SDL_CondWait(Self.cond, Self.mutex);
      end;
    end;
  finally
    SDL_UnlockMutex(Self.mutex);
  end;
end;



initialization
  singleton_MusicFFMpeg := TAudio_FFMpeg.create();

  writeln( 'UAudio_FFMpeg - Register Playback' );
  AudioManager.add( IAudioPlayback( singleton_MusicFFMpeg ) );

finalization
  AudioManager.Remove( IAudioPlayback( singleton_MusicFFMpeg ) );


end.