aboutsummaryrefslogblamecommitdiffstats
path: root/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
blob: f1c9b364baaedf9d42447f15eeaf6e7e5650cb91 (plain) (tree)
1
2
3
4
5
6
7





                                                                                
 











                                                                                
                 
 
                             


          

           



              






                                            
           

       
          
          
 









                                 
                            





                                             
                                                                            
                                                                
                        
                        

      


                                    





                                         


                               





                                                                          




                                                                                





                                              
 
                               

                            
 


                           



                                

                                    



                                        

                                    
 

                                                       
                                   



                                  
                                     

                                

                                     








                                                                              

                             



                                                                                
                                                                              




                                                      
                                                                

                                                      
                                                      
                                                      






                                                                            
                                                                                  



                                            
                                          


                                                                
                                                                       








                                                                    
                                                                         

                                   


                     







                                       
                                      



                                

                                              
 








                                                                  


                                        
                

    



                                   
                                 
 
                                                           

    







                                                                                          

                                         



                                                                             
          
 
                    




                             

                         




                                    


                          








                                

    


                                      
     



























                                                                     

    
                                                          
     
                       
    
 
                                            
     





                                              



                                               
                                                

    
                                                                    
     
                       

    

                                              


                     



                                                     
















                                                       


                                                 
     
                                                      
                                            
                       

    
                                                      
     

               
                                        










                                                                              

                             

                 
    
 
                                                              
   
                              








                                           
                          
                    
                       
                          

                 
     
                       
 
                 
       

                 
                                  
                         
         


                                                   

        
                         
         
                     



                          
                         
         




                           
                                                                                   





                                                                                                      
           
                                                                                          


           
                            
                                                          
          
                           

        
                   
 
                                                




                    
                                                   
         

                                             
                                             
                                
             
                                 
              

                                                    
                                      
           


                                                        



                         
                                        
           


                                                                              

                 
 



                                        
 
                                                     
         
                               


         
                              

        








                                                                                              
             

         
                


                                 

                           













                                                                                                 


                       

                                  
                                           
                
                            
              






                                

                                       

          



                                                               
                                                             





                             
                           

        

                                      

           

                                                       


                                            

                                             
         


























                                                                             


               




                                                    
         
                                                              
        




                                                                                 

                                                                    
     
               
 



                                                           
             

         


                                   

                                               
                                                    


                                                                     
           
                                          

                                                



                           




                                                                                                     
 
                                                            
 


                                     

      
                    

    




                                              
                             







                                                               

                 
 


                                                         

    
                                                                                                 

              
                       

                     
                                
                    








                                                                               
                       



            
                        








                                                                               

                                       
                






                                                                                            
                    
                                                                             

         


                                                                  
                                


                                               


                                                                                    
                            
                                                 
          
 
                                                     


                              
                                                                                      










                                                         





                                                  



                                                                            
 
                                             




                                                           


                                                                                  



                                                                     
                   



















                                  
          




                          









                               





                                                    




                                             

                                    
      







                                           

                            







                                 

                                            



                                






















                                                                                   





                                                                       
               




                            
                            


                            

                           
                                    
                                     
                              

                            






                                                
                              













                                            



                               
















                                                  
    












                                                                
unit UAudioDecoder_FFMpeg;

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

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}

//{$DEFINE DebugFFMpegDecode}

uses
  Classes,
  SysUtils,
  UMusic;

implementation

uses
  UIni,
  UMain,
  avcodec,     // FFMpeg Audio file decoding
  avformat,
  avutil,
  avio,        // used for url_ferror
  mathematics, // used for av_rescale_q
  rational,
  SDL,
  ULog,
  UCommon,
  UConfig;

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

    public
      constructor Create();
      destructor Destroy(); override;

      function Put(pkt : PAVPacket): integer;
      function PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
      function Get(var pkt: TAVPacket; block: boolean): integer;
      procedure Flush();
      procedure Abort();
  end;

const
  MAX_AUDIOQ_SIZE = (5 * 16 * 1024);

const
  STATUS_PACKET: PChar = 'STATUS_PACKET';
const
  PKT_STATUS_FLAG_EOF   = 1;
  PKT_STATUS_FLAG_FLUSH = 2;
  PKT_STATUS_FLAG_ERROR = 3;

type
  PAudioBuffer = ^TAudioBuffer;
  // TODO: should (or must?) be aligned at a 2-byte boundary.
  //   ffmpeg provides a C-macro called DECLARE_ALIGNED for this.
  //   Records can be aligned with the $PACKRECORDS compiler-directive but
  //   are already aligned at a 2-byte boundary by default in FPC.
  //   But what about arrays, are they aligned by a 2-byte boundary too?
  //   Or maybe we have to define a fake record with only an array in it?
  TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;

type
  TFFMpegDecodeStream = class(TAudioDecodeStream)
    private
      decoderLock : PSDL_Mutex;
      parserLock : PSDL_Mutex;
      myint: integer;

      EOFState: boolean; // end-of-stream flag
      ErrorState: boolean;

      resumeCond   : PSDL_Cond;

      quitRequest : boolean;

      seekRequest: boolean;
      seekFlags  : integer;
      seekPos    : int64;

      parseThread: PSDL_Thread;
      packetQueue: TPacketQueue;

      formatInfo : TAudioFormatInfo;

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

      audioClock: double; // stream position in seconds

      // state-vars for DecodeFrame
      pkt             : TAVPacket;
      audio_pkt_data  : PChar;
      audio_pkt_size  : integer;

      // state-vars for AudioCallback
      audio_buf_index : integer;
      audio_buf_size  : integer;
      audio_buf       : TAudioBuffer;

      procedure LockParser();   {$IFDEF HasInline}inline;{$ENDIF}
      procedure UnlockParser(); {$IFDEF HasInline}inline;{$ENDIF}
      function GetParserMutex(): PSDL_Mutex; {$IFDEF HasInline}inline;{$ENDIF}

      procedure LockDecoder();   {$IFDEF HasInline}inline;{$ENDIF}
      procedure UnlockDecoder(); {$IFDEF HasInline}inline;{$ENDIF}

      procedure SetEOF(state: boolean);   {$IFDEF HasInline}inline;{$ENDIF}
      procedure SetError(state: boolean); {$IFDEF HasInline}inline;{$ENDIF}

      procedure ParseAudio();
      function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
    public
      constructor Create(pFormatCtx: PAVFormatContext;
                         pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
                         ffmpegStreamIndex: Integer; ffmpegStream: PAVStream);
      destructor Destroy(); override;

      procedure Close();                     override;

      function GetLength(): real;            override;
      function GetAudioFormatInfo(): TAudioFormatInfo; override;
      function GetPosition: real;            override;
      procedure SetPosition(Time: real);     override;
      function IsEOF(): boolean;             override;
      function IsError(): boolean;           override;

      function ReadData(Buffer: PChar; BufSize: integer): integer; override;
  end;

type
  TAudioDecoder_FFMpeg = class( TInterfacedObject, IAudioDecoder )
    private
      class function FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
    public
      function GetName: String;

      function InitializeDecoder(): boolean;
      function FinalizeDecoder(): boolean;
      function Open(const Filename: string): TAudioDecodeStream;
  end;

function DecodeThreadMain(streamPtr: Pointer): integer; cdecl; forward;

var
  singleton_AudioDecoderFFMpeg : IAudioDecoder;


{ TFFMpegDecodeStream }

constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
                   pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
                   ffmpegStreamIndex : Integer; ffmpegStream: PAVStream);
var
  sampleFormat: TAudioSampleFormat;
begin
  inherited Create();

  packetQueue := TPacketQueue.Create();

  audio_pkt_data := nil;
  audio_pkt_size := 0;

  audio_buf_index := 0;
  audio_buf_size  := 0;

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

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

  case pCodecCtx^.sample_fmt of
    SAMPLE_FMT_U8:  sampleFormat := asfU8;
    SAMPLE_FMT_S16: sampleFormat := asfS16;
    SAMPLE_FMT_S24: sampleFormat := asfS24;
    SAMPLE_FMT_S32: sampleFormat := asfS32;
    SAMPLE_FMT_FLT: sampleFormat := asfFloat;
    else            sampleFormat := asfS16; // try standard format
  end;

  formatInfo := TAudioFormatInfo.Create(
    pCodecCtx^.channels,
    pCodecCtx^.sample_rate,
    sampleFormat
  );

  EOFState := false;
  ErrorState := false;
  decoderLock := SDL_CreateMutex();
  parserLock := SDL_CreateMutex();
  resumeCond := SDL_CreateCond();

  parseThread := SDL_CreateThread(@DecodeThreadMain, Self);
end;

{*
 * Frees the decode-stream data.
 * IMPORTANT: call Close() before freeing the decode-stream to avoid dead-locks.
 * This wakes-up every waiting audio-thread waiting in the packet-queue while
 * performing a ReadData() request.
 * Then assure that no thread uses ReadData anymore (e.g. by stopping the audio-callback).
 * Now you can free the decode-stream.
 *}
destructor TFFMpegDecodeStream.Destroy();
begin
  // wake-up and terminate threads
  // Note: should be called by the caller before Destroy() was called instead
  //   to wake-up a waiting audio-callback thread in the packet-queue.
  //   Otherwise dead-locks are possible.
  Close();

  // Close the codec
  if (pCodecCtx <> nil) then
  begin
    avcodec_close(pCodecCtx);
    pCodecCtx := nil;
  end;

  // Close the video file
  if (pFormatCtx <> nil) then
  begin
    av_close_input_file(pFormatCtx);
    pFormatCtx := nil;
  end;

  FreeAndNil(packetQueue);
  FreeAndNil(formatInfo);

  SDL_DestroyMutex(decoderLock);
  decoderLock := nil;
  SDL_DestroyMutex(parserLock);
  parserLock := nil;
  SDL_DestroyCond(resumeCond);
  resumeCond := nil;

  inherited;
end;

procedure TFFMpegDecodeStream.Close();
var
  status: integer;
begin
  // wake threads waiting for packet-queue data
  packetQueue.Abort();

  // abort parse-thread
  LockParser();
  quitRequest := true;
  SDL_CondSignal(resumeCond);
  UnlockParser();
  // and wait until it terminates
  if (parseThread <> nil) then
  begin
    SDL_WaitThread(parseThread, status);
    parseThread := nil;
  end;

  // NOTE: we cannot free the codecCtx or formatCtx here because
  // a formerly waiting thread in the packet-queue might require them
  // and crash if it tries to access them.
end;

procedure TFFMpegDecodeStream.LockParser();
begin
  SDL_mutexP(parserLock);
end;

procedure TFFMpegDecodeStream.UnlockParser();
begin
  SDL_mutexV(parserLock);
end;

function TFFMpegDecodeStream.GetParserMutex(): PSDL_Mutex;
begin
  Result := parserLock;
end;

procedure TFFMpegDecodeStream.LockDecoder();
begin
  SDL_mutexP(decoderLock);
end;

procedure TFFMpegDecodeStream.UnlockDecoder();
begin
  SDL_mutexV(decoderLock);
end;

function TFFMpegDecodeStream.GetLength(): real;
begin
  Result := pFormatCtx^.duration / AV_TIME_BASE;
end;

function TFFMpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
begin
  Result := formatInfo;
end;

function TFFMpegDecodeStream.IsEOF(): boolean;
begin
  LockDecoder();
  Result := EOFState;
  UnlockDecoder();
end;

procedure TFFMpegDecodeStream.SetEOF(state: boolean);
begin
  LockDecoder();
  EOFState := state;
  UnlockDecoder();
end;

function TFFMpegDecodeStream.IsError(): boolean;
begin
  LockDecoder();
  Result := ErrorState;
  UnlockDecoder();
end;

procedure TFFMpegDecodeStream.SetError(state: boolean);
begin
  LockDecoder();
  ErrorState := state;
  UnlockDecoder();
end;

function TFFMpegDecodeStream.GetPosition(): real;
begin
  // FIXME: the audio-clock might not be that accurate
  // see: tutorial on synching (audio-clock)
  Result := audioClock;
end;

procedure TFFMpegDecodeStream.SetPosition(Time: real);
begin
  LockParser();

  seekPos := Trunc(Time * AV_TIME_BASE);

  seekFlags := 0;
  // Note: the BACKWARD-flag seeks to the first position <= the position
  // searched for. Otherwise e.g. position 0 might not be seeked correct.
  // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
  // following. In streams with few key-frames (like many flv-files) the next
  // key-frame after 0 might be 5secs ahead.
  if (Time < audioClock) then
    seekFlags := AVSEEK_FLAG_BACKWARD;
  seekFlags := AVSEEK_FLAG_ANY;

  seekRequest := true;
  SDL_CondSignal(resumeCond);

  UnlockParser();
end;

function DecodeThreadMain(streamPtr: Pointer): integer; cdecl;
var
  stream: TFFMpegDecodeStream;
begin
  stream := TFFMpegDecodeStream(streamPtr);
  stream.ParseAudio();
  result := 0;
end;

procedure TFFMpegDecodeStream.ParseAudio();
var
  packet: TAVPacket;
  statusPacket: PAVPacket;
  seekTarget: int64;
  stopParsing: boolean;
  pbIOCtx: PByteIOContext;
  err: integer;
  index: integer;
begin
  stopParsing := false;

  while (true) do
  begin
    LockParser();

    // wait if end-of-file reached
    if (stopParsing) then
    begin
      // wait for reuse or destruction of stream
      while not (seekRequest or quitRequest) do
        SDL_CondWait(resumeCond, GetParserMutex());
    end;

    if (quitRequest) then
    begin
      UnlockParser();
      break;
    end;

    // handle seek-request
    if (seekRequest) then
    begin
      // reset status
      SetEOF(false);
      SetError(false);
      stopParsing := false;

      seekTarget := av_rescale_q(seekPos, AV_TIME_BASE_Q, ffmpegStream^.time_base);
      err := av_seek_frame(pFormatCtx, ffmpegStreamIndex, seekTarget, seekFlags);
      // seeking failed -> retry with the default stream (necessary for flv-videos and some ogg-files)
      if (err < 0) then
        err := av_seek_frame(pFormatCtx, -1, seekPos, seekFlags);
      // check if seeking failed
      if (err < 0) then
      begin
        Log.LogStatus('Seek Error in "'+pFormatCtx^.filename+'"', 'UAudioDecoder_FFMpeg');
      end
      else
      begin
        packetQueue.Flush();
        packetQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil);
      end;
      seekRequest := false;
    end;

    UnlockParser();

    if (packetQueue.size > MAX_AUDIOQ_SIZE) then
    begin
      SDL_Delay(10);
      continue;
    end;

    if (av_read_frame(pFormatCtx, packet) < 0) then
    begin
      // failed to read a frame, check reason

      {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
      pbIOCtx := pFormatCtx^.pb;
      {$ELSE}
      pbIOCtx := @pFormatCtx^.pb;
      {$IFEND}

      // check for end-of-file (eof is not an error)
      if (url_feof(pbIOCtx) <> 0) then
      begin
        // signal end-of-file
        packetQueue.putStatus(PKT_STATUS_FLAG_EOF, nil);
        stopParsing := true;
        continue;
      end;

      // check for errors
      if (url_ferror(pbIOCtx) <> 0) then
      begin
        // an error occured -> abort and wait for repositioning or termination
        packetQueue.putStatus(PKT_STATUS_FLAG_ERROR, nil);
        stopParsing := true;
        continue;
      end;

      // no error -> wait for user input
      SDL_Delay(100);
      continue;
    end;

    if (packet.stream_index = ffmpegStreamIndex) then
    begin
      packetQueue.put(@packet);
    end
    else
    begin
      av_free_packet(@packet);
    end;
  end;
end;

function TFFMpegDecodeStream.DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
var
  len1,
  data_size: integer;
begin
  result := -1;

  if EOF then
    exit;

  while(true) do
  begin
    while (audio_pkt_size > 0) do
    begin
      data_size := bufSize;

      try
        {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
        len1 := avcodec_decode_audio2(pCodecCtx, @buffer,
                    data_size, audio_pkt_data, audio_pkt_size);
        {$ELSE}
        // FIXME: with avcodec_decode_audio a package could contain several frames
        //        this is not handled yet
        len1 := avcodec_decode_audio(pCodecCtx, @buffer,
                    data_size, audio_pkt_data, audio_pkt_size);
        {$IFEND}
      except
        Log.LogError('Exception at avcodec_decode_audio(2)!', 'TFFMpegDecodeStream.DecodeFrame');
        len1 := -1;
      end;

      if(len1 < 0) then
      begin
        // if error, skip frame
        {$IFDEF DebugFFMpegDecode}
        DebugWriteln( 'Skip audio frame' );
        {$ENDIF}
        audio_pkt_size := 0;
        break;
      end;

      Inc(audio_pkt_data, len1);
      Dec(audio_pkt_size, len1);

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

      //pts := audioClock;
      audioClock := audioClock + data_size /
          (1.0 * formatInfo.FrameSize * formatInfo.SampleRate);

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

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

    // do not use an aborted queue
    if (packetQueue.abortRequest) then
      exit;

    // request a new packet and block if non available.
    // If this fails, the queue was aborted.
    if (packetQueue.Get(pkt, true) < 0) then
      exit;

    // handle Status-packet
    if (PChar(pkt.data) = STATUS_PACKET) then
    begin
      pkt.data := nil;
      audio_pkt_data := nil;
      audio_pkt_size := 0;

      case (pkt.flags) of
        PKT_STATUS_FLAG_FLUSH:
        begin
          avcodec_flush_buffers(pCodecCtx);
        end;
        PKT_STATUS_FLAG_EOF: // end-of-file
        begin
          SetEOF(true);
          // buffer contains no data -> result = -1
          exit;
        end;
        PKT_STATUS_FLAG_ERROR:
        begin
          SetError(true);
          Log.LogStatus('I/O Error', 'TFFMpegDecodeStream.DecodeFrame');
          exit;
        end;
        else
        begin
          Log.LogStatus('Unknown status', 'TFFMpegDecodeStream.DecodeFrame');
        end;
      end;

      continue;
    end;

    audio_pkt_data := PChar(pkt.data);
    audio_pkt_size := pkt.size;

    // if available, update the audio clock with pts
    if(pkt.pts <> AV_NOPTS_VALUE) then
    begin
      audioClock := av_q2d(ffmpegStream^.time_base) * pkt.pts;
    end;
  end;
end;

function TFFMpegDecodeStream.ReadData(Buffer : PChar; BufSize: integer): integer;
var
  nBytesCopy:   integer; // number of bytes to copy
  nBytesRemain: integer; // number of bytes left (remaining) to read
begin
  result := -1;

  // init number of bytes left to copy to the output buffer
  nBytesRemain := BufSize;

  // leave if end-of-file was reached previously
  if EOF then
    exit;

  // copy data to output buffer
  while (nBytesRemain > 0) do begin
    // check if we need more data
    if (audio_buf_index >= audio_buf_size) then
    begin
      // we have already sent all our data; get more
      audio_buf_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
      // check for errors or EOF
      if(audio_buf_size < 0) then
      begin
        // fill decode-buffer with silence
        audio_buf_size := 1024;
        FillChar(audio_buf, audio_buf_size, #0);
      end;
      audio_buf_index := 0;
    end;

    // calc number of new bytes in the decode-buffer
    nBytesCopy := audio_buf_size - audio_buf_index;
    // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
    if (nBytesCopy > nBytesRemain) then
      nBytesCopy := nBytesRemain;

    Move(audio_buf[audio_buf_index], Buffer[0], nBytesCopy);

    Dec(nBytesRemain,    nBytesCopy);
    Inc(Buffer,          nBytesCopy);
    Inc(audio_buf_index, nBytesCopy);
  end;

  Result := BufSize;
end;


{ TAudioDecoder_FFMpeg }

function TAudioDecoder_FFMpeg.GetName: String;
begin
  Result := 'FFMpeg_Decoder';
end;

function TAudioDecoder_FFMpeg.InitializeDecoder: boolean;
begin
  //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFMpeg');

  av_register_all();

  Result := true;
end;

function TAudioDecoder_FFMpeg.FinalizeDecoder(): boolean;
begin
  Result := true;
end;

class function TAudioDecoder_FFMpeg.FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
var
  i : integer;
  streamIndex: integer;
  stream : PAVStream;
begin
  // find the first audio stream
  streamIndex := -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');
      streamIndex := i;
      break;
    end;
  end;

  Result := streamIndex;
end;

function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
var
  pFormatCtx     : PAVFormatContext;
  pCodecCtx      : PAVCodecContext;
  pCodec         : PAVCodec;
  ffmpegStreamID : Integer;
  ffmpegStream   : PAVStream;
  stream         : TFFMpegDecodeStream;
begin
  Result := nil;

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

  // open audio file
  if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) <> 0) then
    exit;

  // TODO: do we need to generate PTS values if they do not exist?
  //pFormatCtx^.flags := pFormatCtx^.flags or AVFMT_FLAG_GENPTS;
  
  // retrieve stream information
  if (av_find_stream_info(pFormatCtx) < 0) then
    exit;

  // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
  pFormatCtx^.pb.eof_reached := 0;

  {$IFDEF DebugFFMpegDecode}
  dump_format(pFormatCtx, 0, pchar(Filename), 0);
  {$ENDIF}

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

  //Log.LogStatus('AudioStreamIndex 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;

  // set debug options
  pCodecCtx^.debug_mv := 0;
  pCodecCtx^.debug := 0;

  // detect bug-workarounds automatically
  pCodecCtx^.workaround_bugs := FF_BUG_AUTODETECT;
  // error resilience strategy (careful/compliant/agressive/very_aggressive)
  //pCodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
  // allow non spec compliant speedup tricks.
  //pCodecCtx^.flags2 := pCodecCtx^.flags2 or CODEC_FLAG2_FAST;

  // Note: avcodec_open() is not thread-safe!
  if (avcodec_open(pCodecCtx, pCodec) < 0) then
  begin
    Log.LogStatus('avcodec_open failed!', 'UAudio_FFMpeg');
    exit;
  end;

  // TODO: what about pCodecCtx^.start_time? Should we seek to this position here?
  // ...

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

  Result := stream;
end;


{ TPacketQueue }

constructor TPacketQueue.Create();
begin
  inherited;

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

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

destructor TPacketQueue.Destroy();
begin
  Flush();
  SDL_DestroyMutex(mutex);
  SDL_DestroyCond(cond);
  inherited;
end;

procedure TPacketQueue.Abort();
begin
  SDL_LockMutex(mutex);

  abortRequest := true;

  SDL_CondSignal(cond);
  SDL_UnlockMutex(mutex);
end;

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

  if (pkt = nil) then
    exit;
  
  if (PChar(pkt^.data) <> STATUS_PACKET) then
  begin
    if (av_dup_packet(pkt) < 0) then
      exit;
  end;

  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);

    Self.size := Self.size + pkt1^.pkt.size;
    SDL_CondSignal(Self.cond);
  finally
    SDL_UnlockMutex(Self.mutex);
  end;

  Result := 0;
end;

function TPacketQueue.PutStatus(statusFlag: integer; statusInfo: Pointer): integer;
var
  pkt: PAVPacket;
begin
  // create temp. package
  pkt := av_malloc(SizeOf(TAVPacket));
  if (pkt = nil) then
  begin
    Result := -1;
    Exit;
  end;
  // init package
  av_init_packet(pkt^);
  pkt^.data  := Pointer(STATUS_PACKET);
  pkt^.flags := statusFlag;
  pkt^.priv  := statusInfo;
  // put a copy of the package into the queue
  Result := Put(pkt);
  // data has been copied -> delete temp. package
  av_free(pkt);
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 (abortRequest) 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);

        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;

procedure TPacketQueue.Flush();
var
  pkt, pkt1: PAVPacketList;
begin
  SDL_LockMutex(Self.mutex);

  pkt := Self.firstPkt;
  while(pkt <> nil) do
  begin
    pkt1 := pkt^.next;
    av_free_packet(@pkt^.pkt);
    // Note: param must be a pointer to a pointer!
    av_freep(@pkt);
    pkt := pkt1;
  end;
  Self.lastPkt := nil;
  Self.firstPkt := nil;
  Self.nbPackets := 0;
  Self.size := 0;

  SDL_UnlockMutex(Self.mutex);
end;


initialization
  singleton_AudioDecoderFFMpeg := TAudioDecoder_FFMpeg.create();

  //writeln( 'UAudioDecoder_FFMpeg - Register Decoder' );
  AudioManager.add( singleton_AudioDecoderFFMpeg );

finalization
  AudioManager.Remove( singleton_AudioDecoderFFMpeg );


end.