Correctly distinguish between game and real ticks while recording video,
authorStepan777 <stepik-777@mail.ru>
Fri, 13 Jul 2012 16:39:20 +0400
changeset 7392 bc3306c59a08
parent 7390 27bfd8bbde7e
child 7442 9bb6abdb5675
Correctly distinguish between game and real ticks while recording video, so that paused game and speed-up demo are recorded better. Also support recording videos from demos executed directly (without frontend), such videos will be automatically encoded on frontend startup.
QTfrontend/hwform.cpp
QTfrontend/ui/page/pagevideos.cpp
QTfrontend/ui/page/pagevideos.h
hedgewars/hwengine.pas
hedgewars/uVideoRec.pas
hedgewars/uWorld.pas
--- a/QTfrontend/hwform.cpp	Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/hwform.cpp	Fri Jul 13 16:39:20 2012 +0400
@@ -142,7 +142,7 @@
 
     config = new GameUIConfig(this, cfgdir->absolutePath() + "/hedgewars.ini");
 
-    ui.pageVideos->config = config;
+    ui.pageVideos->init(config);
 
 #ifdef __APPLE__
     panel = new M3Panel;
@@ -1428,18 +1428,7 @@
         }
     }
 
-    // encode videos
-    QDir videosDir(cfgdir->absolutePath() + "/VideoTemp/");
-    QStringList files = videosDir.entryList(QStringList("*.txtout"), QDir::Files);
-    foreach (const QString & str, files)
-    {
-        QString prefix = str;
-        prefix.chop(7); // remove ".txtout"
-        videosDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice
-        HWRecorder* pRecorder = new HWRecorder(config, prefix);
-        ui.pageVideos->addRecorder(pRecorder);
-        pRecorder->EncodeVideo(record);
-    }
+    ui.pageVideos->startEncoding(record);
 }
 
 void HWForm::startTraining(const QString & scriptName)
--- a/QTfrontend/ui/page/pagevideos.cpp	Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.cpp	Fri Jul 13 16:39:20 2012 +0400
@@ -301,13 +301,7 @@
     connect(btnPlay,   SIGNAL(clicked()), this, SLOT(playSelectedFile()));
     connect(btnDelete, SIGNAL(clicked()), this, SLOT(deleteSelectedFiles()));
     connect(btnOpenDir, SIGNAL(clicked()), this, SLOT(openVideosDirectory()));
-
-    QString path = cfgdir->absolutePath() + "/Videos";
-    QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this);
-    pWatcher->addPath(path);
-    connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &)));
-    updateFileList(path);
-}
+ }
 
 PageVideos::PageVideos(QWidget* parent) : AbstractPage(parent),
     config(0)
@@ -317,6 +311,19 @@
     initPage();
 }
 
+void PageVideos::init(GameUIConfig * config)
+{
+    this->config = config;
+
+    QString path = cfgdir->absolutePath() + "/Videos";
+    QFileSystemWatcher * pWatcher = new QFileSystemWatcher(this);
+    pWatcher->addPath(path);
+    connect(pWatcher, SIGNAL(directoryChanged(const QString &)), this, SLOT(updateFileList(const QString &)));
+    updateFileList(path);
+
+    startEncoding(); // this is for videos recorded from demos which were executed directly (without frontend)
+}
+
 // user changed file format, we need to update list of codecs
 void PageVideos::changeAVFormat(int index)
 {
@@ -844,3 +851,32 @@
     }
     return list;
 }
+
+void PageVideos::startEncoding(const QByteArray & record)
+{
+    QDir videoTempDir(cfgdir->absolutePath() + "/VideoTemp/");
+    QStringList files = videoTempDir.entryList(QStringList("*.txtout"), QDir::Files);
+    foreach (const QString & str, files)
+    {
+        QString prefix = str;
+        prefix.chop(7); // remove ".txtout"
+        videoTempDir.rename(prefix + ".txtout", prefix + ".txtin"); // rename this file to not open it twice
+
+        HWRecorder* pRecorder = new HWRecorder(config, prefix);
+
+        if (!record.isEmpty())
+            pRecorder->EncodeVideo(record);
+        else
+        {
+            // this is for videos recorded from demos which were executed directly (without frontend)
+            QFile demofile(videoTempDir.absoluteFilePath(prefix + ".hwd"));
+            if (!demofile.open(QIODevice::ReadOnly))
+                continue;
+            QByteArray demo = demofile.readAll();
+            if (demo.isEmpty())
+                continue;
+            pRecorder->EncodeVideo(demo);
+        }
+        addRecorder(pRecorder);
+    }
+}
--- a/QTfrontend/ui/page/pagevideos.h	Fri Jul 13 16:35:42 2012 +0400
+++ b/QTfrontend/ui/page/pagevideos.h	Fri Jul 13 16:39:20 2012 +0400
@@ -41,7 +41,6 @@
         QCheckBox *checkUseGameRes;
         QCheckBox *checkRecordAudio;
 
-        GameUIConfig * config;
 
         QString format()
         { return comboAVFormats->itemData(comboAVFormats->currentIndex()).toString(); }
@@ -57,6 +56,8 @@
         void addRecorder(HWRecorder* pRecorder);
         bool tryQuit(HWForm *form);
         QString getVideosInProgress(); // get multi-line string with list of videos in progress
+        void startEncoding(const QByteArray & record = QByteArray());
+        void init(GameUIConfig * config);
 
     private:
         // virtuals from AbstractPage
@@ -76,6 +77,8 @@
         void clearTemp();
         void clearThumbnail();
 
+        GameUIConfig * config;
+
         // options group
         QComboBox *comboAVFormats;
         QComboBox *comboVideoCodecs;
--- a/hedgewars/hwengine.pas	Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/hwengine.pas	Fri Jul 13 16:39:20 2012 +0400
@@ -82,15 +82,15 @@
             end;
         gsConfirm, gsGame:
             begin
+            DrawWorld(Lag);
             DoGameTick(Lag);
             ProcessVisualGears(Lag);
-            DrawWorld(Lag);
             end;
         gsChat:
             begin
+            DrawWorld(Lag);
             DoGameTick(Lag);
             ProcessVisualGears(Lag);
-            DrawWorld(Lag);
             end;
         gsExit:
             begin
@@ -273,27 +273,32 @@
 
 {$IFDEF USE_VIDEO_RECORDING}
 procedure RecorderMainLoop;
-var CurrTime, PrevTime: LongInt;
+var oldGameTicks, oldRealTicks, newGameTicks, newRealTicks: LongInt;
 begin
     if not BeginVideoRecording() then
         exit;
     DoTimer(0); // gsLandGen -> gsStart
     DoTimer(0); // gsStart -> gsGame
 
-    CurrTime:= LoadNextCameraPosition();
+    if not LoadNextCameraPosition(newRealTicks, newGameTicks) then
+        exit;
     fastScrolling:= true;
-    DoTimer(CurrTime);
+    DoGameTick(newGameTicks);
     fastScrolling:= false;
-    while true do
+    oldRealTicks:= 0;
+    oldGameTicks:= newGameTicks;
+
+    while LoadNextCameraPosition(newRealTicks, newGameTicks) do
     begin
+        IPCCheckSock();
+        DoGameTick(newGameTicks - oldGameTicks);
+        if GameState = gsExit then
+            break;
+        ProcessVisualGears(newRealTicks - oldRealTicks);
+        DrawWorld(newRealTicks - oldRealTicks);
         EncodeFrame();
-        PrevTime:= CurrTime;
-        CurrTime:= LoadNextCameraPosition();
-        if CurrTime = -1 then
-            break;
-        if DoTimer(CurrTime - PrevTime) then
-            break;
-        IPCCheckSock();
+        oldRealTicks:= newRealTicks;
+        oldGameTicks:= newGameTicks;
     end;
     StopVideoRecording();
 end;
--- a/hedgewars/uVideoRec.pas	Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/uVideoRec.pas	Fri Jul 13 16:39:20 2012 +0400
@@ -39,7 +39,7 @@
 var flagPrerecording: boolean = false;
 
 function BeginVideoRecording: Boolean;
-function LoadNextCameraPosition: LongInt;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
 procedure EncodeFrame;
 procedure StopVideoRecording;
 
@@ -88,7 +88,7 @@
     audioFile: File;
     numPixels: LongWord;
     startTime, numFrames, curTime, progress, maxProgress: LongWord;
-    cameraFilePath, soundFilePath: shortstring;
+    soundFilePath: shortstring;
     thumbnailSaved : Boolean;
 
 function BeginVideoRecording: Boolean;
@@ -98,13 +98,13 @@
 
 {$IOCHECKS OFF}
     // open file with prerecorded camera positions
-    cameraFilePath:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
-    Assign(cameraFile, cameraFilePath);
+    filename:= UserPathPrefix + '/VideoTemp/' + RecPrefix + '.txtin';
+    Assign(cameraFile, filename);
     Reset(cameraFile);
     maxProgress:= FileSize(cameraFile);
     if IOResult <> 0 then
     begin
-        AddFileLog('Error: Could not read from ' + cameraFilePath);
+        AddFileLog('Error: Could not read from ' + filename);
         exit(false);
     end;
 {$IOCHECKS ON}
@@ -163,7 +163,7 @@
     FreeMem(RGB_Buffer, 4*numPixels);
     Close(cameraFile);
     AVWrapper_Close();
-    DeleteFile(cameraFilePath);
+    Erase(cameraFile);
     DeleteFile(soundFilePath);
     SendIPC(_S'v'); // inform frontend that we finished
 end;
@@ -207,17 +207,15 @@
     inc(numFrames);
 end;
 
-// returns new game ticks
-function LoadNextCameraPosition: LongInt;
+function LoadNextCameraPosition(var newRealTicks, newGameTicks: LongInt): Boolean;
 var frame: TFrame;
 begin
-    LoadNextCameraPosition:= GameTicks;
     // we need to skip or duplicate frames to match target framerate
     while Int64(curTime)*cVideoFramerateNum <= Int64(numFrames)*cVideoFramerateDen*1000 do
     begin
     {$IOCHECKS OFF}
         if eof(cameraFile) then
-            exit(-1);
+            exit(false);
         BlockRead(cameraFile, frame, 1);
     {$IOCHECKS ON}
         curTime:= frame.realTicks;
@@ -226,8 +224,10 @@
         zoom:= frame.zoom*cScreenWidth;
         ZoomValue:= zoom;
         inc(progress);
-        LoadNextCameraPosition:= frame.gameTicks;
+        newRealTicks:= frame.realTicks;
+        newGameTicks:= frame.gameTicks;
     end;
+    LoadNextCameraPosition:= true;
 end;
 
 // Callback which records sound.
@@ -251,6 +251,38 @@
     thumbnailSaved:= true;
 end;
 
+// copy file (free pascal doesn't have copy file function)
+procedure CopyFile(src, dest: shortstring);
+var inF, outF: file;
+    buffer: array[0..1023] of byte;
+    result: LongInt;
+begin
+{$IOCHECKS OFF}
+    result:= 0; // avoid compiler hint
+
+    Assign(inF, src);
+    Reset(inF, 1);
+    if IOResult <> 0 then
+    begin
+        AddFileLog('Error: Could not read from ' + src);
+        exit;
+    end;
+
+    Assign(outF, dest);
+    Rewrite(outF, 1);
+    if IOResult <> 0 then
+    begin
+        AddFileLog('Error: Could not write to ' + dest);
+        exit;
+    end;
+
+    repeat
+        BlockRead(inF, buffer, 1024, result);
+        BlockWrite(outF, buffer, result);
+    until result < 1024;
+{$IOCHECKS ON}
+end;
+
 procedure BeginPreRecording;
 var format: word;
     filename: shortstring;
@@ -261,6 +293,15 @@
     thumbnailSaved:= false;
     RecPrefix:= 'hw-' + FormatDateTime('YYYY-MM-DD_HH-mm-ss-z', 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.
+    if recordFileName <> '' then
+    begin
+        if GameType <> gmtDemo then // this is save and game demo is not recording, abort
+            exit;
+        CopyFile(recordFileName, UserPathPrefix + '/VideoTemp/' + RecPrefix + '.hwd');
+    end;
+
     Mix_QuerySpec(@frequency, @format, @channels);
     AddFileLog('sound: frequency = ' + IntToStr(frequency) + ', format = ' + IntToStr(format) + ', channels = ' + IntToStr(channels));
     if format <> $8010 then
--- a/hedgewars/uWorld.pas	Fri Jul 13 16:35:42 2012 +0400
+++ b/hedgewars/uWorld.pas	Fri Jul 13 16:39:20 2012 +0400
@@ -1392,7 +1392,7 @@
     end;
 
 // Lag alert
-if isInLag and (GameType <> gmtRecord) then
+if isInLag then
     DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);
 
 // Wind bar