aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mediaplugin/src/base/UMusic.pas2
-rw-r--r--mediaplugin/src/media/UAudioDecoder_FFmpeg.pas982
-rw-r--r--mediaplugin/src/media/UMediaPlugin.pas132
-rw-r--r--mediaplugin/src/ultrastardx.dpr2
4 files changed, 162 insertions, 956 deletions
diff --git a/mediaplugin/src/base/UMusic.pas b/mediaplugin/src/base/UMusic.pas
index c775fd51..945ad7b9 100644
--- a/mediaplugin/src/base/UMusic.pas
+++ b/mediaplugin/src/base/UMusic.pas
@@ -140,6 +140,7 @@ const
type
TAudioSampleFormat = (
+ asfUnknown, // unknown format
asfU8, asfS8, // unsigned/signed 8 bits
asfU16LSB, asfS16LSB, // unsigned/signed 16 bits (endianness: LSB)
asfU16MSB, asfS16MSB, // unsigned/signed 16 bits (endianness: MSB)
@@ -152,6 +153,7 @@ type
const
// Size of one sample (one channel only) in bytes
AudioSampleSize: array[TAudioSampleFormat] of integer = (
+ 0, // asfUnknown
1, 1, // asfU8, asfS8
2, 2, // asfU16LSB, asfS16LSB
2, 2, // asfU16MSB, asfS16MSB
diff --git a/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas b/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas
index b44c7b11..9c734786 100644
--- a/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas
+++ b/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas
@@ -44,120 +44,26 @@ interface
{$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
- SDL, // SDL redefines some base types -> include before SysUtils to ignore them
+ ctypes,
Classes,
- Math,
SysUtils,
- avcodec,
- avformat,
- avutil,
- avio,
- mathematics, // used for av_rescale_q
- rational,
+ UMediaPlugin,
UMusic,
UIni,
UMain,
- UMediaCore_FFmpeg,
ULog,
- UCommon,
- UConfig,
UPath;
-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
- fStateLock: PSDL_Mutex;
-
- fEOFState: boolean; // end-of-stream flag (locked by StateLock)
- fErrorState: boolean; // error flag (locked by StateLock)
-
- fQuitRequest: boolean; // (locked by StateLock)
- fParserIdleCond: PSDL_Cond;
-
- // parser pause/resume data
- 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;
-
- fLoop: boolean; // (locked by StateLock)
-
- fParseThread: PSDL_Thread;
- fPacketQueue: TPacketQueue;
-
- fFormatInfo: TAudioFormatInfo;
-
- // FFmpeg specific data
- fFormatCtx: PAVFormatContext;
- fCodecCtx: PAVCodecContext;
- fCodec: PAVCodec;
-
- fAudioStreamIndex: integer;
- fAudioStream: PAVStream;
- fAudioStreamPos: double; // stream position in seconds (locked by DecoderLock)
-
- // decoder pause/resume data
- fDecoderLocked: boolean;
- fDecoderPauseRequestCount: integer;
- fDecoderUnlockedCond: PSDL_Cond;
- fDecoderResumeCond: PSDL_Cond;
-
- // state-vars for DecodeFrame (locked by DecoderLock)
- fAudioPaket: TAVPacket;
- fAudioPaketData: PByteArray;
- fAudioPaketSize: integer;
- fAudioPaketSilence: integer; // number of bytes of silence to return
-
- // state-vars for AudioCallback (locked by DecoderLock)
- fAudioBufferPos: integer;
- fAudioBufferSize: integer;
- fAudioBuffer: PByteArray;
-
fFilename: IPath;
+ fStream: PDecodeStream;
+ fFormatInfo: TAudioFormatInfo;
- 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: PByteArray; BufferSize: integer): integer;
- procedure FlushCodecBuffers();
- procedure PauseDecoder();
- procedure ResumeDecoder();
public
constructor Create();
destructor Destroy(); override;
@@ -187,68 +93,12 @@ type
function Open(const Filename: IPath): TAudioDecodeStream;
end;
-var
- FFmpegCore: TMediaCore_FFmpeg;
-
-function ParseThreadMain(Data: Pointer): integer; cdecl; forward;
-
-
{ TFFmpegDecodeStream }
constructor TFFmpegDecodeStream.Create();
begin
inherited Create();
-
- fStateLock := SDL_CreateMutex();
- fParserUnlockedCond := SDL_CreateCond();
- fParserResumeCond := SDL_CreateCond();
- fParserIdleCond := SDL_CreateCond();
- SeekFinishedCond := 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
- // (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.
- fAudioBuffer := GetAlignedMem(AUDIO_BUFFER_SIZE, 16);
-
- Reset();
-end;
-
-procedure TFFmpegDecodeStream.Reset();
-begin
- fParseThread := nil;
-
- fEOFState := false;
- fErrorState := false;
- fLoop := false;
- fQuitRequest := false;
-
- fAudioPaketData := nil;
- fAudioPaketSize := 0;
- fAudioPaketSilence := 0;
-
- fAudioBufferPos := 0;
- fAudioBufferSize := 0;
-
- fParserLocked := false;
- fParserPauseRequestCount := 0;
- fDecoderLocked := false;
- fDecoderPauseRequestCount := 0;
-
- FillChar(fAudioPaket, SizeOf(TAVPacket), 0);
+ fFilename := PATH_NONE;
end;
{*
@@ -257,201 +107,46 @@ end;
destructor TFFmpegDecodeStream.Destroy();
begin
Close();
-
- SDL_DestroyMutex(fStateLock);
- SDL_DestroyCond(fParserUnlockedCond);
- SDL_DestroyCond(fParserResumeCond);
- SDL_DestroyCond(fParserIdleCond);
- SDL_DestroyCond(SeekFinishedCond);
- SDL_DestroyCond(fDecoderUnlockedCond);
- SDL_DestroyCond(fDecoderResumeCond);
-
- FreeAlignedMem(fAudioBuffer);
-
inherited;
end;
function TFFmpegDecodeStream.Open(const Filename: IPath): boolean;
var
- SampleFormat: TAudioSampleFormat;
- AVResult: integer;
+ Info: TCAudioFormatInfo;
begin
Result := false;
Close();
- Reset();
-
- if (not Filename.IsFile) then
- begin
- Log.LogError('Audio-file does not exist: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
- Exit;
- end;
-
- Self.fFilename := Filename;
-
- // use custom 'ufile' protocol for UTF-8 support
- 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
- fFormatCtx^.flags := fFormatCtx^.flags or AVFMT_FLAG_GENPTS;
-
- // retrieve stream information
- if (av_find_stream_info(fFormatCtx) < 0) then
- begin
- Log.LogError('av_find_stream_info failed: "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
- Close();
- Exit;
- end;
-
- // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
- fFormatCtx^.pb.eof_reached := 0;
-
- {$IFDEF DebugFFmpegDecode}
- dump_format(fFormatCtx, 0, PAnsiChar(Filename.ToNative), 0);
- {$ENDIF}
- fAudioStreamIndex := FFmpegCore.FindAudioStreamIndex(fFormatCtx);
- if (fAudioStreamIndex < 0) then
- begin
- Log.LogError('FindAudioStreamIndex: No Audio-stream found "' + Filename.ToNative + '"', 'UAudio_FFmpeg');
- Close();
- Exit;
- end;
-
- //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFmpeg');
-
- fAudioStream := fFormatCtx.streams[fAudioStreamIndex];
- fAudioStreamPos := 0;
- fCodecCtx := fAudioStream^.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}
- *)
-
- fCodec := avcodec_find_decoder(fCodecCtx^.codec_id);
- if (fCodec = nil) then
- begin
- Log.LogError('Unsupported codec!', 'UAudio_FFmpeg');
- fCodecCtx := nil;
- Close();
- Exit;
- end;
-
- // set debug options
- fCodecCtx^.debug_mv := 0;
- fCodecCtx^.debug := 0;
-
- // detect bug-workarounds automatically
- 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.
- //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(fCodecCtx, fCodec);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- if (AVResult < 0) then
- begin
- Log.LogError('avcodec_open failed!', 'UAudio_FFmpeg');
- Close();
+ fStream := DecodeStream_open(PChar(Filename.ToUTF8()));
+ if (fStream = nil) then
Exit;
- end;
- // now initialize the audio-format
+ fFilename := Filename;
- if (not FFmpegCore.ConvertFFmpegToAudioFormat(fCodecCtx^.sample_fmt, SampleFormat)) then
- begin
- // try standard format
- SampleFormat := asfS16;
- end;
- if fCodecCtx^.channels > 255 then
- Log.LogStatus('Error: CodecCtx^.channels > 255', 'TFFmpegDecodeStream.Open');
+ DecodeStream_getAudioFormatInfo(fStream, Info);
fFormatInfo := TAudioFormatInfo.Create(
- byte(fCodecCtx^.channels),
- fCodecCtx^.sample_rate,
- SampleFormat
+ Info.channels,
+ Info.sampleRate,
+ TAudioSampleFormat(Info.format)
);
- fPacketQueue := TPacketQueue.Create();
-
- // finally start the decode thread
- fParseThread := 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(fPacketQueue)) then
- fPacketQueue.Abort();
-
- // send quit request (to parse-thread etc)
- SDL_mutexP(fStateLock);
- fQuitRequest := true;
- SDL_CondBroadcast(fParserIdleCond);
- SDL_mutexV(fStateLock);
-
- // abort parse-thread
- if (fParseThread <> nil) then
- begin
- // and wait until it terminates
- SDL_WaitThread(fParseThread, ThreadResult);
- fParseThread := nil;
- end;
-
- // Close the codec
- if (fCodecCtx <> nil) then
- begin
- // avcodec_close() is not thread-safe
- FFmpegCore.LockAVCodec();
- try
- avcodec_close(fCodecCtx);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- fCodecCtx := nil;
- end;
-
- // Close the video file
- if (fFormatCtx <> nil) then
+ Self.fFilename := PATH_NONE;
+ if (fStream <> nil) then
begin
- av_close_input_file(fFormatCtx);
- fFormatCtx := nil;
+ DecodeStream_close(fStream);
+ fStream := nil;
end;
-
- PerformOnClose();
-
- FreeAndNil(fPacketQueue);
- FreeAndNil(fFormatInfo);
end;
function TFFmpegDecodeStream.GetLength(): real;
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 := (fFormatCtx^.start_time + fFormatCtx^.duration) / AV_TIME_BASE;
+ Result := DecodeStream_getLength(fStream);
end;
function TFFmpegDecodeStream.GetAudioFormatInfo(): TAudioFormatInfo;
@@ -461,642 +156,37 @@ end;
function TFFmpegDecodeStream.IsEOF(): boolean;
begin
- SDL_mutexP(fStateLock);
- Result := fEOFState;
- SDL_mutexV(fStateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetEOF(State: boolean);
-begin
- SDL_mutexP(fStateLock);
- fEOFState := State;
- SDL_mutexV(fStateLock);
+ Result := DecodeStream_isEOF(fStream);
end;
function TFFmpegDecodeStream.IsError(): boolean;
begin
- SDL_mutexP(fStateLock);
- Result := fErrorState;
- SDL_mutexV(fStateLock);
-end;
-
-procedure TFFmpegDecodeStream.SetError(State: boolean);
-begin
- SDL_mutexP(fStateLock);
- fErrorState := State;
- SDL_mutexV(fStateLock);
-end;
-
-function TFFmpegDecodeStream.IsSeeking(): boolean;
-begin
- SDL_mutexP(fStateLock);
- Result := fSeekRequest;
- SDL_mutexV(fStateLock);
-end;
-
-function TFFmpegDecodeStream.IsQuit(): boolean;
-begin
- SDL_mutexP(fStateLock);
- Result := fQuitRequest;
- SDL_mutexV(fStateLock);
+ Result := DecodeStream_isError(fStream);
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 := (fAudioBufferSize - fAudioBufferPos) /
- fFormatInfo.BytesPerSec;
-
- // subtract the size of unused buffer-data from the audio clock.
- Result := fAudioStreamPos - BufferSizeSec;
-
- ResumeDecoder();
+ Result := DecodeStream_getPosition(fStream);
end;
procedure TFFmpegDecodeStream.SetPosition(Time: real);
begin
- SetPositionIntern(Time, true, true);
+ DecodeStream_setPosition(fStream, Time);
end;
function TFFmpegDecodeStream.GetLoop(): boolean;
begin
- SDL_mutexP(fStateLock);
- Result := fLoop;
- SDL_mutexV(fStateLock);
+ Result := DecodeStream_getLoop(fStream);
end;
procedure TFFmpegDecodeStream.SetLoop(Enabled: boolean);
begin
- SDL_mutexP(fStateLock);
- fLoop := Enabled;
- SDL_mutexV(fStateLock);
-end;
-
-
-(********************************************
- * Parser section
- ********************************************)
-
-procedure TFFmpegDecodeStream.PauseParser();
-begin
- if (SDL_ThreadID() = fParseThread.threadid) then
- Exit;
-
- SDL_mutexP(fStateLock);
- Inc(fParserPauseRequestCount);
- while (fParserLocked) do
- SDL_CondWait(fParserUnlockedCond, fStateLock);
- SDL_mutexV(fStateLock);
-end;
-
-procedure TFFmpegDecodeStream.ResumeParser();
-begin
- if (SDL_ThreadID() = fParseThread.threadid) then
- Exit;
-
- SDL_mutexP(fStateLock);
- Dec(fParserPauseRequestCount);
- SDL_CondSignal(fParserResumeCond);
- SDL_mutexV(fStateLock);
-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(fStateLock);
- try
- 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 = fAudioStreamPos) then
- Exit;
-
- // configure seek parameters
- 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 <= 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(fParserIdleCond);
- finally
- SDL_mutexV(fStateLock);
- ResumeDecoder();
- ResumeParser();
- end;
-
- // in blocking mode, wait until seeking is done
- if (Blocking) then
- begin
- SDL_mutexP(fStateLock);
- while (fSeekRequest) do
- SDL_CondWait(SeekFinishedCond, fStateLock);
- SDL_mutexV(fStateLock);
- 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(fStateLock);
- while (not (fSeekRequest or fQuitRequest)) do
- SDL_CondWait(fParserIdleCond, fStateLock);
- SDL_mutexV(fStateLock);
- 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;
- 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(fStateLock);
- while (fParserPauseRequestCount > 0) do
- SDL_CondWait(fParserResumeCond, fStateLock);
- fParserLocked := true;
- SDL_mutexV(fStateLock);
- end;
-
- procedure UnlockParser();
- begin
- SDL_mutexP(fStateLock);
- fParserLocked := false;
- SDL_CondBroadcast(fParserUnlockedCond);
- SDL_mutexV(fStateLock);
- 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 (fSeekRequest) then
- begin
- // first try: seek on the audio stream
- SeekTarget := Round(fSeekPos / av_q2d(fAudioStream^.time_base));
- StartSilence := 0;
- 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(fSeekPos * AV_TIME_BASE);
- StartSilence := 0;
- 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(fStateLock);
- try
- if (ErrorCode < 0) then
- begin
- // seeking failed
- fErrorState := true;
- Log.LogError('Seek Error in "'+fFormatCtx^.filename+'"', 'UAudioDecoder_FFmpeg');
- end
- else
- begin
- if (fSeekFlush) then
- begin
- // flush queue (we will send a Flush-Packet when seeking is finished)
- fPacketQueue.Flush();
-
- // flush the decode buffers
- 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.
- fAudioStreamPos := fSeekPos;
- end
- else
- begin
- // request avcodec buffer flush
- 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 fLoop)) then
- begin
- GetMem(StartSilencePtr, SizeOf(StartSilence));
- StartSilencePtr^ := StartSilence;
- fPacketQueue.PutStatus(PKT_STATUS_FLAG_EMPTY, StartSilencePtr);
- end;
- end;
-
- fSeekRequest := false;
- SDL_CondBroadcast(SeekFinishedCond);
- finally
- SDL_mutexV(fStateLock);
- ResumeDecoder();
- end;
- end;
-
- if (fPacketQueue.GetSize() > MAX_AUDIOQ_SIZE) then
- begin
- SDL_Delay(10);
- Continue;
- end;
-
- if (av_read_frame(fFormatCtx, Packet) < 0) then
- begin
- // failed to read a frame, check reason
- {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)}
- ByteIOCtx := fFormatCtx^.pb;
- {$ELSE}
- ByteIOCtx := @fFormatCtx^.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
- fPacketQueue.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
- 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 ((fFormatCtx^.file_size <> 0) and
- (ByteIOCtx^.pos >= fFormatCtx^.file_size)) then
- begin
- fPacketQueue.PutStatus(PKT_STATUS_FLAG_EOF, nil);
- Exit;
- end;
-
- // unknown error occured, exit
- fPacketQueue.PutStatus(PKT_STATUS_FLAG_ERROR, nil);
- Exit;
- end;
-
- if (Packet.stream_index = fAudioStreamIndex) then
- fPacketQueue.Put(@Packet)
- else
- av_free_packet(@Packet);
-
- finally
- UnlockParser();
- end;
- end;
-end;
-
-
-(********************************************
- * Decoder section
- ********************************************)
-
-procedure TFFmpegDecodeStream.PauseDecoder();
-begin
- SDL_mutexP(fStateLock);
- Inc(fDecoderPauseRequestCount);
- while (fDecoderLocked) do
- SDL_CondWait(fDecoderUnlockedCond, fStateLock);
- SDL_mutexV(fStateLock);
-end;
-
-procedure TFFmpegDecodeStream.ResumeDecoder();
-begin
- 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 (@fCodecCtx.codec.flush <> nil) then
- begin
- // flush buffers used by avcodec_decode_audio, etc.
- avcodec_flush_buffers(fCodecCtx);
- 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(fCodecCtx);
- avcodec_open(fCodecCtx, fCodec);
- finally
- FFmpegCore.UnlockAVCodec();
- end;
- end;
-end;
-
-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
- 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 (fAudioPaketSilence > 0) then
- begin
- DataSize := Min(fAudioPaketSilence, BufferSize);
- FillChar(Buffer[0], DataSize, 0);
- Dec(fAudioPaketSilence, DataSize);
- fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec;
- Result := DataSize;
- Exit;
- end;
-
- // read packet data
- while (fAudioPaketSize > 0) do
- begin
- DataSize := BufferSize;
-
- {$IF LIBAVCODEC_VERSION >= 51030000} // 51.30.0
- PaketDecodedSize := avcodec_decode_audio2(fCodecCtx, PSmallint(Buffer),
- DataSize, fAudioPaketData, fAudioPaketSize);
- {$ELSE}
- PaketDecodedSize := avcodec_decode_audio(fCodecCtx, PSmallint(Buffer),
- DataSize, fAudioPaketData, fAudioPaketSize);
- {$IFEND}
-
- if(PaketDecodedSize < 0) then
- begin
- // if error, skip frame
- {$IFDEF DebugFFmpegDecode}
- DebugWriteln('Skip audio frame');
- {$ENDIF}
- fAudioPaketSize := 0;
- Break;
- end;
-
- Inc(PByte(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
- fAudioStreamPos := fAudioStreamPos + DataSize / fFormatInfo.BytesPerSec;
-
- // we have data, return it and come back for more later
- Result := DataSize;
- Exit;
- end;
-
- // free old packet data
- if (fAudioPaket.data <> nil) then
- av_free_packet(@fAudioPaket);
-
- // 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 (fPacketQueue.Get(fAudioPaket, BlockQueue) <= 0) then
- Exit;
-
- // handle Status-packet
- if (PAnsiChar(fAudioPaket.data) = STATUS_PACKET) then
- begin
- fAudioPaket.data := nil;
- fAudioPaketData := nil;
- fAudioPaketSize := 0;
-
- case (fAudioPaket.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(fPacketQueue.GetStatusInfo(fAudioPaket))^;
- fAudioPaketSilence := Round(SilenceDuration * fFormatInfo.SampleRate) * fFormatInfo.FrameSize;
- fPacketQueue.FreeStatusInfo(fAudioPaket);
- end
- else
- begin
- Log.LogStatus('Unknown status', 'TFFmpegDecodeStream.DecodeFrame');
- end;
- end;
-
- Continue;
- end;
-
- fAudioPaketData := fAudioPaket.data;
- fAudioPaketSize := fAudioPaket.size;
-
- // if available, update the stream position to the presentation time of this package
- if(fAudioPaket.pts <> AV_NOPTS_VALUE) then
- begin
- {$IFDEF DebugFFmpegDecode}
- TmpPos := fAudioStreamPos;
- {$ENDIF}
- fAudioStreamPos := av_q2d(fAudioStream^.time_base) * fAudioPaket.pts;
- {$IFDEF DebugFFmpegDecode}
- DebugWriteln('Timestamp: ' + floattostrf(fAudioStreamPos, ffFixed, 15, 3) + ' ' +
- '(Calc: ' + floattostrf(TmpPos, ffFixed, 15, 3) + '), ' +
- 'Diff: ' + floattostrf(fAudioStreamPos-TmpPos, ffFixed, 15, 3));
- {$ENDIF}
- end;
- end;
+ DecodeStream_setLoop(fStream, Enabled);
end;
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
- BufferPos: integer;
-
- // prioritize pause requests
- procedure LockDecoder();
- begin
- SDL_mutexP(fStateLock);
- while (fDecoderPauseRequestCount > 0) do
- SDL_CondWait(fDecoderResumeCond, fStateLock);
- fDecoderLocked := true;
- SDL_mutexV(fStateLock);
- end;
-
- procedure UnlockDecoder();
- begin
- SDL_mutexP(fStateLock);
- fDecoderLocked := false;
- SDL_CondBroadcast(fDecoderUnlockedCond);
- SDL_mutexV(fStateLock);
- 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 (fAudioBufferPos >= fAudioBufferSize) then
- begin
- fAudioBufferPos := 0;
-
- // we have already sent all our data; get more
- fAudioBufferSize := DecodeFrame(fAudioBuffer, AUDIO_BUFFER_SIZE);
-
- // check for errors or EOF
- if(fAudioBufferSize < 0) then
- begin
- Result := BufferPos;
- Exit;
- end;
- end;
-
- // calc number of new bytes in the decode-buffer
- 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(fAudioBuffer[fAudioBufferPos], Buffer[BufferPos], CopyByteCount);
-
- Inc(BufferPos, CopyByteCount);
- Inc(fAudioBufferPos, CopyByteCount);
- end;
- finally
- UnlockDecoder();
- end;
-
- Result := BufferSize;
+ Result := DecodeStream_readData(fStream, PCUint8(Buffer), BufferSize);
end;
@@ -1109,26 +199,7 @@ 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;
+ Result := Plugin_initialize(MediaPluginCore);
end;
function TAudioDecoder_FFmpeg.FinalizeDecoder(): boolean;
@@ -1152,7 +223,6 @@ begin
Result := Stream;
end;
-
initialization
MediaManager.Add(TAudioDecoder_FFmpeg.Create);
diff --git a/mediaplugin/src/media/UMediaPlugin.pas b/mediaplugin/src/media/UMediaPlugin.pas
new file mode 100644
index 00000000..097a28f2
--- /dev/null
+++ b/mediaplugin/src/media/UMediaPlugin.pas
@@ -0,0 +1,132 @@
+{* 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 UMediaPlugin;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE DELPHI }
+ {$PACKENUM 4} (* use 4-byte enums *)
+ {$PACKRECORDS C} (* C/C++-compatible record packing *)
+{$ELSE}
+ {$MINENUMSIZE 4} (* use 4-byte enums *)
+{$ENDIF}
+
+uses
+ ctypes;
+
+type
+ PMediaPluginCore = ^TMediaPluginCore;
+ TMediaPluginCore = record
+ log: procedure(level: cint; msg: PChar; context: PChar); cdecl;
+ end;
+
+ PDecodeStream = Pointer;
+
+ PCAudioFormatInfo = ^TCAudioFormatInfo;
+ TCAudioFormatInfo = record
+ sampleRate: double;
+ channels: cuint8;
+ format: cint;
+ end;
+
+const
+{$IFDEF MSWINDOWS}
+ ffmpegPlugin = 'ffmpeg_playback.dll';
+{$ENDIF}
+{$IFDEF LINUX}
+ ffmpegPlugin = 'ffmpeg_playback';
+{$ENDIF}
+{$IFDEF DARWIN}
+ ffmpegPlugin = 'ffmpeg_playback.dylib';
+ {$linklib ffmpegPlugin}
+{$ENDIF}
+
+function Plugin_initialize(core: PMediaPluginCore): cbool;
+ cdecl; external ffmpegPlugin;
+
+function DecodeStream_open(filename: PAnsiChar): PDecodeStream;
+ cdecl; external ffmpegPlugin;
+procedure DecodeStream_close(stream: PDecodeStream);
+ cdecl; external ffmpegPlugin;
+function DecodeStream_getLength(stream: PDecodeStream): double;
+ cdecl; external ffmpegPlugin;
+procedure DecodeStream_getAudioFormatInfo(stream: PDecodeStream; var info: TCAudioFormatInfo);
+ cdecl; external ffmpegPlugin;
+function DecodeStream_getPosition(stream: PDecodeStream): double;
+ cdecl; external ffmpegPlugin;
+procedure DecodeStream_setPosition(stream: PDecodeStream; time: double);
+ cdecl; external ffmpegPlugin;
+function DecodeStream_getLoop(stream: PDecodeStream): cbool;
+ cdecl; external ffmpegPlugin;
+procedure DecodeStream_setLoop(stream: PDecodeStream; enabled: cbool);
+ cdecl; external ffmpegPlugin;
+function DecodeStream_isEOF(stream: PDecodeStream): cbool;
+ cdecl; external ffmpegPlugin;
+function DecodeStream_isError(stream: PDecodeStream): cbool;
+ cdecl; external ffmpegPlugin;
+function DecodeStream_readData(stream: PDecodeStream; buffer: PCUint8; bufferSize: cint): cint;
+ cdecl; external ffmpegPlugin;
+
+function MediaPluginCore: PMediaPluginCore;
+
+implementation
+
+uses
+ ULog;
+
+var
+ MediaPluginCore_Instance: TMediaPluginCore;
+
+const
+ DebugLogLevels: array[0 .. 5] of integer = (
+ LOG_LEVEL_DEBUG,
+ LOG_LEVEL_INFO,
+ LOG_LEVEL_STATUS,
+ LOG_LEVEL_WARN,
+ LOG_LEVEL_ERROR,
+ LOG_LEVEL_CRITICAL
+ );
+
+procedure LogFunc(level: cint; msg: PChar; context: PChar); cdecl;
+begin
+ Log.LogMsg(msg, context, DebugLogLevels[level]);
+end;
+
+function MediaPluginCore: PMediaPluginCore;
+begin
+ Result := @MediaPluginCore_Instance;
+end;
+
+procedure InitializeMediaPluginCore;
+begin
+ MediaPluginCore.log := LogFunc;
+end;
+
+initialization
+ InitializeMediaPluginCore;
+
+end.
diff --git a/mediaplugin/src/ultrastardx.dpr b/mediaplugin/src/ultrastardx.dpr
index 6cdda69d..16890ad9 100644
--- a/mediaplugin/src/ultrastardx.dpr
+++ b/mediaplugin/src/ultrastardx.dpr
@@ -267,6 +267,8 @@ uses
// This means the first entry has highest priority, the last lowest.
//******************************
+ UMediaPlugin in 'media\UMediaPlugin.pas',
+
{$IFDEF UseFFmpegVideo}
UVideo in 'media\UVideo.pas',
{$ENDIF}