Merge branch 'avi_writer'

This commit is contained in:
Benau 2017-03-22 09:14:35 +08:00
commit 58c665efc6
10 changed files with 1003 additions and 27 deletions

View File

@ -107,14 +107,20 @@ if((WIN32 AND NOT MINGW) OR APPLE)
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/libpng")
include_directories("${PROJECT_SOURCE_DIR}/lib/libpng")
#build jpeg library
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/jpeglib")
include_directories("${PROJECT_SOURCE_DIR}/lib/jpeglib")
# Add jpeg-turbo library
if (APPLE)
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/jpeglib")
include_directories("${PROJECT_SOURCE_DIR}/lib/jpeglib")
set(JPEG_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lib/jpeglib/")
set(JPEG_LIBRARY jpeglib)
else()
find_library(JPEG_LIBRARY NAMES libjpeg-62 PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
find_path(JPEG_INCLUDE_DIRS NAMES jpeglib.h PATHS "${PROJECT_SOURCE_DIR}/dependencies/include")
set(JPEG_LIBRARIES ${JPEG_LIBRARY})
endif()
set(PNG_PNG_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lib/libpng/")
set(PNG_LIBRARY png15_static)
set(JPEG_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lib/jpeglib/")
set(JPEG_LIBRARY jpeglib)
endif()
if(NOT SERVER_ONLY AND NOT USE_GLES2)
@ -414,6 +420,10 @@ if(USE_FRIBIDI)
add_definitions(-DENABLE_BIDI)
endif()
if(MSVC)
target_link_libraries(supertuxkart ${JPEG_LIBRARIES})
endif()
# Wiiuse
# ------
if(USE_WIIUSE)

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -558,6 +558,24 @@ namespace UserConfigParams
&m_video_group, "Generate mipmap for textures using "
"high quality method with SSE"));
PARAM_PREFIX BoolUserConfigParam m_record_bmp
PARAM_DEFAULT(BoolUserConfigParam(false, "record_bmp",
&m_video_group, "Record video using uncompressed bitmap, notice: this "
"will require a lot of space and fast disk access."));
PARAM_PREFIX BoolUserConfigParam m_limit_game_fps
PARAM_DEFAULT(BoolUserConfigParam(true, "limit_game_fps",
&m_video_group, "Limit game framerate not beyond the fps of recording "
"video."));
PARAM_PREFIX IntUserConfigParam m_record_compression
PARAM_DEFAULT(IntUserConfigParam(90, "record_compression",
&m_video_group, "Specify the compression level of recording video"));
PARAM_PREFIX IntUserConfigParam m_record_fps
PARAM_DEFAULT(IntUserConfigParam(30, "record_fps",
&m_video_group, "Specify the fps of recording video"));
// ---- Debug - not saved to config file
/** If gamepad debugging is enabled. */
PARAM_PREFIX bool m_unit_testing PARAM_DEFAULT(false);

View File

@ -61,6 +61,7 @@
#include "states_screens/dialogs/confirm_resolution_dialog.hpp"
#include "states_screens/state_manager.hpp"
#include "tracks/track_manager.hpp"
#include "utils/avi_writer.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
#include "utils/profiler.hpp"
@ -146,6 +147,7 @@ IrrDriver::IrrDriver()
m_last_light_bucket_distance = 0;
m_clear_color = video::SColor(255, 100, 101, 140);
m_skinning_joint = 0;
m_recording = false;
} // IrrDriver
@ -167,6 +169,9 @@ IrrDriver::~IrrDriver()
#endif
delete m_wind;
delete m_renderer;
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
AVIWriter::kill();
#endif
} // ~IrrDriver
// ----------------------------------------------------------------------------
@ -921,6 +926,9 @@ void IrrDriver::applyResolutionSettings()
// (we're sure to update main.cpp at some point and forget this one...)
VAOManager::getInstance()->kill();
STKTexManager::getInstance()->kill();
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
AVIWriter::kill();
#endif
// initDevice will drop the current device.
if (CVS->isGLSL())
{
@ -1885,8 +1893,38 @@ void IrrDriver::update(float dt)
// menu.
//if(World::getWorld() && World::getWorld()->isRacePhase())
// printRenderStats();
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
if (m_recording)
AVIWriter::getInstance()->captureFrameBufferImage(dt);
#endif
} // update
// ----------------------------------------------------------------------------
void IrrDriver::setRecording(bool val)
{
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
if (!CVS->isARBPixelBufferObjectUsable())
return;
if (m_recording == val)
return;
m_recording = val;
if (m_recording == true)
{
std::string track_name = World::getWorld() != NULL ?
race_manager->getTrackName() : "menu";
AVIWriter::getInstance()->setRecordingTarget(file_manager
->getScreenshotDir() + track_name);
AVIWriter::getInstance()->resetFrameBufferImage();
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video recording started."));
}
else
{
AVIWriter::getInstance()->stopRecording();
}
#endif
} // setRecording
// ----------------------------------------------------------------------------
void IrrDriver::requestScreenshot()
@ -2096,4 +2134,3 @@ GLuint IrrDriver::getDepthStencilTexture()
return m_renderer->getDepthStencilTexture();
} // getDepthStencilTexture

View File

@ -163,6 +163,7 @@ private:
bool m_lightviz;
bool m_distortviz;
bool m_boundingboxesviz;
bool m_recording;
/** Background colour to reset a buffer. Can be changed by each track. */
irr::video::SColor m_clear_color;
@ -414,6 +415,10 @@ public:
// ------------------------------------------------------------------------
bool getBoundingBoxesViz() { return m_boundingboxesviz; }
// ------------------------------------------------------------------------
bool isRecording() const { return m_recording; }
// ------------------------------------------------------------------------
void setRecording(bool val);
// ------------------------------------------------------------------------
u32 getRenderPass() { return m_renderpass; }
// ------------------------------------------------------------------------
std::vector<LightNode *> getLights() { return m_lights; }

View File

@ -24,6 +24,7 @@
#include "guiengine/engine.hpp"
#include "guiengine/scalable_font.hpp"
#include "guiengine/skin.hpp"
#include "utils/synchronised.hpp"
#include "IGUIElement.h"
@ -88,11 +89,10 @@ public:
} // operator ()
}; // operator()
// ============================================================================
/** List of all messages. */
std::priority_queue<Message*, std::vector<Message*>,
CompareMessages> g_all_messages;
Synchronised<std::priority_queue<Message*, std::vector<Message*>,
CompareMessages> > g_all_messages;
/** How long the current message has been displayed. The special value
* -1 indicates that a new message was added when the queue was empty. */
@ -130,9 +130,16 @@ void createLabel(const Message *message)
* position of the message. */
void updatePosition()
{
if (g_all_messages.empty()) return;
Message *last = g_all_messages.top();
g_all_messages.lock();
bool empty = g_all_messages.getData().empty();
if (empty)
{
g_all_messages.unlock();
return;
}
Message *last = g_all_messages.getData().top();
createLabel(last);
g_all_messages.unlock();
} // updatePosition
// ----------------------------------------------------------------------------
@ -143,13 +150,15 @@ void updatePosition()
void add(MessageType mt, const irr::core::stringw &message)
{
Message *m = new Message(mt, message);
if(g_all_messages.empty())
g_all_messages.lock();
if (g_all_messages.getData().empty())
{
// Indicate that there is a new message, which should
// which needs a new label etc. to be computed.
g_current_display_time =-1.0f;
}
g_all_messages.push(m);
g_all_messages.getData().push(m);
g_all_messages.unlock();
} // add
// ----------------------------------------------------------------------------
@ -161,32 +170,41 @@ void add(MessageType mt, const irr::core::stringw &message)
*/
void update(float dt)
{
if(g_all_messages.empty()) return;
g_all_messages.lock();
bool empty = g_all_messages.getData().empty();
g_all_messages.unlock();
if (empty) return;
g_all_messages.lock();
g_current_display_time += dt;
if(g_current_display_time > g_max_display_time)
if (g_current_display_time > g_max_display_time)
{
Message *last = g_all_messages.top();
g_all_messages.pop();
Message *last = g_all_messages.getData().top();
g_all_messages.getData().pop();
delete last;
if(g_all_messages.empty()) return;
if (g_all_messages.getData().empty())
{
g_all_messages.unlock();
return;
}
g_current_display_time = -1.0f;
}
Message *current = g_all_messages.getData().top();
// Create new data for the display.
if(g_current_display_time < 0)
if (g_current_display_time < 0)
{
createLabel(g_all_messages.top());
createLabel(current);
}
g_all_messages.unlock();
Message *current = g_all_messages.top();
GUIEngine::getSkin()->drawMessage(g_container, g_area,
current->getRenderType());
gui::ScalableFont *font = GUIEngine::getFont();
video::SColor color(255, 0, 0, 0);
font->draw(current->getMessage(), g_area, color, true, true);
} // update
} // namespace GUIEngine

View File

@ -117,7 +117,9 @@ float MainLoop::getLimitedDt()
// Throttle fps if more than maximum, which can reduce
// the noise the fan on a graphics card makes.
// When in menus, reduce FPS much, it's not necessary to push to the maximum for plain menus
const int max_fps = (StateManager::get()->throttleFPS() ? 30 : UserConfigParams::m_max_fps);
const int max_fps = (irr_driver->isRecording() &&
UserConfigParams::m_limit_game_fps ? UserConfigParams::m_record_fps :
StateManager::get()->throttleFPS() ? 30 : UserConfigParams::m_max_fps);
if (dt > 0)
{
const int current_fps = (int)(1000.0f / dt);

617
src/utils/avi_writer.cpp Normal file
View File

@ -0,0 +1,617 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Dawid Gan
//
// 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; either version 3
// of the License, or (at your option) any later version.
//
// 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.
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
#include "utils/avi_writer.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "guiengine/message_queue.hpp"
#include "utils/translation.hpp"
#include "utils/vs.hpp"
#include <jpeglib.h>
#include <cstring>
// ----------------------------------------------------------------------------
AVIWriter::AVIWriter() : m_idle(true)
{
resetFrameBufferImage();
resetCaptureFormat();
m_file = NULL;
m_last_junk_chunk = 0;
m_end_of_header = 0;
m_movi_start = 0;
m_stream_bytes = 0;
m_total_frames = 0;
m_chunk_fcc = 0;
m_width = irr_driver->getActualScreenSize().Width;
m_height = irr_driver->getActualScreenSize().Height;
glGenBuffers(3, m_pbo);
for (int i = 0; i < 3; i++)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[i]);
glBufferData(GL_PIXEL_PACK_BUFFER, m_width * m_height * 4, NULL,
GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
pthread_cond_init(&m_cond_request, NULL);
pthread_create(&m_thread, NULL, &startRoutine, this);
} // AVIWriter
// ----------------------------------------------------------------------------
AVIWriter::~AVIWriter()
{
glDeleteBuffers(3, m_pbo);
addFrameBufferImage(NULL, 0);
if (!waitForReadyToDeleted(2.0f))
Log::info("AVIWriter", "AVIWriter not stopping, exiting anyway.");
pthread_join(m_thread, NULL);
pthread_cond_destroy(&m_cond_request);
} // ~AVIWriter
// ----------------------------------------------------------------------------
void AVIWriter::resetFrameBufferImage()
{
m_pbo_use = 0;
m_accumulated_time = 0.0f;
m_remaining_time = 0.0f;
} // resetFrameBufferImage
// ----------------------------------------------------------------------------
void AVIWriter::resetCaptureFormat()
{
m_img_quality = UserConfigParams::m_record_compression;
m_msec_per_frame = unsigned(1000 / UserConfigParams::m_record_fps);
m_avi_format =
UserConfigParams::m_record_bmp ? AVI_FORMAT_BMP : AVI_FORMAT_JPG;
} // resetCaptureFormat
// ----------------------------------------------------------------------------
void* AVIWriter::startRoutine(void *obj)
{
VS::setThreadName("AVIWriter");
AVIWriter* avi_writer = (AVIWriter*)obj;
while (true)
{
avi_writer->m_fbi_queue.lock();
bool waiting = avi_writer->m_fbi_queue.getData().empty();
while (waiting)
{
pthread_cond_wait(&avi_writer->m_cond_request,
avi_writer->m_fbi_queue.getMutex());
waiting = avi_writer->m_fbi_queue.getData().empty();
}
auto& p = avi_writer->m_fbi_queue.getData().front();
uint8_t* fbi = p.first;
int frame_count = p.second;
if (frame_count == -1)
{
avi_writer->closeFile();
avi_writer->m_idle.setAtomic(true);
avi_writer->m_fbi_queue.getData().pop_front();
avi_writer->m_fbi_queue.unlock();
continue;
}
else if (fbi == NULL)
{
avi_writer->closeFile();
avi_writer->setCanBeDeleted();
avi_writer->m_fbi_queue.getData().pop_front();
avi_writer->m_fbi_queue.unlock();
return NULL;
}
const bool too_slow = avi_writer->m_fbi_queue.getData().size() > 50;
avi_writer->m_fbi_queue.getData().pop_front();
avi_writer->m_fbi_queue.unlock();
if (too_slow)
{
MessageQueue::add(MessageQueue::MT_ERROR,
_("Encoding is too slow, dropping frames."));
delete [] fbi;
avi_writer->cleanAllFrameBufferImages();
continue;
}
if (avi_writer->m_file == NULL)
{
bool ret = avi_writer->createFile();
if (!ret)
{
delete [] fbi;
avi_writer->cleanAllFrameBufferImages();
continue;
}
}
uint8_t* orig_fbi = fbi;
const unsigned width = avi_writer->m_width;
const unsigned height = avi_writer->m_height;
const unsigned area = width * height;
int size = area * 4;
int dest = size - 3;
int src = size - 4;
int copied = 0;
while (true)
{
if (copied++ > 1)
memcpy(fbi + dest, fbi + src, 3);
else
memmove(fbi + dest, fbi + src, 3);
if (src == 0)
break;
dest -= 3;
src -= 4;
}
fbi = fbi + area;
const int pitch = width * 3;
uint8_t* p2 = fbi + (height - 1) * pitch;
uint8_t* tmp_buf = new uint8_t[pitch];
for (unsigned i = 0; i < height; i += 2)
{
memcpy(tmp_buf, fbi, pitch);
memcpy(fbi, p2, pitch);
memcpy(p2, tmp_buf, pitch);
fbi += pitch;
p2 -= pitch;
}
delete [] tmp_buf;
size = area * 3;
if (avi_writer->m_avi_format == AVI_FORMAT_JPG)
{
uint8_t* jpg = new uint8_t[size];
size = avi_writer->bmpToJpg(orig_fbi + area, jpg, size);
delete [] orig_fbi;
orig_fbi = jpg;
}
while (frame_count != 0)
{
AVIErrCode code = avi_writer->addImage
(avi_writer->m_avi_format == AVI_FORMAT_JPG ? orig_fbi :
orig_fbi + area, size);
if (code == AVI_SIZE_LIMIT_ERR)
{
avi_writer->createFile();
continue;
}
else if (code == AVI_IO_ERR)
break;
frame_count--;
}
delete [] orig_fbi;
}
return NULL;
} // startRoutine
// ----------------------------------------------------------------------------
int AVIWriter::getFrameCount(float dt)
{
const float frame_rate = 0.001f * m_msec_per_frame;
m_accumulated_time += dt;
if (m_accumulated_time < frame_rate && m_remaining_time < frame_rate)
{
return 0;
}
int frame_count = 1;
m_remaining_time += m_accumulated_time - frame_rate;
m_accumulated_time = 0.0f;
while (m_remaining_time > frame_rate)
{
frame_count++;
m_remaining_time -= frame_rate;
}
return frame_count;
} // getFrameCount
// ----------------------------------------------------------------------------
void AVIWriter::captureFrameBufferImage(float dt)
{
glReadBuffer(GL_BACK);
int pbo_read = -1;
if (m_pbo_use > 3 && m_pbo_use % 3 == 0)
m_pbo_use = 3;
if (m_pbo_use >= 3)
{
int frame_count = getFrameCount(dt);
if (frame_count != 0)
{
pbo_read = m_pbo_use % 3;
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[pbo_read]);
void* ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
const unsigned size = m_width * m_height * 4;
uint8_t* fbi = new uint8_t[size];
memcpy(fbi, ptr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
addFrameBufferImage(fbi, frame_count);
}
}
int pbo_use = m_pbo_use++ % 3;
assert(pbo_read == -1 || pbo_use == pbo_read);
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[pbo_use]);
glReadPixels(0, 0, m_width, m_height,
m_avi_format == AVI_FORMAT_JPG ? GL_RGBA: GL_BGRA,
GL_UNSIGNED_BYTE, NULL);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
} // captureFrameBufferImage
// ----------------------------------------------------------------------------
bool AVIWriter::addJUNKChunk(std::string str, unsigned int min_size)
{
int size = str.size() < min_size ? min_size : str.size() + 1;
size = (size + 1) & 0xfffffffe;
CHUNK chunk;
chunk.fcc = FOURCC('J', 'U', 'N', 'K');
chunk.cb = size;
char* buffer = (char*)calloc(size, 1);
strcpy(buffer, str.c_str());
int num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
num = fwrite(buffer, 1, size * sizeof(char), m_file);
free(buffer);
if (num != size)
goto error;
m_last_junk_chunk = ftell(m_file);
if (m_last_junk_chunk < 0)
goto error;
return true;
error:
closeFile(true);
return false;
} // addJUNKChunk
// ----------------------------------------------------------------------------
AVIErrCode AVIWriter::addImage(unsigned char* buffer, int buf_size)
{
if (m_file == NULL)
goto error;
int num; num = ftell(m_file);
if (num < 0)
goto error;
if (m_total_frames >= MAX_FRAMES)
goto size_limit;
CHUNK chunk;
chunk.fcc = m_chunk_fcc;
chunk.cb = buf_size;
m_index_table[m_total_frames].Offset = num;
m_index_table[m_total_frames].Length = chunk.cb;
m_index_table[m_total_frames].fcc = chunk.fcc;
num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
num = fwrite(buffer, 1, buf_size, m_file);
if (num != buf_size)
goto error;
int fill_size; fill_size = (sizeof(chunk) + buf_size) & 0x00000001;
if (fill_size > 0)
{
uint32_t filler = 0;
num = fwrite(&filler, 1, fill_size, m_file);
if (num != fill_size)
goto error;
}
m_stream_bytes += sizeof(chunk) + buf_size + fill_size;
m_total_frames++;
num = ftell(m_file);
if (num < 0)
goto error;
if (((num - m_last_junk_chunk) > 20000) && (!addJUNKChunk("", 1)))
goto error;
// check if we reached the file size limit
if (num >= MAX_FILE_SIZE)
goto size_limit;
return AVI_SUCCESS;
error:
closeFile(true);
return AVI_IO_ERR;
size_limit:
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video exceeded size limit, starting a new one."));
closeFile();
return AVI_SIZE_LIMIT_ERR;
} // addImage
// ----------------------------------------------------------------------------
bool AVIWriter::closeFile(bool delete_file)
{
if (m_file == NULL)
return false;
if (delete_file)
goto error;
// add the index
int idx_start; idx_start = ftell(m_file);
if (idx_start < 0)
goto error;
CHUNK chunk;
chunk.fcc = FOURCC('i', 'd', 'x', '1');
chunk.cb = sizeof(AVIINDEXENTRY) * m_total_frames;
int num; num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
for (unsigned int i = 0; i < m_total_frames; i++)
{
AVIINDEXENTRY Index;
Index.ckid = m_index_table[i].fcc;
Index.dwFlags = AVIIF_KEYFRAME;
Index.dwChunkOffset = m_index_table[i].Offset;
Index.dwChunkLength = m_index_table[i].Length;
num = fwrite(&Index, 1, sizeof(Index), m_file);
if (num != sizeof(Index))
goto error;
}
// update the header
if (m_total_frames > 0 && m_msec_per_frame > 0)
{
num = fseek(m_file, 0, SEEK_END);
if (num < 0)
goto error;
int size; size = ftell(m_file);
if (size < 0)
goto error;
num = fseek(m_file, 0, SEEK_SET);
if (num < 0)
goto error;
m_avi_hdr.riff.cb = size - sizeof(m_avi_hdr.riff);
m_avi_hdr.avih.dwMaxBytesPerSec = (uint32_t)
(((m_stream_bytes / m_total_frames) * m_format_hdr.strh.dwRate) /
m_msec_per_frame + 0.5f);
m_avi_hdr.avih.dwTotalFrames = m_total_frames;
num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file);
if (num != sizeof(m_avi_hdr))
goto error;
m_format_hdr.strh.dwLength = m_total_frames;
num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file);
if (num != sizeof(m_format_hdr))
goto error;
}
// update the movi section
m_movi_chunk.cb = idx_start - m_movi_start;
num = fseek(m_file, m_movi_start - sizeof(m_movi_chunk), SEEK_SET);
if (num < 0)
goto error;
num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file);
if (num != sizeof(m_movi_chunk))
goto error;
fclose(m_file);
m_file = NULL;
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video saved in \"%s\".", m_filename.c_str()));
return true;
error:
MessageQueue::add(MessageQueue::MT_ERROR, _("Error when saving video."));
fclose(m_file);
remove(m_filename.c_str());
m_file = NULL;
return false;
} // closeFile
// ----------------------------------------------------------------------------
bool AVIWriter::createFile()
{
m_idle.setAtomic(false);
time_t rawtime;
time(&rawtime);
tm* timeInfo = localtime(&rawtime);
char time_buffer[256];
sprintf(time_buffer, "%i.%02i.%02i_%02i.%02i.%02i",
timeInfo->tm_year + 1900, timeInfo->tm_mon + 1,
timeInfo->tm_mday, timeInfo->tm_hour,
timeInfo->tm_min, timeInfo->tm_sec);
m_filename = m_recording_target.getAtomic() + "-" + time_buffer + ".avi";
m_stream_bytes = 0;
m_total_frames = 0;
m_movi_start = 0;
m_last_junk_chunk = 0;
BitmapInfoHeader bitmap_hdr;
bitmap_hdr.biSize = sizeof(BitmapInfoHeader);
bitmap_hdr.biWidth = m_width;
bitmap_hdr.biHeight = m_height;
bitmap_hdr.biPlanes = 1;
bitmap_hdr.biBitCount = 24;
bitmap_hdr.biCompression = 0;
bitmap_hdr.biSizeImage = (m_width * m_height * 3 * bitmap_hdr.biPlanes);
bitmap_hdr.biXPelsPerMeter = 0;
bitmap_hdr.biYPelsPerMeter = 0;
bitmap_hdr.biClrUsed = 0;
bitmap_hdr.biClrImportant = 0;
memset(&m_avi_hdr, '\0', sizeof(m_avi_hdr));
m_avi_hdr.riff.fcc = FOURCC('R', 'I', 'F', 'F');
m_avi_hdr.riff.cb = 0; // update when finished (size of the file - 8)
m_avi_hdr.avi = FOURCC('A', 'V', 'I', ' ');
m_avi_hdr.list1.fcc = FOURCC('L', 'I', 'S', 'T');
m_avi_hdr.list1.cb = 0;
m_avi_hdr.hdrl = FOURCC('h', 'd', 'r', 'l');
m_avi_hdr.avihhdr.fcc = FOURCC('a', 'v', 'i', 'h');
m_avi_hdr.avihhdr.cb = sizeof(m_avi_hdr.avih);
m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame * 1000;
m_avi_hdr.avih.dwMaxBytesPerSec = 0; // update when finished
m_avi_hdr.avih.dwPaddingGranularity = 0;
m_avi_hdr.avih.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX;
m_avi_hdr.avih.dwTotalFrames = 0; // update when finished
m_avi_hdr.avih.dwInitialFrames = 0;
m_avi_hdr.avih.dwStreams = 1; // 1 = video, 2 = video and audio
m_avi_hdr.avih.dwSuggestedBufferSize = 0; // can be just 0
m_avi_hdr.avih.dwWidth = m_width;
m_avi_hdr.avih.dwHeight = m_height;
m_format_hdr.list.fcc = FOURCC('L', 'I', 'S', 'T');
m_format_hdr.list.cb = (sizeof(m_format_hdr) - 8) +
sizeof(BitmapInfoHeader);
m_format_hdr.strl = FOURCC('s', 't', 'r', 'l');
m_format_hdr.strhhdr.fcc = FOURCC('s', 't', 'r', 'h');
m_format_hdr.strhhdr.cb = sizeof(m_format_hdr.strh);
m_format_hdr.strh.fccType = FOURCC('v', 'i', 'd', 's');
m_format_hdr.strh.fccHandler = CC_DIB;
m_format_hdr.strh.dwFlags = 0;
m_format_hdr.strh.wPriority = 0;
m_format_hdr.strh.wLanguage = 0;
m_format_hdr.strh.dwInitialFrames = 0;
m_format_hdr.strh.dwScale = m_msec_per_frame;
m_format_hdr.strh.dwRate = 1000;
m_format_hdr.strh.dwStart = 0;
m_format_hdr.strh.dwLength = 0; // update when finished
m_format_hdr.strh.dwSuggestedBufferSize = 0; // can be just 0
m_format_hdr.strh.dwQuality = m_img_quality * 100;
m_format_hdr.strh.dwSampleSize = 0;
m_format_hdr.strh.Left = 0;
m_format_hdr.strh.Top = 0;
m_format_hdr.strh.Right = m_avi_hdr.avih.dwWidth;
m_format_hdr.strh.Bottom = m_avi_hdr.avih.dwHeight;
m_format_hdr.strfhdr.fcc = FOURCC('s', 't', 'r', 'f');
m_format_hdr.strfhdr.cb = sizeof(BitmapInfoHeader);
// Format specific changes
if (m_avi_format == AVI_FORMAT_JPG)
{
m_format_hdr.strh.fccHandler = CC_MJPG;
bitmap_hdr.biCompression = FOURCC('M', 'J', 'P', 'G');
m_chunk_fcc = FOURCC('0', '0', 'd', 'c');
}
else if (m_avi_format == AVI_FORMAT_BMP)
{
bitmap_hdr.biHeight = -m_height;
bitmap_hdr.biCompression = 0;
m_chunk_fcc = FOURCC('0', '0', 'd', 'b');
}
const uint32_t fcc_movi = FOURCC('m', 'o', 'v', 'i');
m_file = fopen(m_filename.c_str(), "wb");
if (m_file == NULL)
return false;
int num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file);
if (num != sizeof(m_avi_hdr))
goto error;
num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file);
if (num != sizeof(m_format_hdr))
goto error;
num = fwrite(&bitmap_hdr, 1, sizeof(BitmapInfoHeader), m_file);
if (num != sizeof(BitmapInfoHeader))
goto error;
m_end_of_header = ftell(m_file);
if (m_end_of_header < 0)
goto error;
if (!addJUNKChunk("", 2840))
goto error;
m_avi_hdr.list1.cb = m_end_of_header - sizeof(m_avi_hdr.riff) -
sizeof(m_avi_hdr.avi) - sizeof(m_avi_hdr.list1);
m_movi_chunk.fcc = FOURCC('L', 'I', 'S', 'T');
m_movi_chunk.cb = 0; // update when finished
num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file);
if (num != sizeof(m_movi_chunk))
goto error;
m_movi_start = ftell(m_file);
if (m_movi_start < 0)
goto error;
num = fwrite(&fcc_movi, 1, sizeof(fcc_movi), m_file);
if (num != sizeof(fcc_movi))
goto error;
return true;
error:
closeFile(true);
return false;
} // createFile
// ----------------------------------------------------------------------------
int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output,
unsigned long buf_length)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
cinfo.image_width = m_width;
cinfo.image_height = m_height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, m_img_quality, true);
jpeg_mem_dest(&cinfo, &image_output, &buf_length);
jpeg_start_compress(&cinfo, true);
JSAMPROW jrow[1];
while (cinfo.next_scanline < cinfo.image_height)
{
jrow[0] = &image_data[cinfo.next_scanline * m_width * 3];
jpeg_write_scanlines(&cinfo, jrow, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return buf_length;
} // bmpToJpg
#endif

256
src/utils/avi_writer.hpp Normal file
View File

@ -0,0 +1,256 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Dawid Gan
//
// 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; either version 3
// of the License, or (at your option) any later version.
//
// 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.
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
#include "graphics/gl_headers.hpp"
#include "utils/can_be_deleted.hpp"
#include "utils/no_copy.hpp"
#include "utils/singleton.hpp"
#include "utils/synchronised.hpp"
#include <string>
#include <list>
#include <pthread.h>
#define FOURCC(a,b,c,d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)))
const uint32_t CC_MJPG = FOURCC('m', 'j', 'p', 'g');
const uint32_t CC_DIB = FOURCC('\0', '\0', '\0', '\0');
const uint32_t CC_VIDS = FOURCC('v', 'i', 'd', 's');
const uint32_t AVIF_HASINDEX = 0x00000010;
const uint32_t AVIF_MUSTUSEINDEX = 0x00000020;
const uint32_t AVIF_ISINTERLEAVED = 0x00000100;
const uint32_t AVIF_TRUSTCKTYPE = 0x00000800;
const uint32_t AVIF_WASCAPTUREFILE = 0x00010000;
const uint32_t AVIF_COPYRIGHTED = 0x00020000;
const uint32_t AVISF_DISABLED = 0x00000001;
const uint32_t AVISF_VIDEO_PALCHANGES = 0x00010000;
const uint32_t AVIIF_LIST = 0x00000001;
const uint32_t AVIIF_KEYFRAME = 0x00000010;
const uint32_t AVIIF_FIRSTPART = 0x00000020;
const uint32_t AVIIF_LASTPART = 0x00000040;
const uint32_t AVIIF_MIDPART = 0x00000060;
const uint32_t AVIIF_NOTIME = 0x00000100;
const uint32_t AVIIF_COMPUSE = 0x0FFF0000;
enum AVIFormat
{
AVI_FORMAT_BMP,
AVI_FORMAT_JPG
};
enum AVIErrCode
{
AVI_SUCCESS,
AVI_SIZE_LIMIT_ERR,
AVI_IO_ERR
};
const int MAX_FRAMES = 1000000;
const int MAX_FILE_SIZE = 2000000000;
struct MainAVIHeader
{
uint32_t dwMicroSecPerFrame;
uint32_t dwMaxBytesPerSec;
uint32_t dwPaddingGranularity;
uint32_t dwFlags;
uint32_t dwTotalFrames;
uint32_t dwInitialFrames;
uint32_t dwStreams;
uint32_t dwSuggestedBufferSize;
uint32_t dwWidth;
uint32_t dwHeight;
uint32_t dwReserved[4];
};
struct AVIStreamHeader
{
uint32_t fccType;
uint32_t fccHandler;
uint32_t dwFlags;
uint16_t wPriority;
uint16_t wLanguage;
uint32_t dwInitialFrames;
uint32_t dwScale;
uint32_t dwRate;
uint32_t dwStart;
uint32_t dwLength;
uint32_t dwSuggestedBufferSize;
uint32_t dwQuality;
uint32_t dwSampleSize;
uint16_t Left;
uint16_t Top;
uint16_t Right;
uint16_t Bottom;
};
struct BitmapInfoHeader
{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
struct AVIINDEXENTRY
{
uint32_t ckid;
uint32_t dwFlags;
uint32_t dwChunkOffset;
uint32_t dwChunkLength;
};
struct CHUNK
{
uint32_t fcc;
uint32_t cb;
};
struct AVIHeader
{
CHUNK riff;
uint32_t avi;
CHUNK list1;
uint32_t hdrl;
CHUNK avihhdr;
MainAVIHeader avih;
};
struct FormatHeader
{
CHUNK list;
uint32_t strl;
CHUNK strhhdr;
AVIStreamHeader strh;
CHUNK strfhdr;
};
struct IndexTable
{
uint32_t Offset;
uint32_t Length;
uint32_t fcc;
};
class AVIWriter : public CanBeDeleted, public NoCopy,
public Singleton<AVIWriter>
{
private:
FILE* m_file;
Synchronised<std::string> m_recording_target;
std::string m_filename;
int m_last_junk_chunk, m_end_of_header, m_movi_start, m_img_quality,
m_width, m_height;
unsigned int m_msec_per_frame, m_stream_bytes, m_total_frames, m_pbo_use;
float m_accumulated_time, m_remaining_time;
AVIFormat m_avi_format;
AVIHeader m_avi_hdr;
CHUNK m_movi_chunk;
FormatHeader m_format_hdr;
IndexTable m_index_table[MAX_FRAMES];
uint32_t m_chunk_fcc;
Synchronised<std::list<std::pair<uint8_t*, int> > > m_fbi_queue;
Synchronised<bool> m_idle;
pthread_t m_thread;
pthread_cond_t m_cond_request;
GLuint m_pbo[3];
// ------------------------------------------------------------------------
int bmpToJpg(unsigned char* image_data, unsigned char* image_output,
unsigned long buf_length);
// ------------------------------------------------------------------------
AVIErrCode addImage(unsigned char* buffer, int size);
// ------------------------------------------------------------------------
bool closeFile(bool delete_file = false);
// ------------------------------------------------------------------------
bool createFile();
// ------------------------------------------------------------------------
bool addJUNKChunk(std::string str, unsigned int min_size);
// ------------------------------------------------------------------------
void addFrameBufferImage(uint8_t* fbi, int frame_count)
{
m_fbi_queue.lock();
m_fbi_queue.getData().emplace_back(fbi, frame_count);
pthread_cond_signal(&m_cond_request);
m_fbi_queue.unlock();
}
// ------------------------------------------------------------------------
int getFrameCount(float dt);
// ------------------------------------------------------------------------
void cleanAllFrameBufferImages()
{
m_fbi_queue.lock();
for (auto& p : m_fbi_queue.getData())
delete p.first;
m_fbi_queue.unlock();
}
// ------------------------------------------------------------------------
bool isIdle() const { return m_idle.getAtomic(); }
public:
// ------------------------------------------------------------------------
AVIWriter();
// ------------------------------------------------------------------------
~AVIWriter();
// ------------------------------------------------------------------------
static void* startRoutine(void *obj);
// ------------------------------------------------------------------------
void captureFrameBufferImage(float dt);
// ------------------------------------------------------------------------
void resetFrameBufferImage();
// ------------------------------------------------------------------------
void resetCaptureFormat();
// ------------------------------------------------------------------------
void stopRecording() { addFrameBufferImage(NULL, -1); }
// ------------------------------------------------------------------------
void setRecordingTarget(const std::string& name)
{ m_recording_target.setAtomic(name); }
};
#endif

View File

@ -134,6 +134,8 @@ enum DebugMenuCommand
DEBUG_SCRIPT_CONSOLE,
DEBUG_RUN_CUTSCENE,
DEBUG_TEXTURE_CONSOLE,
DEBUG_START_RECORDING,
DEBUG_STOP_RECORDING
}; // DebugMenuCommand
// -----------------------------------------------------------------------------
@ -711,6 +713,12 @@ bool handleContextMenuAction(s32 cmd_id)
return false;
});
break;
case DEBUG_START_RECORDING:
irr_driver->setRecording(true);
break;
case DEBUG_STOP_RECORDING:
irr_driver->setRecording(false);
break;
} // switch
return false;
}
@ -793,8 +801,13 @@ bool onEvent(const SEvent &event)
sub->addItem(L"Toggle smooth camera", DEBUG_GUI_CAM_SMOOTH);
sub->addItem(L"Attach fps camera to kart", DEBUG_GUI_CAM_ATTACH);
mnu->addItem(L"Change camera target >",-1,true, true);
mnu->addItem(L"Recording >",-1,true, true);
sub = mnu->getSubMenu(4);
sub->addItem(L"Start recording", DEBUG_START_RECORDING);
sub->addItem(L"Stop recording", DEBUG_STOP_RECORDING);
mnu->addItem(L"Change camera target >",-1,true, true);
sub = mnu->getSubMenu(5);
sub->addItem(L"To kart one", DEBUG_VIEW_KART_ONE);
sub->addItem(L"To kart two", DEBUG_VIEW_KART_TWO);
sub->addItem(L"To kart three", DEBUG_VIEW_KART_THREE);
@ -805,7 +818,7 @@ bool onEvent(const SEvent &event)
sub->addItem(L"To kart eight", DEBUG_VIEW_KART_EIGHT);
mnu->addItem(L"Font >",-1,true, true);
sub = mnu->getSubMenu(5);
sub = mnu->getSubMenu(6);
sub->addItem(L"Dump glyph pages of fonts", DEBUG_FONT_DUMP_GLYPH_PAGE);
sub->addItem(L"Reload all fonts", DEBUG_FONT_RELOAD);