aboutsummaryrefslogtreecommitdiffstats
path: root/src/media/UVideo.pas
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/media/UVideo.pas966
1 files changed, 0 insertions, 966 deletions
diff --git a/src/media/UVideo.pas b/src/media/UVideo.pas
deleted file mode 100644
index 6db9cd20..00000000
--- a/src/media/UVideo.pas
+++ /dev/null
@@ -1,966 +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 UVideo;
-
-{*
- * 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}
-{.$define VideoBenchmark}
-{.$define Info}
-
-interface
-
-{$IFDEF FPC}
- {$MODE Delphi}
-{$ENDIF}
-
-{$I switches.inc}
-
-// use BGR-format for accelerated colorspace conversion with swscale
-{$IFDEF UseSWScale}
- {$DEFINE PIXEL_FMT_BGR}
-{$ENDIF}
-
-type
- {**
- * vacStretch: Stretch to screen width and height
- * - ignores aspect
- * + no borders
- * + no image data loss
- * vacCrop: Stretch to screen width or height, crop the other dimension
- * + keeps aspect
- * + no borders
- * - frame borders are cropped (image data loss)
- * vacLetterBox: Stretch to screen width, add bars at or crop top and bottom
- * + keeps aspect
- * - borders at top and bottom
- * o top/bottom is cropped if width < height (unusual)
- *}
- TAspectCorrection = (acoStretch, acoCrop, acoLetterBox);
-
-
-implementation
-
-uses
- SysUtils,
- Math,
- SDL,
- avcodec,
- avformat,
- avutil,
- avio,
- rational,
- {$IFDEF UseSWScale}
- swscale,
- {$ENDIF}
- gl,
- glext,
- textgl,
- UMediaCore_FFmpeg,
- UCommon,
- UConfig,
- ULog,
- UMusic,
- UGraphicClasses,
- UGraphic,
- UPath;
-
-const
-{$IFDEF PIXEL_FMT_BGR}
- PIXEL_FMT_OPENGL = GL_BGR;
- PIXEL_FMT_FFMPEG = PIX_FMT_BGR24;
-{$ELSE}
- PIXEL_FMT_OPENGL = GL_RGB;
- PIXEL_FMT_FFMPEG = PIX_FMT_RGB24;
-{$ENDIF}
-
-type
- TRectCoords = record
- Left, Right: double;
- Upper, Lower: double;
- end;
-
- TVideoPlayback_FFmpeg = class( TInterfacedObject, IVideoPlayback )
- private
- fOpened: boolean; //**< stream successfully opened
- fPaused: boolean; //**< stream paused
- fInitialized: boolean;
- 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
- fFrameTex: GLuint; //**< OpenGL texture for FrameBuffer
- fFrameTexValid: boolean; //**< if true, fFrameTex contains the current frame
- fTexWidth, fTexHeight: cardinal;
-
- {$IFDEF UseSWScale}
- fSwScaleContext: PSwsContext;
- {$ENDIF}
-
- fAspect: real; //**< width/height ratio
- fAspectCorrection: TAspectCorrection;
-
- fTimeBase: extended; //**< FFmpeg time base per time unit
- fTime: 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);
-
- procedure GetVideoRect(var ScreenRect, TexRect: TRectCoords);
-
- procedure ShowDebugInfo();
-
- public
- function GetName: String;
-
- function Init(): boolean;
- function Finalize: boolean;
-
- function Open(const FileName : IPath): boolean; // true if succeed
- procedure Close;
-
- procedure Play;
- procedure Pause;
- procedure Stop;
-
- procedure SetPosition(Time: real);
- function GetPosition: real;
-
- procedure GetFrame(Time: Extended);
- procedure DrawGL(Screen: integer);
- 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;
-
-
-{*------------------------------------------------------------------------------
- * TVideoPlayback_ffmpeg
- *------------------------------------------------------------------------------}
-
-function TVideoPlayback_FFmpeg.GetName: String;
-begin
- result := 'FFmpeg_Video';
-end;
-
-function TVideoPlayback_FFmpeg.Init(): boolean;
-begin
- Result := true;
-
- if (fInitialized) then
- Exit;
- fInitialized := true;
-
- FFmpegCore := TMediaCore_FFmpeg.GetInstance();
-
- Reset();
- av_register_all();
- glGenTextures(1, PGLuint(@fFrameTex));
-end;
-
-function TVideoPlayback_FFmpeg.Finalize(): boolean;
-begin
- Close();
- glDeleteTextures(1, PGLuint(@fFrameTex));
- Result := true;
-end;
-
-procedure TVideoPlayback_FFmpeg.Reset();
-begin
- // close previously opened video
- Close();
-
- fOpened := False;
- fPaused := False;
- fTimeBase := 0;
- fTime := 0;
- fStream := nil;
- fStreamIndex := -1;
- fFrameTexValid := false;
-
- fEOF := false;
-
- // TODO: do we really want this by default?
- fLoop := true;
- fLoopTime := 0;
-
- fAspectCorrection := acoCrop;
-end;
-
-function TVideoPlayback_FFmpeg.Open(const FileName : IPath): boolean; // true if succeed
-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.ToNative), 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;
-
- // 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();
- 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;
-
- fTimeBase := 1/av_q2d(fStream^.r_frame_rate);
-
- // hack to get reasonable timebase (for divx and others)
- if (fTimeBase < 0.02) then // 0.02 <-> 50 fps
- begin
- fTimeBase := av_q2d(fStream^.r_frame_rate);
- while (fTimeBase > 50) do
- fTimeBase := fTimeBase/10;
- fTimeBase := 1/fTimeBase;
- end;
-
- Log.LogInfo('VideoTimeBase: ' + floattostr(fTimeBase), 'TVideoPlayback_ffmpeg.Open');
- Log.LogInfo('Framerate: '+inttostr(floor(1/fTimeBase))+'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}
-
- fTexWidth := Round(Power(2, Ceil(Log2(fCodecContext^.width))));
- fTexHeight := Round(Power(2, Ceil(Log2(fCodecContext^.height))));
-
- // we retrieve a texture just once with glTexImage2D and update it with glTexSubImage2D later.
- // Benefits: glTexSubImage2D is faster and supports non-power-of-two widths/height.
- glBindTexture(GL_TEXTURE_2D, fFrameTex);
- glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexImage2D(GL_TEXTURE_2D, 0, 3, fTexWidth, fTexHeight, 0,
- PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, nil);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-
- fOpened := True;
- Result := true;
-end;
-
-procedure TVideoPlayback_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 TVideoPlayback_FFmpeg.SynchronizeTime(Frame: PAVFrame; var pts: double);
-var
- FrameDelay: double;
-begin
- if (pts <> 0) then
- begin
- // if we have pts, set video clock to it
- fTime := pts;
- end else
- begin
- // if we aren't given a pts, set it to the clock
- pts := fTime;
- 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);
- fTime := fTime + FrameDelay;
-end;
-
-{**
- * Decode a new frame from the video stream.
- * The decoded frame is stored in fAVFrame. fTime 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 TVideoPlayback_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
- Exit;
-
- // 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;
-
- // no error -> wait for user input
-{
- SDL_Delay(100); // initial version, left for documentation
- continue;
-}
-
- // Patch by Hawkear:
- // Why should this function loop in an endless loop if there is an error?
- // This runs in the main thread, so it halts the whole program
- // Therefore, it is better to exit when an error occurs
- 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;
-
-procedure TVideoPlayback_FFmpeg.GetFrame(Time: Extended);
-var
- errnum: Integer;
- NewTime: Extended;
- TimeDifference: Extended;
- DropFrameCount: Integer;
- i: Integer;
- Success: boolean;
-const
- FRAME_DROPCOUNT = 3;
-begin
- if not fOpened then
- Exit;
-
- if fPaused then
- Exit;
-
- // requested stream position (relative to the last loop's start)
- NewTime := Time - fLoopTime;
-
- // check if current texture still contains the active frame
- if (fFrameTexValid) then
- begin
- // time since the last frame was returned
- TimeDifference := NewTime - fTime;
-
- {$IFDEF DebugDisplay}
- DebugWriteln('Time: '+inttostr(floor(Time*1000)) + sLineBreak +
- 'VideoTime: '+inttostr(floor(fTime*1000)) + sLineBreak +
- 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
- 'TimeDiff: '+inttostr(floor(TimeDifference*1000)));
- {$endif}
-
- // check if last time is more than one frame in the past
- if (TimeDifference < fTimeBase) 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(fTime*1000)) + sLineBreak +
- 'TimeBase: '+inttostr(floor(fTimeBase*1000)) + sLineBreak +
- 'TimeDiff: '+inttostr(floor(TimeDifference*1000)));
- {$endif}
-
- // we do not need a new frame now
- Exit;
- end;
- end;
-
- {$IFDEF VideoBenchmark}
- Log.BenchmarkStart(15);
- {$ENDIF}
-
- // fetch new frame (updates fTime)
- Success := DecodeFrame();
- TimeDifference := NewTime - fTime;
-
- // check if we have to skip frames
- if (TimeDifference >= FRAME_DROPCOUNT*fTimeBase) 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(fTimeBase*1000)) + sLineBreak +
- 'TimeDiff: '+inttostr(floor(TimeDifference*1000)));
- {$endif}
-
- // update video-time
- DropFrameCount := Trunc(TimeDifference / fTimeBase);
- fTime := fTime + DropFrameCount*fTimeBase;
-
- // skip half of the frames, this is much smoother than to skip all at once
- for i := 1 to DropFrameCount (*div 2*) 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 (fTime-fLoopTime) later.
- fLoopTime := Time;
- end;
- Exit;
- 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;
-
- {$IFDEF VideoBenchmark}
- Log.BenchmarkEnd(15);
- Log.BenchmarkStart(16);
- {$ENDIF}
-
- // TODO: data is not padded, so we will need to tell OpenGL.
- // Or should we add padding with avpicture_fill? (check which one is faster)
- //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- glBindTexture(GL_TEXTURE_2D, fFrameTex);
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
- fCodecContext^.width, fCodecContext^.height,
- PIXEL_FMT_OPENGL, GL_UNSIGNED_BYTE, fAVFrameRGB^.data[0]);
-
- if (not fFrameTexValid) then
- fFrameTexValid := true;
-
- {$ifdef DebugFrames}
- //frame decode debug display
- GoldenRec.Spawn(200, 35, 1, 16, 0, -1, ColoredStar, $ffff00);
- {$endif}
-
- {$IFDEF VideoBenchmark}
- Log.BenchmarkEnd(16);
- Log.LogBenchmark('FFmpeg', 15);
- Log.LogBenchmark('Texture', 16);
- {$ENDIF}
-end;
-
-procedure TVideoPlayback_FFmpeg.GetVideoRect(var ScreenRect, TexRect: TRectCoords);
-var
- ScreenAspect: double; // aspect of screen resolution
- ScaledVideoWidth, ScaledVideoHeight: double;
-begin
- // Three aspects to take into account:
- // 1. Screen/display resolution (e.g. 1920x1080 -> 16:9)
- // 2. Render aspect (fixed to 800x600 -> 4:3)
- // 3. Movie aspect (video frame aspect stored in fAspect)
- ScreenAspect := ScreenW / ScreenH;
-
- case fAspectCorrection of
- acoStretch: begin
- ScaledVideoWidth := RenderW;
- ScaledVideoHeight := RenderH;
- end;
- acoCrop: begin
- if (ScreenAspect >= fAspect) then
- begin
- ScaledVideoWidth := RenderW;
- ScaledVideoHeight := RenderH * ScreenAspect/fAspect;
- end
- else
- begin
- ScaledVideoHeight := RenderH;
- ScaledVideoWidth := RenderW * fAspect/ScreenAspect;
- end;
- end;
- acoLetterBox: begin
- ScaledVideoWidth := RenderW;
- ScaledVideoHeight := RenderH * ScreenAspect/fAspect;
- end
- else
- raise Exception.Create('Unhandled aspect correction!');
- end;
-
- // center video
- ScreenRect.Left := (RenderW - ScaledVideoWidth) / 2;
- ScreenRect.Right := ScreenRect.Left + ScaledVideoWidth;
- ScreenRect.Upper := (RenderH - ScaledVideoHeight) / 2;
- ScreenRect.Lower := ScreenRect.Upper + ScaledVideoHeight;
-
- // texture contains right/lower (power-of-2) padding.
- // Determine the texture coords of the video frame.
- TexRect.Left := 0;
- TexRect.Right := fCodecContext^.width / fTexWidth;
- TexRect.Upper := 0;
- TexRect.Lower := fCodecContext^.height / fTexHeight;
-end;
-
-procedure TVideoPlayback_FFmpeg.DrawGL(Screen: integer);
-var
- ScreenRect: TRectCoords;
- TexRect: TRectCoords;
-begin
- // have a nice black background to draw on
- // (even if there were errors opening the vid)
- // TODO: Philipp: IMO TVideoPlayback should not clear the screen at
- // all, because clearing is already done by the background class
- // at this moment.
- if (Screen = 1) then
- begin
- // It is important that we just clear once before we start
- // drawing the first screen otherwise the first screen
- // would be cleared by the drawgl called when the second
- // screen is drawn
- glClearColor(0, 0, 0, 0);
- glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
- end;
-
- // exit if there's nothing to draw
- if (not fOpened) then
- Exit;
-
- {$IFDEF VideoBenchmark}
- Log.BenchmarkStart(15);
- {$ENDIF}
-
- // get texture and screen positions
- GetVideoRect(ScreenRect, TexRect);
-
- // we could use blending for brightness control, but do we need this?
- glDisable(GL_BLEND);
-
- glEnable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, fFrameTex);
- glColor3f(1, 1, 1);
- glBegin(GL_QUADS);
- // upper-left coord
- glTexCoord2f(TexRect.Left, TexRect.Upper);
- glVertex2f(ScreenRect.Left, ScreenRect.Upper);
- // lower-left coord
- glTexCoord2f(TexRect.Left, TexRect.Lower);
- glVertex2f(ScreenRect.Left, ScreenRect.Lower);
- // lower-right coord
- glTexCoord2f(TexRect.Right, TexRect.Lower);
- glVertex2f(ScreenRect.Right, ScreenRect.Lower);
- // upper-right coord
- glTexCoord2f(TexRect.Right, TexRect.Upper);
- glVertex2f(ScreenRect.Right, ScreenRect.Upper);
- glEnd;
- glDisable(GL_TEXTURE_2D);
-
- {$IFDEF VideoBenchmark}
- Log.BenchmarkEnd(15);
- Log.LogBenchmark('DrawGL', 15);
- {$ENDIF}
-
- {$IF Defined(Info) or Defined(DebugFrames)}
- ShowDebugInfo();
- {$IFEND}
-end;
-
-procedure TVideoPlayback_FFmpeg.ShowDebugInfo();
-begin
- {$IFDEF Info}
- if (fTime+fTimeBase < 0) then
- begin
- glColor4f(0.7, 1, 0.3, 1);
- SetFontStyle (1);
- SetFontItalic(False);
- SetFontSize(27);
- SetFontPos (300, 0);
- glPrint('Delay due to negative VideoGap');
- glColor4f(1, 1, 1, 1);
- end;
- {$ENDIF}
-
- {$IFDEF DebugFrames}
- glColor4f(0, 0, 0, 0.2);
- glbegin(GL_QUADS);
- glVertex2f(0, 0);
- glVertex2f(0, 70);
- glVertex2f(250, 70);
- glVertex2f(250, 0);
- glEnd;
-
- glColor4f(1, 1, 1, 1);
- SetFontStyle (1);
- SetFontItalic(False);
- SetFontSize(27);
- SetFontPos (5, 0);
- glPrint('delaying frame');
- SetFontPos (5, 20);
- glPrint('fetching frame');
- SetFontPos (5, 40);
- glPrint('dropping frame');
- {$ENDIF}
-end;
-
-procedure TVideoPlayback_FFmpeg.Play;
-begin
-end;
-
-procedure TVideoPlayback_FFmpeg.Pause;
-begin
- fPaused := not fPaused;
-end;
-
-procedure TVideoPlayback_FFmpeg.Stop;
-begin
-end;
-
-{**
- * Sets the stream's position.
- * The stream is set to the first keyframe with timestamp <= Time.
- * Note that fTime is set to Time no matter if the actual position seeked to is
- * at Time or the time of a preceding keyframe. fTime will be updated to the
- * actual frame time when GetFrame() is called the next time.
- * @param Time new position in seconds
- *}
-procedure TVideoPlayback_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;
-
- fTime := Time;
- fEOF := false;
- fFrameTexValid := false;
-
- if (av_seek_frame(fFormatContext, fStreamIndex, Floor(Time/fTimeBase), SeekFlags) < 0) then
- begin
- Log.LogError('av_seek_frame() failed', 'TVideoPlayback_ffmpeg.SetPosition');
- Exit;
- end;
-
- avcodec_flush_buffers(fCodecContext);
-end;
-
-function TVideoPlayback_FFmpeg.GetPosition: real;
-begin
- Result := fTime;
-end;
-
-initialization
- MediaManager.Add(TVideoPlayback_FFmpeg.Create);
-
-end.