diff options
Diffstat (limited to '')
-rw-r--r-- | mediaplugin/src/media/UAudioDecoder_FFmpeg.pas | 982 | ||||
-rw-r--r-- | mediaplugin/src/media/UMediaPlugin.pas | 132 |
2 files changed, 158 insertions, 956 deletions
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. |