# HG changeset patch # User Stepan777 # Date 1339076552 -14400 # Node ID d8e68cbca7ee4e7e651778db0ba51a0405352b7c # Parent 53ffc88530083c8ffee50724a4b73527ccda2ca8# Parent e6c379b486d5b01d691136f9e9562ae9ae1ded81 merge diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/binds.cpp --- 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:")} }; diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/binds.h --- 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 -#define BINDS_NUMBER 44 +#define BINDS_NUMBER 45 struct BindAction { diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/game.cpp --- 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); } diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/game.h --- 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: diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/hwform.cpp --- 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) diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/hwform.h --- 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); diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/main.cpp --- 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()); diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/net/recorder.cpp --- /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 + * + * 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 +#include + +#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; +} diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/net/recorder.h --- /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 + * + * 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 +#include + +#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 diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/net/tcpBase.cpp --- 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 srvsList; -QPointer TCPBase::IPCServer(0); +//QList srvsList; +//QPointer 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() { diff -r e6c379b486d5 -r d8e68cbca7ee QTfrontend/net/tcpBase.h --- 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 IPCServer; + /*static*/ QPointer IPCServer; bool m_isDemoMode; - void RealStart(); QPointer IPCSocket; private slots: @@ -75,7 +75,7 @@ void ClientRead(); void StartProcessError(QProcess::ProcessError error); - void tcpServerReady(); + // void tcpServerReady(); }; #endif // _TCPBASE_INCLUDED diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/ArgParsers.inc --- 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); diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/CMakeLists.txt --- 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() diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/SDLh.pas --- 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} diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/avwrapper.c --- /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 +#include +#include +#include +#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); +} diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/hwengine.pas --- 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(); diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uCommandHandlers.pas --- 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; diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uGame.pas --- 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 diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uGearsHedgehog.pas --- 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); diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uIO.pas --- 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]); diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uInputHandler.pas --- 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'; diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uStore.pas --- 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} diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uTeams.pas --- 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') diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uTypes.pas --- 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, diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uUtils.pas --- 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; diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uVariables.pas --- 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 := ''; diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uVideoRec.pas --- /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 + * + * 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. diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uVisualGears.pas --- 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; diff -r e6c379b486d5 -r d8e68cbca7ee hedgewars/uWorld.pas --- 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. diff -r e6c379b486d5 -r d8e68cbca7ee project_files/hedgewars.pro --- 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