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.