123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.AVIRecorder;
- (* Component to make it easy to record frames into an AVI file *)
- interface
- {$I Stage.Defines.inc}
- uses
- Winapi.Windows,
- System.Classes,
- System.SysUtils,
- System.UITypes,
- FMX.Controls,
- FMX.Forms,
- FMX.Extctrls,
- FMX.Graphics,
- FMX.Dialogs,
- FMX.Types,
- GXS.Graphics,
- GXS.Scene,
- GXS.SceneViewer,
- Formatx.VFW;
- type
- TAVICompressor = (acDefault, acShowDialog, acDivX);
- PAVIStream = ^IAVIStream;
- (* Frame size restriction.
- Forces frame dimensions to be a multiple of 2, 4, or 8. Some compressors
- require this. e.g. DivX 5.2.1 requires mutiples of 2. *)
- TAVISizeRestriction = (srNoRestriction, srForceBlock2x2, srForceBlock4x4,
- srForceBlock8x8);
- TAVIRecorderState = (rsNone, rsRecording);
- (* Image retrieval mode for frame capture.
- Following modes are supported:
- irmSnapShot : retrieve OpenGL framebuffer content using glReadPixels
- irmRenderToBitmap : renders the whole scene to a bitmap, this is
- the slowest mode, but it won't be affected by driver-side specifics.
- irmBitBlt : tranfers the framebuffer using the BitBlt function,
- usually the fastest solution *)
- TAVIImageRetrievalMode = (irmSnapShot, irmRenderToBitmap, irmBitBlt);
- TAVIRecorderPostProcessEvent = procedure(Sender: TObject; frame: TBitmap)
- of object;
- // Component to make it easy to record GLScene frames into an AVI file.
- TgxAVIRecorder = class(TComponent)
- private
- AVIBitmap: TBitmap;
- AVIFrameIndex: integer;
- AVI_DPI: integer;
- asi: TAVIStreamInfo;
- pfile: IAVIFile;
- Stream, Stream_c: IAVIStream; // AVI stream and stream to be compressed
- FBitmapInfo: PBitmapInfoHeader;
- FBitmapBits: Pointer;
- FBitmapSize: Dword;
- FTempName: String;
- // so that we know the filename to delete case of user abort
- FAVIFilename: string;
- FFPS: byte;
- FWidth: integer;
- FHeight: integer;
- FSizeRestriction: TAVISizeRestriction;
- FImageRetrievalMode: TAVIImageRetrievalMode;
- RecorderState: TAVIRecorderState;
- FOnPostProcessEvent: TAVIRecorderPostProcessEvent;
- FBuffer: TgxSceneBuffer;
- procedure SetHeight(const val: integer);
- procedure SetWidth(const val: integer);
- procedure SetSizeRestriction(const val: TAVISizeRestriction);
- procedure SetGLXceneViewer(const Value: TgxSceneViewer);
- procedure SetVKNonVisualViewer(const Value: TgxNonVisualViewer);
- protected
- // Now, TAVIRecorder is tailored for GLScene. Maybe we should make a generic
- // TAVIRecorder, and then sub-class it to use with GLScene
- FGLXceneViewer: TgxSceneViewer;
- // FGLNonVisualViewer accepts GLNonVisualViewer and GLFullScreenViewer
- FVKNonVisualViewer: TgxNonVisualViewer;
- // FCompressor determines if the user is to choose a compressor via a dialog box, or
- // just use a default compressor without showing a dialog box.
- FCompressor: TAVICompressor;
- // some video compressor assumes input dimensions to be multiple of 2, 4 or 8.
- // Restricted() is for rounding off the width and height.
- // Currently I can't find a simple way to know which compressor imposes
- // what resiction, so the SizeRestiction property is there for the user to set.
- // The source code of VirtualDub (http://www.virtualdub.org/)
- // may give us some cues on this.
- // ( BTW, VirtualDub is an excellent freeware for editing your AVI. For
- // converting AVI into MPG, try AVI2MPG1 - http://www.mnsi.net/~jschlic1 )
- function Restricted(s: integer): integer;
- procedure InternalAddAVIFrame;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- function CreateAVIFile(DPI: integer = 0): boolean;
- procedure AddAVIFrame; overload;
- procedure AddAVIFrame(bmp: TBitmap); overload;
- procedure CloseAVIFile(UserAbort: boolean = false);
- function Recording: boolean;
- published
- property FPS: byte read FFPS write FFPS default 25;
- property GLXceneViewer: TgxSceneViewer read FGLXceneViewer
- write SetGLXceneViewer;
- property VKNonVisualViewer: TgxNonVisualViewer read FVKNonVisualViewer
- write SetVKNonVisualViewer;
- property Width: integer read FWidth write SetWidth;
- property Height: integer read FHeight write SetHeight;
- property Filename: String read FAVIFilename write FAVIFilename;
- property Compressor: TAVICompressor read FCompressor write FCompressor
- default acDefault;
- property SizeRestriction: TAVISizeRestriction read FSizeRestriction
- write SetSizeRestriction default srForceBlock8x8;
- property ImageRetrievalMode: TAVIImageRetrievalMode read FImageRetrievalMode
- write FImageRetrievalMode default irmBitBlt;
- property OnPostProcessEvent: TAVIRecorderPostProcessEvent
- read FOnPostProcessEvent write FOnPostProcessEvent;
- end;
- // ---------------------------------------------------------------------
- implementation
- // ---------------------------------------------------------------------
- // DIB support rountines for AVI output
- procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP;
- var BI: TBitmapInfoHeader);
- var
- BM: TBitmap;
- begin
- GetObject(Bitmap, SizeOf(BM), @BM);
- with BI do
- begin
- biSize := SizeOf(BI);
- biWidth := BM.Width;
- biHeight := BM.Height;
- biPlanes := 1;
- biXPelsPerMeter := 0;
- biYPelsPerMeter := 0;
- biClrUsed := 0;
- biClrImportant := 0;
- biCompression := BI_RGB;
- biBitCount := 24;
- // force 24 bits. Most video compressors would deal with 24-bit frames only.
- biSizeImage := (((biWidth * biBitCount) + 31) div 32) * 4 * biHeight;
- end;
- end;
- procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: integer;
- var ImageSize: Dword);
- var
- BI: TBitmapInfoHeader;
- begin
- InitializeBitmapInfoHeader(Bitmap, BI);
- InfoHeaderSize := SizeOf(TBitmapInfoHeader);
- ImageSize := BI.biSizeImage;
- end;
- function InternalGetDIB(Bitmap: HBITMAP; var bitmapInfo; var bits): boolean;
- var
- focus: HWND;
- dc: HDC;
- errCode: integer;
- begin
- InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(bitmapInfo));
- focus := GetFocus;
- dc := GetDC(focus);
- try
- errCode := GetDIBits(dc, Bitmap, 0, TBitmapInfoHeader(bitmapInfo).biHeight,
- @bits, TBitmapInfo(bitmapInfo), DIB_RGB_COLORS);
- Result := (errCode <> 0);
- finally
- ReleaseDC(focus, dc);
- end;
- end;
- // ------------------
- // ------------------ TAVIRecorder ------------------
- // ------------------
- constructor TgxAVIRecorder.Create(AOwner: TComponent);
- begin
- inherited;
- FWidth := 320; // default values
- FHeight := 200;
- FFPS := 25;
- FCompressor := acDefault;
- RecorderState := rsNone;
- FSizeRestriction := srForceBlock8x8;
- FImageRetrievalMode := irmBitBlt;
- end;
- destructor TgxAVIRecorder.Destroy;
- begin
- // if still open here, abort it
- if RecorderState = rsRecording then
- CloseAVIFile(True);
- inherited;
- end;
- function TgxAVIRecorder.Restricted(s: integer): integer;
- begin
- case FSizeRestriction of
- srForceBlock2x2:
- Result := (s div 2) * 2;
- srForceBlock4x4:
- Result := (s div 4) * 4;
- srForceBlock8x8:
- Result := (s div 8) * 8;
- else
- Result := s;
- end;
- end;
- procedure TgxAVIRecorder.SetHeight(const val: integer);
- begin
- if (RecorderState <> rsRecording) and (val <> FHeight) and (val > 0) then
- FHeight := Restricted(val);
- end;
- procedure TgxAVIRecorder.SetWidth(const val: integer);
- begin
- if (RecorderState <> rsRecording) and (val <> FWidth) and (val > 0) then
- FWidth := Restricted(val);
- end;
- procedure TgxAVIRecorder.SetSizeRestriction(const val: TAVISizeRestriction);
- begin
- if val <> FSizeRestriction then
- begin
- FSizeRestriction := val;
- FHeight := Restricted(FHeight);
- FWidth := Restricted(FWidth);
- end;
- end;
- procedure TgxAVIRecorder.AddAVIFrame;
- var
- bmp32: TgxBitmap32;
- bmp: TBitmap;
- begin
- if RecorderState <> rsRecording then
- raise Exception.Create('Cannot add frame to AVI. AVI file not created.');
- if FBuffer <> nil then
- case ImageRetrievalMode of
- irmSnapShot:
- begin
- bmp32 := FBuffer.CreateSnapShot;
- try
- bmp := bmp32.Create32BitsBitmap;
- try
- { TODO : E2003 Undeclared identifier: 'Draw' }
- (*AVIBitmap.Canvas.Draw(0, 0, bmp);*)
- finally
- bmp.Free;
- end;
- finally
- bmp32.Free;
- end;
- end;
- irmBitBlt:
- begin
- FBuffer.RenderingContext.Activate;
- try
- BitBlt(AVIBitmap.Handle, 0, 0, AVIBitmap.Width,
- AVIBitmap.Height, wglGetCurrentDC, 0, 0, SRCCOPY);
- finally
- FBuffer.RenderingContext.Deactivate;
- end;
- end;
- irmRenderToBitmap:
- begin
- FBuffer.RenderToBitmap(AVIBitmap, AVI_DPI);
- end;
- else
- Assert(false);
- end;
- InternalAddAVIFrame;
- end;
- // AddAVIFrame (from custom bitmap)
- //
- procedure TgxAVIRecorder.AddAVIFrame(bmp: TBitmap);
- begin
- if RecorderState <> rsRecording then
- raise Exception.Create('Cannot add frame to AVI. AVI file not created.');
- { TODO : E2003 Undeclared identifier: 'Draw' }
- (*AVIBitmap.Canvas.Draw(0, 0, bmp);*)
- InternalAddAVIFrame;
- end;
- procedure TgxAVIRecorder.InternalAddAVIFrame;
- begin
- if Assigned(FOnPostProcessEvent) then
- FOnPostProcessEvent(Self, AVIBitmap);
- with AVIBitmap do
- begin
- InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
- if AVIStreamWrite(Stream_c, AVIFrameIndex, 1, FBitmapBits, FBitmapSize,
- AVIIF_KEYFRAME, nil, nil) <> AVIERR_OK then
- raise Exception.Create('Add Frame Error');
- Inc(AVIFrameIndex);
- end;
- end;
- function TgxAVIRecorder.CreateAVIFile(DPI: integer = 0): boolean;
- var
- SaveDialog: TSaveDialog;
- gaAVIOptions: TAVICOMPRESSOPTIONS;
- galpAVIOptions: PAVICOMPRESSOPTIONS;
- bitmapInfoSize: integer;
- AVIResult: Cardinal;
- ResultString: String;
- begin
- FTempName := FAVIFilename;
- if FTempName = '' then
- begin
- // if user didn't supply a filename, then ask for it
- SaveDialog := TSaveDialog.Create(Application);
- try
- with SaveDialog do
- begin
- Options := [TOpenOption.ofHideReadOnly, TOpenOption.ofNoReadOnlyReturn];
- DefaultExt := '.avi';
- Filter := 'AVI Files (*.avi)|*.avi';
- if Execute then
- FTempName := SaveDialog.Filename;
- end;
- finally
- SaveDialog.Free;
- end;
- end;
- Result := (FTempName <> '');
- if Result then
- begin
- if FileExists(FTempName) then
- begin
- Result := (MessageDlg(Format('Overwrite file %s?', [FTempName]),
- TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0) = mrYes);
- // AVI streamers don't trim the file they're written to, so start from zero
- if Result then
- DeleteFile(FTempName);
- end;
- end;
- if not Result then
- Exit;
- AVIFileInit; // initialize the AVI lib.
- AVIBitmap := TBitmap.Create;
- AVIFrameIndex := 0;
- RecorderState := rsRecording;
- try
- { TODO : E2129 Cannot assign to a read-only property }
- (*AVIBitmap.PixelFormat := TPixelFormat.RGBA;*)
- AVIBitmap.Width := FWidth;
- AVIBitmap.Height := FHeight;
- // note: a filename with extension other then AVI give generate an error.
- if AVIFileOpen(pfile, PChar(FTempName), OF_WRITE or OF_CREATE, nil) <> AVIERR_OK
- then
- raise Exception.Create
- ('Cannot create AVI file. Disk full or file in use?');
- with AVIBitmap do
- begin
- InternalGetDIBSizes(Handle, bitmapInfoSize, FBitmapSize);
- FBitmapInfo := AllocMem(bitmapInfoSize);
- FBitmapBits := AllocMem(FBitmapSize);
- InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
- end;
- FillChar(asi, SizeOf(asi), 0);
- with asi do
- begin
- fccType := streamtypeVIDEO; // Now prepare the stream
- fccHandler := 0;
- dwScale := 1; // dwRate / dwScale = frames/second
- dwRate := FFPS;
- dwSuggestedBufferSize := FBitmapSize;
- rcFrame.Right := FBitmapInfo.biWidth;
- rcFrame.Bottom := FBitmapInfo.biHeight;
- end;
- if AVIFileCreateStream(pfile, Stream, asi) <> AVIERR_OK then
- raise Exception.Create('Cannot create AVI stream.');
- with AVIBitmap do
- InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
- galpAVIOptions := @gaAVIOptions;
- FillChar(gaAVIOptions, SizeOf(gaAVIOptions), 0);
- gaAVIOptions.fccType := streamtypeVIDEO;
- case FCompressor of
- acShowDialog:
- begin
- // call a dialog box for the user to choose the compressor options
- AVISaveOptions(0, ICMF_CHOOSE_KEYFRAME or ICMF_CHOOSE_DATARATE, 1,
- Stream, galpAVIOptions);
- end;
- acDivX:
- with gaAVIOptions do
- begin
- // ask for generic divx, using current default settings
- fccHandler := mmioFOURCC('d', 'i', 'v', 'x');
- end;
- else
- with gaAVIOptions do
- begin // or, you may want to fill the compression options yourself
- fccHandler := mmioFOURCC('M', 'S', 'V', 'C');
- // User MS video 1 as default.
- // I guess it is installed on every Win95 or later.
- dwQuality := 7500; // compress quality 0-10,000
- dwFlags := 0;
- // setting dwFlags to 0 would lead to some default settings
- end;
- end;
- AVIResult := AVIMakeCompressedStream(Stream_c, Stream, galpAVIOptions, nil);
- if AVIResult <> AVIERR_OK then
- begin
- if AVIResult = AVIERR_NOCOMPRESSOR then
- ResultString := 'No such compressor found'
- else
- ResultString := '';
- raise Exception.Create('Cannot make compressed stream. ' + ResultString);
- end;
- if AVIStreamSetFormat(Stream_c, 0, FBitmapInfo, bitmapInfoSize) <> AVIERR_OK
- then
- raise Exception.Create('AVIStreamSetFormat Error');
- // no error description found in MSDN.
- AVI_DPI := DPI;
- except
- CloseAVIFile(True);
- raise;
- end;
- end;
- procedure TgxAVIRecorder.CloseAVIFile(UserAbort: boolean = false);
- begin
- // if UserAbort, CloseAVIFile will also delete the unfinished file.
- try
- if RecorderState <> rsRecording then
- raise Exception.Create('Cannot close AVI file. AVI file not created.');
- AVIBitmap.Free;
- FreeMem(FBitmapInfo);
- FreeMem(FBitmapBits);
- AVIFileExit; // finalize the AVI lib.
- // release the interfaces explicitly (can't rely on automatic release)
- Stream := nil;
- Stream_c := nil;
- pfile := nil;
- if UserAbort then
- DeleteFile(FTempName);
- finally
- RecorderState := rsNone;
- end;
- end;
- // Recording
- //
- function TgxAVIRecorder.Recording: boolean;
- begin
- Result := (RecorderState = rsRecording);
- end;
- procedure TgxAVIRecorder.SetGLXceneViewer(const Value: TgxSceneViewer);
- begin
- FGLXceneViewer := Value;
- if Assigned(FGLXceneViewer) then
- FBuffer := FGLXceneViewer.Buffer
- else
- FBuffer := nil;
- end;
- procedure TgxAVIRecorder.SetVKNonVisualViewer(const Value: TgxNonVisualViewer);
- begin
- FVKNonVisualViewer := Value;
- if Assigned(FVKNonVisualViewer) then
- FBuffer := FVKNonVisualViewer.Buffer
- else
- FBuffer := nil;
- end;
- end.
|