--- a/hedgewars/uVideoRec.pas Sun Oct 28 15:18:26 2012 +0100
+++ b/hedgewars/uVideoRec.pas Fri Dec 06 22:20:53 2019 +0100
@@ -1,6 +1,6 @@
(*
* Hedgewars, a free turn based strategy game
- * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
*
* 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
@@ -13,7 +13,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*)
@@ -27,13 +27,8 @@
end.
{$ELSE}
-{$IFNDEF WIN32}
- {$LINKLIB ../bin/libavwrapper.a}
-{$ENDIF}
-{$IFDEF DARWIN}
- {$LINKLIB bz2}
- {$LINKFRAMEWORK CoreVideo}
- {$LINKFRAMEWORK VideoDecodeAcceleration}
+{$IFNDEF WINDOWS}
+ {$linklib avwrapper}
{$ENDIF}
interface
@@ -41,7 +36,7 @@
var flagPrerecording: boolean = false;
function BeginVideoRecording: Boolean;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
procedure EncodeFrame;
procedure StopVideoRecording;
@@ -53,17 +48,17 @@
procedure freeModule;
implementation
-
-uses uVariables, uUtils, GLunit, SDLh, SysUtils, uIO, uMisc, uTypes;
+uses uVariables, GLunit, SDLh, SysUtils, uUtils, uSound, uIO, uMisc, uTypes, uDebug;
type TAddFileLogRaw = procedure (s: pchar); cdecl;
+const AvwrapperLibName = {$IFDEF WIN32_VCPKG}'avwrapper'{$ELSE}'libavwrapper'{$ENDIF};
-procedure AVWrapper_Init(
+function AVWrapper_Init(
AddLog: TAddFileLogRaw;
filename, desc, soundFile, format, vcodec, acodec: PChar;
- width, height, framerateNum, framerateDen, vquality: LongInt); cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF};
-procedure AVWrapper_Close; cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF};
-procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external {$IFDEF WIN32}'libavwrapper.dll'{$ENDIF};
+ width, height, framerateNum, framerateDen, vquality: LongInt): LongInt; cdecl; external AvwrapperLibName;
+function AVWrapper_Close: LongInt; cdecl; external AvwrapperLibName;
+function AVWrapper_WriteFrame(rgb: PByte): LongInt; cdecl; external AvwrapperLibName;
type TFrame = record
realTicks: LongWord;
@@ -72,34 +67,43 @@
zoom: single;
end;
-var YCbCr_Planes: array[0..2] of PByte;
- RGB_Buffer: PByte;
- cameraFile: File of TFrame;
+var RGB_Buffer: PByte;
+ cameraFile: File;
+ cameraFileName: shortstring;
audioFile: File;
numPixels: LongWord;
startTime, numFrames, curTime, progress, maxProgress: LongWord;
soundFilePath: shortstring;
- thumbnailSaved : Boolean;
+ thumbnailSaved: boolean;
+ recordAudio: boolean;
function BeginVideoRecording: Boolean;
var filename, desc: shortstring;
+ filenameA, descA, soundFilePathA, cAVFormatA, cVideoCodecA, cAudioCodecA: ansistring;
begin
AddFileLog('BeginVideoRecording');
{$IOCHECKS OFF}
// open file with prerecorded camera positions
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
- Assign(cameraFile, filename);
- Reset(cameraFile);
+ cameraFileName:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.txtin';
+ Assign(cameraFile, cameraFileName);
+ Reset(cameraFile, SizeOf(TFrame));
maxProgress:= FileSize(cameraFile);
if IOResult <> 0 then
begin
- AddFileLog('Error: Could not read from ' + filename);
+ AddFileLog('Error: Could not read from ' + cameraFileName);
exit(false);
end;
{$IOCHECKS ON}
- // store some description in output file
+ { Store some description in output file.
+ The comment must follow a particular format and must be in English.
+ This will be parsed by the frontend.
+ The frontend will parse lines of this format:
+ Key: Value
+ The key names will be localized in the frontend.
+ If you add a key/value pair, don't forget to add a localization
+ in the frontend! }
desc:= '';
if UserNick <> '' then
desc:= desc + 'Player: ' + UserNick + #10;
@@ -111,28 +115,32 @@
desc:= desc + 'Theme: ' + Theme + #10;
desc:= desc + 'prefix[' + RecPrefix + ']prefix';
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix;
- soundFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw';
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix);
+
+ recordAudio:= (cAudioCodec <> 'no');
+ if recordAudio then
+ soundFilePath:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.sw'
+ else
+ soundFilePath:= '';
- AVWrapper_Init(@AddFileLogRaw
- , PChar(ansistring(filename))
- , PChar(ansistring(desc))
- , PChar(ansistring(soundFilePath))
- , PChar(ansistring(cAVFormat))
- , PChar(ansistring(cVideoCodec))
- , PChar(ansistring(cAudioCodec))
- , cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cVideoQuality);
+ filenameA:= ansistring(filename);
+ descA:= ansistring(desc);
+ soundFilePathA:= ansistring(soundFilePath);
+ cAVFormatA:= ansistring(cAVFormat);
+ cVideoCodecA:= ansistring(cVideoCodec);
+ cAudioCodecA:= ansistring(cAudioCodec);
+ if checkFails(AVWrapper_Init(@AddFileLogRaw
+ , PChar(filenameA)
+ , PChar(descA)
+ , PChar(soundFilePathA)
+ , PChar(cAVFormatA)
+ , PChar(cVideoCodecA)
+ , PChar(cAudioCodecA)
+ , cScreenWidth, cScreenHeight, cVideoFramerateNum, cVideoFramerateDen, cVideoQuality) >= 0,
+ 'AVWrapper_Init failed',
+ true) then exit(false);
numPixels:= cScreenWidth*cScreenHeight;
- YCbCr_Planes[0]:= GetMem(numPixels);
- YCbCr_Planes[1]:= GetMem(numPixels div 4);
- YCbCr_Planes[2]:= GetMem(numPixels div 4);
-
- if (YCbCr_Planes[0] = nil) or (YCbCr_Planes[1] = nil) or (YCbCr_Planes[2] = nil) then
- begin
- AddFileLog('Error: Could not allocate memory for video recording (YCbCr buffer).');
- exit(false);
- end;
RGB_Buffer:= GetMem(4*numPixels);
if RGB_Buffer = nil then
@@ -150,48 +158,34 @@
procedure StopVideoRecording;
begin
AddFileLog('StopVideoRecording');
- FreeMem(YCbCr_Planes[0], numPixels);
- FreeMem(YCbCr_Planes[1], numPixels div 4);
- FreeMem(YCbCr_Planes[2], numPixels div 4);
FreeMem(RGB_Buffer, 4*numPixels);
Close(cameraFile);
- AVWrapper_Close();
- Erase(cameraFile);
- DeleteFile(soundFilePath);
+ if AVWrapper_Close() < 0 then
+ begin
+ OutError('AVWrapper_Close() has failed.', true);
+ end;
+{$IOCHECKS OFF}
+ if FileExists(cameraFileName) then
+ DeleteFile(cameraFileName)
+ else
+ AddFileLog('Warning: Tried to delete the cameraFile but it was already deleted');
+{$IOCHECKS ON}
+ if recordAudio and FileExists(soundFilePath) then
+ DeleteFile(soundFilePath);
SendIPC(_S'v'); // inform frontend that we finished
end;
-function pixel(x, y, color: LongInt): LongInt;
-begin
- pixel:= RGB_Buffer[(cScreenHeight-y-1)*cScreenWidth*4 + x*4 + color];
-end;
-
procedure EncodeFrame;
-var x, y, r, g, b: LongInt;
- s: shortstring;
+var s: shortstring;
begin
// read pixels from OpenGL
glReadPixels(0, 0, cScreenWidth, cScreenHeight, GL_RGBA, GL_UNSIGNED_BYTE, RGB_Buffer);
- // convert to YCbCr 4:2:0 format
- // Y
- for y := 0 to cScreenHeight-1 do
- for x := 0 to cScreenWidth-1 do
- YCbCr_Planes[0][y*cScreenWidth + x]:= Byte(16 + ((16828*pixel(x,y,0) + 33038*pixel(x,y,1) + 6416*pixel(x,y,2)) shr 16));
-
- // Cb and Cr
- for y := 0 to cScreenHeight div 2 - 1 do
- for x := 0 to cScreenWidth div 2 - 1 do
+ if AVWrapper_WriteFrame(RGB_Buffer) < 0 then
begin
- r:= pixel(2*x,2*y,0) + pixel(2*x+1,2*y,0) + pixel(2*x,2*y+1,0) + pixel(2*x+1,2*y+1,0);
- g:= pixel(2*x,2*y,1) + pixel(2*x+1,2*y,1) + pixel(2*x,2*y+1,1) + pixel(2*x+1,2*y+1,1);
- b:= pixel(2*x,2*y,2) + pixel(2*x+1,2*y,2) + pixel(2*x,2*y+1,2) + pixel(2*x+1,2*y+1,2);
- YCbCr_Planes[1][y*(cScreenWidth div 2) + x]:= Byte(128 + ((-2428*r - 4768*g + 7196*b) shr 16));
- YCbCr_Planes[2][y*(cScreenWidth div 2) + x]:= Byte(128 + (( 7196*r - 6026*g - 1170*b) shr 16));
+ OutError('AVWrapper_WriteFrame(RGB_Buffer) has failed.', true);
end;
- AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]);
-
// inform frontend that we have encoded new frame
s[0]:= #3;
s[1]:= 'p'; // p for progress
@@ -200,16 +194,18 @@
inc(numFrames);
end;
-function LoadNextCameraPosition(out newRealTicks, newGameTicks: LongInt): Boolean;
-var frame: TFrame;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
+var frame: TFrame = (realTicks: 0; gameTicks: 0; CamX: 0; CamY: 0; zoom: 0);
+ res: LongInt;
begin
// we need to skip or duplicate frames to match target framerate
while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do
begin
+ res:= 0;
{$IOCHECKS OFF}
if eof(cameraFile) then
exit(false);
- BlockRead(cameraFile, frame, 1);
+ BlockRead(cameraFile, frame, 1, res);
{$IOCHECKS ON}
curTime:= frame.realTicks;
WorldDx:= frame.CamX;
@@ -226,10 +222,12 @@
// Callback which records sound.
// This procedure may be called from different thread.
procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl;
+var result: LongInt;
begin
+ result:= 0; // avoid warning
udata:= udata; // avoid warning
{$IOCHECKS OFF}
- BlockWrite(audioFile, stream^, len);
+ BlockWrite(audioFile, stream^, len, result);
{$IOCHECKS ON}
end;
@@ -237,10 +235,10 @@
var thumbpath: shortstring;
k: LongInt;
begin
- thumbpath:= '/VideoTemp/' + RecPrefix;
+ thumbpath:= '/VideoThumbnails/' + RecPrefix;
AddFileLog('Saving thumbnail ' + thumbpath);
k:= max(max(cScreenWidth, cScreenHeight) div 400, 1); // here 400 is minimum size of thumbnail
- MakeScreenshot(thumbpath, k);
+ MakeScreenshot(thumbpath, k, 0);
thumbnailSaved:= true;
end;
@@ -248,10 +246,14 @@
procedure CopyFile(src, dest: shortstring);
var inF, outF: file;
buffer: array[0..1023] of byte;
- result: LongInt;
+ result, result2: LongInt;
+ i: integer;
begin
{$IOCHECKS OFF}
- result:= 0; // avoid compiler hint
+ result:= 0; // avoid compiler hint and warning
+ result2:= 0; // avoid compiler hint and warning
+ for i:= 0 to 1023 do
+ buffer[i]:= 0;
Assign(inF, src);
Reset(inF, 1);
@@ -271,7 +273,7 @@
repeat
BlockRead(inF, buffer, 1024, result);
- BlockWrite(outF, buffer, result);
+ BlockWrite(outF, buffer, result, result2);
until result < 1024;
{$IOCHECKS ON}
end;
@@ -280,11 +282,21 @@
var format: word;
filename: shortstring;
frequency, channels: LongInt;
+ result: LongInt;
begin
+ result:= 0;
AddFileLog('BeginPreRecording');
+ // Videos don't work if /lua command was used, so we forbid them
+ if luaCmdUsed then
+ begin
+ // TODO: Show message to player
+ PlaySound(sndDenied);
+ AddFileLog('Pre-recording prevented; /lua command was used before');
+ exit;
+ end;
thumbnailSaved:= false;
- RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', Now());
+ RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', TDateTime(Now()));
// If this video is recorded from demo executed directly (without frontend)
// then we need to copy demo so that frontend will be able to find it later.
@@ -292,46 +304,52 @@
begin
if GameType <> gmtDemo then // this is save and game demo is not recording, abort
exit;
- CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd');
+ CopyFile(recordFileName, shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.hwd');
end;
- Mix_QuerySpec(@frequency, @format, @channels);
- AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
- if format <> $8010 then
- begin
- // TODO: support any audio format
- AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
- exit;
- end;
+ if cIsSoundEnabled then
+ begin
+ Mix_QuerySpec(@frequency, @format, @channels);
+ AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
+ if format <> $8010 then
+ begin
+ // TODO: support any audio format
+ AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
+ exit;
+ end;
{$IOCHECKS OFF}
- // create sound file
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.sw';
- Assign(audioFile, filename);
- Rewrite(audioFile, 1);
- if IOResult <> 0 then
- begin
- AddFileLog('Error: Could not write to ' + filename);
- exit;
- end;
+ // create sound file
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.sw';
+ Assign(audioFile, filename);
+ Rewrite(audioFile, 1);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not write to ' + filename);
+ exit;
+ end;
+ end;
// create file with camera positions
- filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtout';
+ filename:= shortstring(UserPathPrefix) + '/VideoTemp/' + shortstring(RecPrefix) + '.txtout';
Assign(cameraFile, filename);
- Rewrite(cameraFile);
+ Rewrite(cameraFile, SizeOf(TFrame));
if IOResult <> 0 then
- begin
+ begin
AddFileLog('Error: Could not write to ' + filename);
exit;
- end;
+ end;
- // save audio parameters in sound file
- BlockWrite(audioFile, frequency, 4);
- BlockWrite(audioFile, channels, 4);
+ if cIsSoundEnabled then
+ begin
+ // save audio parameters in sound file
+ BlockWrite(audioFile, frequency, 4, result);
+ BlockWrite(audioFile, channels, 4, result);
{$IOCHECKS ON}
- // register callback for actual audio recording
- Mix_SetPostMix(@RecordPostMix, nil);
+ // register callback for actual audio recording
+ Mix_SetPostMix(@RecordPostMix, nil);
+ end;
startTime:= SDL_GetTicks();
flagPrerecording:= true;
@@ -342,12 +360,18 @@
AddFileLog('StopPreRecording');
flagPrerecording:= false;
- // call SDL_LockAudio because RecordPostMix may be executing right now
- SDL_LockAudio();
- Close(audioFile);
+ if cIsSoundEnabled then
+ begin
+ // call SDL_LockAudio because RecordPostMix may be executing right now
+ SDL_LockAudio();
+ Close(audioFile);
+ end;
Close(cameraFile);
- Mix_SetPostMix(nil, nil);
- SDL_UnlockAudio();
+ if cIsSoundEnabled then
+ begin
+ Mix_SetPostMix(nil, nil);
+ SDL_UnlockAudio();
+ end;
if not thumbnailSaved then
SaveThumbnail();
@@ -355,7 +379,9 @@
procedure SaveCameraPosition;
var frame: TFrame;
+ result: LongInt;
begin
+ result:= 0;
if (not thumbnailSaved) and (ScreenFade = sfNone) then
SaveThumbnail();
@@ -364,11 +390,15 @@
frame.CamX:= WorldDx;
frame.CamY:= WorldDy - cScreenHeight div 2;
frame.zoom:= zoom/cScreenWidth;
- BlockWrite(cameraFile, frame, 1);
+ BlockWrite(cameraFile, frame, 1, result);
end;
procedure initModule;
begin
+ // we need to make sure these variables are initialized before the main loop
+ // or the wrapper will keep the default values of preinit
+ cScreenWidth:= max(cWindowedWidth, 640);
+ cScreenHeight:= max(cWindowedHeight, 480);
end;
procedure freeModule;