From f8f21b447558c911bcd4bbaaf45e9d7ba52e8fe1 Mon Sep 17 00:00:00 2001 From: tobigun Date: Thu, 28 Oct 2010 14:38:26 +0000 Subject: removed ffmpeg stuff git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2694 b956fd51-792f-4845-bead-9b4dfca2ff2c --- mediaplugin/src/media/UAudioDecoder_FFmpeg.pas | 2 +- mediaplugin/src/media/UMediaCore_FFmpeg.pas | 343 ------------- mediaplugin/src/media/UMediaPlugin.pas | 16 + mediaplugin/src/media/UVideoDecoder_FFmpeg.pas | 654 ++----------------------- 4 files changed, 54 insertions(+), 961 deletions(-) delete mode 100644 mediaplugin/src/media/UMediaCore_FFmpeg.pas (limited to 'mediaplugin/src/media') diff --git a/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas b/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas index bef162bd..2865a7d8 100644 --- a/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas +++ b/mediaplugin/src/media/UAudioDecoder_FFmpeg.pas @@ -207,7 +207,7 @@ end; function TAudioDecoder_FFmpeg.GetName: String; begin - Result := 'Plugin:' + fPluginInfo.name; + Result := 'Plugin:AudioDecoder:' + fPluginInfo.name; end; function TAudioDecoder_FFmpeg.InitializeDecoder: boolean; diff --git a/mediaplugin/src/media/UMediaCore_FFmpeg.pas b/mediaplugin/src/media/UMediaCore_FFmpeg.pas deleted file mode 100644 index 2f589442..00000000 --- a/mediaplugin/src/media/UMediaCore_FFmpeg.pas +++ /dev/null @@ -1,343 +0,0 @@ -{* 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 UMediaCore_FFmpeg; - -interface - -{$IFDEF FPC} - {$MODE Delphi} -{$ENDIF} - -{$I switches.inc} - -uses - Classes, - ctypes, - sdl, - avcodec, - avformat, - avutil, - avio, - swscale, - UMusic, - ULog, - UPath; - -const - STATUS_PACKET: PChar = 'STATUS_PACKET'; -const - PKT_STATUS_FLAG_EOF = 1; // signal end-of-file - PKT_STATUS_FLAG_FLUSH = 2; // request the decoder to flush its avcodec decode buffers - PKT_STATUS_FLAG_ERROR = 3; // signal an error state - PKT_STATUS_FLAG_EMPTY = 4; // request the decoder to output empty data (silence or black frames) - -type - TMediaCore_FFmpeg = class - private - AVCodecLock: PSDL_Mutex; - public - constructor Create(); - destructor Destroy(); override; - class function GetInstance(): TMediaCore_FFmpeg; - - function GetErrorString(ErrorNum: integer): string; - function FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer ): boolean; - function FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer; - function ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean; - procedure LockAVCodec(); - procedure UnlockAVCodec(); - end; - -implementation - -uses - SysUtils, - UConfig; - -function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl; forward; -function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward; -function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; forward; -function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl; forward; -function FFmpegStreamClose(h: PURLContext): cint; cdecl; forward; - -const - UTF8FileProtocol: TURLProtocol = ( - name: 'ufile'; - url_open: FFmpegStreamOpen; - url_read: FFmpegStreamRead; - url_write: FFmpegStreamWrite; - url_seek: FFmpegStreamSeek; - url_close: FFmpegStreamClose; - ); - -var - Instance: TMediaCore_FFmpeg; - -procedure CheckVersions(); -begin -end; - -constructor TMediaCore_FFmpeg.Create(); -begin - inherited; - - CheckVersions(); - av_register_protocol(@UTF8FileProtocol); - AVCodecLock := SDL_CreateMutex(); -end; - -destructor TMediaCore_FFmpeg.Destroy(); -begin - SDL_DestroyMutex(AVCodecLock); - inherited; -end; - -class function TMediaCore_FFmpeg.GetInstance(): TMediaCore_FFmpeg; -begin - if (not Assigned(Instance)) then - Instance := TMediaCore_FFmpeg.Create(); - Result := Instance; -end; - -procedure TMediaCore_FFmpeg.LockAVCodec(); -begin - SDL_mutexP(AVCodecLock); -end; - -procedure TMediaCore_FFmpeg.UnlockAVCodec(); -begin - SDL_mutexV(AVCodecLock); -end; - -function TMediaCore_FFmpeg.GetErrorString(ErrorNum: integer): string; -begin - case ErrorNum of - AVERROR_IO: Result := 'AVERROR_IO'; - AVERROR_NUMEXPECTED: Result := 'AVERROR_NUMEXPECTED'; - AVERROR_INVALIDDATA: Result := 'AVERROR_INVALIDDATA'; - AVERROR_NOMEM: Result := 'AVERROR_NOMEM'; - AVERROR_NOFMT: Result := 'AVERROR_NOFMT'; - AVERROR_NOTSUPP: Result := 'AVERROR_NOTSUPP'; - AVERROR_NOENT: Result := 'AVERROR_NOENT'; - AVERROR_PATCHWELCOME: Result := 'AVERROR_PATCHWELCOME'; - else Result := 'AVERROR_#'+inttostr(ErrorNum); - end; -end; - -{ - @param(FormatCtx is a PAVFormatContext returned from av_open_input_file ) - @param(FirstVideoStream is an OUT value of type integer, this is the index of the video stream) - @param(FirstAudioStream is an OUT value of type integer, this is the index of the audio stream) - @returns(@true on success, @false otherwise) -} -function TMediaCore_FFmpeg.FindStreamIDs(FormatCtx: PAVFormatContext; out FirstVideoStream, FirstAudioStream: integer): boolean; -var - i: integer; - Stream: PAVStream; -begin - // find the first video stream - FirstAudioStream := -1; - FirstVideoStream := -1; - - for i := 0 to FormatCtx.nb_streams-1 do - begin - Stream := FormatCtx.streams[i]; - -{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0 - if (Stream.codec.codec_type = CODEC_TYPE_VIDEO) and - (FirstVideoStream < 0) then - begin - FirstVideoStream := i; - end; - - if (Stream.codec.codec_type = CODEC_TYPE_AUDIO) and - (FirstAudioStream < 0) then - begin - FirstAudioStream := i; - end; - end; -{$ELSE} - if (Stream.codec.codec_type = AVMEDIA_TYPE_VIDEO) and - (FirstVideoStream < 0) then - begin - FirstVideoStream := i; - end; - - if (Stream.codec.codec_type = AVMEDIA_TYPE_AUDIO) and - (FirstAudioStream < 0) then - begin - FirstAudioStream := i; - end; - end; -{$IFEND} - - // return true if either an audio- or video-stream was found - Result := (FirstAudioStream > -1) or - (FirstVideoStream > -1) ; -end; - -function TMediaCore_FFmpeg.FindAudioStreamIndex(FormatCtx: PAVFormatContext): integer; -var - i: integer; - StreamIndex: integer; - Stream: PAVStream; -begin - // find the first audio stream - StreamIndex := -1; - - for i := 0 to FormatCtx^.nb_streams-1 do - begin - Stream := FormatCtx^.streams[i]; - -{$IF LIBAVCODEC_VERSION < 52064000} // < 52.64.0 - if (Stream.codec^.codec_type = CODEC_TYPE_AUDIO) then -{$ELSE} - if (Stream.codec^.codec_type = AVMEDIA_TYPE_AUDIO) then -{$IFEND} - begin - StreamIndex := i; - Break; - end; - end; - - Result := StreamIndex; -end; - -function TMediaCore_FFmpeg.ConvertFFmpegToAudioFormat(FFmpegFormat: TSampleFormat; out Format: TAudioSampleFormat): boolean; -begin - case FFmpegFormat of - SAMPLE_FMT_U8: Format := asfU8; - SAMPLE_FMT_S16: Format := asfS16; - SAMPLE_FMT_S32: Format := asfS32; - SAMPLE_FMT_FLT: Format := asfFloat; - SAMPLE_FMT_DBL: Format := asfDouble; - else begin - Result := false; - Exit; - end; - end; - Result := true; -end; - - -{** - * UTF-8 Filename wrapper based on: - * http://www.mail-archive.com/libav-user@mplayerhq.hu/msg02460.html - *} - -function FFmpegStreamOpen(h: PURLContext; filename: PChar; flags: cint): cint; cdecl; -var - Stream: TStream; - Mode: word; - ProtPrefix: string; - FilePath: IPath; -begin - // check for protocol prefix ('ufile:') and strip it - ProtPrefix := Format('%s:', [UTF8FileProtocol.name]); - if (StrLComp(filename, PChar(ProtPrefix), Length(ProtPrefix)) = 0) then - begin - Inc(filename, Length(ProtPrefix)); - end; - - FilePath := Path(filename); - - if ((flags and URL_RDWR) <> 0) then - Mode := fmCreate - else if ((flags and URL_WRONLY) <> 0) then - Mode := fmCreate // TODO: fmCreate is Read+Write -> reopen with fmOpenWrite - else - Mode := fmOpenRead or fmShareDenyWrite; - - Result := 0; - - try - Stream := TBinaryFileStream.Create(FilePath, Mode); - h.priv_data := Stream; - except - Result := AVERROR_NOENT; - end; -end; - -function FFmpegStreamRead(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; -var - Stream: TStream; -begin - Stream := TStream(h.priv_data); - if (Stream = nil) then - raise EInvalidContainer.Create('FFmpegStreamRead on nil'); - try - Result := Stream.Read(buf[0], size); - except - Result := -1; - end; -end; - -function FFmpegStreamWrite(h: PURLContext; buf: PByteArray; size: cint): cint; cdecl; -var - Stream: TStream; -begin - Stream := TStream(h.priv_data); - if (Stream = nil) then - raise EInvalidContainer.Create('FFmpegStreamWrite on nil'); - try - Result := Stream.Write(buf[0], size); - except - Result := -1; - end; -end; - -function FFmpegStreamSeek(h: PURLContext; pos: int64; whence: cint): int64; cdecl; -var - Stream : TStream; - Origin : TSeekOrigin; -begin - Stream := TStream(h.priv_data); - if (Stream = nil) then - raise EInvalidContainer.Create('FFmpegStreamSeek on nil'); - case whence of - 0 {SEEK_SET}: Origin := soBeginning; - 1 {SEEK_CUR}: Origin := soCurrent; - 2 {SEEK_END}: Origin := soEnd; - AVSEEK_SIZE: begin - Result := Stream.Size; - Exit; - end - else - Origin := soBeginning; - end; - Result := Stream.Seek(pos, Origin); -end; - -function FFmpegStreamClose(h: PURLContext): cint; cdecl; -var - Stream : TStream; -begin - Stream := TStream(h.priv_data); - Stream.Free; - Result := 0; -end; - -end. diff --git a/mediaplugin/src/media/UMediaPlugin.pas b/mediaplugin/src/media/UMediaPlugin.pas index c676619c..2eb0772e 100644 --- a/mediaplugin/src/media/UMediaPlugin.pas +++ b/mediaplugin/src/media/UMediaPlugin.pas @@ -80,6 +80,7 @@ type PAudioDecodeStream = Pointer; PAudioConvertStream = Pointer; + PVideoDecodeStream = Pointer; PCAudioFormatInfo = ^TCAudioFormatInfo; TCAudioFormatInfo = record @@ -112,6 +113,20 @@ type getRatio: function(stream: PAudioConvertStream): double; cdecl; end; + PVideoDecoderInfo = ^TVideoDecoderInfo; + TVideoDecoderInfo = record + open: function(filename: PAnsiChar): PVideoDecodeStream; cdecl; + close: procedure(stream: PVideoDecodeStream); cdecl; + setLoop: procedure(stream: PVideoDecodeStream; enable: cbool); cdecl; + getLoop: function(stream: PVideoDecodeStream): cbool; cdecl; + setPosition: procedure(stream: PVideoDecodeStream; time: double); cdecl; + getPosition: function(stream: PVideoDecodeStream): double; cdecl; + getFrameWidth: function(stream: PVideoDecodeStream): cint; cdecl; + getFrameHeight: function(stream: PVideoDecodeStream): cint; cdecl; + getFrameAspect: function(stream: PVideoDecodeStream): double; cdecl; + getFrame: function (stream: PVideoDecodeStream; time: clongdouble): PCuint8; cdecl; + end; + PMediaPluginInfo = ^TMediaPluginInfo; TMediaPluginInfo = record version: cint; @@ -120,6 +135,7 @@ type finalize: function(): cbool; cdecl; audioDecoder: PAudioDecoderInfo; audioConverter: PAudioConverterInfo; + videoDecoder: PVideoDecoderInfo; end; Plugin_registerFunc = function(core: PMediaPluginCore): PMediaPluginInfo; cdecl; diff --git a/mediaplugin/src/media/UVideoDecoder_FFmpeg.pas b/mediaplugin/src/media/UVideoDecoder_FFmpeg.pas index b781b0d2..51916276 100644 --- a/mediaplugin/src/media/UVideoDecoder_FFmpeg.pas +++ b/mediaplugin/src/media/UVideoDecoder_FFmpeg.pas @@ -25,14 +25,6 @@ unit UVideoDecoder_FFmpeg; -{* - * based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/) - *} - -// uncomment if you want to see the debug stuff -{.$define DebugDisplay} -{.$define DebugFrames} - interface {$IFDEF FPC} @@ -41,80 +33,24 @@ interface {$I switches.inc} -// use BGR-format for accelerated colorspace conversion with swscale -{$IFDEF UseSWScale} - {$DEFINE PIXEL_FMT_BGR} -{$ENDIF} - implementation uses SysUtils, Math, - avcodec, - avformat, - avutil, - avio, - rational, - {$IFDEF UseSWScale} - swscale, - {$ENDIF} - UMediaCore_FFmpeg, + UMediaPlugin, UCommon, UConfig, ULog, UMusic, UPath; -{$DEFINE PIXEL_FMT_BGR} - -const -{$IFDEF PIXEL_FMT_BGR} - PIXEL_FMT_FFMPEG = PIX_FMT_BGR24; - PIXEL_FMT_SIZE = 3; - - // looks strange on linux: - //PIXEL_FMT_FFMPEG = PIX_FMT_BGR32; - //PIXEL_FMT_SIZE = 4; -{$ELSE} - // looks strange on linux: - PIXEL_FMT_FFMPEG = PIX_FMT_RGB24; - PIXEL_FMT_SIZE = 3; -{$ENDIF} - type TVideoDecodeStream_FFmpeg = class (TVideoDecodeStream) private - fOpened: boolean; //**< stream successfully opened - fEOF: boolean; //**< end-of-file state - - fLoop: boolean; //**< looping enabled - - fStream: PAVStream; - fStreamIndex : integer; - fFormatContext: PAVFormatContext; - fCodecContext: PAVCodecContext; - fCodec: PAVCodec; - - fAVFrame: PAVFrame; - fAVFrameRGB: PAVFrame; - - fFrameBuffer: PByte; //**< stores a FFmpeg video frame - fFrameTexValid: boolean; //**< if true, fFrameTex contains the current frame - - {$IFDEF UseSWScale} - fSwScaleContext: PSwsContext; - {$ENDIF} - - fAspect: real; //**< width/height ratio - - fFrameDuration: extended; //**< duration of a video frame in seconds (= 1/fps) - fFrameTime: extended; //**< video time position (absolute) - fLoopTime: extended; //**< start time of the current loop - - procedure Reset(); - function DecodeFrame(): boolean; - procedure SynchronizeTime(Frame: PAVFrame; var pts: double); + private + fFilename: IPath; + fStream: PVideoDecodeStream; public constructor Create; @@ -138,9 +74,10 @@ type TVideoDecoder_FFmpeg = class( TInterfacedObject, IVideoDecoder ) private - fInitialized: boolean; - + fPluginInfo: PMediaPluginInfo; public + constructor Create(); + function GetName: String; function InitializeDecoder(): boolean; @@ -150,60 +87,33 @@ type end; var - FFmpegCore: TMediaCore_FFmpeg; - - -// These are called whenever we allocate a frame buffer. -// We use this to store the global_pts in a frame at the time it is allocated. -function PtsGetBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame): integer; cdecl; -var - pts: Pint64; - VideoPktPts: Pint64; -begin - Result := avcodec_default_get_buffer(CodecCtx, Frame); - VideoPktPts := CodecCtx^.opaque; - if (VideoPktPts <> nil) then - begin - // Note: we must copy the pts instead of passing a pointer, because the packet - // (and with it the pts) might change before a frame is returned by av_decode_video. - pts := av_malloc(sizeof(int64)); - pts^ := VideoPktPts^; - Frame^.opaque := pts; - end; -end; - -procedure PtsReleaseBuffer(CodecCtx: PAVCodecContext; Frame: PAVFrame); cdecl; -begin - if (Frame <> nil) then - av_freep(@Frame^.opaque); - avcodec_default_release_buffer(CodecCtx, Frame); -end; - + VideoDecoderInfo: PVideoDecoderInfo; {*------------------------------------------------------------------------------ * TVideoPlayback_ffmpeg *------------------------------------------------------------------------------} +constructor TVideoDecoder_FFmpeg.Create(); +begin + inherited Create(); + fPluginInfo := Plugin_register(MediaPluginCore); +end; + function TVideoDecoder_FFmpeg.GetName: String; begin - result := 'FFmpeg_VideoDecoder'; + Result := 'Plugin:VideoDecoder:' + fPluginInfo.name; end; function TVideoDecoder_FFmpeg.InitializeDecoder(): boolean; begin + fPluginInfo.initialize(); + VideoDecoderInfo := fPluginInfo.videoDecoder; Result := true; - - if (fInitialized) then - Exit; - fInitialized := true; - - FFmpegCore := TMediaCore_FFmpeg.GetInstance(); - - av_register_all(); end; function TVideoDecoder_FFmpeg.FinalizeDecoder(): boolean; begin + fPluginInfo.finalize(); Result := true; end; @@ -228,569 +138,79 @@ end; constructor TVideoDecodeStream_FFmpeg.Create; begin - Reset(); + inherited Create(); + fFilename := PATH_NONE; end; destructor TVideoDecodeStream_FFmpeg.Destroy; begin Close(); + inherited; end; function TVideoDecodeStream_FFmpeg.Open(const FileName: IPath): boolean; -var - errnum: Integer; - AudioStreamIndex: integer; begin Result := false; - Reset(); - - // use custom 'ufile' protocol for UTF-8 support - errnum := av_open_input_file(fFormatContext, PAnsiChar('ufile:'+FileName.ToUTF8), nil, 0, nil); - if (errnum <> 0) then - begin - Log.LogError('Failed to open file "'+ FileName.ToNative +'" ('+FFmpegCore.GetErrorString(errnum)+')'); - Exit; - end; - - // update video info - if (av_find_stream_info(fFormatContext) < 0) then - begin - Log.LogError('No stream info found', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; - Log.LogInfo('VideoStreamIndex : ' + inttostr(fStreamIndex), 'TVideoPlayback_ffmpeg.Open'); - - // find video stream - FFmpegCore.FindStreamIDs(fFormatContext, fStreamIndex, AudioStreamIndex); - if (fStreamIndex < 0) then - begin - Log.LogError('No video stream found', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; - - fStream := fFormatContext^.streams[fStreamIndex]; - fCodecContext := fStream^.codec; - fCodec := avcodec_find_decoder(fCodecContext^.codec_id); - if (fCodec = nil) then - begin - Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; - - // set debug options - fCodecContext^.debug_mv := 0; - fCodecContext^.debug := 0; - - // detect bug-workarounds automatically - fCodecContext^.workaround_bugs := FF_BUG_AUTODETECT; - // error resilience strategy (careful/compliant/agressive/very_aggressive) - //fCodecContext^.error_resilience := FF_ER_CAREFUL; //FF_ER_COMPLIANT; - // allow non spec compliant speedup tricks. - //fCodecContext^.flags2 := fCodecContext^.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 - errnum := avcodec_open(fCodecContext, fCodec); - finally - FFmpegCore.UnlockAVCodec(); - end; - if (errnum < 0) then - begin - Log.LogError('No matching codec found', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; - - // register custom callbacks for pts-determination - fCodecContext^.get_buffer := PtsGetBuffer; - fCodecContext^.release_buffer := PtsReleaseBuffer; - - {$ifdef DebugDisplay} - DebugWriteln('Found a matching Codec: '+ fCodecContext^.Codec.Name + sLineBreak + - sLineBreak + - ' Width = '+inttostr(fCodecContext^.width) + - ', Height='+inttostr(fCodecContext^.height) + sLineBreak + - ' Aspect : '+inttostr(fCodecContext^.sample_aspect_ratio.num) + '/' + - inttostr(fCodecContext^.sample_aspect_ratio.den) + sLineBreak + - ' Framerate : '+inttostr(fCodecContext^.time_base.num) + '/' + - inttostr(fCodecContext^.time_base.den)); - {$endif} - - // allocate space for decoded frame and rgb frame - fAVFrame := avcodec_alloc_frame(); - fAVFrameRGB := avcodec_alloc_frame(); - fFrameBuffer := av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG, - fCodecContext^.width, fCodecContext^.height)); - - if ((fAVFrame = nil) or (fAVFrameRGB = nil) or (fFrameBuffer = nil)) then - begin - Log.LogError('Failed to allocate buffers', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; + Close(); - // TODO: pad data for OpenGL to GL_UNPACK_ALIGNMENT - // (otherwise video will be distorted if width/height is not a multiple of the alignment) - errnum := avpicture_fill(PAVPicture(fAVFrameRGB), fFrameBuffer, PIXEL_FMT_FFMPEG, - fCodecContext^.width, fCodecContext^.height); - if (errnum < 0) then - begin - Log.LogError('avpicture_fill failed: ' + FFmpegCore.GetErrorString(errnum), 'TVideoPlayback_ffmpeg.Open'); - Close(); + fStream := VideoDecoderInfo.open(PChar(Filename.ToUTF8())); + if (fStream = nil) then Exit; - end; - // calculate some information for video display - fAspect := av_q2d(fCodecContext^.sample_aspect_ratio); - if (fAspect = 0) then - fAspect := fCodecContext^.width / - fCodecContext^.height - else - fAspect := fAspect * fCodecContext^.width / - fCodecContext^.height; + fFilename := Filename; - fFrameDuration := 1/av_q2d(fStream^.r_frame_rate); - - // hack to get reasonable framerate (for divx and others) - if (fFrameDuration < 0.02) then // 0.02 <-> 50 fps - begin - fFrameDuration := av_q2d(fStream^.r_frame_rate); - while (fFrameDuration > 50) do - fFrameDuration := fFrameDuration/10; - fFrameDuration := 1/fFrameDuration; - end; - - Log.LogInfo('Framerate: '+inttostr(floor(1/fFrameDuration))+'fps', 'TVideoPlayback_ffmpeg.Open'); - - {$IFDEF UseSWScale} - // if available get a SWScale-context -> faster than the deprecated img_convert(). - // SWScale has accelerated support for PIX_FMT_RGB32/PIX_FMT_BGR24/PIX_FMT_BGR565/PIX_FMT_BGR555. - // Note: PIX_FMT_RGB32 is a BGR- and not an RGB-format (maybe a bug)!!! - // The BGR565-formats (GL_UNSIGNED_SHORT_5_6_5) is way too slow because of its - // bad OpenGL support. The BGR formats have MMX(2) implementations but no speed-up - // could be observed in comparison to the RGB versions. - fSwScaleContext := sws_getContext( - fCodecContext^.width, fCodecContext^.height, - fCodecContext^.pix_fmt, - fCodecContext^.width, fCodecContext^.height, - PIXEL_FMT_FFMPEG, - SWS_FAST_BILINEAR, nil, nil, nil); - if (fSwScaleContext = nil) then - begin - Log.LogError('Failed to get swscale context', 'TVideoPlayback_ffmpeg.Open'); - Close(); - Exit; - end; - {$ENDIF} - - fOpened := true; Result := true; end; -procedure TVideoDecodeStream_FFmpeg.Reset(); -begin - // close previously opened video - Close(); - - fOpened := False; - fFrameDuration := 0; - fFrameTime := 0; - fStream := nil; - fStreamIndex := -1; - fFrameTexValid := false; - - fEOF := false; - - fLoop := false; - fLoopTime := 0; -end; - procedure TVideoDecodeStream_FFmpeg.Close; begin - if (fFrameBuffer <> nil) then - av_free(fFrameBuffer); - if (fAVFrameRGB <> nil) then - av_free(fAVFrameRGB); - if (fAVFrame <> nil) then - av_free(fAVFrame); - - fAVFrame := nil; - fAVFrameRGB := nil; - fFrameBuffer := nil; - - if (fCodecContext <> nil) then - begin - // avcodec_close() is not thread-safe - FFmpegCore.LockAVCodec(); - try - avcodec_close(fCodecContext); - finally - FFmpegCore.UnlockAVCodec(); - end; - end; - - if (fFormatContext <> nil) then - av_close_input_file(fFormatContext); - - fCodecContext := nil; - fFormatContext := nil; - - fOpened := False; -end; - -procedure TVideoDecodeStream_FFmpeg.SynchronizeTime(Frame: PAVFrame; var pts: double); -var - FrameDelay: double; -begin - if (pts <> 0) then + Self.fFilename := PATH_NONE; + if (fStream <> nil) then begin - // if we have pts, set video clock to it - fFrameTime := pts; - end else - begin - // if we aren't given a pts, set it to the clock - pts := fFrameTime; + VideoDecoderInfo.close(fStream); + fStream := nil; end; - // update the video clock - FrameDelay := av_q2d(fCodecContext^.time_base); - // if we are repeating a frame, adjust clock accordingly - FrameDelay := FrameDelay + Frame^.repeat_pict * (FrameDelay * 0.5); - fFrameTime := fFrameTime + FrameDelay; -end; - -{** - * Decode a new frame from the video stream. - * The decoded frame is stored in fAVFrame. fFrameTime is updated to the new frame's - * time. - * @param pts will be updated to the presentation time of the decoded frame. - * returns true if a frame could be decoded. False if an error or EOF occured. - *} -function TVideoDecodeStream_FFmpeg.DecodeFrame(): boolean; -var - FrameFinished: Integer; - VideoPktPts: int64; - pbIOCtx: PByteIOContext; - errnum: integer; - AVPacket: TAVPacket; - pts: double; -begin - Result := false; - FrameFinished := 0; - - if fEOF then - Exit; - - // read packets until we have a finished frame (or there are no more packets) - while (FrameFinished = 0) do - begin - errnum := av_read_frame(fFormatContext, AVPacket); - if (errnum < 0) then - begin - // failed to read a frame, check reason - - {$IF (LIBAVFORMAT_VERSION_MAJOR >= 52)} - pbIOCtx := fFormatContext^.pb; - {$ELSE} - pbIOCtx := @fFormatContext^.pb; - {$IFEND} - - // check for end-of-file (EOF is not an error) - if (url_feof(pbIOCtx) <> 0) then - begin - fEOF := true; - Exit; - end; - - // check for errors - if (url_ferror(pbIOCtx) <> 0) then - begin - Log.LogError('Video decoding file error', 'TVideoPlayback_FFmpeg.DecodeFrame'); - Exit; - end; - - // url_feof() does not detect an EOF for some mov-files (e.g. deluxe.mov) - // so we have to do it this way. - if ((fFormatContext^.file_size <> 0) and - (pbIOCtx^.pos >= fFormatContext^.file_size)) then - begin - fEOF := true; - Exit; - end; - - // error occured, log and exit - Log.LogError('Video decoding error', 'TVideoPlayback_FFmpeg.DecodeFrame'); - Exit; - end; - - // if we got a packet from the video stream, then decode it - if (AVPacket.stream_index = fStreamIndex) then - begin - // save pts to be stored in pFrame in first call of PtsGetBuffer() - VideoPktPts := AVPacket.pts; - fCodecContext^.opaque := @VideoPktPts; - - // decode packet - avcodec_decode_video(fCodecContext, fAVFrame, - frameFinished, AVPacket.data, AVPacket.size); - - // reset opaque data - fCodecContext^.opaque := nil; - - // update pts - if (AVPacket.dts <> AV_NOPTS_VALUE) then - begin - pts := AVPacket.dts; - end - else if ((fAVFrame^.opaque <> nil) and - (Pint64(fAVFrame^.opaque)^ <> AV_NOPTS_VALUE)) then - begin - pts := Pint64(fAVFrame^.opaque)^; - end - else - begin - pts := 0; - end; - - if fStream^.start_time <> AV_NOPTS_VALUE then - pts := pts - fStream^.start_time; - - pts := pts * av_q2d(fStream^.time_base); - - // synchronize time on each complete frame - if (frameFinished <> 0) then - SynchronizeTime(fAVFrame, pts); - end; - - // free the packet from av_read_frame - av_free_packet( @AVPacket ); - end; - - Result := true; end; function TVideoDecodeStream_FFmpeg.GetFrame(Time: Extended): PByteArray; -var - errnum: Integer; - CurrentTime: Extended; - TimeDiff: Extended; - DropFrameCount: Integer; - i: Integer; - Success: boolean; -const - SKIP_FRAME_DIFF = 0.010; // start skipping if we are >= 10ms too late begin - Result := nil; - - if not fOpened then - Exit; - - {* - * Synchronization - begin - *} - - // requested stream position (relative to the last loop's start) - if (fLoop) then - CurrentTime := Time - fLoopTime - else - CurrentTime := Time; - - // check if current texture still contains the active frame - if (fFrameTexValid) then - begin - // time since the last frame was returned - TimeDiff := CurrentTime - fFrameTime; - - {$IFDEF DebugDisplay} - DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak + - 'VideoTime: '+inttostr(floor(fFrameTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + - 'TimeDiff: '+inttostr(floor(TimeDiff*1000))); - {$endif} - - // check if time has reached the next frame - if (TimeDiff < fFrameDuration) then - begin - {$ifdef DebugFrames} - // frame delay debug display - GoldenRec.Spawn(200,15,1,16,0,-1,ColoredStar,$00ff00); - {$endif} - - {$IFDEF DebugDisplay} - DebugWriteln('not getting new frame' + sLineBreak + - 'Time: '+inttostr(floor(Time*1000)) + sLineBreak + - 'VideoTime: '+inttostr(floor(fFrameTime*1000)) + sLineBreak + - 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + - 'TimeDiff: '+inttostr(floor(TimeDiff*1000))); - {$endif} - - // we do not need a new frame now - Exit; - end; - end; - - // fetch new frame (updates fFrameTime) - Success := DecodeFrame(); - TimeDiff := CurrentTime - fFrameTime; - - // check if we have to skip frames - // Either if we are one frame behind or if the skip threshold has been reached. - // Do not skip if the difference is less than fFrameDuration as there is no next frame. - // Note: We assume that fFrameDuration is the length of one frame. - if (TimeDiff >= Max(fFrameDuration, SKIP_FRAME_DIFF)) then - begin - {$IFDEF DebugFrames} - //frame drop debug display - GoldenRec.Spawn(200,55,1,16,0,-1,ColoredStar,$ff0000); - {$ENDIF} - {$IFDEF DebugDisplay} - DebugWriteln('skipping frames' + sLineBreak + - 'TimeBase: '+inttostr(floor(fFrameDuration*1000)) + sLineBreak + - 'TimeDiff: '+inttostr(floor(TimeDiff*1000))); - {$endif} - - // update video-time - DropFrameCount := Trunc(TimeDiff / fFrameDuration); - fFrameTime := fFrameTime + DropFrameCount*fFrameDuration; - - // skip frames - for i := 1 to DropFrameCount do - Success := DecodeFrame(); - end; - - // check if we got an EOF or error - if (not Success) then - begin - if fLoop then - begin - // we have to loop, so rewind - SetPosition(0); - // record the start-time of the current loop, so we can - // determine the position in the stream (fFrameTime-fLoopTime) later. - fLoopTime := Time; - end; - Exit; - end; - - {* - * Synchronization - end - *} - - // TODO: support for pan&scan - //if (fAVFrame.pan_scan <> nil) then - //begin - // Writeln(Format('PanScan: %d/%d', [fAVFrame.pan_scan.width, fAVFrame.pan_scan.height])); - //end; - - // otherwise we convert the pixeldata from YUV to RGB - {$IFDEF UseSWScale} - errnum := sws_scale(fSwScaleContext, @fAVFrame.data, @fAVFrame.linesize, - 0, fCodecContext^.Height, - @fAVFrameRGB.data, @fAVFrameRGB.linesize); - {$ELSE} - // img_convert from lib/ffmpeg/avcodec.pas is actually deprecated. - // If ./configure does not find SWScale then this gives the error - // that the identifier img_convert is not known or similar. - // I think this should be removed, but am not sure whether there should - // be some other replacement or a warning, Therefore, I leave it for now. - // April 2009, mischi - errnum := img_convert(PAVPicture(fAVFrameRGB), PIXEL_FMT_FFMPEG, - PAVPicture(fAVFrame), fCodecContext^.pix_fmt, - fCodecContext^.width, fCodecContext^.height); - {$ENDIF} - - if (errnum < 0) then - begin - Log.LogError('Image conversion failed', 'TVideoPlayback_ffmpeg.GetFrame'); - Exit; - end; - - if (not fFrameTexValid) then - fFrameTexValid := true; - - Result := PByteArray(fAVFrameRGB^.data[0]); + Result := PByteArray(VideoDecoderInfo.getFrame(fStream, Time)); end; procedure TVideoDecodeStream_FFmpeg.SetLoop(Enable: boolean); begin - fLoop := Enable; - fLoopTime := 0; + VideoDecoderInfo.setLoop(fStream, Enable); end; function TVideoDecodeStream_FFmpeg.GetLoop(): boolean; begin - Result := fLoop; + Result := VideoDecoderInfo.getLoop(fStream); end; -{** - * Sets the stream's position. - * The stream is set to the first keyframe with timestamp <= Time. - * Note that fFrameTime is set to Time no matter if the actual position seeked to is - * at Time or the time of a preceding keyframe. fFrameTime will be updated to the - * actual frame time when GetFrame() is called the next time. - * @param Time new position in seconds - *} procedure TVideoDecodeStream_FFmpeg.SetPosition(Time: real); -var - SeekFlags: integer; begin - if not fOpened then - Exit; - - if (Time < 0) then - Time := 0; - - // TODO: handle fLoop-times - //Time := Time mod VideoDuration; - - // Do not use the AVSEEK_FLAG_ANY here. It will seek to any frame, even - // non keyframes (P-/B-frames). It will produce corrupted video frames as - // FFmpeg does not use the information of the preceding I-frame. - // The picture might be gray or green until the next keyframe occurs. - // Instead seek the first keyframe smaller than the requested time - // (AVSEEK_FLAG_BACKWARD). As this can be some seconds earlier than the - // requested time, let the sync in GetFrame() do its job. - SeekFlags := AVSEEK_FLAG_BACKWARD; - - fFrameTime := Time; - fEOF := false; - fFrameTexValid := false; - - if (av_seek_frame(fFormatContext, - fStreamIndex, - Round(Time / av_q2d(fStream^.time_base)), - SeekFlags) < 0) then - begin - Log.LogError('av_seek_frame() failed', 'TVideoPlayback_ffmpeg.SetPosition'); - Exit; - end; - - avcodec_flush_buffers(fCodecContext); + VideoDecoderInfo.setPosition(fStream, Time); end; function TVideoDecodeStream_FFmpeg.GetPosition: real; begin - Result := fFrameTime; + Result := VideoDecoderInfo.getPosition(fStream); end; function TVideoDecodeStream_FFmpeg.GetFrameWidth(): integer; begin - Result := fCodecContext^.width; + Result := VideoDecoderInfo.getFrameWidth(fStream); end; function TVideoDecodeStream_FFmpeg.GetFrameHeight(): integer; begin - Result := fCodecContext^.height; + Result := VideoDecoderInfo.getFrameHeight(fStream); end; function TVideoDecodeStream_FFmpeg.GetFrameAspect(): real; begin - Result := fAspect; + Result := VideoDecoderInfo.getFrameAspect(fStream); end; initialization -- cgit v1.2.3