--- a/QTfrontend/binds.cpp Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/binds.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -64,5 +64,6 @@
{"+volup", "0", QT_TRANSLATE_NOOP("binds", "volume up"), NULL, NULL},
{"fullscr", "f12", QT_TRANSLATE_NOOP("binds", "change mode"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle fullscreen mode:")},
{"capture", "c", QT_TRANSLATE_NOOP("binds", "capture"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Take a screenshot:")},
- {"rotmask", "delete", QT_TRANSLATE_NOOP("binds", "hedgehogs\ninfo"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle labels above hedgehogs:")}
+ {"rotmask", "delete", QT_TRANSLATE_NOOP("binds", "hedgehogs\ninfo"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Toggle labels above hedgehogs:")},
+ {"record", "r", QT_TRANSLATE_NOOP("binds", "record"), NULL, QT_TRANSLATE_NOOP("binds (descriptions)", "Record video:")}
};
--- a/QTfrontend/binds.h Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/binds.h Thu Jun 07 17:42:32 2012 +0400
@@ -21,7 +21,7 @@
#include <QString>
-#define BINDS_NUMBER 44
+#define BINDS_NUMBER 45
struct BindAction
{
--- a/QTfrontend/game.cpp Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/game.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -53,20 +53,18 @@
{
switch (gameType)
{
- case gtSave:
- if (gameState == gsInterrupted || gameState == gsHalted)
- emit HaveRecord(false, demo);
- else if (gameState == gsFinished)
- emit HaveRecord(true, demo);
- break;
case gtDemo:
+ // for video recording we need demo anyway
+ emit HaveRecord(rtNeither, demo);
break;
case gtNet:
- emit HaveRecord(true, demo);
+ emit HaveRecord(rtDemo, demo);
break;
default:
- if (gameState == gsInterrupted || gameState == gsHalted) emit HaveRecord(false, demo);
- else if (gameState == gsFinished) emit HaveRecord(true, demo);
+ if (gameState == gsInterrupted || gameState == gsHalted)
+ emit HaveRecord(rtSave, demo);
+ else if (gameState == gsFinished)
+ emit HaveRecord(rtDemo, demo);
}
SetGameState(gsStopped);
}
--- a/QTfrontend/game.h Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/game.h Thu Jun 07 17:42:32 2012 +0400
@@ -40,6 +40,13 @@
gsHalted = 6
};
+enum RecordType
+{
+ rtDemo,
+ rtSave,
+ rtNeither,
+};
+
bool checkForDir(const QString & dir);
class HWGame : public TCPBase
@@ -70,7 +77,7 @@
void SendTeamMessage(const QString & msg);
void GameStateChanged(GameState gameState);
void GameStats(char type, const QString & info);
- void HaveRecord(bool isDemo, const QByteArray & record);
+ void HaveRecord(RecordType type, const QByteArray & record);
void ErrorMessage(const QString &);
public slots:
--- a/QTfrontend/hwform.cpp Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/hwform.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -90,6 +90,7 @@
#include "drawmapwidget.h"
#include "mouseoverfilter.h"
#include "roomslistmodel.h"
+#include "recorder.h"
#include "DataManager.h"
@@ -1357,7 +1358,7 @@
connect(game, SIGNAL(GameStateChanged(GameState)), this, SLOT(GameStateChanged(GameState)));
connect(game, SIGNAL(GameStats(char, const QString &)), ui.pageGameStats, SLOT(GameStats(char, const QString &)));
connect(game, SIGNAL(ErrorMessage(const QString &)), this, SLOT(ShowErrorMessage(const QString &)), Qt::QueuedConnection);
- connect(game, SIGNAL(HaveRecord(bool, const QByteArray &)), this, SLOT(GetRecord(bool, const QByteArray &)));
+ connect(game, SIGNAL(HaveRecord(RecordType, const QByteArray &)), this, SLOT(GetRecord(RecordType, const QByteArray &)));
m_lastDemo = QByteArray();
}
@@ -1368,43 +1369,56 @@
msg);
}
-void HWForm::GetRecord(bool isDemo, const QByteArray & record)
+void HWForm::GetRecord(RecordType type, const QByteArray & record)
{
- QString filename;
- QByteArray demo = record;
- QString recordFileName =
- config->appendDateTimeToRecordName() ?
- QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm") :
- "LastRound";
+ if (type != rtNeither)
+ {
+ QString filename;
+ QByteArray demo = record;
+ QString recordFileName =
+ config->appendDateTimeToRecordName() ?
+ QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm") :
+ "LastRound";
- QStringList versionParts = cVersionString->split('-');
- if ( (versionParts.size() == 2) && (!versionParts[1].isEmpty()) && (versionParts[1].contains(':')) )
- recordFileName = recordFileName + "_" + versionParts[1].replace(':','-');
+ QStringList versionParts = cVersionString->split('-');
+ if ( (versionParts.size() == 2) && (!versionParts[1].isEmpty()) && (versionParts[1].contains(':')) )
+ recordFileName = recordFileName + "_" + versionParts[1].replace(':','-');
- if (isDemo)
- {
- demo.replace(QByteArray("\x02TL"), QByteArray("\x02TD"));
- demo.replace(QByteArray("\x02TN"), QByteArray("\x02TD"));
- demo.replace(QByteArray("\x02TS"), QByteArray("\x02TD"));
- filename = cfgdir->absolutePath() + "/Demos/" + recordFileName + "." + *cProtoVer + ".hwd";
- m_lastDemo = demo;
- }
- else
- {
- demo.replace(QByteArray("\x02TL"), QByteArray("\x02TS"));
- demo.replace(QByteArray("\x02TN"), QByteArray("\x02TS"));
- filename = cfgdir->absolutePath() + "/Saves/" + recordFileName + "." + *cProtoVer + ".hws";
+ if (type == rtDemo)
+ {
+ demo.replace(QByteArray("\x02TL"), QByteArray("\x02TD"));
+ demo.replace(QByteArray("\x02TN"), QByteArray("\x02TD"));
+ demo.replace(QByteArray("\x02TS"), QByteArray("\x02TD"));
+ filename = cfgdir->absolutePath() + "/Demos/" + recordFileName + "." + *cProtoVer + ".hwd";
+ m_lastDemo = demo;
+ }
+ else
+ {
+ demo.replace(QByteArray("\x02TL"), QByteArray("\x02TS"));
+ demo.replace(QByteArray("\x02TN"), QByteArray("\x02TS"));
+ filename = cfgdir->absolutePath() + "/Saves/" + recordFileName + "." + *cProtoVer + ".hws";
+ }
+
+ QFile demofile(filename);
+ if (!demofile.open(QIODevice::WriteOnly))
+ ShowErrorMessage(tr("Cannot save record to file %1").arg(filename));
+ else
+ {
+ demofile.write(demo);
+ demofile.close();
+ }
}
-
- QFile demofile(filename);
- if (!demofile.open(QIODevice::WriteOnly))
+ QDir videosDir(cfgdir->absolutePath() + "/Videos/");
+ QStringList files = videosDir.entryList(QStringList("*.txtout"), QDir::Files);
+ for (QStringList::iterator str = files.begin(); str != files.end(); str++)
{
- ShowErrorMessage(tr("Cannot save record to file %1").arg(filename));
- return ;
+ str->chop(7); // remove ".txtout"
+ // need to rename this file to not open it twice
+ videosDir.rename(*str + ".txtout", *str + ".txtin");
+ HWRecorder* pRecorder = new HWRecorder(config);
+ pRecorder->EncodeVideo(record, *str);
}
- demofile.write(demo);
- demofile.close();
}
void HWForm::startTraining(const QString & scriptName)
--- a/QTfrontend/hwform.h Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/hwform.h Thu Jun 07 17:42:32 2012 +0400
@@ -114,7 +114,7 @@
void GameStateChanged(GameState gameState);
void ForcedDisconnect(const QString & reason);
void ShowErrorMessage(const QString &);
- void GetRecord(bool isDemo, const QByteArray & record);
+ void GetRecord(RecordType type, const QByteArray & record);
void CreateNetGame();
void UpdateWeapons();
void onFrontendFullscreen(bool value);
--- a/QTfrontend/main.cpp Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/main.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -199,6 +199,7 @@
checkForDir(cfgdir->absolutePath() + "/Screenshots");
checkForDir(cfgdir->absolutePath() + "/Teams");
checkForDir(cfgdir->absolutePath() + "/Logs");
+ checkForDir(cfgdir->absolutePath() + "/Videos");
}
datadir->cd(bindir->absolutePath());
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/net/recorder.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -0,0 +1,93 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2012 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
+ * the Free Software Foundation; version 2 of the License
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <QString>
+#include <QByteArray>
+
+#include "recorder.h"
+#include "gameuiconfig.h"
+#include "hwconsts.h"
+#include "game.h"
+
+HWRecorder::HWRecorder(GameUIConfig * config) :
+ TCPBase(false)
+{
+ this->config = config;
+}
+
+HWRecorder::~HWRecorder()
+{
+}
+
+void HWRecorder::onClientDisconnect()
+{
+}
+
+void HWRecorder::onClientRead()
+{
+ quint8 msglen;
+ quint32 bufsize;
+ while (!readbuffer.isEmpty() && ((bufsize = readbuffer.size()) > 0) &&
+ ((msglen = readbuffer.data()[0]) < bufsize))
+ {
+ QByteArray msg = readbuffer.left(msglen + 1);
+ readbuffer.remove(0, msglen + 1);
+ if (msg.at(1) == '?')
+ SendIPC("!");
+ }
+}
+
+void HWRecorder::EncodeVideo( const QByteArray & record, const QString & prefix )
+{
+ this->prefix = prefix;
+
+ toSendBuf = record;
+ toSendBuf.replace(QByteArray("\x02TD"), QByteArray("\x02TV"));
+ toSendBuf.replace(QByteArray("\x02TL"), QByteArray("\x02TV"));
+ toSendBuf.replace(QByteArray("\x02TN"), QByteArray("\x02TV"));
+ toSendBuf.replace(QByteArray("\x02TS"), QByteArray("\x02TV"));
+
+ // run engine
+ Start();
+}
+
+QStringList HWRecorder::getArguments()
+{
+ QStringList arguments;
+ QRect resolution = config->vid_Resolution();
+ arguments << cfgdir->absolutePath();
+ arguments << QString::number(resolution.width());
+ arguments << QString::number(resolution.height());
+ arguments << QString::number(config->bitDepth()); // bpp
+ arguments << QString("%1").arg(ipc_port);
+ arguments << "0"; // fullscreen
+ arguments << "0"; // sound
+ arguments << "0"; // music
+ arguments << "0"; // sound volume
+ arguments << QString::number(config->timerInterval());
+ arguments << datadir->absolutePath();
+ arguments << (config->isShowFPSEnabled() ? "1" : "0");
+ arguments << (config->isAltDamageEnabled() ? "1" : "0");
+ arguments << config->netNick().toUtf8().toBase64();
+ arguments << QString::number(config->translateQuality());
+ arguments << QString::number(config->stereoMode());
+ arguments << HWGame::tr("en.txt");
+ arguments << prefix;
+
+ return arguments;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/QTfrontend/net/recorder.h Thu Jun 07 17:42:32 2012 +0400
@@ -0,0 +1,52 @@
+/*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2012 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
+ * the Free Software Foundation; version 2 of the License
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef RECORDER_H
+#define RECORDER_H
+
+#include <QString>
+#include <QByteArray>
+
+#include "tcpBase.h"
+
+class GameUIConfig;
+
+class HWRecorder : public TCPBase
+{
+ Q_OBJECT
+ public:
+ HWRecorder(GameUIConfig * config);
+ virtual ~HWRecorder();
+
+ void EncodeVideo(const QByteArray & record, const QString & prefix);
+
+ protected:
+ virtual QStringList getArguments();
+ virtual void onClientRead();
+ virtual void onClientDisconnect();
+
+ signals:
+
+ public slots:
+
+ private:
+ GameUIConfig * config;
+ QString prefix;
+};
+
+#endif // RECORDER_H
--- a/QTfrontend/net/tcpBase.cpp Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/net/tcpBase.cpp Thu Jun 07 17:42:32 2012 +0400
@@ -26,8 +26,8 @@
#include "hwconsts.h"
-QList<TCPBase*> srvsList;
-QPointer<QTcpServer> TCPBase::IPCServer(0);
+//QList<TCPBase*> srvsList;
+//QPointer<QTcpServer> TCPBase::IPCServer(0);
TCPBase::~TCPBase()
{
@@ -35,7 +35,7 @@
TCPBase::TCPBase(bool demoMode) :
m_isDemoMode(demoMode),
- IPCSocket(0)
+ IPCSocket(0), IPCServer(0)
{
if(!IPCServer)
{
@@ -67,7 +67,7 @@
SendToClientFirst();
}
-void TCPBase::RealStart()
+void TCPBase::/*Real*/Start()
{
connect(IPCServer, SIGNAL(newConnection()), this, SLOT(NewConnection()));
IPCSocket = 0;
@@ -88,8 +88,8 @@
disconnect(IPCSocket, SIGNAL(readyRead()), this, SLOT(ClientRead()));
onClientDisconnect();
- if(srvsList.size()==1) srvsList.pop_front();
- emit isReadyNow();
+ /* if(srvsList.size()==1) srvsList.pop_front();
+ emit isReadyNow();*/
IPCSocket->deleteLater();
deleteLater();
}
@@ -109,13 +109,13 @@
.arg(error) + bindir->absolutePath() + "/hwengine)");
}
+/*
void TCPBase::tcpServerReady()
{
disconnect(srvsList.takeFirst(), SIGNAL(isReadyNow()), this, SLOT(tcpServerReady()));
RealStart();
}
-
void TCPBase::Start()
{
if(srvsList.isEmpty())
@@ -130,7 +130,7 @@
}
RealStart();
-}
+}*/
void TCPBase::onClientRead()
{
--- a/QTfrontend/net/tcpBase.h Thu Jun 07 01:28:39 2012 +0200
+++ b/QTfrontend/net/tcpBase.h Thu Jun 07 17:42:32 2012 +0400
@@ -62,11 +62,11 @@
virtual void onClientDisconnect();
virtual void SendToClientFirst();
+ // void RealStart();
private:
- static QPointer<QTcpServer> IPCServer;
+ /*static*/ QPointer<QTcpServer> IPCServer;
bool m_isDemoMode;
- void RealStart();
QPointer<QTcpSocket> IPCSocket;
private slots:
@@ -75,7 +75,7 @@
void ClientRead();
void StartProcessError(QProcess::ProcessError error);
- void tcpServerReady();
+ // void tcpServerReady();
};
#endif // _TCPBASE_INCLUDED
--- a/hedgewars/ArgParsers.inc Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/ArgParsers.inc Thu Jun 07 17:42:32 2012 +0400
@@ -61,6 +61,11 @@
else
cStereoMode:= TStereoMode(max(0, min(ord(high(TStereoMode)), tmp-6)));
cLocaleFName:= ParamStr(17);
+ if ParamCount > 17 then
+ begin
+ cRecPrefix:= ParamStr(18);
+ GameType:= gmtRecord;
+ end;
end;
procedure setVideo(screenWidth: LongInt; screenHeight: LongInt; bitsStr: LongInt);
--- a/hedgewars/CMakeLists.txt Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/CMakeLists.txt Thu Jun 07 17:42:32 2012 +0400
@@ -59,6 +59,7 @@
uTypes.pas
uUtils.pas
uVariables.pas
+ uVideoRec.pas
uVisualGears.pas
uWorld.pas
GSHandlers.inc
@@ -182,6 +183,15 @@
set(fpc_flags ${noexecstack_flags} ${pascal_flags} ${hwengine_project})
+IF (WIN32)
+ set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
+ include_directories(${CMAKE_SOURCE_DIR}/misc/winutils/include)
+ link_directories(${CMAKE_SOURCE_DIR}/misc/winutils/lib)
+ add_library(avwrapper SHARED avwrapper.c)
+ target_link_libraries(avwrapper avcodec avformat avutil)
+ELSE()
+ add_library(avwrapper STATIC avwrapper.c)
+ENDIF()
IF(NOT APPLE)
#here is the command for standard executables or for shared library
@@ -222,10 +232,12 @@
#this command is a workaround to some inlining issues present in older
# FreePascal versions and fixed in 2.6, That version is mandatory on OSX,
# hence the command is not needed there
-if(NOT APPLE)
- add_custom_target(ENGINECLEAN COMMAND ${CMAKE_BUILD_TOOL} "clean" "${PROJECT_BINARY_DIR}" "${hedgewars_SOURCE_DIR}/hedgewars")
- add_dependencies(${engine_output_name} ENGINECLEAN)
-endif()
+# if(NOT APPLE)
+# add_custom_target(ENGINECLEAN COMMAND ${CMAKE_BUILD_TOOL} "clean" "${PROJECT_BINARY_DIR}" "${hedgewars_SOURCE_DIR}/hedgewars")
+# add_dependencies(${engine_output_name} ENGINECLEAN)
+# endif()
install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/${engine_output_name}${CMAKE_EXECUTABLE_SUFFIX}" DESTINATION ${target_dir})
-
+IF (WIN32)
+ install(PROGRAMS "${EXECUTABLE_OUTPUT_PATH}/${CMAKE_SHARED_LIBRARY_PREFIX}avwrapper${CMAKE_SHARED_LIBRARY_SUFFIX}" DESTINATION ${target_dir})
+ENDIF()
--- a/hedgewars/SDLh.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/SDLh.pas Thu Jun 07 17:42:32 2012 +0400
@@ -829,6 +829,8 @@
TMixMusic = record
end;
+ TPostMix = procedure(udata: pointer; stream: PByte; len: LongInt); cdecl;
+
{* SDL_net *}
TIPAddress = record
host: LongWord;
@@ -959,6 +961,9 @@
function SDL_GL_SetAttribute(attr: TSDL_GLattr; value: LongInt): LongInt; cdecl; external SDLLibName;
procedure SDL_GL_SwapBuffers; cdecl; external SDLLibName;
+procedure SDL_LockAudio; cdecl; external SDLLibName;
+procedure SDL_UnlockAudio; cdecl; external SDLLibName;
+
function SDL_NumJoysticks: LongInt; cdecl; external SDLLibName;
function SDL_JoystickName(idx: LongInt): PChar; cdecl; external SDLLibName;
function SDL_JoystickOpen(idx: LongInt): PSDL_Joystick; cdecl; external SDLLibName;
@@ -1013,6 +1018,7 @@
function Mix_OpenAudio(frequency: LongInt; format: Word; channels: LongInt; chunksize: LongInt): LongInt; cdecl; external SDL_MixerLibName;
procedure Mix_CloseAudio; cdecl; external SDL_MixerLibName;
+function Mix_QuerySpec(frequency: PLongInt; format: PWord; channels: PLongInt): LongInt; cdecl; external SDL_MixerLibName;
function Mix_Volume(channel: LongInt; volume: LongInt): LongInt; cdecl; external SDL_MixerLibName;
function Mix_SetDistance(channel: LongInt; distance: Byte): LongInt; cdecl; external SDL_MixerLibName;
@@ -1040,6 +1046,8 @@
function Mix_FadeInChannelTimed(channel: LongInt; chunk: PMixChunk; loops: LongInt; fadems: LongInt; ticks: LongInt): LongInt; cdecl; external SDL_MixerLibName;
function Mix_FadeOutChannel(channel: LongInt; fadems: LongInt): LongInt; cdecl; external SDL_MixerLibName;
+procedure Mix_SetPostMix( mix_func: TPostMix; arg: pointer); cdecl; external SDL_MixerLibName;
+
(* SDL_image *)
function IMG_Init(flags: LongInt): LongInt; {$IFDEF SDL_IMAGE_NEWER}cdecl; external SDL_ImageLibName;{$ENDIF}
procedure IMG_Quit; {$IFDEF SDL_IMAGE_NEWER}cdecl; external SDL_ImageLibName;{$ENDIF}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/avwrapper.c Thu Jun 07 17:42:32 2012 +0400
@@ -0,0 +1,470 @@
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include "libavformat/avformat.h"
+
+static AVFormatContext* g_pContainer;
+static AVOutputFormat* g_pFormat;
+static AVStream* g_pAStream;
+static AVStream* g_pVStream;
+static AVFrame* g_pAFrame;
+static AVFrame* g_pVFrame;
+static AVCodec* g_pACodec;
+static AVCodec* g_pVCodec;
+static AVCodecContext* g_pAudio;
+static AVCodecContext* g_pVideo;
+
+static int g_Width, g_Height, g_Framerate;
+static int g_Frequency, g_Channels;
+
+static FILE* g_pSoundFile;
+static int16_t* g_pSamples;
+static int g_NumSamples;
+
+/*
+Initially I wrote code for latest ffmpeg, but on Linux (Ubuntu)
+only older version is available from repository. That's why you see here
+all of this #if LIBAVCODEC_VERSION_MAJOR < 54.
+Actually, it may be possible to remove code for newer version
+and use only code for older version.
+*/
+
+#if LIBAVCODEC_VERSION_MAJOR < 54
+#define OUTBUFFER_SIZE 200000
+static uint8_t g_OutBuffer[OUTBUFFER_SIZE];
+#endif
+
+// pointer to function from hwengine (uUtils.pas)
+static void (*AddFileLogRaw)(const char* pString);
+
+static void FatalError(const char* pFmt, ...)
+{
+ const char Buffer[1024];
+ va_list VaArgs;
+
+ va_start(VaArgs, pFmt);
+ vsnprintf(Buffer, 1024, pFmt, VaArgs);
+ va_end(VaArgs);
+
+ AddFileLogRaw("Error in av-wrapper: ");
+ AddFileLogRaw(Buffer);
+ AddFileLogRaw("\n");
+ exit(1);
+}
+
+// Function to be called from libav for logging.
+// Note: libav can call LogCallback from different threads
+// (there is mutex in AddFileLogRaw).
+static void LogCallback(void* p, int Level, const char* pFmt, va_list VaArgs)
+{
+ const char Buffer[1024];
+
+ vsnprintf(Buffer, 1024, pFmt, VaArgs);
+ AddFileLogRaw(Buffer);
+}
+
+static void Log(const char* pFmt, ...)
+{
+ const char Buffer[1024];
+ va_list VaArgs;
+
+ va_start(VaArgs, pFmt);
+ vsnprintf(Buffer, 1024, pFmt, VaArgs);
+ va_end(VaArgs);
+
+ AddFileLogRaw(Buffer);
+}
+
+static void AddAudioStream(enum CodecID codec_id)
+{
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ g_pAStream = avformat_new_stream(g_pContainer, g_pACodec);
+#else
+ g_pAStream = av_new_stream(g_pContainer, 1);
+#endif
+ if(!g_pAStream)
+ FatalError("Could not allocate audio stream");
+ g_pAStream->id = 1;
+
+ g_pAudio = g_pAStream->codec;
+ avcodec_get_context_defaults3(g_pAudio, g_pACodec);
+ g_pAudio->codec_id = codec_id;
+
+ // put parameters
+ g_pAudio->sample_fmt = AV_SAMPLE_FMT_S16;
+ // pContext->bit_rate = 128000;
+ g_pAudio->sample_rate = g_Frequency;
+ g_pAudio->channels = g_Channels;
+
+ // some formats want stream headers to be separate
+ if (g_pFormat->flags & AVFMT_GLOBALHEADER)
+ g_pAudio->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ // open it
+ if (avcodec_open2(g_pAudio, g_pACodec, NULL) < 0)
+ FatalError("Could not open audio codec %s", g_pACodec->long_name);
+
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ if (g_pACodec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE)
+#else
+ if (g_pAudio->frame_size == 0)
+#endif
+ g_NumSamples = 4096;
+ else
+ g_NumSamples = g_pAudio->frame_size;
+ g_pSamples = (int16_t*)av_malloc(g_NumSamples*g_Channels*sizeof(int16_t));
+ g_pAFrame = avcodec_alloc_frame();
+ if (!g_pAFrame)
+ FatalError("Could not allocate frame");
+}
+
+// returns non-zero if there is more sound
+static int WriteAudioFrame()
+{
+ AVPacket Packet = { 0 };
+ av_init_packet(&Packet);
+
+ int NumSamples = fread(g_pSamples, 2*g_Channels, g_NumSamples, g_pSoundFile);
+
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ AVFrame* pFrame = NULL;
+ if (NumSamples > 0)
+ {
+ g_pAFrame->nb_samples = NumSamples;
+ avcodec_fill_audio_frame(g_pAFrame, g_Channels, AV_SAMPLE_FMT_S16,
+ (uint8_t*)g_pSamples, NumSamples*2*g_Channels, 1);
+ pFrame = g_pAFrame;
+ }
+ // when NumSamples == 0 we still need to call encode_audio2 to flush
+ int got_packet;
+ if (avcodec_encode_audio2(g_pAudio, &Packet, pFrame, &got_packet) != 0)
+ FatalError("avcodec_encode_audio2 failed");
+ if (!got_packet)
+ return 0;
+#else
+ if (NumSamples == 0)
+ return 0;
+ int BufferSize = OUTBUFFER_SIZE;
+ if (g_pAudio->frame_size == 0)
+ BufferSize = NumSamples*g_Channels*2;
+ Packet.size = avcodec_encode_audio(g_pAudio, g_OutBuffer, BufferSize, g_pSamples);
+ if (Packet.size == 0)
+ return 1;
+ if (g_pAudio->coded_frame && g_pAudio->coded_frame->pts != AV_NOPTS_VALUE)
+ Packet.pts = av_rescale_q(g_pAudio->coded_frame->pts, g_pAudio->time_base, g_pAStream->time_base);
+ Packet.flags |= AV_PKT_FLAG_KEY;
+ Packet.data = g_OutBuffer;
+#endif
+
+ // Write the compressed frame to the media file.
+ Packet.stream_index = g_pAStream->index;
+ if (av_interleaved_write_frame(g_pContainer, &Packet) != 0)
+ FatalError("Error while writing audio frame");
+ return 1;
+}
+
+// add a video output stream
+static void AddVideoStream(enum CodecID codec_id)
+{
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ g_pVStream = avformat_new_stream(g_pContainer, g_pVCodec);
+#else
+ g_pVStream = av_new_stream(g_pContainer, 0);
+#endif
+ if (!g_pVStream)
+ FatalError("Could not allocate video stream");
+
+ g_pVideo = g_pVStream->codec;
+ avcodec_get_context_defaults3( g_pVideo, g_pVCodec );
+ g_pVideo->codec_id = codec_id;
+
+ // put parameters
+ // resolution must be a multiple of two
+ g_pVideo->width = g_Width;
+ g_pVideo->height = g_Height;
+ /* time base: this is the fundamental unit of time (in seconds) in terms
+ of which frame timestamps are represented. for fixed-fps content,
+ timebase should be 1/framerate and timestamp increments should be
+ identically 1. */
+ g_pVideo->time_base.den = g_Framerate;
+ g_pVideo->time_base.num = 1;
+ //g_pVideo->gop_size = 12; /* emit one intra frame every twelve frames at most */
+ g_pVideo->pix_fmt = PIX_FMT_YUV420P;
+
+ // some formats want stream headers to be separate
+ if (g_pFormat->flags & AVFMT_GLOBALHEADER)
+ g_pVideo->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ AVDictionary* pDict = NULL;
+ if (codec_id == CODEC_ID_H264)
+ {
+ // av_dict_set(&pDict, "tune", "animation", 0);
+ // av_dict_set(&pDict, "preset", "veryslow", 0);
+ av_dict_set(&pDict, "crf", "20", 0);
+ }
+ else
+ {
+ g_pVideo->flags |= CODEC_FLAG_QSCALE;
+ // g_pVideo->bit_rate = g_Width*g_Height*g_Framerate/4;
+ g_pVideo->global_quality = 15*FF_QP2LAMBDA;
+ }
+
+ // open the codec
+ if (avcodec_open2(g_pVideo, g_pVCodec, &pDict) < 0)
+ FatalError("Could not open video codec %s", g_pVCodec->long_name);
+
+ g_pVFrame = avcodec_alloc_frame();
+ if (!g_pVFrame)
+ FatalError("Could not allocate frame");
+
+ g_pVFrame->linesize[0] = g_Width;
+ g_pVFrame->linesize[1] = g_Width/2;
+ g_pVFrame->linesize[2] = g_Width/2;
+ g_pVFrame->linesize[3] = 0;
+}
+
+static int WriteFrame( AVFrame* pFrame )
+{
+ double AudioTime, VideoTime;
+
+ // write interleaved audio frame
+ if (g_pAStream)
+ {
+ VideoTime = (double)g_pVStream->pts.val*g_pVStream->time_base.num/g_pVStream->time_base.den;
+ do
+ AudioTime = (double)g_pAStream->pts.val*g_pAStream->time_base.num/g_pAStream->time_base.den;
+ while (AudioTime < VideoTime && WriteAudioFrame());
+ }
+
+ AVPacket Packet;
+ av_init_packet(&Packet);
+ Packet.data = NULL;
+ Packet.size = 0;
+
+ g_pVFrame->pts++;
+ if (g_pFormat->flags & AVFMT_RAWPICTURE)
+ {
+ /* raw video case. The API will change slightly in the near
+ future for that. */
+ Packet.flags |= AV_PKT_FLAG_KEY;
+ Packet.stream_index = g_pVStream->index;
+ Packet.data = (uint8_t*)pFrame;
+ Packet.size = sizeof(AVPicture);
+
+ if (av_interleaved_write_frame(g_pContainer, &Packet) != 0)
+ FatalError("Error while writing video frame");
+ return 0;
+ }
+ else
+ {
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ int got_packet;
+ if (avcodec_encode_video2(g_pVideo, &Packet, pFrame, &got_packet) < 0)
+ FatalError("avcodec_encode_video2 failed");
+ if (!got_packet)
+ return 0;
+
+ if (Packet.pts != AV_NOPTS_VALUE)
+ Packet.pts = av_rescale_q(Packet.pts, g_pVideo->time_base, g_pVStream->time_base);
+ if (Packet.dts != AV_NOPTS_VALUE)
+ Packet.dts = av_rescale_q(Packet.dts, g_pVideo->time_base, g_pVStream->time_base);
+#else
+ Packet.size = avcodec_encode_video(g_pVideo, g_OutBuffer, OUTBUFFER_SIZE, pFrame);
+ if (Packet.size < 0)
+ FatalError("avcodec_encode_video failed");
+ if (Packet.size == 0)
+ return 0;
+
+ if( g_pVideo->coded_frame->pts != AV_NOPTS_VALUE)
+ Packet.pts = av_rescale_q(g_pVideo->coded_frame->pts, g_pVideo->time_base, g_pVStream->time_base);
+ if( g_pVideo->coded_frame->key_frame )
+ Packet.flags |= AV_PKT_FLAG_KEY;
+ Packet.data = g_OutBuffer;
+#endif
+ // write the compressed frame in the media file
+ Packet.stream_index = g_pVStream->index;
+ if (av_interleaved_write_frame(g_pContainer, &Packet) != 0)
+ FatalError("Error while writing video frame");
+
+ return 1;
+ }
+}
+
+void AVWrapper_WriteFrame(uint8_t* pY, uint8_t* pCb, uint8_t* pCr)
+{
+ g_pVFrame->data[0] = pY;
+ g_pVFrame->data[1] = pCb;
+ g_pVFrame->data[2] = pCr;
+ WriteFrame(g_pVFrame);
+}
+
+void AVWrapper_GetList()
+{
+ // initialize libav and register all codecs and formats
+ av_register_all();
+
+#if 0
+ AVOutputFormat* pFormat = NULL;
+ while (pFormat = av_oformat_next(pFormat))
+ {
+ Log("%s; %s; %s;\n", pFormat->name, pFormat->long_name, pFormat->mime_type);
+
+ AVCodec* pCodec = NULL;
+ while (pCodec = av_codec_next(pCodec))
+ {
+ if (!av_codec_is_encoder(pCodec))
+ continue;
+ if (avformat_query_codec(pFormat, pCodec->id, FF_COMPLIANCE_NORMAL) != 1)
+ continue;
+ if (pCodec->type = AVMEDIA_TYPE_VIDEO)
+ {
+ if (pCodec->supported_framerate != NULL)
+ continue;
+ Log(" Video: %s; %s;\n", pCodec->name, pCodec->long_name);
+ }
+ if (pCodec->type = AVMEDIA_TYPE_AUDIO)
+ {
+ /* if (pCodec->supported_samplerates == NULL)
+ continue;
+ int i;
+ for(i = 0; i <)
+ supported_samplerates*/
+ Log(" Audio: %s; %s;\n", pCodec->name, pCodec->long_name);
+ }
+ }
+ /* struct AVCodecTag** pTags = pCur->codec_tag;
+ int i;
+ for (i = 0; ; i++)
+ {
+ enum CodecID id = av_codec_get_id(pTags, i);
+ if (id == CODEC_ID_NONE)
+ break;
+ AVCodec* pCodec = avcodec_find_encoder(id);
+ Log(" %i: %s; %s;\n", id, pCodec->name, pCodec->long_name);
+ }*/
+ }
+#endif
+}
+
+void AVWrapper_Init(void (*pAddFileLogRaw)(const char*), const char* pFilename, const char* pSoundFile, int Width, int Height, int Framerate, int Frequency, int Channels)
+{
+ AddFileLogRaw = pAddFileLogRaw;
+ av_log_set_callback( &LogCallback );
+
+ g_Width = Width;
+ g_Height = Height;
+ g_Framerate = Framerate;
+ g_Frequency = Frequency;
+ g_Channels = Channels;
+
+ // initialize libav and register all codecs and formats
+ av_register_all();
+
+ AVWrapper_GetList();
+
+ // allocate the output media context
+#if LIBAVCODEC_VERSION_MAJOR >= 54
+ avformat_alloc_output_context2(&g_pContainer, NULL, "mp4", pFilename);
+#else
+ g_pFormat = av_guess_format(NULL, pFilename, NULL);
+ if (!g_pFormat)
+ FatalError("guess_format");
+
+ // allocate the output media context
+ g_pContainer = avformat_alloc_context();
+ if (g_pContainer)
+ {
+ g_pContainer->oformat = g_pFormat;
+ snprintf(g_pContainer->filename, sizeof(g_pContainer->filename), "%s", pFilename);
+ }
+#endif
+ if (!g_pContainer)
+ FatalError("Could not allocate output context");
+
+ g_pFormat = g_pContainer->oformat;
+
+ enum CodecID VideoCodecID = g_pFormat->video_codec;//CODEC_ID_H264;
+ enum CodecID AudioCodecID = g_pFormat->audio_codec;
+
+ g_pVStream = NULL;
+ g_pAStream = NULL;
+ if (VideoCodecID != CODEC_ID_NONE)
+ {
+ g_pVCodec = avcodec_find_encoder(VideoCodecID);
+ if (!g_pVCodec)
+ FatalError("Video codec not found");
+ AddVideoStream(VideoCodecID);
+ }
+
+ if (AudioCodecID != CODEC_ID_NONE)
+ {
+ g_pACodec = avcodec_find_encoder(AudioCodecID);
+ if (!g_pACodec)
+ FatalError("Audio codec not found");
+ AddAudioStream(AudioCodecID);
+ }
+
+ if (g_pAStream)
+ {
+ g_pSoundFile = fopen(pSoundFile, "rb");
+ if (!g_pSoundFile)
+ FatalError("Could not open %s", pSoundFile);
+ }
+
+ // write format info to log
+ av_dump_format(g_pContainer, 0, pFilename, 1);
+
+ // open the output file, if needed
+ if (!(g_pFormat->flags & AVFMT_NOFILE))
+ {
+ if (avio_open(&g_pContainer->pb, pFilename, AVIO_FLAG_WRITE) < 0)
+ FatalError("Could not open output file (%s)", pFilename);
+ }
+
+ // write the stream header, if any
+ avformat_write_header(g_pContainer, NULL);
+ g_pVFrame->pts = -1;
+}
+
+void AVWrapper_Close()
+{
+ // output buffered frames
+ if (g_pVCodec->capabilities & CODEC_CAP_DELAY)
+ while( WriteFrame(NULL) );
+ // output any remaining audio
+ while( WriteAudioFrame() );
+
+ // write the trailer, if any.
+ av_write_trailer(g_pContainer);
+
+ // close each codec
+ if( g_pVStream )
+ {
+ avcodec_close(g_pVStream->codec);
+ av_free(g_pVFrame);
+ }
+ if( g_pAStream )
+ {
+ avcodec_close(g_pAStream->codec);
+ av_free(g_pAFrame);
+ av_free(g_pSamples);
+ fclose(g_pSoundFile);
+ }
+
+ // free the streams
+ int i;
+ for (i = 0; i < g_pContainer->nb_streams; i++)
+ {
+ av_freep(&g_pContainer->streams[i]->codec);
+ av_freep(&g_pContainer->streams[i]);
+ }
+
+ // close the output file
+ if (!(g_pFormat->flags & AVFMT_NOFILE))
+ avio_close(g_pContainer->pb);
+
+ // free the stream
+ av_free(g_pContainer);
+}
--- a/hedgewars/hwengine.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/hwengine.pas Thu Jun 07 17:42:32 2012 +0400
@@ -31,7 +31,7 @@
uses SDLh, uMisc, uConsole, uGame, uConsts, uLand, uAmmos, uVisualGears, uGears, uStore, uWorld, uInputHandler, uSound,
uScript, uTeams, uStats, uIO, uLocale, uChat, uAI, uAIMisc, uRandom, uLandTexture, uCollisions,
- SysUtils, uTypes, uVariables, uCommands, uUtils, uCaptions, uDebug, uCommandHandlers, uLandPainted
+ SysUtils, uTypes, uVariables, uCommands, uUtils, uCaptions, uDebug, uCommandHandlers, uLandPainted, uVideoRec
{$IFDEF SDL13}, uTouch{$ENDIF}{$IFDEF ANDROID}, GLUnit{$ENDIF};
{$IFDEF HWLIBRARY}
@@ -101,6 +101,9 @@
SwapBuffers;
+ if flagPrerecording then
+ SaveCameraPosition;
+
if flagMakeCapture then
begin
flagMakeCapture:= false;
@@ -261,6 +264,32 @@
end;
end;
+////////////////
+procedure RecorderMainLoop;
+var CurrTime, PrevTime: LongInt;
+begin
+ if not BeginVideoRecording() then
+ exit;
+ DoTimer(0); // gsLandGen -> gsStart
+ DoTimer(0); // gsStart -> gsGame
+
+ CurrTime:= LoadNextCameraPosition();
+ fastScrolling:= true;
+ DoTimer(CurrTime);
+ fastScrolling:= false;
+ while true do
+ begin
+ EncodeFrame();
+ PrevTime:= CurrTime;
+ CurrTime:= LoadNextCameraPosition();
+ if CurrTime = -1 then
+ break;
+ DoTimer(CurrTime - PrevTime);
+ IPCCheckSock();
+ end;
+ StopVideoRecording();
+end;
+
///////////////
procedure Game{$IFDEF HWLIBRARY}(gameArgs: PPChar); cdecl; export{$ENDIF};
var p: TPathType;
@@ -327,11 +356,16 @@
SDLTry(TTF_Init() <> -1, true);
WriteLnToConsole(msgOK);
- // show main window
- if cFullScreen then
- ParseCommand('fullscr 1', true)
+ if GameType = gmtRecord then
+ InitOffscreenOpenGL()
else
- ParseCommand('fullscr 0', true);
+ begin
+ // show main window
+ if cFullScreen then
+ ParseCommand('fullscr 1', true)
+ else
+ ParseCommand('fullscr 0', true);
+ end;
ControllerInit(); // has to happen before InitKbdKeyTable to map keys
InitKbdKeyTable();
@@ -368,12 +402,20 @@
InitTeams();
AssignStores();
+
+ if GameType = gmtRecord then
+ SetSound(false);
+
InitSound();
isDeveloperMode:= false;
TryDo(InitStepsFlags = cifAllInited, 'Some parameters not set (flags = ' + inttostr(InitStepsFlags) + ')', true);
ParseCommand('rotmask', true);
- MainLoop();
+
+ if GameType = gmtRecord then
+ RecorderMainLoop()
+ else
+ MainLoop();
// clean up all the memory allocated
freeEverything(true);
@@ -456,6 +498,7 @@
//uAIAmmoTests does not need to be freed
//uAIActions does not need to be freed
uStore.freeModule;
+ uVideoRec.freeModule;
end;
uIO.freeModule;
@@ -530,7 +573,7 @@
if (ParamCount = 3) and ((ParamStr(3) = '--stats-only') or (ParamStr(3) = 'landpreview')) then
internalSetGameTypeLandPreviewFromParameters()
else
- if (ParamCount = cDefaultParamNum) then
+ if (ParamCount = cDefaultParamNum) or (ParamCount = cDefaultParamNum+1) then
internalStartGameWithParameters()
else
playReplayFileWithParameters();
--- a/hedgewars/uCommandHandlers.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uCommandHandlers.pas Thu Jun 07 17:42:32 2012 +0400
@@ -26,7 +26,7 @@
procedure freeModule;
implementation
-uses uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uRandom, uCaptions;
+uses SysUtils, uCommands, uTypes, uVariables, uIO, uDebug, uConsts, uScript, uUtils, SDLh, uRandom, uCaptions, uVideoRec;
var prevGState: TGameState = gsConfirm;
@@ -529,6 +529,15 @@
flagMakeCapture:= true
end;
+procedure chRecord(var s: shortstring);
+begin
+s:= s; // avoid compiler hint
+if flagPrerecording then
+ StopPreRecording
+else
+ BeginPreRecording(FormatDateTime('YYYY-MM-DD_HH-mm-ss', Now()));
+end;
+
procedure chSetMap(var s: shortstring);
begin
if isDeveloperMode then
@@ -864,6 +873,7 @@
RegisterVariable('-cur_l' , @chCurL_m , true );
RegisterVariable('+cur_r' , @chCurR_p , true );
RegisterVariable('-cur_r' , @chCurR_m , true );
+ RegisterVariable('record' , @chRecord , true );
end;
procedure freeModule;
--- a/hedgewars/uGame.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uGame.pas Thu Jun 07 17:42:32 2012 +0400
@@ -39,17 +39,20 @@
isInLag:= false;
SendKeepAliveMessage(Lag)
end;
-if Lag > 100 then
- Lag:= 100
-else if (GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) then
- Lag:= 2500;
+if GameType <> gmtRecord then
+ begin
+ if Lag > 100 then
+ Lag:= 100
+ else if (GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) then
+ Lag:= 2500;
-if (GameType = gmtDemo) then
- if isSpeed then
- Lag:= Lag * 10
- else
- if cOnlyStats then
- Lag:= High(LongInt);
+ if (GameType = gmtDemo) then
+ if isSpeed then
+ Lag:= Lag * 10
+ else
+ if cOnlyStats then
+ Lag:= High(LongInt);
+ end;
PlayNextVoice;
i:= 1;
while (GameState <> gsExit) and (i <= Lag) do
--- a/hedgewars/uGearsHedgehog.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uGearsHedgehog.pas Thu Jun 07 17:42:32 2012 +0400
@@ -601,7 +601,7 @@
if (not (HH^.Hedgehog^.Team^.ExtDriven
or (HH^.Hedgehog^.BotLevel > 0)))
or (HH^.Hedgehog^.Team^.Clan^.ClanIndex = LocalClan)
- or (GameType = gmtDemo) then
+ or (GameType in [gmtDemo, gmtRecord]) then
begin
s:= trammo[Ammoz[a].NameId] + ' (+' + IntToStr(Ammoz[a].NumberInCase) + ')';
AddCaption(s, HH^.Hedgehog^.Team^.Clan^.Color, capgrpAmmoinfo);
--- a/hedgewars/uIO.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uIO.pas Thu Jun 07 17:42:32 2012 +0400
@@ -126,6 +126,7 @@
'D': GameType:= gmtDemo;
'N': GameType:= gmtNet;
'S': GameType:= gmtSave;
+ 'V': GameType:= gmtRecord;
else OutError(errmsgIncorrectUse + ' IPC "T" :' + s[2], true) end;
else
loTicks:= SDLNet_Read16(@s[byte(s[0]) - 1]);
--- a/hedgewars/uInputHandler.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uInputHandler.pas Thu Jun 07 17:42:32 2012 +0400
@@ -237,6 +237,7 @@
DefaultBinds[KeyNameToCode(_S'0')]:= '+volup';
DefaultBinds[KeyNameToCode(_S'9')]:= '+voldown';
DefaultBinds[KeyNameToCode(_S'c')]:= 'capture';
+DefaultBinds[KeyNameToCode(_S'r')]:= 'record';
DefaultBinds[KeyNameToCode(_S'h')]:= 'findhh';
DefaultBinds[KeyNameToCode(_S'p')]:= 'pause';
DefaultBinds[KeyNameToCode(_S's')]:= '+speedup';
--- a/hedgewars/uStore.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uStore.pas Thu Jun 07 17:42:32 2012 +0400
@@ -40,13 +40,14 @@
procedure ShowWeaponTooltip(x, y: LongInt);
procedure FreeWeaponTooltip;
procedure MakeCrossHairs;
+procedure InitOffscreenOpenGL;
procedure WarpMouse(x, y: Word); inline;
procedure SwapBuffers; inline;
implementation
uses uMisc, uConsole, uMobile, uVariables, uUtils, uTextures, uRender, uRenderUtils, uCommands,
- uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF};
+ uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF}, glut;
//type TGPUVendor = (gvUnknown, gvNVIDIA, gvATI, gvIntel, gvApple);
@@ -438,6 +439,29 @@
IMG_Quit();
end;
+procedure CreateFramebuffer(var frame, depth, tex: GLuint);
+begin
+ glGenFramebuffersEXT(1, @frame);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame);
+ glGenRenderbuffersEXT(1, @depth);
+ glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth);
+ glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight);
+ glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth);
+ glGenTextures(1, @tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);
+end;
+
+procedure DeleteFramebuffer(var frame, depth, tex: GLuint);
+begin
+ glDeleteTextures(1, @tex);
+ glDeleteRenderbuffersEXT(1, @depth);
+ glDeleteFramebuffersEXT(1, @frame);
+end;
+
procedure StoreRelease(reload: boolean);
var ii: TSprite;
ai: TAmmoType;
@@ -511,15 +535,13 @@
end;
end;
end;
+ if defaultFrame <> 0 then
+ DeleteFramebuffer(defaultFrame, depthv, texv);
{$IFNDEF S3D_DISABLED}
if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then
begin
- glDeleteTextures(1, @texl);
- glDeleteRenderbuffersEXT(1, @depthl);
- glDeleteFramebuffersEXT(1, @framel);
- glDeleteTextures(1, @texr);
- glDeleteRenderbuffersEXT(1, @depthr);
- glDeleteFramebuffersEXT(1, @framer)
+ DeleteFramebuffer(framel, depthl, texl);
+ DeleteFramebuffer(framer, depthr, texr);
end
{$ENDIF}
end;
@@ -628,6 +650,7 @@
procedure SetupOpenGL;
//var vendor: shortstring = '';
var buf: array[byte] of char;
+ AuxBufNum: LongInt;
begin
buf[0]:= char(0); // avoid compiler hint
AddFileLog('Setting up OpenGL (using driver: ' + shortstring(SDL_VideoDriverName(buf, sizeof(buf))) + ')');
@@ -673,51 +696,53 @@
{$ENDIF}
//SupportNPOTT:= glLoadExtension('GL_ARB_texture_non_power_of_two');
*)
+ glGetIntegerv(GL_AUX_BUFFERS, @AuxBufNum);
// everyone love debugging
AddFileLog('OpenGL-- Renderer: ' + shortstring(pchar(glGetString(GL_RENDERER))));
AddFileLog(' |----- Vendor: ' + shortstring(pchar(glGetString(GL_VENDOR))));
AddFileLog(' |----- Version: ' + shortstring(pchar(glGetString(GL_VERSION))));
AddFileLog(' |----- Texture Size: ' + inttostr(MaxTextureSize));
- AddFileLog(' \----- Extensions: ' + shortstring(pchar(glGetString(GL_EXTENSIONS))));
+ AddFileLog(' |----- Number of auxilary buffers: ' + inttostr(AuxBufNum));
+ AddFileLog(' \----- Extensions: ');
+ AddFileLogRaw(glGetString(GL_EXTENSIONS));
+ AddFileLog('');
//TODO: don't have the Extensions line trimmed but slipt it into multiple lines
+ defaultFrame:= 0;
+ if GameType = gmtRecord then
+ begin
+ if AuxBufNum > 0 then
+ begin
+ glDrawBuffer(GL_AUX0);
+ glReadBuffer(GL_AUX0);
+ AddFileLog('Using auxilary buffer for video recording.');
+ end
+ else if glLoadExtension('GL_EXT_framebuffer_object') then
+ begin
+ CreateFramebuffer(defaultFrame, depthv, texv);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame);
+ AddFileLog('Using framebuffer for video recording.');
+ end
+ else
+ begin
+ glDrawBuffer(GL_BACK);
+ glReadBuffer(GL_BACK);
+ AddFileLog('Warning: off-screen rendering is not supported; using back buffer but it may not work.');
+ end;
+ end;
+
{$IFNDEF S3D_DISABLED}
if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then
begin
// prepare left and right frame buffers and associated textures
if glLoadExtension('GL_EXT_framebuffer_object') then
begin
- // left
- glGenFramebuffersEXT(1, @framel);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framel);
- glGenRenderbuffersEXT(1, @depthl);
- glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthl);
- glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthl);
- glGenTextures(1, @texl);
- glBindTexture(GL_TEXTURE_2D, texl);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texl, 0);
-
- // right
- glGenFramebuffersEXT(1, @framer);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framer);
- glGenRenderbuffersEXT(1, @depthr);
- glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthr);
- glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, cScreenWidth, cScreenHeight);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthr);
- glGenTextures(1, @texr);
- glBindTexture(GL_TEXTURE_2D, texr);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cScreenWidth, cScreenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texr, 0);
+ CreateFramebuffer(framel, depthl, texl);
+ CreateFramebuffer(framer, depthr, texr);
// reset
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame)
end
else
cStereoMode:= smNone;
@@ -991,6 +1016,19 @@
WeaponTooltipTex:= nil
end;
+procedure InitOffscreenOpenGL;
+var ArgCount: LongInt;
+ PrgName: pchar;
+begin
+ ArgCount:= 1;
+ PrgName:= 'hwengine';
+ glutInit(@ArgCount, @PrgName);
+ glutInitWindowSize(cScreenWidth, cScreenHeight);
+ glutCreateWindow('You don''t see this'); // we don't need a window, but if this function is not called then OpenGL will not be initialized
+ glutHideWindow();
+ SetupOpenGL();
+end;
+
procedure chFullScr(var s: shortstring);
var flags: Longword = 0;
reinit: boolean = false;
@@ -1171,6 +1209,8 @@
procedure SwapBuffers; inline;
begin
+ if GameType = gmtRecord then
+ exit;
{$IFDEF SDL13}
SDL_GL_SwapWindow(SDLwindow);
{$ELSE}
--- a/hedgewars/uTeams.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uTeams.pas Thu Jun 07 17:42:32 2012 +0400
@@ -530,7 +530,7 @@
AddTeam(Color);
CurrentTeam^.TeamName:= ts;
CurrentTeam^.PlayerHash:= s;
- if GameType in [gmtDemo, gmtSave] then
+ if GameType in [gmtDemo, gmtSave, gmtRecord] then
CurrentTeam^.ExtDriven:= true;
CurrentTeam^.voicepack:= AskForVoicepack('Default')
--- a/hedgewars/uTypes.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uTypes.pas Thu Jun 07 17:42:32 2012 +0400
@@ -39,7 +39,7 @@
TGameState = (gsLandGen, gsStart, gsGame, gsChat, gsConfirm, gsExit, gsSuspend);
// Game types that help determining what the engine is actually supposed to do
- TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtSyntax);
+ TGameType = (gmtLocal, gmtDemo, gmtNet, gmtSave, gmtLandPreview, gmtSyntax, gmtRecord);
// Different files are stored in different folders, this enumeration is used to tell which folder to use
TPathType = (ptNone, ptData, ptGraphics, ptThemes, ptCurrTheme, ptTeams, ptMaps,
--- a/hedgewars/uUtils.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uUtils.pas Thu Jun 07 17:42:32 2012 +0400
@@ -61,6 +61,7 @@
function CheckCJKFont(s: ansistring; font: THWFont): THWFont;
procedure AddFileLog(s: shortstring);
+procedure AddFileLogRaw(s: pchar); cdecl;
function CheckNoTeamOrHH: boolean; inline;
@@ -81,6 +82,7 @@
{$IFDEF DEBUGFILE}
var f: textfile;
+ logMutex: TRTLCriticalSection; // mutex for debug file
{$ENDIF}
var CharArray: array[byte] of Char;
@@ -303,11 +305,23 @@
begin
s:= s;
{$IFDEF DEBUGFILE}
+EnterCriticalSection(logMutex);
writeln(f, inttostr(GameTicks) + ': ' + s);
-flush(f)
+flush(f);
+LeaveCriticalSection(logMutex);
{$ENDIF}
end;
+procedure AddFileLogRaw(s: pchar); cdecl;
+begin
+s:= s;
+{$IFDEF DEBUGFILE}
+EnterCriticalSection(logMutex);
+write(f, s);
+flush(f);
+LeaveCriticalSection(logMutex);
+{$ENDIF}
+end;
function CheckCJKFont(s: ansistring; font: THWFont): THWFont;
var l, i : LongInt;
@@ -400,6 +414,7 @@
logfileBase:= 'game'
else
logfileBase:= 'preview';
+ InitCriticalSection(logMutex);
{$I-}
{$IFDEF MOBILE}
{$IFDEF IPHONEOS} Assign(f,'../Documents/hw-' + logfileBase + '.log'); {$ENDIF}
@@ -436,6 +451,7 @@
writeln(f, 'halt at ' + inttostr(GameTicks) + ' ticks. TurnTimeLeft = ' + inttostr(TurnTimeLeft));
flush(f);
close(f);
+ DoneCriticalSection(logMutex);
{$ENDIF}
end;
--- a/hedgewars/uVariables.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uVariables.pas Thu Jun 07 17:42:32 2012 +0400
@@ -52,6 +52,7 @@
cReadyDelay : Longword = 5000;
cStereoMode : TStereoMode = smNone;
cOnlyStats : boolean = False;
+ cRecPrefix : shortstring = '';
//////////////////////////
cMapName : shortstring = '';
@@ -62,6 +63,7 @@
isSpeed : boolean;
fastUntilLag : boolean;
+ fastScrolling : boolean;
autoCameraOn : boolean;
GameTicks : LongWord;
@@ -2437,6 +2439,10 @@
framel, framer, depthl, depthr: GLuint;
texl, texr: GLuint;
+ // video recorder framebuffer and texture
+ defaultFrame, depthv: GLuint;
+ texv: GLuint;
+
VisualGearLayers: array[0..6] of PVisualGear;
lastVisualGearByUID: PVisualGear;
vobFrameTicks, vobFramesCount, vobCount: Longword;
@@ -2561,7 +2567,7 @@
cExplosives := 2;
GameState := Low(TGameState);
- GameType := gmtLocal;
+// GameType := gmtLocal;
zoom := cDefaultZoomLevel;
ZoomValue := cDefaultZoomLevel;
WeaponTooltipTex:= nil;
@@ -2577,6 +2583,7 @@
isInMultiShoot := false;
isSpeed := false;
fastUntilLag := false;
+ fastScrolling := false;
autoCameraOn := true;
cScriptName := '';
cSeed := '';
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uVideoRec.pas Thu Jun 07 17:42:32 2012 +0400
@@ -0,0 +1,272 @@
+(*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2012 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
+ * the Free Software Foundation; version 2 of the License
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *)
+
+{$INCLUDE "options.inc"}
+
+unit uVideoRec;
+
+{$IFDEF UNIX}
+ {$LINKLIB avwrapper}
+ {$LINKLIB avutil}
+ {$LINKLIB avcodec}
+ {$LINKLIB avformat}
+{$ENDIF}
+
+interface
+
+var flagPrerecording: boolean = false;
+
+function BeginVideoRecording: Boolean;
+function LoadNextCameraPosition: LongInt;
+procedure EncodeFrame;
+procedure StopVideoRecording;
+
+function BeginPreRecording(filePrefix: shortstring): Boolean;
+procedure StopPreRecording;
+procedure SaveCameraPosition;
+
+procedure freeModule;
+
+implementation
+
+uses uVariables, uUtils, GLunit, SDLh, SysUtils;
+
+{$IFDEF WIN32}
+const AVWrapperLibName = 'libavwrapper.dll';
+{$ENDIF}
+
+type TAddFileLogRaw = procedure (s: pchar); cdecl;
+
+{$IFDEF WIN32}
+procedure AVWrapper_Init(AddLog: TAddFileLogRaw; filename, soundFile: PChar; width, height, framerate, frequency, channels: LongInt); cdecl; external AVWrapperLibName;
+procedure AVWrapper_Close; cdecl; external AVWrapperLibName;
+procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external AVWrapperLibName;
+{$ELSE}
+procedure AVWrapper_Init(AddLog: TAddFileLogRaw; filename, soundFile: PChar; width, height, framerate, frequency, channels: LongInt); cdecl; external;
+procedure AVWrapper_Close; cdecl; external;
+procedure AVWrapper_WriteFrame( pY, pCb, pCr: PByte ); cdecl; external;
+{$ENDIF}
+
+var YCbCr_Planes: array[0..2] of PByte;
+ RGB_Buffer: PByte;
+
+ frequency, channels: LongInt;
+
+ cameraFile: TextFile;
+ audioFile: File;
+
+ numPixels: LongInt;
+
+ framerate: Int64 = 30;
+ firstTick, nframes: Int64;
+
+ cameraFilePath, soundFilePath: shortstring;
+
+function BeginVideoRecording: Boolean;
+var filename: shortstring;
+begin
+ AddFileLog('BeginVideoRecording');
+
+ numPixels:= cScreenWidth*cScreenHeight;
+
+{$IOCHECKS OFF}
+ // open file with prerecorded camera positions
+ cameraFilePath:= UserPathPrefix + '/Videos/' + cRecPrefix + '.txtin';
+ Assign(cameraFile, cameraFilePath);
+ Reset(cameraFile);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not read from ' + cameraFilePath);
+ exit(false);
+ end;
+
+ ReadLn(cameraFile, frequency, channels);
+{$IOCHECKS ON}
+
+ filename:= UserPathPrefix + '/Videos/' + cRecPrefix + '.mp4' + #0;
+ soundFilePath:= UserPathPrefix + '/Videos/' + cRecPrefix + '.hwsound' + #0;
+ AVWrapper_Init(@AddFileLogRaw, @filename[1], @soundFilePath[1], cScreenWidth, cScreenHeight, framerate, frequency, channels);
+
+ 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
+ begin
+ AddFileLog('Error: Could not allocate memory for video recording (RGB buffer).');
+ exit(false);
+ end;
+
+ BeginVideoRecording:= true;
+end;
+
+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();
+ DeleteFile(cameraFilePath);
+ DeleteFile(soundFilePath);
+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;
+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
+ 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));
+ end;
+
+ AVWrapper_WriteFrame(YCbCr_Planes[0], YCbCr_Planes[1], YCbCr_Planes[2]);
+end;
+
+function LoadNextCameraPosition: LongInt;
+var NextTime: LongInt;
+ NextZoom: LongInt;
+ NextWorldDx, NextWorldDy: LongInt;
+begin
+{$IOCHECKS OFF}
+ if eof(cameraFile) then
+ exit(-1);
+ ReadLn(cameraFile, NextTime, NextWorldDx, NextWorldDy, NextZoom);
+{$IOCHECKS ON}
+ if NextTime = 0 then
+ exit(-1);
+ WorldDx:= NextWorldDx;
+ WorldDy:= NextWorldDy;
+ zoom:= NextZoom/10000;
+ ZoomValue:= NextZoom/10000;
+ LoadNextCameraPosition:= NextTime;
+end;
+
+// this procedure may be called from different thread
+procedure RecordPostMix(udata: pointer; stream: PByte; len: LongInt); cdecl;
+begin
+ udata:= udata;
+{$IOCHECKS OFF}
+ BlockWrite(audioFile, stream^, len);
+{$IOCHECKS ON}
+end;
+
+function BeginPreRecording(filePrefix: shortstring): Boolean;
+var format: word;
+ filename: shortstring;
+begin
+ AddFileLog('BeginPreRecording');
+
+ nframes:= 0;
+ firstTick:= SDL_GetTicks();
+
+ Mix_QuerySpec(@frequency, @format, @channels);
+ if format <> $8010 then
+ begin
+ // TODO: support any audio format
+ AddFileLog('Error: Unexpected audio format ' + IntToStr(format));
+ exit(false);
+ end;
+
+{$IOCHECKS OFF}
+ filename:= UserPathPrefix + '/Videos/' + filePrefix + '.hwsound';
+ Assign(audioFile, filename);
+ Rewrite(audioFile, 1);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not write to ' + filename);
+ exit(false);
+ end;
+
+ filename:= UserPathPrefix + '/Videos/' + filePrefix + '.txtout';
+ Assign(cameraFile, filename);
+ Rewrite(cameraFile);
+ if IOResult <> 0 then
+ begin
+ AddFileLog('Error: Could not write to ' + filename);
+ exit(false);
+ end;
+{$IOCHECKS ON}
+ WriteLn(cameraFile, inttostr(frequency) + ' ' + inttostr(channels));
+
+ // register callback for actual audio recording
+ Mix_SetPostMix(@RecordPostMix, nil);
+
+ flagPrerecording:= true;
+ BeginPreRecording:= true;
+end;
+
+procedure StopPreRecording;
+begin
+ AddFileLog('StopPreRecording');
+ flagPrerecording:= false;
+
+ // call SDL_LockAudio because RecordPostMix may be executing right now
+ SDL_LockAudio();
+ Close(audioFile);
+ Close(cameraFile);
+ Mix_SetPostMix(nil, nil);
+ SDL_UnlockAudio();
+end;
+
+procedure SaveCameraPosition;
+var Ticks: LongInt;
+begin
+ Ticks:= SDL_GetTicks();
+ while (Ticks - firstTick)*framerate > nframes*1000 do
+ begin
+ WriteLn(cameraFile, inttostr(GameTicks) + ' ' + inttostr(WorldDx) + ' ' + inttostr(WorldDy) + ' ' + inttostr(Round(zoom*10000)));
+ inc(nframes);
+ end;
+end;
+
+procedure freeModule;
+begin
+ if flagPrerecording then
+ StopPreRecording();
+end;
+
+end.
--- a/hedgewars/uVisualGears.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uVisualGears.pas Thu Jun 07 17:42:32 2012 +0400
@@ -135,7 +135,7 @@
sp: real;
begin
AddVisualGear:= nil;
-if ((GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet))) and // we are scrolling now
+if ((GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) or fastScrolling) and // we are scrolling now
((Kind <> vgtCloud) and (not Critical)) then
exit;
--- a/hedgewars/uWorld.pas Thu Jun 07 01:28:39 2012 +0200
+++ b/hedgewars/uWorld.pas Thu Jun 07 17:42:32 2012 +0400
@@ -60,7 +60,8 @@
uCaptions,
uCursor,
uCommands,
- uMobile
+ uMobile,
+ uVideoRec
;
var cWaveWidth, cWaveHeight: LongInt;
@@ -80,6 +81,7 @@
stereoDepth: GLfloat;
isFirstFrame: boolean;
AMAnimType: LongInt;
+ recTexture: PTexture;
const cStereo_Sky = 0.0500;
cStereo_Horizon = 0.0250;
@@ -381,6 +383,8 @@
timeTexture:= nil;
FreeTexture(missionTex);
missionTex:= nil;
+ FreeTexture(recTexture);
+ recTexture:= nil;
end;
function GetAmmoMenuTexture(Ammo: PHHAmmo): PTexture;
@@ -958,7 +962,7 @@
//glPushMatrix;
//glScalef(1.0, 1.0, 1.0);
- if not isPaused then
+ if (not isPaused) and (GameType <> gmtRecord) then
MoveCamera;
if cStereoMode = smNone then
@@ -989,7 +993,7 @@
DrawWorldStereo(0, rmRightEye);
// detatch drawing from fbs
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, defaultFrame);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
SetScale(cDefaultZoomLevel);
@@ -1570,6 +1574,31 @@
end
end;
+// rec
+if flagPrerecording then
+ begin
+ if recTexture = nil then
+ begin
+ s:= 'rec';
+ tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fntBig].Handle, Str2PChar(s), cWhiteColorChannels);
+ tmpSurface:= doSurfaceConversion(tmpSurface);
+ FreeTexture(recTexture);
+ recTexture:= Surface2Tex(tmpSurface, false);
+ SDL_FreeSurface(tmpSurface)
+ end;
+ DrawTexture( -(cScreenWidth shr 1) + 50, 20, recTexture);
+
+ // draw red circle
+ glDisable(GL_TEXTURE_2D);
+ Tint($FF, $00, $00, Byte(Round(127*(1 + sin(SDL_GetTicks()*0.007)))));
+ glBegin(GL_POLYGON);
+ for i:= 0 to 20 do
+ glVertex2f(-(cScreenWidth shr 1) + 30 + sin(i*2*Pi/20)*10, 35 + cos(i*2*Pi/20)*10);
+ glEnd();
+ Tint($FF, $FF, $FF, $FF);
+ glEnable(GL_TEXTURE_2D);
+ end;
+
SetScale(zoom);
// Cursor
@@ -1752,8 +1781,12 @@
if (not cHasFocus) and (GameState <> gsConfirm) then
ParseCommand('quit', true);
-if not cHasFocus then DampenAudio()
-else UndampenAudio();
+// do not change volume during prerecording as it will affect sound in video file
+if not flagPrerecording then
+ begin
+ if not cHasFocus then DampenAudio()
+ else UndampenAudio();
+ end;
end;
procedure SetUtilityWidgetState(ammoType: TAmmoType);
@@ -1810,6 +1843,7 @@
procedure initModule;
begin
fpsTexture:= nil;
+ recTexture:= nil;
FollowGear:= nil;
WindBarWidth:= 0;
bShowAmmoMenu:= false;
@@ -1840,7 +1874,9 @@
FreeTexture(timeTexture);
timeTexture:= nil;
FreeTexture(missionTex);
- missionTex:= nil
+ missionTex:= nil;
+ FreeTexture(recTexture);
+ recTexture:= nil;
end;
end.
--- a/project_files/hedgewars.pro Thu Jun 07 01:28:39 2012 +0200
+++ b/project_files/hedgewars.pro Thu Jun 07 17:42:32 2012 +0400
@@ -104,7 +104,8 @@
../QTfrontend/ui/dialog/input_password.h \
../QTfrontend/ui/widget/colorwidget.h \
../QTfrontend/model/HatModel.h \
- ../QTfrontend/model/GameStyleModel.h
+ ../QTfrontend/model/GameStyleModel.h \
+ ../QTfrontend/recorder.h
SOURCES += ../QTfrontend/model/ammoSchemeModel.cpp \
../QTfrontend/model/MapModel.cpp \
@@ -186,7 +187,8 @@
../QTfrontend/ui/dialog/input_password.cpp \
../QTfrontend/ui/widget/colorwidget.cpp \
../QTfrontend/model/HatModel.cpp \
- ../QTfrontend/model/GameStyleModel.cpp
+ ../QTfrontend/model/GameStyleModel.cpp \
+ ../QTfrontend/recorder.cpp
win32 {
SOURCES += ../QTfrontend/xfire.cpp