From 4c6d2f4cd49a4f6016026ef81db31c3656bb5e8c Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 13 Sep 2008 08:27:50 +0000 Subject: Media modules moved from base to media git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1374 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 1114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1114 insertions(+) create mode 100644 src/media/UAudioDecoder_FFmpeg.pas (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas new file mode 100644 index 00000000..d9b4c93c --- /dev/null +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -0,0 +1,1114 @@ +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. -- cgit v1.2.3 From f16756422a5dbb24ce1b751bb9e2bb1de4f19713 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 23 Sep 2008 21:17:50 +0000 Subject: added file headers git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1404 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index d9b4c93c..399c2f52 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -1,3 +1,28 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + *} + unit UAudioDecoder_FFmpeg; (******************************************************************************* -- cgit v1.2.3 From e0337089ed8777756ecfbdfcbc417a5392e90838 Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 4 Feb 2009 17:42:12 +0000 Subject: PChar replaced by PByteArray (for byte buffers) or PAnsiString (for zero-terminated strings) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1583 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 399c2f52..2d221f40 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -57,7 +57,6 @@ implementation uses Classes, - SysUtils, Math, UMusic, UIni, @@ -68,8 +67,9 @@ uses avio, mathematics, // used for av_rescale_q rational, - UMediaCore_FFmpeg, SDL, + SysUtils, + UMediaCore_FFmpeg, ULog, UCommon, UConfig; @@ -129,14 +129,14 @@ type // state-vars for DecodeFrame (locked by DecoderLock) AudioPaket: TAVPacket; - AudioPaketData: PChar; + AudioPaketData: PByteArray; AudioPaketSize: integer; AudioPaketSilence: integer; // number of bytes of silence to return // state-vars for AudioCallback (locked by DecoderLock) AudioBufferPos: integer; AudioBufferSize: integer; - AudioBuffer: PChar; + AudioBuffer: PByteArray; Filename: string; @@ -153,7 +153,7 @@ type procedure PauseParser(); procedure ResumeParser(); - function DecodeFrame(Buffer: PChar; BufferSize: integer): integer; + function DecodeFrame(Buffer: PByteArray; BufferSize: integer): integer; procedure FlushCodecBuffers(); procedure PauseDecoder(); procedure ResumeDecoder(); @@ -173,11 +173,11 @@ type function IsEOF(): boolean; override; function IsError(): boolean; override; - function ReadData(Buffer: PChar; BufferSize: integer): integer; override; + function ReadData(Buffer: PByteArray; BufferSize: integer): integer; override; end; type - TAudioDecoder_FFmpeg = class( TInterfacedObject, IAudioDecoder ) + TAudioDecoder_FFmpeg = class(TInterfacedObject, IAudioDecoder) public function GetName: string; @@ -289,7 +289,7 @@ begin Self.Filename := Filename; // open audio file - if (av_open_input_file(FormatCtx, PChar(Filename), nil, 0, nil) <> 0) then + if (av_open_input_file(FormatCtx, PAnsiChar(Filename), nil, 0, nil) <> 0) then begin Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg'); Exit; @@ -310,7 +310,7 @@ begin FormatCtx^.pb.eof_reached := 0; {$IFDEF DebugFFmpegDecode} - dump_format(FormatCtx, 0, pchar(Filename), 0); + dump_format(FormatCtx, 0, PAnsiChar(Filename), 0); {$ENDIF} AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); @@ -862,7 +862,7 @@ begin end; end; -function TFFmpegDecodeStream.DecodeFrame(Buffer: PChar; BufferSize: integer): integer; +function TFFmpegDecodeStream.DecodeFrame(Buffer: PByteArray; BufferSize: integer): integer; var PaketDecodedSize: integer; // size of packet data used for decoding DataSize: integer; // size of output data decoded by FFmpeg @@ -945,7 +945,7 @@ begin Exit; // handle Status-packet - if (PChar(AudioPaket.data) = STATUS_PACKET) then + if (PAnsiChar(AudioPaket.data) = STATUS_PACKET) then begin AudioPaket.data := nil; AudioPaketData := nil; @@ -986,7 +986,7 @@ begin Continue; end; - AudioPaketData := PChar(AudioPaket.data); + AudioPaketData := AudioPaket.data; AudioPaketSize := AudioPaket.size; // if available, update the stream position to the presentation time of this package @@ -1005,7 +1005,7 @@ begin end; end; -function TFFmpegDecodeStream.ReadData(Buffer: PChar; BufferSize: integer): integer; +function TFFmpegDecodeStream.ReadData(Buffer: PByteArray; BufferSize: integer): integer; var CopyByteCount: integer; // number of bytes to copy RemainByteCount: integer; // number of bytes left (remain) to read -- cgit v1.2.3 From 9ec474eb703bfb72a88b26f63d40039c860919e6 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 9 Apr 2009 22:48:04 +0000 Subject: Warnings and Notes cleanup git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1662 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 1 - 1 file changed, 1 deletion(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 2d221f40..f62cb92c 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -643,7 +643,6 @@ end; function TFFmpegDecodeStream.ParseLoop(): boolean; var Packet: TAVPacket; - StatusPacket: PAVPacket; SeekTarget: int64; ByteIOCtx: PByteIOContext; ErrorCode: integer; -- cgit v1.2.3 From 62aea784507707eac56c5305fb191365987ae596 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Fri, 15 May 2009 16:09:25 +0000 Subject: resolve some type size mismatch warnings + an additional check. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1727 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index f62cb92c..97d8a8df 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -378,14 +378,14 @@ begin // try standard format SampleFormat := asfS16; end; - + if CodecCtx^.channels > 255 then + Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open'); FormatInfo := TAudioFormatInfo.Create( - CodecCtx^.channels, + byte(CodecCtx^.channels), CodecCtx^.sample_rate, SampleFormat ); - PacketQueue := TPacketQueue.Create(); // finally start the decode thread @@ -446,7 +446,9 @@ end; function TFFmpegDecodeStream.GetLength(): real; begin - // do not forget to consider the start_time value here + // do not forget to consider the start_time value here + // there is a type size mismatch warnign because start_time and duration are cint64. + // So, in principle there could be an overflow when doing the sum. Result := (FormatCtx^.start_time + FormatCtx^.duration) / AV_TIME_BASE; end; -- cgit v1.2.3 From 917901e8e33438c425aef50a0a7417f32d77b760 Mon Sep 17 00:00:00 2001 From: s_alexander Date: Mon, 9 Nov 2009 00:27:55 +0000 Subject: merged unicode branch (r1931) into trunk git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1939 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 97d8a8df..d079afdc 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -56,23 +56,24 @@ interface implementation uses + SDL, // SDL redefines some base types -> include before SysUtils to ignore them Classes, Math, - UMusic, - UIni, - UMain, + SysUtils, avcodec, avformat, avutil, avio, mathematics, // used for av_rescale_q rational, - SDL, - SysUtils, + UMusic, + UIni, + UMain, UMediaCore_FFmpeg, ULog, UCommon, - UConfig; + UConfig, + UPath; const MAX_AUDIOQ_SIZE = (5 * 16 * 1024); @@ -138,7 +139,7 @@ type AudioBufferSize: integer; AudioBuffer: PByteArray; - Filename: string; + Filename: IPath; procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF} @@ -161,7 +162,7 @@ type constructor Create(); destructor Destroy(); override; - function Open(const Filename: string): boolean; + function Open(const Filename: IPath): boolean; procedure Close(); override; function GetLength(): real; override; @@ -183,7 +184,7 @@ type function InitializeDecoder(): boolean; function FinalizeDecoder(): boolean; - function Open(const Filename: string): TAudioDecodeStream; + function Open(const Filename: IPath): TAudioDecodeStream; end; var @@ -270,7 +271,7 @@ begin inherited; end; -function TFFmpegDecodeStream.Open(const Filename: string): boolean; +function TFFmpegDecodeStream.Open(const Filename: IPath): boolean; var SampleFormat: TAudioSampleFormat; AVResult: integer; @@ -280,18 +281,18 @@ begin Close(); Reset(); - if (not FileExists(Filename)) then + if (not Filename.IsFile) then begin - Log.LogError('Audio-file does not exist: "' + Filename + '"', 'UAudio_FFmpeg'); + Log.LogError('Audio-file does not exist: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Exit; end; Self.Filename := Filename; - // open audio file - if (av_open_input_file(FormatCtx, PAnsiChar(Filename), nil, 0, nil) <> 0) then + // use custom 'ufile' protocol for UTF-8 support + if (av_open_input_file(FormatCtx, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil) <> 0) then begin - Log.LogError('av_open_input_file failed: "' + Filename + '"', 'UAudio_FFmpeg'); + Log.LogError('av_open_input_file failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Exit; end; @@ -301,7 +302,7 @@ begin // retrieve stream information if (av_find_stream_info(FormatCtx) < 0) then begin - Log.LogError('av_find_stream_info failed: "' + Filename + '"', 'UAudio_FFmpeg'); + Log.LogError('av_find_stream_info failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); Exit; end; @@ -310,13 +311,13 @@ begin FormatCtx^.pb.eof_reached := 0; {$IFDEF DebugFFmpegDecode} - dump_format(FormatCtx, 0, PAnsiChar(Filename), 0); + dump_format(FormatCtx, 0, PAnsiChar(Filename.ToNative), 0); {$ENDIF} AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); if (AudioStreamIndex < 0) then begin - Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename + '"', 'UAudio_FFmpeg'); + Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); Exit; end; @@ -1117,7 +1118,7 @@ begin Result := true; end; -function TAudioDecoder_FFmpeg.Open(const Filename: string): TAudioDecodeStream; +function TAudioDecoder_FFmpeg.Open(const Filename: IPath): TAudioDecodeStream; var Stream: TFFmpegDecodeStream; begin -- cgit v1.2.3 From 7a9910634301c97ef9b785913ce4d72af8bfcfbc Mon Sep 17 00:00:00 2001 From: tobigun Date: Wed, 21 Apr 2010 23:30:30 +0000 Subject: fixed choppy start of some songs when using ffmpeg git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2270 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 57 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 24 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index d079afdc..7ca98885 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -325,6 +325,7 @@ begin //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg'); AudioStream := FormatCtx.streams[AudioStreamIndex]; + AudioStreamPos := 0; CodecCtx := AudioStream^.codec; // TODO: should we use this or not? Should we allow 5.1 channel audio? @@ -575,30 +576,38 @@ begin 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(); + try + EOFState := false; + ErrorState := false; + + // do not seek if we are already at the correct position. + // This is important especially for seeking to position 0 if we already are + // at the beginning. Although seeking with AVSEEK_FLAG_BACKWARD for pos 0 works, + // it is still a bit choppy (although much better than w/o AVSEEK_FLAG_BACKWARD). + if (Time = AudioStreamPos) then + Exit; + + // 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; + + // send a reuse signal in case the parser was stopped (e.g. because of an EOF) + SDL_CondSignal(ParserIdleCond); + finally + SDL_mutexV(StateLock); + ResumeDecoder(); + ResumeParser(); + end; // in blocking mode, wait until seeking is done if (Blocking) then -- cgit v1.2.3 From 6894d29e66396ef47b246347fcae1132e5daf84e Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 3 Jun 2010 15:58:43 +0000 Subject: better EOF handling for FFmpeg audio decoding (EOF handled like it is done in UVideo) git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2438 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 7ca98885..f7f9c661 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -812,9 +812,18 @@ begin Exit; end; - // no error -> wait for user input - SDL_Delay(100); - Continue; + // url_feof() does not detect an EOF for some files + // so we have to do it this way. + if ((FormatCtx^.file_size <> 0) and + (ByteIOCtx^.pos >= FormatCtx^.file_size)) then + begin + PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + Exit; + end; + + // unknown error occured, exit + PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + Exit; end; if (Packet.stream_index = AudioStreamIndex) then -- cgit v1.2.3 From 85e05243b664f1b1b82c8c79ad370ae22b6e49c5 Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 3 Jun 2010 15:59:38 +0000 Subject: prefix class fields with 'f' git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2439 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 544 ++++++++++++++++++------------------- 1 file changed, 272 insertions(+), 272 deletions(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index f7f9c661..2badb84d 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -86,60 +86,60 @@ const type TFFmpegDecodeStream = class(TAudioDecodeStream) private - StateLock: PSDL_Mutex; + fStateLock: PSDL_Mutex; - EOFState: boolean; // end-of-stream flag (locked by StateLock) - ErrorState: boolean; // error flag (locked by StateLock) + fEOFState: boolean; // end-of-stream flag (locked by StateLock) + fErrorState: boolean; // error flag (locked by StateLock) - QuitRequest: boolean; // (locked by StateLock) - ParserIdleCond: PSDL_Cond; + fQuitRequest: boolean; // (locked by StateLock) + fParserIdleCond: 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) + fParserLocked: boolean; + fParserPauseRequestCount: integer; + fParserUnlockedCond: PSDL_Cond; + fParserResumeCond: PSDL_Cond; + + fSeekRequest: boolean; // (locked by StateLock) + fSeekFlags: integer; // (locked by StateLock) + fSeekPos: double; // stream position to seek for (in secs) (locked by StateLock) + fSeekFlush: boolean; // true if the buffers should be flushed after seeking (locked by StateLock) SeekFinishedCond: PSDL_Cond; - Loop: boolean; // (locked by StateLock) + fLoop: boolean; // (locked by StateLock) - ParseThread: PSDL_Thread; - PacketQueue: TPacketQueue; + fParseThread: PSDL_Thread; + fPacketQueue: TPacketQueue; - FormatInfo: TAudioFormatInfo; + fFormatInfo: TAudioFormatInfo; // FFmpeg specific data - FormatCtx: PAVFormatContext; - CodecCtx: PAVCodecContext; - Codec: PAVCodec; + fFormatCtx: PAVFormatContext; + fCodecCtx: PAVCodecContext; + fCodec: PAVCodec; - AudioStreamIndex: integer; - AudioStream: PAVStream; - AudioStreamPos: double; // stream position in seconds (locked by DecoderLock) + fAudioStreamIndex: integer; + fAudioStream: PAVStream; + fAudioStreamPos: double; // stream position in seconds (locked by DecoderLock) // decoder pause/resume data - DecoderLocked: boolean; - DecoderPauseRequestCount: integer; - DecoderUnlockedCond: PSDL_Cond; - DecoderResumeCond: PSDL_Cond; + fDecoderLocked: boolean; + fDecoderPauseRequestCount: integer; + fDecoderUnlockedCond: PSDL_Cond; + fDecoderResumeCond: PSDL_Cond; // state-vars for DecodeFrame (locked by DecoderLock) - AudioPaket: TAVPacket; - AudioPaketData: PByteArray; - AudioPaketSize: integer; - AudioPaketSilence: integer; // number of bytes of silence to return + fAudioPaket: TAVPacket; + fAudioPaketData: PByteArray; + fAudioPaketSize: integer; + fAudioPaketSilence: integer; // number of bytes of silence to return // state-vars for AudioCallback (locked by DecoderLock) - AudioBufferPos: integer; - AudioBufferSize: integer; - AudioBuffer: PByteArray; + fAudioBufferPos: integer; + fAudioBufferSize: integer; + fAudioBuffer: PByteArray; - Filename: IPath; + fFilename: IPath; procedure SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); procedure SetEOF(State: boolean); {$IFDEF HasInline}inline;{$ENDIF} @@ -199,13 +199,13 @@ constructor TFFmpegDecodeStream.Create(); begin inherited Create(); - StateLock := SDL_CreateMutex(); - ParserUnlockedCond := SDL_CreateCond(); - ParserResumeCond := SDL_CreateCond(); - ParserIdleCond := SDL_CreateCond(); + fStateLock := SDL_CreateMutex(); + fParserUnlockedCond := SDL_CreateCond(); + fParserResumeCond := SDL_CreateCond(); + fParserIdleCond := SDL_CreateCond(); SeekFinishedCond := SDL_CreateCond(); - DecoderUnlockedCond := SDL_CreateCond(); - DecoderResumeCond := SDL_CreateCond(); + fDecoderUnlockedCond := SDL_CreateCond(); + fDecoderResumeCond := SDL_CreateCond(); // according to the documentation of avcodec_decode_audio(2), sample-data // should be aligned on a 16 byte boundary. Otherwise internal calls @@ -222,33 +222,33 @@ begin // 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); + fAudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16); Reset(); end; procedure TFFmpegDecodeStream.Reset(); begin - ParseThread := nil; + fParseThread := nil; - EOFState := false; - ErrorState := false; - Loop := false; - QuitRequest := false; + fEOFState := false; + fErrorState := false; + fLoop := false; + fQuitRequest := false; - AudioPaketData := nil; - AudioPaketSize := 0; - AudioPaketSilence := 0; + fAudioPaketData := nil; + fAudioPaketSize := 0; + fAudioPaketSilence := 0; - AudioBufferPos := 0; - AudioBufferSize := 0; + fAudioBufferPos := 0; + fAudioBufferSize := 0; - ParserLocked := false; - ParserPauseRequestCount := 0; - DecoderLocked := false; - DecoderPauseRequestCount := 0; + fParserLocked := false; + fParserPauseRequestCount := 0; + fDecoderLocked := false; + fDecoderPauseRequestCount := 0; - FillChar(AudioPaket, SizeOf(TAVPacket), 0); + FillChar(fAudioPaket, SizeOf(TAVPacket), 0); end; {* @@ -258,15 +258,15 @@ destructor TFFmpegDecodeStream.Destroy(); begin Close(); - SDL_DestroyMutex(StateLock); - SDL_DestroyCond(ParserUnlockedCond); - SDL_DestroyCond(ParserResumeCond); - SDL_DestroyCond(ParserIdleCond); + SDL_DestroyMutex(fStateLock); + SDL_DestroyCond(fParserUnlockedCond); + SDL_DestroyCond(fParserResumeCond); + SDL_DestroyCond(fParserIdleCond); SDL_DestroyCond(SeekFinishedCond); - SDL_DestroyCond(DecoderUnlockedCond); - SDL_DestroyCond(DecoderResumeCond); + SDL_DestroyCond(fDecoderUnlockedCond); + SDL_DestroyCond(fDecoderResumeCond); - FreeAlignedMem(AudioBuffer); + FreeAlignedMem(fAudioBuffer); inherited; end; @@ -287,20 +287,20 @@ begin Exit; end; - Self.Filename := Filename; + Self.fFilename := Filename; // use custom 'ufile' protocol for UTF-8 support - if (av_open_input_file(FormatCtx, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil) <> 0) then + if (av_open_input_file(fFormatCtx, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil) <> 0) then begin Log.LogError('av_open_input_file failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Exit; end; // generate PTS values if they do not exist - FormatCtx^.flags := FormatCtx^.flags or AVFMT_FLAG_GENPTS; + fFormatCtx^.flags := fFormatCtx^.flags or AVFMT_FLAG_GENPTS; // retrieve stream information - if (av_find_stream_info(FormatCtx) < 0) then + if (av_find_stream_info(fFormatCtx) < 0) then begin Log.LogError('av_find_stream_info failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); @@ -308,14 +308,14 @@ begin end; // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end - FormatCtx^.pb.eof_reached := 0; + fFormatCtx^.pb.eof_reached := 0; {$IFDEF DebugFFmpegDecode} - dump_format(FormatCtx, 0, PAnsiChar(Filename.ToNative), 0); + dump_format(fFormatCtx, 0, PAnsiChar(Filename.ToNative), 0); {$ENDIF} - AudioStreamIndex := FFmpegCore.FindAudioStreamIndex(FormatCtx); - if (AudioStreamIndex < 0) then + fAudioStreamIndex := FFmpegCore.FindAudioStreamIndex(fFormatCtx); + if (fAudioStreamIndex < 0) then begin Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', 'UAudio_FFmpeg'); Close(); @@ -324,9 +324,9 @@ begin //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg'); - AudioStream := FormatCtx.streams[AudioStreamIndex]; - AudioStreamPos := 0; - CodecCtx := AudioStream^.codec; + fAudioStream := fFormatCtx.streams[fAudioStreamIndex]; + fAudioStreamPos := 0; + fCodecCtx := fAudioStream^.codec; // TODO: should we use this or not? Should we allow 5.1 channel audio? (* @@ -338,21 +338,21 @@ begin {$IFEND} *) - Codec := avcodec_find_decoder(CodecCtx^.codec_id); - if (Codec = nil) then + fCodec := avcodec_find_decoder(fCodecCtx^.codec_id); + if (fCodec = nil) then begin Log.LogError('Unsupported codec!', 'UAudio_FFmpeg'); - CodecCtx := nil; + fCodecCtx := nil; Close(); Exit; end; // set debug options - CodecCtx^.debug_mv := 0; - CodecCtx^.debug := 0; + fCodecCtx^.debug_mv := 0; + fCodecCtx^.debug := 0; // detect bug-workarounds automatically - CodecCtx^.workaround_bugs := FF_BUG_AUTODETECT; + fCodecCtx^.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. @@ -362,7 +362,7 @@ begin // fail if called concurrently by different threads. FFmpegCore.LockAVCodec(); try - AVResult := avcodec_open(CodecCtx, Codec); + AVResult := avcodec_open(fCodecCtx, fCodec); finally FFmpegCore.UnlockAVCodec(); end; @@ -375,23 +375,23 @@ begin // now initialize the audio-format - if (not FFmpegCore.ConvertFFmpegToAudioFormat(CodecCtx^.sample_fmt, SampleFormat)) then + if (not FFmpegCore.ConvertFFmpegToAudioFormat(fCodecCtx^.sample_fmt, SampleFormat)) then begin // try standard format SampleFormat := asfS16; end; - if CodecCtx^.channels > 255 then + if fCodecCtx^.channels > 255 then Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open'); - FormatInfo := TAudioFormatInfo.Create( - byte(CodecCtx^.channels), - CodecCtx^.sample_rate, + fFormatInfo := TAudioFormatInfo.Create( + byte(fCodecCtx^.channels), + fCodecCtx^.sample_rate, SampleFormat ); - PacketQueue := TPacketQueue.Create(); + fPacketQueue := TPacketQueue.Create(); // finally start the decode thread - ParseThread := SDL_CreateThread(@ParseThreadMain, Self); + fParseThread := SDL_CreateThread(@ParseThreadMain, Self); Result := true; end; @@ -403,47 +403,47 @@ 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(); + if (assigned(fPacketQueue)) then + fPacketQueue.Abort(); // send quit request (to parse-thread etc) - SDL_mutexP(StateLock); - QuitRequest := true; - SDL_CondBroadcast(ParserIdleCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fQuitRequest := true; + SDL_CondBroadcast(fParserIdleCond); + SDL_mutexV(fStateLock); // abort parse-thread - if (ParseThread <> nil) then + if (fParseThread <> nil) then begin // and wait until it terminates - SDL_WaitThread(ParseThread, ThreadResult); - ParseThread := nil; + SDL_WaitThread(fParseThread, ThreadResult); + fParseThread := nil; end; // Close the codec - if (CodecCtx <> nil) then + if (fCodecCtx <> nil) then begin // avcodec_close() is not thread-safe FFmpegCore.LockAVCodec(); try - avcodec_close(CodecCtx); + avcodec_close(fCodecCtx); finally FFmpegCore.UnlockAVCodec(); end; - CodecCtx := nil; + fCodecCtx := nil; end; // Close the video file - if (FormatCtx <> nil) then + if (fFormatCtx <> nil) then begin - av_close_input_file(FormatCtx); - FormatCtx := nil; + av_close_input_file(fFormatCtx); + fFormatCtx := nil; end; PerformOnClose(); - FreeAndNil(PacketQueue); - FreeAndNil(FormatInfo); + FreeAndNil(fPacketQueue); + FreeAndNil(fFormatInfo); end; function TFFmpegDecodeStream.GetLength(): real; @@ -451,54 +451,54 @@ begin // do not forget to consider the start_time value here // there is a type size mismatch warnign because start_time and duration are cint64. // So, in principle there could be an overflow when doing the sum. - Result := (FormatCtx^.start_time + FormatCtx^.duration) / AV_TIME_BASE; + Result := (fFormatCtx^.start_time + fFormatCtx^.duration) / AV_TIME_BASE; end; function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo; begin - Result := FormatInfo; + Result := fFormatInfo; end; function TFFmpegDecodeStream.IsEOF(): boolean; begin - SDL_mutexP(StateLock); - Result := EOFState; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fEOFState; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetEOF(State: boolean); begin - SDL_mutexP(StateLock); - EOFState := State; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fEOFState := State; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsError(): boolean; begin - SDL_mutexP(StateLock); - Result := ErrorState; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fErrorState; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetError(State: boolean); begin - SDL_mutexP(StateLock); - ErrorState := State; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fErrorState := State; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsSeeking(): boolean; begin - SDL_mutexP(StateLock); - Result := SeekRequest; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fSeekRequest; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.IsQuit(): boolean; begin - SDL_mutexP(StateLock); - Result := QuitRequest; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fQuitRequest; + SDL_mutexV(fStateLock); end; function TFFmpegDecodeStream.GetPosition(): real; @@ -509,11 +509,11 @@ begin // 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; + BufferSizeSec := (fAudioBufferSize - fAudioBufferPos) / + fFormatInfo.BytesPerSec; // subtract the size of unused buffer-data from the audio clock. - Result := AudioStreamPos - BufferSizeSec; + Result := fAudioStreamPos - BufferSizeSec; ResumeDecoder(); end; @@ -525,16 +525,16 @@ end; function TFFmpegDecodeStream.GetLoop(): boolean; begin - SDL_mutexP(StateLock); - Result := Loop; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Result := fLoop; + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean); begin - SDL_mutexP(StateLock); - Loop := Enabled; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fLoop := Enabled; + SDL_mutexV(fStateLock); end; @@ -544,25 +544,25 @@ end; procedure TFFmpegDecodeStream.PauseParser(); begin - if (SDL_ThreadID() = ParseThread.threadid) then + if (SDL_ThreadID() = fParseThread.threadid) then Exit; - SDL_mutexP(StateLock); - Inc(ParserPauseRequestCount); - while (ParserLocked) do - SDL_CondWait(ParserUnlockedCond, StateLock); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Inc(fParserPauseRequestCount); + while (fParserLocked) do + SDL_CondWait(fParserUnlockedCond, fStateLock); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.ResumeParser(); begin - if (SDL_ThreadID() = ParseThread.threadid) then + if (SDL_ThreadID() = fParseThread.threadid) then Exit; - SDL_mutexP(StateLock); - Dec(ParserPauseRequestCount); - SDL_CondSignal(ParserResumeCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Dec(fParserPauseRequestCount); + SDL_CondSignal(fParserResumeCond); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.SetPositionIntern(Time: real; Flush: boolean; Blocking: boolean); @@ -575,36 +575,36 @@ begin // - Last lock the state lock because we are manipulating some shared state-vars. PauseParser(); PauseDecoder(); - SDL_mutexP(StateLock); + SDL_mutexP(fStateLock); try - EOFState := false; - ErrorState := false; + fEOFState := false; + fErrorState := false; // do not seek if we are already at the correct position. // This is important especially for seeking to position 0 if we already are // at the beginning. Although seeking with AVSEEK_FLAG_BACKWARD for pos 0 works, // it is still a bit choppy (although much better than w/o AVSEEK_FLAG_BACKWARD). - if (Time = AudioStreamPos) then + if (Time = fAudioStreamPos) then Exit; // configure seek parameters - SeekPos := Time; - SeekFlush := Flush; - SeekFlags := AVSEEK_FLAG_ANY; - SeekRequest := true; + fSeekPos := Time; + fSeekFlush := Flush; + fSeekFlags := AVSEEK_FLAG_ANY; + fSeekRequest := 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; + if (Time <= fAudioStreamPos) then + fSeekFlags := fSeekFlags or AVSEEK_FLAG_BACKWARD; // send a reuse signal in case the parser was stopped (e.g. because of an EOF) - SDL_CondSignal(ParserIdleCond); + SDL_CondSignal(fParserIdleCond); finally - SDL_mutexV(StateLock); + SDL_mutexV(fStateLock); ResumeDecoder(); ResumeParser(); end; @@ -612,10 +612,10 @@ begin // 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); + SDL_mutexP(fStateLock); + while (fSeekRequest) do + SDL_CondWait(SeekFinishedCond, fStateLock); + SDL_mutexV(fStateLock); end; end; @@ -635,10 +635,10 @@ begin 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); + SDL_mutexP(fStateLock); + while (not (fSeekRequest or fQuitRequest)) do + SDL_CondWait(fParserIdleCond, fStateLock); + SDL_mutexV(fStateLock); end; end; @@ -669,19 +669,19 @@ var // 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); + SDL_mutexP(fStateLock); + while (fParserPauseRequestCount > 0) do + SDL_CondWait(fParserResumeCond, fStateLock); + fParserLocked := true; + SDL_mutexV(fStateLock); end; procedure UnlockParser(); begin - SDL_mutexP(StateLock); - ParserLocked := false; - SDL_CondBroadcast(ParserUnlockedCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fParserLocked := false; + SDL_CondBroadcast(fParserUnlockedCond); + SDL_mutexV(fStateLock); end; begin @@ -699,92 +699,92 @@ begin end; // handle seek-request (Note: no need to lock SeekRequest here) - if (SeekRequest) then + if (fSeekRequest) then begin // first try: seek on the audio stream - SeekTarget := Round(SeekPos / av_q2d(AudioStream^.time_base)); + SeekTarget := Round(fSeekPos / av_q2d(fAudioStream^.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 (SeekTarget < fAudioStream^.start_time) then + StartSilence := (fAudioStream^.start_time - SeekTarget) * av_q2d(fAudioStream^.time_base); + ErrorCode := av_seek_frame(fFormatCtx, fAudioStreamIndex, SeekTarget, fSeekFlags); 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); + SeekTarget := Round(fSeekPos * 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); + if (SeekTarget < fFormatCtx^.start_time) then + StartSilence := (fFormatCtx^.start_time - SeekTarget) / AV_TIME_BASE; + ErrorCode := av_seek_frame(fFormatCtx, -1, SeekTarget, fSeekFlags); 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); + SDL_mutexP(fStateLock); try if (ErrorCode < 0) then begin // seeking failed - ErrorState := true; - Log.LogStatus('Seek Error in "'+FormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); + fErrorState := true; + Log.LogStatus('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); end else begin - if (SeekFlush) then + if (fSeekFlush) then begin // flush queue (we will send a Flush-Packet when seeking is finished) - PacketQueue.Flush(); + fPacketQueue.Flush(); // flush the decode buffers - AudioBufferSize := 0; - AudioBufferPos := 0; - AudioPaketSize := 0; - AudioPaketSilence := 0; + fAudioBufferSize := 0; + fAudioBufferPos := 0; + fAudioPaketSize := 0; + fAudioPaketSilence := 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; + fAudioStreamPos := fSeekPos; end else begin // request avcodec buffer flush - PacketQueue.PutStatus(PKT_STATUS_FLAG_FLUSH, nil); + fPacketQueue.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 + if ((StartSilence > 0) and (not fLoop)) then begin GetMem(StartSilencePtr, SizeOf(StartSilence)); StartSilencePtr^ := StartSilence; - PacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr); end; end; - SeekRequest := false; + fSeekRequest := false; SDL_CondBroadcast(SeekFinishedCond); finally - SDL_mutexV(StateLock); + SDL_mutexV(fStateLock); ResumeDecoder(); end; end; - if (PacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then + if (fPacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then begin SDL_Delay(10); Continue; end; - if (av_read_frame(FormatCtx, Packet) < 0) then + if (av_read_frame(fFormatCtx, Packet) < 0) then begin // failed to read a frame, check reason {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} - ByteIOCtx := FormatCtx^.pb; + ByteIOCtx := fFormatCtx^.pb; {$ELSE} - ByteIOCtx := @FormatCtx^.pb; + ByteIOCtx := @fFormatCtx^.pb; {$IFEND} // check for end-of-file (eof is not an error) @@ -799,7 +799,7 @@ begin else begin // signal end-of-file - PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); Exit; end; end; @@ -808,26 +808,26 @@ begin if (url_ferror(ByteIOCtx) <> 0) then begin // an error occured -> abort and wait for repositioning or termination - PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); Exit; end; // url_feof() does not detect an EOF for some files // so we have to do it this way. - if ((FormatCtx^.file_size <> 0) and - (ByteIOCtx^.pos >= FormatCtx^.file_size)) then + if ((fFormatCtx^.file_size <> 0) and + (ByteIOCtx^.pos >= fFormatCtx^.file_size)) then begin - PacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil); Exit; end; // unknown error occured, exit - PacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); + fPacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil); Exit; end; - if (Packet.stream_index = AudioStreamIndex) then - PacketQueue.Put(@Packet) + if (Packet.stream_index = fAudioStreamIndex) then + fPacketQueue.Put(@Packet) else av_free_packet(@Packet); @@ -844,28 +844,28 @@ end; procedure TFFmpegDecodeStream.PauseDecoder(); begin - SDL_mutexP(StateLock); - Inc(DecoderPauseRequestCount); - while (DecoderLocked) do - SDL_CondWait(DecoderUnlockedCond, StateLock); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Inc(fDecoderPauseRequestCount); + while (fDecoderLocked) do + SDL_CondWait(fDecoderUnlockedCond, fStateLock); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.ResumeDecoder(); begin - SDL_mutexP(StateLock); - Dec(DecoderPauseRequestCount); - SDL_CondSignal(DecoderResumeCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + Dec(fDecoderPauseRequestCount); + SDL_CondSignal(fDecoderResumeCond); + SDL_mutexV(fStateLock); end; procedure TFFmpegDecodeStream.FlushCodecBuffers(); begin // if no flush operation is specified, avcodec_flush_buffers will not do anything. - if (@CodecCtx.codec.flush <> nil) then + if (@fCodecCtx.codec.flush <> nil) then begin // flush buffers used by avcodec_decode_audio, etc. - avcodec_flush_buffers(CodecCtx); + avcodec_flush_buffers(fCodecCtx); end else begin @@ -874,8 +874,8 @@ begin // We will just reopen the codec. FFmpegCore.LockAVCodec(); try - avcodec_close(CodecCtx); - avcodec_open(CodecCtx, Codec); + avcodec_close(fCodecCtx); + avcodec_open(fCodecCtx, fCodec); finally FFmpegCore.UnlockAVCodec(); end; @@ -901,27 +901,27 @@ begin 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 + if (fAudioPaketSilence > 0) then begin - DataSize := Min(AudioPaketSilence, BufferSize); + DataSize := Min(fAudioPaketSilence, BufferSize); FillChar(Buffer[0], DataSize, 0); - Dec(AudioPaketSilence, DataSize); - AudioStreamPos := AudioStreamPos + DataSize / FormatInfo.BytesPerSec; + Dec(fAudioPaketSilence, DataSize); + fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec; Result := DataSize; Exit; end; // read packet data - while (AudioPaketSize > 0) do + while (fAudioPaketSize > 0) do begin DataSize := BufferSize; {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0 - PaketDecodedSize := avcodec_decode_audio2(CodecCtx, PSmallint(Buffer), - DataSize, AudioPaketData, AudioPaketSize); + PaketDecodedSize := avcodec_decode_audio2(fCodecCtx, PSmallint(Buffer), + DataSize, fAudioPaketData, fAudioPaketSize); {$ELSE} - PaketDecodedSize := avcodec_decode_audio(CodecCtx, PSmallint(Buffer), - DataSize, AudioPaketData, AudioPaketSize); + PaketDecodedSize := avcodec_decode_audio(fCodecCtx, PSmallint(Buffer), + DataSize, fAudioPaketData, fAudioPaketSize); {$IFEND} if(PaketDecodedSize < 0) then @@ -930,19 +930,19 @@ begin {$IFDEF DebugFFmpegDecode} DebugWriteln('Skip audio frame'); {$ENDIF} - AudioPaketSize := 0; + fAudioPaketSize := 0; Break; end; - Inc(AudioPaketData, PaketDecodedSize); - Dec(AudioPaketSize, PaketDecodedSize); + Inc(fAudioPaketData, PaketDecodedSize); + Dec(fAudioPaketSize, 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; + fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec; // we have data, return it and come back for more later Result := DataSize; @@ -950,8 +950,8 @@ begin end; // free old packet data - if (AudioPaket.data <> nil) then - av_free_packet(@AudioPaket); + if (fAudioPaket.data <> nil) then + av_free_packet(@fAudioPaket); // do not block queue on seeking (to avoid deadlocks on the DecoderLock) if (IsSeeking()) then @@ -961,17 +961,17 @@ begin // request a new packet and block if none available. // If this fails, the queue was aborted. - if (PacketQueue.Get(AudioPaket, BlockQueue) <= 0) then + if (fPacketQueue.Get(fAudioPaket, BlockQueue) <= 0) then Exit; // handle Status-packet - if (PAnsiChar(AudioPaket.data) = STATUS_PACKET) then + if (PAnsiChar(fAudioPaket.data) = STATUS_PACKET) then begin - AudioPaket.data := nil; - AudioPaketData := nil; - AudioPaketSize := 0; + fAudioPaket.data := nil; + fAudioPaketData := nil; + fAudioPaketSize := 0; - case (AudioPaket.flags) of + case (fAudioPaket.flags) of PKT_STATUS_FLAG_FLUSH: begin // just used if SetPositionIntern was called without the flush flag. @@ -993,9 +993,9 @@ begin end; PKT_STATUS_FLAG_EMPTY: begin - SilenceDuration := PDouble(PacketQueue.GetStatusInfo(AudioPaket))^; - AudioPaketSilence := Round(SilenceDuration * FormatInfo.SampleRate) * FormatInfo.FrameSize; - PacketQueue.FreeStatusInfo(AudioPaket); + SilenceDuration := PDouble(fPacketQueue.GetStatusInfo(fAudioPaket))^; + fAudioPaketSilence := Round(SilenceDuration * fFormatInfo.SampleRate) * fFormatInfo.FrameSize; + fPacketQueue.FreeStatusInfo(fAudioPaket); end else begin @@ -1006,20 +1006,20 @@ begin Continue; end; - AudioPaketData := AudioPaket.data; - AudioPaketSize := AudioPaket.size; + fAudioPaketData := fAudioPaket.data; + fAudioPaketSize := fAudioPaket.size; // if available, update the stream position to the presentation time of this package - if(AudioPaket.pts <> AV_NOPTS_VALUE) then + if(fAudioPaket.pts <> AV_NOPTS_VALUE) then begin {$IFDEF DebugFFmpegDecode} - TmpPos := AudioStreamPos; + TmpPos := fAudioStreamPos; {$ENDIF} - AudioStreamPos := av_q2d(AudioStream^.time_base) * AudioPaket.pts; + fAudioStreamPos := av_q2d(fAudioStream^.time_base) * fAudioPaket.pts; {$IFDEF DebugFFmpegDecode} - DebugWriteln('Timestamp: ' + floattostrf(AudioStreamPos, ffFixed, 15, 3) + ' ' + + DebugWriteln('Timestamp: ' + floattostrf(fAudioStreamPos, ffFixed, 15, 3) + ' ' + '(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' + - 'Diff: ' + floattostrf(AudioStreamPos-TmpPos, ffFixed, 15, 3)); + 'Diff: ' + floattostrf(fAudioStreamPos-TmpPos, ffFixed, 15, 3)); {$ENDIF} end; end; @@ -1034,19 +1034,19 @@ var // prioritize pause requests procedure LockDecoder(); begin - SDL_mutexP(StateLock); - while (DecoderPauseRequestCount > 0) do - SDL_CondWait(DecoderResumeCond, StateLock); - DecoderLocked := true; - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + while (fDecoderPauseRequestCount > 0) do + SDL_CondWait(fDecoderResumeCond, fStateLock); + fDecoderLocked := true; + SDL_mutexV(fStateLock); end; procedure UnlockDecoder(); begin - SDL_mutexP(StateLock); - DecoderLocked := false; - SDL_CondBroadcast(DecoderUnlockedCond); - SDL_mutexV(StateLock); + SDL_mutexP(fStateLock); + fDecoderLocked := false; + SDL_CondBroadcast(fDecoderUnlockedCond); + SDL_mutexV(fStateLock); end; begin @@ -1065,15 +1065,15 @@ begin while (BufferPos < BufferSize) do begin // check if we need more data - if (AudioBufferPos >= AudioBufferSize) then + if (fAudioBufferPos >= fAudioBufferSize) then begin - AudioBufferPos := 0; + fAudioBufferPos := 0; // we have already sent all our data; get more - AudioBufferSize := DecodeFrame(AudioBuffer, AUDIO_BUFFER_SIZE); + fAudioBufferSize := DecodeFrame(fAudioBuffer, AUDIO_BUFFER_SIZE); // check for errors or EOF - if(AudioBufferSize < 0) then + if(fAudioBufferSize < 0) then begin Result := BufferPos; Exit; @@ -1081,16 +1081,16 @@ begin end; // calc number of new bytes in the decode-buffer - CopyByteCount := AudioBufferSize - AudioBufferPos; + CopyByteCount := fAudioBufferSize - fAudioBufferPos; // 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); + Move(fAudioBuffer[fAudioBufferPos], Buffer[BufferPos], CopyByteCount); Inc(BufferPos, CopyByteCount); - Inc(AudioBufferPos, CopyByteCount); + Inc(fAudioBufferPos, CopyByteCount); end; finally UnlockDecoder(); -- cgit v1.2.3 From fd0388550ddbe2b817a427b6ee08be9157fff287 Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 13 Jun 2010 09:42:50 +0000 Subject: Fix for invalid song lengths git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2511 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index 2badb84d..c64d79c2 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -728,7 +728,7 @@ begin begin // seeking failed fErrorState := true; - Log.LogStatus('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); + Log.LogError('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg'); end else begin -- cgit v1.2.3 From 008c58c6ac1cdbe0fdb01ce79567b7f0e815b83a Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 19 Jun 2010 16:36:25 +0000 Subject: Bugfix: do not call Inc() on PByteArray pointers. It will not increment by one byte, but by SizeOf(ByteArray) bytes which is some KB. As a result the pointer will point to an illegal memory address. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2549 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioDecoder_FFmpeg.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/media/UAudioDecoder_FFmpeg.pas') diff --git a/src/media/UAudioDecoder_FFmpeg.pas b/src/media/UAudioDecoder_FFmpeg.pas index c64d79c2..b44c7b11 100644 --- a/src/media/UAudioDecoder_FFmpeg.pas +++ b/src/media/UAudioDecoder_FFmpeg.pas @@ -934,7 +934,7 @@ begin Break; end; - Inc(fAudioPaketData, PaketDecodedSize); + Inc(PByte(fAudioPaketData), PaketDecodedSize); Dec(fAudioPaketSize, PaketDecodedSize); // check if avcodec_decode_audio returned data, otherwise fetch more frames -- cgit v1.2.3