2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Component to make it easy to record GLScene frames into an AVI file
8
17/11/14 - PW - Refactored TAVIRecorder to TGLAVIRecorder
9
12/07/07 - DaStr - Improved Cross-Platform compatibility
10
(Bugtracker ID = 1684432)
11
17/03/07 - DaStr - Dropped Kylix support in favor of FPC (BugTracekrID=1681585)
12
29/01/07 - DaStr - Moved registration to GLSceneRegister.pas
13
01/06/05 - NelC - Replaced property GLFullScreenViewer with GLNonVisualViewer
14
26/01/05 - JAJ - Can now operate with a GLFullScreenViewer
15
22/10/04 - EG - Can now operate without a SceneViewer
16
13/05/04 - EG - Added irmBitBlt mode (now the default mode)
17
05/01/04 - EG - Added Recording function and ability to record arbitrary bitmap,
18
Added OnPostProcessEvent
19
08/07/03 - NelC - Fixed access violation on exit (thx Solerman Kaplon)
21
11/12/01 - EG - Minor changes for compatibility with JEDI VfW.pas
22
<li<02/03/01 - EG - Added TAVIImageRetrievalMode
23
24/02/01 - NelC - Creation and initial code
31
{$IFNDEF MSWINDOWS}{$MESSAGE Error 'Unit not supported'}{$ENDIF}
35
Messages, Classes, SysUtils,
36
Controls, Forms, Extctrls, Graphics, Dialogs,
37
GLGraphics, GLSVfw, GLScene, GLViewer;
40
TAVICompressor = (acDefault, acShowDialog, acDivX);
42
PAVIStream = ^IAVIStream;
44
// TAVISizeRestriction
46
{ Frame size restriction.
47
Forces frame dimensions to be a multiple of 2, 4, or 8. Some compressors
48
require this. e.g. DivX 5.2.1 requires mutiples of 2. }
49
TAVISizeRestriction = (srNoRestriction, srForceBlock2x2, srForceBlock4x4,
52
TAVIRecorderState = (rsNone, rsRecording);
54
// TAVIImageRetrievalMode
56
{ Image retrieval mode for frame capture.
57
Following modes are supported:
58
irmSnapShot : retrieve OpenGL framebuffer content using glReadPixels
59
irmRenderToBitmap : renders the whole scene to a bitmap, this is
60
the slowest mode, but it won't be affected by driver-side specifics.
61
irmBitBlt : tranfers the framebuffer using the BitBlt function,
62
usually the fastest solution
64
TAVIImageRetrievalMode = (irmSnapShot, irmRenderToBitmap, irmBitBlt);
66
TAVIRecorderPostProcessEvent = procedure(Sender: TObject; frame: TBitmap)
71
{ Component to make it easy to record GLScene frames into an AVI file. }
72
TGLAVIRecorder = class(TComponent)
76
AVIFrameIndex: integer;
83
Stream, Stream_c: IAVIStream; // AVI stream and stream to be compressed
85
FBitmapInfo: PBitmapInfoHeader;
90
// so that we know the filename to delete case of user abort
96
FSizeRestriction: TAVISizeRestriction;
97
FImageRetrievalMode: TAVIImageRetrievalMode;
98
RecorderState: TAVIRecorderState;
99
FOnPostProcessEvent: TAVIRecorderPostProcessEvent;
101
FBuffer: TGLSceneBuffer;
103
procedure SetHeight(const val: integer);
104
procedure SetWidth(const val: integer);
105
procedure SetSizeRestriction(const val: TAVISizeRestriction);
106
procedure SetGLSceneViewer(const Value: TGLSceneViewer);
107
procedure SetGLNonVisualViewer(const Value: TGLNonVisualViewer);
111
// Now, TAVIRecorder is tailored for GLScene. Maybe we should make a generic
112
// TAVIRecorder, and then sub-class it to use with GLScene
113
FGLSceneViewer: TGLSceneViewer;
114
// FGLNonVisualViewer accepts GLNonVisualViewer and GLFullScreenViewer
115
FGLNonVisualViewer: TGLNonVisualViewer;
116
// FCompressor determines if the user is to choose a compressor via a dialog box, or
117
// just use a default compressor without showing a dialog box.
118
FCompressor: TAVICompressor;
119
// some video compressor assumes input dimensions to be multiple of 2, 4 or 8.
120
// Restricted() is for rounding off the width and height.
121
// Currently I can't find a simple way to know which compressor imposes
122
// what resiction, so the SizeRestiction property is there for the user to set.
123
// The source code of VirtualDub (http://www.virtualdub.org/)
124
// may give us some cues on this.
125
// ( BTW, VirtualDub is an excellent freeware for editing your AVI. For
126
// converting AVI into MPG, try AVI2MPG1 - http://www.mnsi.net/~jschlic1 )
127
function Restricted(s: integer): integer;
129
procedure InternalAddAVIFrame;
133
constructor Create(AOwner: TComponent); override;
134
destructor Destroy; override;
136
function CreateAVIFile(DPI: integer = 0): boolean;
137
procedure AddAVIFrame; overload;
138
procedure AddAVIFrame(bmp: TBitmap); overload;
139
procedure CloseAVIFile(UserAbort: boolean = false);
140
function Recording: boolean;
144
property FPS: byte read FFPS write FFPS default 25;
145
property GLSceneViewer: TGLSceneViewer read FGLSceneViewer
146
write SetGLSceneViewer;
147
property GLNonVisualViewer: TGLNonVisualViewer read FGLNonVisualViewer
148
write SetGLNonVisualViewer;
149
property Width: integer read FWidth write SetWidth;
150
property Height: integer read FHeight write SetHeight;
151
property Filename: String read FAVIFilename write FAVIFilename;
152
property Compressor: TAVICompressor read FCompressor write FCompressor
154
property SizeRestriction: TAVISizeRestriction read FSizeRestriction
155
write SetSizeRestriction default srForceBlock8x8;
156
property ImageRetrievalMode: TAVIImageRetrievalMode read FImageRetrievalMode
157
write FImageRetrievalMode default irmBitBlt;
159
property OnPostProcessEvent: TAVIRecorderPostProcessEvent
160
read FOnPostProcessEvent write FOnPostProcessEvent;
164
// ---------------------------------------------------------------------
165
// ---------------------------------------------------------------------
166
// ---------------------------------------------------------------------
169
// ---------------------------------------------------------------------
170
// ---------------------------------------------------------------------
171
// ---------------------------------------------------------------------
173
// DIB support rountines for AVI output
175
procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP;
176
var BI: TBitmapInfoHeader);
180
GetObject(Bitmap, SizeOf(BM), @BM);
183
biSize := SizeOf(BI);
184
biWidth := BM.bmWidth;
185
biHeight := BM.bmHeight;
187
biXPelsPerMeter := 0;
188
biYPelsPerMeter := 0;
191
biCompression := BI_RGB;
193
// force 24 bits. Most video compressors would deal with 24-bit frames only.
194
biSizeImage := (((biWidth * biBitCount) + 31) div 32) * 4 * biHeight;
198
procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: integer;
199
var ImageSize: Dword);
201
BI: TBitmapInfoHeader;
203
InitializeBitmapInfoHeader(Bitmap, BI);
204
InfoHeaderSize := SizeOf(TBitmapInfoHeader);
205
ImageSize := BI.biSizeImage;
210
function InternalGetDIB(Bitmap: HBITMAP; var bitmapInfo; var bits): boolean;
216
InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(bitmapInfo));
220
errCode := GetDIBits(dc, Bitmap, 0, TBitmapInfoHeader(bitmapInfo).biHeight,
221
@bits, TBitmapInfo(bitmapInfo), DIB_RGB_COLORS);
222
Result := (errCode <> 0);
224
ReleaseDC(focus, dc);
229
// ------------------ TAVIRecorder ------------------
234
constructor TGLAVIRecorder.Create(AOwner: TComponent);
237
FWidth := 320; // default values
240
FCompressor := acDefault;
241
RecorderState := rsNone;
242
FSizeRestriction := srForceBlock8x8;
243
FImageRetrievalMode := irmBitBlt;
248
destructor TGLAVIRecorder.Destroy;
250
// if still open here, abort it
251
if RecorderState = rsRecording then
258
function TGLAVIRecorder.Restricted(s: integer): integer;
260
case FSizeRestriction of
262
Result := (s div 2) * 2;
264
Result := (s div 4) * 4;
266
Result := (s div 8) * 8;
274
procedure TGLAVIRecorder.SetHeight(const val: integer);
276
if (RecorderState <> rsRecording) and (val <> FHeight) and (val > 0) then
277
FHeight := Restricted(val);
282
procedure TGLAVIRecorder.SetWidth(const val: integer);
284
if (RecorderState <> rsRecording) and (val <> FWidth) and (val > 0) then
285
FWidth := Restricted(val);
290
procedure TGLAVIRecorder.SetSizeRestriction(const val: TAVISizeRestriction);
292
if val <> FSizeRestriction then
294
FSizeRestriction := val;
295
FHeight := Restricted(FHeight);
296
FWidth := Restricted(FWidth);
300
// AddAVIFrame (from sceneviewer)
302
procedure TGLAVIRecorder.AddAVIFrame;
307
if RecorderState <> rsRecording then
308
raise Exception.Create('Cannot add frame to AVI. AVI file not created.');
310
if FBuffer <> nil then
311
case ImageRetrievalMode of
314
bmp32 := FBuffer.CreateSnapShot;
316
bmp := bmp32.Create32BitsBitmap;
318
AVIBitmap.Canvas.Draw(0, 0, bmp);
328
FBuffer.RenderingContext.Activate;
330
BitBlt(AVIBitmap.Canvas.Handle, 0, 0, AVIBitmap.Width,
331
AVIBitmap.Height, wglGetCurrentDC, 0, 0, SRCCOPY);
333
FBuffer.RenderingContext.Deactivate;
338
FBuffer.RenderToBitmap(AVIBitmap, AVI_DPI);
347
// AddAVIFrame (from custom bitmap)
349
procedure TGLAVIRecorder.AddAVIFrame(bmp: TBitmap);
351
if RecorderState <> rsRecording then
352
raise Exception.Create('Cannot add frame to AVI. AVI file not created.');
353
AVIBitmap.Canvas.Draw(0, 0, bmp);
358
// InternalAddAVIFrame
360
procedure TGLAVIRecorder.InternalAddAVIFrame;
362
if Assigned(FOnPostProcessEvent) then
363
FOnPostProcessEvent(Self, AVIBitmap);
366
InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
367
if AVIStreamWrite(Stream_c, AVIFrameIndex, 1, FBitmapBits, FBitmapSize,
368
AVIIF_KEYFRAME, nil, nil) <> AVIERR_OK then
369
raise Exception.Create('Add Frame Error');
374
function TGLAVIRecorder.CreateAVIFile(DPI: integer = 0): boolean;
376
SaveDialog: TSaveDialog;
377
gaAVIOptions: TAVICOMPRESSOPTIONS;
378
galpAVIOptions: PAVICOMPRESSOPTIONS;
379
bitmapInfoSize: integer;
381
ResultString: String;
383
FTempName := FAVIFilename;
385
if FTempName = '' then
387
// if user didn't supply a filename, then ask for it
388
SaveDialog := TSaveDialog.Create(Application);
392
Options := [ofHideReadOnly, ofNoReadOnlyReturn];
393
DefaultExt := '.avi';
394
Filter := 'AVI Files (*.avi)|*.avi';
396
FTempName := SaveDialog.Filename;
403
Result := (FTempName <> '');
406
if FileExists(FTempName) then
408
Result := (MessageDlg(Format('Overwrite file %s?', [FTempName]),
409
mtConfirmation, [mbYes, mbNo], 0) = mrYes);
410
// AVI streamers don't trim the file they're written to, so start from zero
412
DeleteFile(FTempName);
419
AVIFileInit; // initialize the AVI lib.
421
AVIBitmap := TBitmap.Create;
424
RecorderState := rsRecording;
427
AVIBitmap.PixelFormat := pf24Bit;
428
AVIBitmap.Width := FWidth;
429
AVIBitmap.Height := FHeight;
431
// note: a filename with extension other then AVI give generate an error.
432
if AVIFileOpen(pfile, PChar(FTempName), OF_WRITE or OF_CREATE, nil) <> AVIERR_OK
434
raise Exception.Create
435
('Cannot create AVI file. Disk full or file in use?');
439
InternalGetDIBSizes(Handle, bitmapInfoSize, FBitmapSize);
440
FBitmapInfo := AllocMem(bitmapInfoSize);
441
FBitmapBits := AllocMem(FBitmapSize);
442
InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
445
FillChar(asi, SizeOf(asi), 0);
449
fccType := streamtypeVIDEO; // Now prepare the stream
451
dwScale := 1; // dwRate / dwScale = frames/second
453
dwSuggestedBufferSize := FBitmapSize;
454
rcFrame.Right := FBitmapInfo.biWidth;
455
rcFrame.Bottom := FBitmapInfo.biHeight;
458
if AVIFileCreateStream(pfile, Stream, asi) <> AVIERR_OK then
459
raise Exception.Create('Cannot create AVI stream.');
462
InternalGetDIB(Handle, FBitmapInfo^, FBitmapBits^);
464
galpAVIOptions := @gaAVIOptions;
465
FillChar(gaAVIOptions, SizeOf(gaAVIOptions), 0);
466
gaAVIOptions.fccType := streamtypeVIDEO;
471
// call a dialog box for the user to choose the compressor options
472
AVISaveOptions(0, ICMF_CHOOSE_KEYFRAME or ICMF_CHOOSE_DATARATE, 1,
473
Stream, galpAVIOptions);
478
// ask for generic divx, using current default settings
479
fccHandler := mmioFOURCC('d', 'i', 'v', 'x');
483
begin // or, you may want to fill the compression options yourself
484
fccHandler := mmioFOURCC('M', 'S', 'V', 'C');
485
// User MS video 1 as default.
486
// I guess it is installed on every Win95 or later.
487
dwQuality := 7500; // compress quality 0-10,000
489
// setting dwFlags to 0 would lead to some default settings
493
AVIResult := AVIMakeCompressedStream(Stream_c, Stream, galpAVIOptions, nil);
495
if AVIResult <> AVIERR_OK then
497
if AVIResult = AVIERR_NOCOMPRESSOR then
498
ResultString := 'No such compressor found'
501
raise Exception.Create('Cannot make compressed stream. ' + ResultString);
504
if AVIStreamSetFormat(Stream_c, 0, FBitmapInfo, bitmapInfoSize) <> AVIERR_OK
506
raise Exception.Create('AVIStreamSetFormat Error');
507
// no error description found in MSDN.
518
procedure TGLAVIRecorder.CloseAVIFile(UserAbort: boolean = false);
520
// if UserAbort, CloseAVIFile will also delete the unfinished file.
522
if RecorderState <> rsRecording then
523
raise Exception.Create('Cannot close AVI file. AVI file not created.');
527
FreeMem(FBitmapInfo);
528
FreeMem(FBitmapBits);
530
AVIFileExit; // finalize the AVI lib.
532
// release the interfaces explicitly (can't rely on automatic release)
538
DeleteFile(FTempName);
540
RecorderState := rsNone;
546
function TGLAVIRecorder.Recording: boolean;
548
Result := (RecorderState = rsRecording);
551
procedure TGLAVIRecorder.SetGLSceneViewer(const Value: TGLSceneViewer);
553
FGLSceneViewer := Value;
554
if Assigned(FGLSceneViewer) then
555
FBuffer := FGLSceneViewer.Buffer
560
procedure TGLAVIRecorder.SetGLNonVisualViewer(const Value: TGLNonVisualViewer);
562
FGLNonVisualViewer := Value;
563
if Assigned(FGLNonVisualViewer) then
564
FBuffer := FGLNonVisualViewer.Buffer