aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code/Classes/UAudioDecoder_FFMpeg.pas')
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas1114
1 files changed, 0 insertions, 1114 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
deleted file mode 100644
index d9b4c93c..00000000
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ /dev/null
@@ -1,1114 +0,0 @@
-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}
-
-// show FFmpeg specific debug output
-{.$DEFINE DebugFFmpegDecode}
-
-// FFmpeg is very verbose and shows a bunch of errors.
-// Those errors (they can be considered as warnings by us) can be ignored
-// as they do not give any useful information.
-// There is no solution to fix this except for turning them off.
-{.$DEFINE EnableFFmpegErrorOutput}
-
-implementation
-
-uses
- Classes,
- SysUtils,
- Math,
- UMusic,
- UIni,
- UMain,
- avcodec,
- avformat,
- avutil,
- avio,
- mathematics, // used for av_rescale_q
- rational,
- UMediaCore_FFmpeg,
- SDL,
- ULog,
- UCommon,
- UConfig;
-
-const
- MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
-
-const
- // TODO: The factor 3/2 might not be necessary as we do not need extra
- // space for synchronizing as in the tutorial.
- AUDIO_BUFFER_SIZE = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) div 2;
-
-type
- TFFmpegDecodeStream = class(TAudioDecodeStream)
- private
- StateLock: PSDL_Mutex;
-
- EOFState: boolean; // end-of-stream flag (locked by StateLock)
- ErrorState: boolean; // error flag (locked by StateLock)
-
- QuitRequest: boolean; // (locked by StateLock)
- ParserIdleCond: PSDL_Cond;
-
- // parser pause/resume data
- ParserLocked: boolean;
- ParserPauseRequestCount: integer;
- ParserUnlockedCond: PSDL_Cond;
- ParserResumeCond: PSDL_Cond;
-
- SeekRequest: boolean; // (locked by StateLock)
- SeekFlags: integer; // (locked by StateLock)
- SeekPos: double; // stream position to seek for (in secs) (locked by StateLock)
- SeekFlush: boolean; // true if the buffers should be flushed after seeking (locked by StateLock)
- SeekFinishedCond: PSDL_Cond;
-
- Loop: boolean; // (locked by StateLock)
-
- ParseThread: PSDL_Thread;
- PacketQueue: TPacketQueue;
-
- FormatInfo: TAudioFormatInfo;
-
- // FFmpeg specific data
- FormatCtx: PAVFormatContext;
- CodecCtx: PAVCodecContext;
- Codec: PAVCodec;
-
- AudioStreamIndex: integer;
- AudioStream: PAVStream;
- AudioStreamPos: double; // stream position in seconds (locked by DecoderLock)
-
- // decoder pause/resume data
- DecoderLocked: boolean;
- DecoderPauseRequestCount: integer;
- DecoderUnlockedCond: PSDL_Cond;
- DecoderResumeCond: PSDL_Cond;
-
- // state-vars for DecodeFrame (locked by DecoderLock)
- AudioPaket: TAVPacket;
- AudioPaketData: PChar;
- AudioPaketSize: integer;
- AudioPaketSilence: integer; // number of bytes of silence to return
-
- // state-vars for AudioCallback (locked by DecoderLock)
- AudioBufferPos: integer;
- AudioBufferSize: integer;
- AudioBuffer: PChar;
-
- Filename: string;
-
- procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean);
- procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF}
- procedure SetError(State: boolean); {$IFDEF HasInline}inline;{$ENDIF}
- function IsSeeking(): boolean;
- function IsQuit(): boolean;
-
- procedure Reset();
-
- procedure Parse();
- function ParseLoop(): boolean;
- procedure PauseParser();
- procedure ResumeParser();
-
- function DecodeFrame(Buffer: PChar; BufferSize: integer): integer;
- procedure FlushCodecBuffers();
- procedure PauseDecoder();
- procedure ResumeDecoder();
- public
- constructor Create();
- destructor Destroy(); override;
-
- function Open(const Filename: string): boolean;
- procedure Close(); override;
-
- function GetLength(): real; override;
- function GetAudioFormatInfo(): TAudioFormatInfo; override;
- function GetPosition: real; override;
- procedure SetPosition(Time: real); override;
- function GetLoop(): boolean; override;
- procedure SetLoop(Enabled: boolean); override;
- function IsEOF(): boolean; override;
- function IsError(): boolean; override;
-
- function ReadData(Buffer: PChar; BufferSize: integer): integer; override;
- end;
-
-type
- TAudioDecoder_FFmpeg = class( TInterfacedObject, IAudioDecoder )
- public
- function GetName: string;
-
- function InitializeDecoder(): boolean;
- function FinalizeDecoder(): boolean;
- function Open(const Filename: string): TAudioDecodeStream;
- end;
-
-var
- FFmpegCore: TMediaCore_FFmpeg;
-
-function ParseThreadMain(Data: Pointer): integer; cdecl; forward;
-
-
-{ TFFmpegDecodeStream }
-
-constructor TFFmpegDecodeStream.Create();
-begin
- inherited Create();
-
- StateLock := SDL_CreateMutex();
- ParserUnlockedCond := SDL_CreateCond();
- ParserResumeCond := SDL_CreateCond();
- ParserIdleCond := SDL_CreateCond();
- SeekFinishedCond := SDL_CreateCond();
- DecoderUnlockedCond := SDL_CreateCond();
- DecoderResumeCond := SDL_CreateCond();
-
- // according to the documentation of avcodec_decode_audio(2), sample-data
- // should be aligned on a 16 byte boundary. Otherwise internal calls
- // (e.g. to SSE or Altivec operations) might fail or lack performance on some
- // CPUs. Although GetMem() in Delphi and FPC seems to use a 16 byte or higher
- // alignment for buffers of this size (alignment depends on the size of the
- // requested buffer), we will set the alignment explicitly as the minimum
- // alignment used by Delphi and FPC is on an 8 byte boundary.
- //
- // Note: AudioBuffer was previously defined as a field of type TAudioBuffer
- // (array[0..AUDIO_BUFFER_SIZE-1] of byte) and hence statically allocated.
- // Fields of records are aligned different to memory allocated with GetMem(),
- // aligning depending on the type but will be at least 2 bytes.
- // AudioBuffer was not aligned to a 16 byte boundary. The {$ALIGN x} directive
- // was not applicable as Delphi in contrast to FPC provides at most 8 byte
- // alignment ({$ALIGN 16} is not supported) by this directive.
- AudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16);
-
- Reset();
-end;
-
-procedure TFFmpegDecodeStream.Reset();
-begin
- ParseThread := nil;
-
- EOFState := false;
- ErrorState := false;
- Loop := false;
- QuitRequest := false;
-
- AudioPaketData := nil;
- AudioPaketSize := 0;
- AudioPaketSilence := 0;
-
- AudioBufferPos := 0;
- AudioBufferSize := 0;
-
- ParserLocked := false;
- ParserPauseRequestCount := 0;
- DecoderLocked := false;
- DecoderPauseRequestCount := 0;
-
- FillChar(AudioPaket, SizeOf(TAVPacket), 0);
-end;
-
-{*
- * Frees the decode-stream data.
- *}
-destructor TFFmpegDecodeStream.Destroy();
-begin
- Close();
-
- SDL_DestroyMutex(StateLock);
- SDL_DestroyCond(ParserUnlockedCond);
- SDL_DestroyCond(ParserResumeCond);
- SDL_DestroyCond(ParserIdleCond);
- SDL_DestroyCond(SeekFinishedCond);
- SDL_DestroyCond(DecoderUnlockedCond);
- SDL_DestroyCond(DecoderResumeCond);
-
- FreeAlignedMem(AudioBuffer);
-
- inherited;
-end;
-
-function TFFmpegDecodeStream.Open(const Filename: string): boolean;
-var
- SampleFormat: TAudioSampleFormat;
- AVResult: integer;
-begin
- Result := false;
-
- Close();
- Reset();
-
- if (not FileExists(Filename)) then
- begin
- Log.LogError('Audio-file does not exist: "' + Filename + '"', 'UAudio_FFmpeg');
- Exit;
- end;
-
- Self.Filename := Filename;
-
- // open audio file
- if (av_open_input_file(FormatCtx, PChar(Filename), nil, 0, nil) <> 0) then
- begin
- Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg');
- Exit;
- end;
-
- // generate PTS values if they do not exist
- FormatCtx^.flags := FormatCtx^.flags or AVFMT_FLAG_GENPTS;
-
- // retrieve stream information
- if (av_find_stream_info(FormatCtx) < 0) then
- begin
- Log.LogError('av_find_stream_info failed: "' + Filename + '"', 'UAudio_FFmpeg');
- Close();
- Exit;
- end;
-
- // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
- FormatCtx^.pb.eof_reached := 0;
-
- {$IFDEF DebugFFmpegDecode}
- dump_format(FormatCtx, 0, pchar(Filename), 0);
- {$ENDIF}
-
- AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx);
- if (AudioStreamIndex < 0) then
- begin
- Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename + '"', 'UAudio_FFmpeg');
- Close();
- Exit;
- end;
-
- //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg');
-
- AudioStream := FormatCtx.streams[AudioStreamIndex];
- CodecCtx := AudioStream^.codec;
-
- // TODO: should we use this or not? Should we allow 5.1 channel audio?
- (*
- {$IF LIBAVCODEC_VERSION >= 51042000}
- if (CodecCtx^.channels > 0) then
- CodecCtx^.request_channels := Min(2, CodecCtx^.channels)
- else
- CodecCtx^.request_channels := 2;
- {$IFEND}
- *)
-
- Codec := avcodec_find_decoder(CodecCtx^.codec_id);
- if (Codec = nil) then
- begin
- Log.LogError('Unsupported codec!', 'UAudio_FFmpeg');
- CodecCtx := nil;
- Close();
- Exit;
- end;
-
- // set debug options
- CodecCtx^.debug_mv := 0;
- CodecCtx^.debug := 0;
-
- // detect bug-workarounds automatically
- CodecCtx^.workaround_bugs := FF_BUG_AUTODETECT;
- // error resilience strategy (careful/compliant/agressive/very_aggressive)
- //CodecCtx^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT;
- // allow non spec compliant speedup tricks.
- //CodecCtx^.flags2 := CodecCtx^.flags2 or CODEC_FLAG2_FAST;
-
- // Note: avcodec_open() and avcodec_close() are not thread-safe and will
- // fail if called concurrently by different threads.
- FFmpegCore.LockAVCodec();
- try
- AVResult := avcodec_open(CodecCtx, Codec);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- if (AVResult < 0) then
- begin
- Log.LogError('avcodec_open failed!', 'UAudio_FFmpeg');
- Close();
- Exit;
- end;
-
- // now initialize the audio-format
-
- if (not FFmpegCore.ConvertFFmpegToAudioFormat(CodecCtx^.sample_fmt, SampleFormat)) then
- begin
- // try standard format
- SampleFormat := asfS16;
- end;
-
- FormatInfo := TAudioFormatInfo.Create(
- CodecCtx^.channels,
- CodecCtx^.sample_rate,
- SampleFormat
- );
-
-
- PacketQueue := TPacketQueue.Create();
-
- // finally start the decode thread
- ParseThread := SDL_CreateThread(@ParseThreadMain, Self);
-
- Result := true;
-end;
-
-procedure TFFmpegDecodeStream.Close();
-var
- ThreadResult: integer;
-begin
- // wake threads waiting for packet-queue data
- // Note: normally, there are no waiting threads. If there were waiting
- // ones, they would block the audio-callback thread.
- if (assigned(PacketQueue)) then
- PacketQueue.Abort();
-
- // send quit request (to parse-thread etc)
- SDL_mutexP(StateLock);
- QuitRequest := true;
- SDL_CondBroadcast(ParserIdleCond);
- SDL_mutexV(StateLock);
-
- // abort parse-thread
- if (ParseThread <> nil) then
- begin
- // and wait until it terminates
- SDL_WaitThread(ParseThread, ThreadResult);
- ParseThread := nil;
- end;
-
- // Close the codec
- if (CodecCtx <> nil) then
- begin
- // avcodec_close() is not thread-safe
- FFmpegCore.LockAVCodec();
- try
- avcodec_close(CodecCtx);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- CodecCtx := nil;
- end;
-
- // Close the video file
- if (FormatCtx <> nil) then
- begin
- av_close_input_file(FormatCtx);
- FormatCtx := nil;
- end;
-
- PerformOnClose();
-
- FreeAndNil(PacketQueue);
- FreeAndNil(FormatInfo);
-end;
-
-function TFFmpegDecodeStream.GetLength(): real;
-begin
- // do not forget to consider the start_time value here
- Result := (FormatCtx^.start_time + FormatCtx^.duration) / AV_TIME_BASE;
-end;
-
-function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
-begin
- Result := FormatInfo;
-end;
-
-function TFFmpegDecodeStream.IsEOF(): boolean;
-begin
- SDL_mutexP(StateLock);
- Result := EOFState;
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetEOF(State: boolean);
-begin
- SDL_mutexP(StateLock);
- EOFState := State;
- SDL_mutexV(StateLock);
-end;
-
-function TFFmpegDecodeStream.IsError(): boolean;
-begin
- SDL_mutexP(StateLock);
- Result := ErrorState;
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetError(State: boolean);
-begin
- SDL_mutexP(StateLock);
- ErrorState := State;
- SDL_mutexV(StateLock);
-end;
-
-function TFFmpegDecodeStream.IsSeeking(): boolean;
-begin
- SDL_mutexP(StateLock);
- Result := SeekRequest;
- SDL_mutexV(StateLock);
-end;
-
-function TFFmpegDecodeStream.IsQuit(): boolean;
-begin
- SDL_mutexP(StateLock);
- Result := QuitRequest;
- SDL_mutexV(StateLock);
-end;
-
-function TFFmpegDecodeStream.GetPosition(): real;
-var
- BufferSizeSec: double;
-begin
- PauseDecoder();
-
- // ReadData() does not return all of the buffer retrieved by DecodeFrame().
- // Determine the size of the unused part of the decode-buffer.
- BufferSizeSec := (AudioBufferSize - AudioBufferPos) /
- FormatInfo.BytesPerSec;
-
- // subtract the size of unused buffer-data from the audio clock.
- Result := AudioStreamPos - BufferSizeSec;
-
- ResumeDecoder();
-end;
-
-procedure TFFmpegDecodeStream.SetPosition(Time: real);
-begin
- SetPositionIntern(Time, true, true);
-end;
-
-function TFFmpegDecodeStream.GetLoop(): boolean;
-begin
- SDL_mutexP(StateLock);
- Result := Loop;
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean);
-begin
- SDL_mutexP(StateLock);
- Loop := Enabled;
- SDL_mutexV(StateLock);
-end;
-
-
-(********************************************
- * Parser section
- ********************************************)
-
-procedure TFFmpegDecodeStream.PauseParser();
-begin
- if (SDL_ThreadID() = ParseThread.threadid) then
- Exit;
-
- SDL_mutexP(StateLock);
- Inc(ParserPauseRequestCount);
- while (ParserLocked) do
- SDL_CondWait(ParserUnlockedCond, StateLock);
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.ResumeParser();
-begin
- if (SDL_ThreadID() = ParseThread.threadid) then
- Exit;
-
- SDL_mutexP(StateLock);
- Dec(ParserPauseRequestCount);
- SDL_CondSignal(ParserResumeCond);
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean);
-begin
- // - Pause the parser first to prevent it from putting obsolete packages
- // into the queue after the queue was flushed and before seeking is done.
- // Otherwise we will hear fragments of old data, if the stream was seeked
- // in stopped mode and resumed afterwards (applies to non-blocking mode only).
- // - Pause the decoder to avoid race-condition that might occur otherwise.
- // - Last lock the state lock because we are manipulating some shared state-vars.
- PauseParser();
- PauseDecoder();
- SDL_mutexP(StateLock);
-
- // configure seek parameters
- SeekPos := Time;
- SeekFlush := Flush;
- SeekFlags := AVSEEK_FLAG_ANY;
- SeekRequest := true;
-
- // 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 < AudioStreamPos) then
- SeekFlags := SeekFlags or AVSEEK_FLAG_BACKWARD;
-
- EOFState := false;
- ErrorState := false;
-
- // send a reuse signal in case the parser was stopped (e.g. because of an EOF)
- SDL_CondSignal(ParserIdleCond);
-
- SDL_mutexV(StateLock);
- ResumeDecoder();
- ResumeParser();
-
- // in blocking mode, wait until seeking is done
- if (Blocking) then
- begin
- SDL_mutexP(StateLock);
- while (SeekRequest) do
- SDL_CondWait(SeekFinishedCond, StateLock);
- SDL_mutexV(StateLock);
- end;
-end;
-
-function ParseThreadMain(Data: Pointer): integer; cdecl;
-var
- Stream: TFFmpegDecodeStream;
-begin
- Stream := TFFmpegDecodeStream(Data);
- if (Stream <> nil) then
- Stream.Parse();
- Result := 0;
-end;
-
-procedure TFFmpegDecodeStream.Parse();
-begin
- // reuse thread as long as the stream is not terminated
- while (ParseLoop()) do
- begin
- // wait for reuse or destruction of stream
- SDL_mutexP(StateLock);
- while (not (SeekRequest or QuitRequest)) do
- SDL_CondWait(ParserIdleCond, StateLock);
- SDL_mutexV(StateLock);
- end;
-end;
-
-(**
- * Parser main loop.
- * Will not return until parsing of the stream is finished.
- * Reasons for the parser to return are:
- * - the end-of-file is reached
- * - an error occured
- * - the stream was quited (received a quit-request)
- * Returns true if the stream can be resumed or false if the stream has to
- * be terminated.
- *)
-function TFFmpegDecodeStream.ParseLoop(): boolean;
-var
- Packet: TAVPacket;
- StatusPacket: PAVPacket;
- SeekTarget: int64;
- ByteIOCtx: PByteIOContext;
- ErrorCode: integer;
- StartSilence: double; // duration of silence at start of stream
- StartSilencePtr: PDouble; // pointer for the EMPTY status packet
-
- // Note: pthreads wakes threads waiting on a mutex in the order of their
- // priority and not in FIFO order. SDL does not provide any option to
- // control priorities. This might (and already did) starve threads waiting
- // on the mutex (e.g. SetPosition) making usdx look like it was froozen.
- // Instead of simply locking the critical section we set a ParserLocked flag
- // instead and give priority to the threads requesting the parser to pause.
- procedure LockParser();
- begin
- SDL_mutexP(StateLock);
- while (ParserPauseRequestCount > 0) do
- SDL_CondWait(ParserResumeCond, StateLock);
- ParserLocked := true;
- SDL_mutexV(StateLock);
- end;
-
- procedure UnlockParser();
- begin
- SDL_mutexP(StateLock);
- ParserLocked := false;
- SDL_CondBroadcast(ParserUnlockedCond);
- SDL_mutexV(StateLock);
- end;
-
-begin
- Result := true;
-
- while (true) do
- begin
- LockParser();
- try
-
- if (IsQuit()) then
- begin
- Result := false;
- Exit;
- end;
-
- // handle seek-request (Note: no need to lock SeekRequest here)
- if (SeekRequest) then
- begin
- // first try: seek on the audio stream
- SeekTarget := Round(SeekPos / av_q2d(AudioStream^.time_base));
- StartSilence := 0;
- if (SeekTarget < AudioStream^.start_time) then
- StartSilence := (AudioStream^.start_time - SeekTarget) * av_q2d(AudioStream^.time_base);
- ErrorCode := av_seek_frame(FormatCtx, AudioStreamIndex, SeekTarget, SeekFlags);
-
- if (ErrorCode < 0) then
- begin
- // second try: seek on the default stream (necessary for flv-videos and some ogg-files)
- SeekTarget := Round(SeekPos * AV_TIME_BASE);
- StartSilence := 0;
- if (SeekTarget < FormatCtx^.start_time) then
- StartSilence := (FormatCtx^.start_time - SeekTarget) / AV_TIME_BASE;
- ErrorCode := av_seek_frame(FormatCtx, -1, SeekTarget, SeekFlags);
- end;
-
- // pause decoder and lock state (keep the lock-order to avoid deadlocks).
- // Note that the decoder does not block in the packet-queue in seeking state,
- // so locking the decoder here does not cause a dead-lock.
- PauseDecoder();
- SDL_mutexP(StateLock);
- try
- if (ErrorCode < 0) then
- begin
- // seeking failed
- ErrorState := true;
- Log.LogStatus('Seek Error in "'+FormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg');
- end
- else
- begin
- if (SeekFlush) then
- begin
- // flush queue (we will send a Flush-Packet when seeking is finished)
- PacketQueue.Flush();
-
- // flush the decode buffers
- AudioBufferSize := 0;
- AudioBufferPos := 0;
- AudioPaketSize := 0;
- AudioPaketSilence := 0;
- FlushCodecBuffers();
-
- // Set preliminary stream position. The position will be set to
- // the correct value as soon as the first packet is decoded.
- AudioStreamPos := SeekPos;
- end
- else
- begin
- // request avcodec buffer flush
- PacketQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil);
- end;
-
- // fill the gap between position 0 and start_time with silence
- // but not if we are in loop mode
- if ((StartSilence > 0) and (not Loop)) then
- begin
- GetMem(StartSilencePtr, SizeOf(StartSilence));
- StartSilencePtr^ := StartSilence;
- PacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr);
- end;
- end;
-
- SeekRequest := false;
- SDL_CondBroadcast(SeekFinishedCond);
- finally
- SDL_mutexV(StateLock);
- ResumeDecoder();
- end;
- end;
-
- if (PacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then
- begin
- SDL_Delay(10);
- Continue;
- end;
-
- if (av_read_frame(FormatCtx, Packet) < 0) then
- begin
- // failed to read a frame, check reason
- {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
- ByteIOCtx := FormatCtx^.pb;
- {$ELSE}
- ByteIOCtx := @FormatCtx^.pb;
- {$IFEND}
-
- // check for end-of-file (eof is not an error)
- if (url_feof(ByteIOCtx) <> 0) then
- begin
- if (GetLoop()) then
- begin
- // rewind stream (but do not flush)
- SetPositionIntern(0, false, false);
- Continue;
- end
- else
- begin
- // signal end-of-file
- PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil);
- Exit;
- end;
- end;
-
- // check for errors
- if (url_ferror(ByteIOCtx) <> 0) then
- begin
- // an error occured -> abort and wait for repositioning or termination
- PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil);
- Exit;
- end;
-
- // no error -> wait for user input
- SDL_Delay(100);
- Continue;
- end;
-
- if (Packet.stream_index = AudioStreamIndex) then
- PacketQueue.Put(@Packet)
- else
- av_free_packet(@Packet);
-
- finally
- UnlockParser();
- end;
- end;
-end;
-
-
-(********************************************
- * Decoder section
- ********************************************)
-
-procedure TFFmpegDecodeStream.PauseDecoder();
-begin
- SDL_mutexP(StateLock);
- Inc(DecoderPauseRequestCount);
- while (DecoderLocked) do
- SDL_CondWait(DecoderUnlockedCond, StateLock);
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.ResumeDecoder();
-begin
- SDL_mutexP(StateLock);
- Dec(DecoderPauseRequestCount);
- SDL_CondSignal(DecoderResumeCond);
- SDL_mutexV(StateLock);
-end;
-
-procedure TFFmpegDecodeStream.FlushCodecBuffers();
-begin
- // if no flush operation is specified, avcodec_flush_buffers will not do anything.
- if (@CodecCtx.codec.flush <> nil) then
- begin
- // flush buffers used by avcodec_decode_audio, etc.
- avcodec_flush_buffers(CodecCtx);
- end
- else
- begin
- // we need a Workaround to avoid plopping noise with ogg-vorbis and
- // mp3 (in older versions of FFmpeg).
- // We will just reopen the codec.
- FFmpegCore.LockAVCodec();
- try
- avcodec_close(CodecCtx);
- avcodec_open(CodecCtx, Codec);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- end;
-end;
-
-function TFFmpegDecodeStream.DecodeFrame(Buffer: PChar; BufferSize: integer): integer;
-var
- PaketDecodedSize: integer; // size of packet data used for decoding
- DataSize: integer; // size of output data decoded by FFmpeg
- BlockQueue: boolean;
- SilenceDuration: double;
- {$IFDEF DebugFFmpegDecode}
- TmpPos: double;
- {$ENDIF}
-begin
- Result := -1;
-
- if (EOF) then
- Exit;
-
- while(true) do
- begin
- // for titles with start_time > 0 we have to generate silence
- // until we reach the pts of the first data packet.
- if (AudioPaketSilence > 0) then
- begin
- DataSize := Min(AudioPaketSilence, BufferSize);
- FillChar(Buffer[0], DataSize, 0);
- Dec(AudioPaketSilence, DataSize);
- AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec;
- Result := DataSize;
- Exit;
- end;
-
- // read packet data
- while (AudioPaketSize > 0) do
- begin
- DataSize := BufferSize;
-
- {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
- PaketDecodedSize := avcodec_decode_audio2(CodecCtx, PSmallint(Buffer),
- DataSize, AudioPaketData, AudioPaketSize);
- {$ELSE}
- PaketDecodedSize := avcodec_decode_audio(CodecCtx, PSmallint(Buffer),
- DataSize, AudioPaketData, AudioPaketSize);
- {$IFEND}
-
- if(PaketDecodedSize < 0) then
- begin
- // if error, skip frame
- {$IFDEF DebugFFmpegDecode}
- DebugWriteln('Skip audio frame');
- {$ENDIF}
- AudioPaketSize := 0;
- Break;
- end;
-
- Inc(AudioPaketData, PaketDecodedSize);
- Dec(AudioPaketSize, PaketDecodedSize);
-
- // check if avcodec_decode_audio returned data, otherwise fetch more frames
- if (DataSize <= 0) then
- Continue;
-
- // update stream position by the amount of fetched data
- AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec;
-
- // we have data, return it and come back for more later
- Result := DataSize;
- Exit;
- end;
-
- // free old packet data
- if (AudioPaket.data <> nil) then
- av_free_packet(@AudioPaket);
-
- // do not block queue on seeking (to avoid deadlocks on the DecoderLock)
- if (IsSeeking()) then
- BlockQueue := false
- else
- BlockQueue := true;
-
- // request a new packet and block if none available.
- // If this fails, the queue was aborted.
- if (PacketQueue.Get(AudioPaket, BlockQueue) <= 0) then
- Exit;
-
- // handle Status-packet
- if (PChar(AudioPaket.data) = STATUS_PACKET) then
- begin
- AudioPaket.data := nil;
- AudioPaketData := nil;
- AudioPaketSize := 0;
-
- case (AudioPaket.flags) of
- PKT_STATUS_FLAG_FLUSH:
- begin
- // just used if SetPositionIntern was called without the flush flag.
- FlushCodecBuffers;
- end;
- PKT_STATUS_FLAG_EOF: // end-of-file
- begin
- // ignore EOF while seeking
- if (not IsSeeking()) then
- 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;
- PKT_STATUS_FLAG_EMPTY:
- begin
- SilenceDuration := PDouble(PacketQueue.GetStatusInfo(AudioPaket))^;
- AudioPaketSilence := Round(SilenceDuration * FormatInfo.SampleRate) * FormatInfo.FrameSize;
- PacketQueue.FreeStatusInfo(AudioPaket);
- end
- else
- begin
- Log.LogStatus('Unknown status', 'TFFmpegDecodeStream.DecodeFrame');
- end;
- end;
-
- Continue;
- end;
-
- AudioPaketData := PChar(AudioPaket.data);
- AudioPaketSize := AudioPaket.size;
-
- // if available, update the stream position to the presentation time of this package
- if(AudioPaket.pts <> AV_NOPTS_VALUE) then
- begin
- {$IFDEF DebugFFmpegDecode}
- TmpPos := AudioStreamPos;
- {$ENDIF}
- AudioStreamPos := av_q2d(AudioStream^.time_base) * AudioPaket.pts;
- {$IFDEF DebugFFmpegDecode}
- DebugWriteln('Timestamp: ' + floattostrf(AudioStreamPos, ffFixed, 15, 3) + ' ' +
- '(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' +
- 'Diff: ' + floattostrf(AudioStreamPos-TmpPos, ffFixed, 15, 3));
- {$ENDIF}
- end;
- end;
-end;
-
-function TFFmpegDecodeStream.ReadData(Buffer: PChar; BufferSize: integer): integer;
-var
- CopyByteCount: integer; // number of bytes to copy
- RemainByteCount: integer; // number of bytes left (remain) to read
- BufferPos: integer;
-
- // prioritize pause requests
- procedure LockDecoder();
- begin
- SDL_mutexP(StateLock);
- while (DecoderPauseRequestCount > 0) do
- SDL_CondWait(DecoderResumeCond, StateLock);
- DecoderLocked := true;
- SDL_mutexV(StateLock);
- end;
-
- procedure UnlockDecoder();
- begin
- SDL_mutexP(StateLock);
- DecoderLocked := false;
- SDL_CondBroadcast(DecoderUnlockedCond);
- SDL_mutexV(StateLock);
- end;
-
-begin
- Result := -1;
-
- // set number of bytes to copy to the output buffer
- BufferPos := 0;
-
- LockDecoder();
- try
- // leave if end-of-file is reached
- if (EOF) then
- Exit;
-
- // copy data to output buffer
- while (BufferPos < BufferSize) do
- begin
- // check if we need more data
- if (AudioBufferPos >= AudioBufferSize) then
- begin
- AudioBufferPos := 0;
-
- // we have already sent all our data; get more
- AudioBufferSize := DecodeFrame(AudioBuffer, AUDIO_BUFFER_SIZE);
-
- // check for errors or EOF
- if(AudioBufferSize < 0) then
- begin
- Result := BufferPos;
- Exit;
- end;
- end;
-
- // calc number of new bytes in the decode-buffer
- CopyByteCount := AudioBufferSize - AudioBufferPos;
- // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
- RemainByteCount := BufferSize - BufferPos;
- if (CopyByteCount > RemainByteCount) then
- CopyByteCount := RemainByteCount;
-
- Move(AudioBuffer[AudioBufferPos], Buffer[BufferPos], CopyByteCount);
-
- Inc(BufferPos, CopyByteCount);
- Inc(AudioBufferPos, CopyByteCount);
- end;
- finally
- UnlockDecoder();
- end;
-
- Result := BufferSize;
-end;
-
-
-{ TAudioDecoder_FFmpeg }
-
-function TAudioDecoder_FFmpeg.GetName: String;
-begin
- Result := 'FFmpeg_Decoder';
-end;
-
-function TAudioDecoder_FFmpeg.InitializeDecoder: boolean;
-begin
- //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFmpeg');
- FFmpegCore := TMediaCore_FFmpeg.GetInstance();
- av_register_all();
-
- // Do not show uninformative error messages by default.
- // FFmpeg prints all error-infos on the console by default what
- // is very confusing as the playback of the files is correct.
- // We consider these errors to be internal to FFMpeg. They can be fixed
- // by the FFmpeg guys only and do not provide any useful information in
- // respect to USDX.
- {$IFNDEF EnableFFmpegErrorOutput}
- {$IF LIBAVUTIL_VERSION_MAJOR >= 50}
- av_log_set_level(AV_LOG_FATAL);
- {$ELSE}
- // FATAL and ERROR share one log-level, so we have to use QUIET
- av_log_set_level(AV_LOG_QUIET);
- {$IFEND}
- {$ENDIF}
-
- Result := true;
-end;
-
-function TAudioDecoder_FFmpeg.FinalizeDecoder(): boolean;
-begin
- Result := true;
-end;
-
-function TAudioDecoder_FFmpeg.Open(const Filename: string): TAudioDecodeStream;
-var
- Stream: TFFmpegDecodeStream;
-begin
- Result := nil;
-
- Stream := TFFmpegDecodeStream.Create();
- if (not Stream.Open(Filename)) then
- begin
- Stream.Free;
- Exit;
- end;
-
- Result := Stream;
-end;
-
-
-initialization
- MediaManager.Add(TAudioDecoder_FFmpeg.Create);
-
-end.