From 3144cd1595ac8680d0fa1e9f2790e07474975813 Mon Sep 17 00:00:00 2001 From: Deve Date: Sat, 28 Nov 2015 16:42:48 +0100 Subject: [PATCH] Implement recording video to avi file. It allows to easily record a video, which can be then for example attached to a bug report. It also allows to record promotion videos (eg. trailers), which should be more smooth than recorded using external application. Main disadventages are: - Constant fps in avi file - Atm. it's not possible to record sounds. I even don't know if it's possible at all (if we can easily get access to the sound buffer). I see about 20-25% fps drop during recording. I think that it's acceptable. But if needed, the fps can be increased by using separated thread for recording. Currently it uses jpeg compression with 70 quality. It can be easily tweaked to use higher/lower compression or even just uncompressed bitmaps. --- src/graphics/irr_driver.cpp | 86 +++++++++ src/graphics/irr_driver.hpp | 6 + src/utils/avi_writer.cpp | 374 ++++++++++++++++++++++++++++++++++++ src/utils/avi_writer.hpp | 178 +++++++++++++++++ src/utils/debug.cpp | 17 +- 5 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 src/utils/avi_writer.cpp create mode 100644 src/utils/avi_writer.hpp diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index e7362930d..9a7040223 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -130,6 +130,9 @@ IrrDriver::IrrDriver() m_boundingboxesviz = false; m_last_light_bucket_distance = 0; memset(object_count, 0, sizeof(object_count)); + + m_avi_writer = NULL; + m_request_recording = false; } // IrrDriver // ---------------------------------------------------------------------------- @@ -137,6 +140,8 @@ IrrDriver::IrrDriver() */ IrrDriver::~IrrDriver() { + stopRecording(); + // Note that we can not simply delete m_post_processing here: // m_post_processing uses a material that has a reference to // m_post_processing (for a callback). So when the material is @@ -2118,6 +2123,56 @@ void IrrDriver::doScreenShot() image->drop(); } // doScreenShot + +void IrrDriver::startRecording() +{ + if (m_avi_writer != NULL) + return; + + 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); + + std::string track_name = World::getWorld() != NULL ? + race_manager->getTrackName() : "menu"; + + std::string path = file_manager->getScreenshotDir() + track_name + "-" + + time_buffer + ".avi"; + + m_request_recording = true; + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer = new AVIWriter(); + m_avi_writer->createFile(path, AVI_FORMAT_JPG, m_actual_screen_size.Width, + m_actual_screen_size.Height, msec_per_frame, 24, 70); + +} + +void IrrDriver::stopRecording() +{ + if (m_avi_writer == NULL) + return; + + m_avi_writer->closeFile(); + delete m_avi_writer; + m_avi_writer = NULL; + m_request_recording = false; + + RaceGUIBase* base = World::getWorld() ? World::getWorld()->getRaceGUI() + : NULL; + if (base) + { + std::string path = file_manager->getScreenshotDir(); + base->addMessage( + core::stringw(("Video saved in\n" + path).c_str()), + NULL, 2.0f, video::SColor(255,255,255,255), true, false); + } +} + // ---------------------------------------------------------------------------- /** Update, called once per frame. * \param dt Time since last update @@ -2207,6 +2262,37 @@ void IrrDriver::update(float dt) // menu. //if(World::getWorld() && World::getWorld()->isRacePhase()) // printRenderStats(); + + if (m_request_recording == true && m_avi_writer != NULL) + { + video::IImage* image = m_video_driver->createScreenShot(); + + if (image != NULL) + { + int bits = image->getBytesPerPixel(); + int w = image->getDimension().Width; + int h = image->getDimension().Height; + + unsigned char* img = (unsigned char*)image->lock(); + + int buf_length = bits * w * h; + unsigned char* img_jpg = new unsigned char[buf_length]; + + int len = m_avi_writer->bmpToJpg(img, img_jpg, buf_length, w, h, + bits, 70); + m_avi_writer->addImage(img_jpg, len); + + delete[] img_jpg; + + image->unlock(); + image->drop(); + + // update msec per frame using the fps during recording to get + // something closer to the real value + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer->updateMsecPerFrame(msec_per_frame); + } + } } // update // ---------------------------------------------------------------------------- diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 606d7e2fe..38f02fd2e 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -42,6 +42,7 @@ #include "graphics/wind.hpp" #include "io/file_manager.hpp" #include "utils/aligned_array.hpp" +#include "utils/avi_writer.hpp" #include "utils/no_copy.hpp" #include "utils/ptr_vector.hpp" #include "utils/vec3.hpp" @@ -306,6 +307,9 @@ private: float m_ssao_radius; float m_ssao_k; float m_ssao_sigma; + + bool m_request_recording; + AVIWriter* m_avi_writer; #ifdef DEBUG /** Used to visualise skeletons. */ @@ -429,6 +433,8 @@ public: void printRenderStats(); bool supportsSplatting(); void requestScreenshot(); + void startRecording(); + void stopRecording(); void setTextureErrorMessage(const std::string &error, const std::string &detail=""); void unsetTextureErrorMessage(); diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp new file mode 100644 index 000000000..d7373a14c --- /dev/null +++ b/src/utils/avi_writer.cpp @@ -0,0 +1,374 @@ +// +// 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. + +#include "avi_writer.hpp" + + +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[size]; + memset(buffer, '\0', size); + 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); + 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; +} + +bool AVIWriter::addImage(unsigned char* buffer, int buf_size) +{ + if (m_file == NULL) + return false; + + int num = ftell(m_file); + if (num < 0) + goto error; + + if (m_total_frames >= MAX_FRAMES) + goto error; + + 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 error; + + return true; + +error: + closeFile(true); + return false; +} + +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.dwMicroSecPerFrame = m_msec_per_frame; + 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.dwScale = m_msec_per_frame; + 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; + + return true; + +error: + fclose(m_file); + remove(m_filename.c_str()); + m_file = NULL; + return false; +} + +bool AVIWriter::createFile(std::string filename, AVIFormat avi_format, + int width, int height, unsigned int msec_per_frame, + int bits, int quality) +{ + if (m_file != NULL) + return false; + + if (width < 1 || height < 1) + return false; + + m_filename = filename; + m_stream_bytes = 0; + m_total_frames = 0; + m_msec_per_frame = msec_per_frame; + m_movi_start = 0; + m_last_junk_chunk = 0; + m_total_frames = 0; + + BitmapInfoHeader bitmap_hdr; + bitmap_hdr.biSize = sizeof(BitmapInfoHeader); + bitmap_hdr.biWidth = width; + bitmap_hdr.biHeight = height; + bitmap_hdr.biPlanes = 1; + bitmap_hdr.biBitCount = bits; + bitmap_hdr.biCompression = 0; + bitmap_hdr.biSizeImage = (width * 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; + 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 = width; + m_avi_hdr.avih.dwHeight = 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 = 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 (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 (avi_format == AVI_FORMAT_BMP) + { + bitmap_hdr.biHeight = -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(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; +} + +int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + jpeg_create_compress(&cinfo); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = num_channels; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 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 * width * num_channels]; + jpeg_write_scanlines(&cinfo, jrow, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return buf_length; +} diff --git a/src/utils/avi_writer.hpp b/src/utils/avi_writer.hpp new file mode 100644 index 000000000..bb1e727e4 --- /dev/null +++ b/src/utils/avi_writer.hpp @@ -0,0 +1,178 @@ +// +// 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. + +#include +#include + +#include + +#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}; +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 +{ +private: + FILE* m_file; + std::string m_filename; + int m_last_junk_chunk; + int m_end_of_header; + int m_movi_start; + unsigned int m_msec_per_frame; + unsigned int m_stream_bytes; + unsigned int m_total_frames; + AVIHeader m_avi_hdr; + CHUNK m_movi_chunk; + FormatHeader m_format_hdr; + IndexTable m_index_table[MAX_FRAMES]; + uint32_t m_chunk_fcc; + + bool addJUNKChunk(std::string str, unsigned int min_size); + +public: + AVIWriter() {m_file = NULL;} + + bool addImage(unsigned char* buffer, int size); + bool closeFile(bool delete_file = false); + bool createFile(std::string filename, AVIFormat avi_format, int width, + int height, unsigned int msec_per_frame, int bits, + int quality); + + void updateMsecPerFrame(unsigned int value) + {m_msec_per_frame = (m_msec_per_frame + value) / 2;} + + int bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality); +}; diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 17544f18d..4498b2cec 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -107,7 +107,9 @@ enum DebugMenuCommand DEBUG_VISUAL_VALUES, DEBUG_PRINT_START_POS, DEBUG_ADJUST_LIGHTS, - DEBUG_SCRIPT_CONSOLE + DEBUG_SCRIPT_CONSOLE, + DEBUG_START_RECORDING, + DEBUG_STOP_RECORDING }; // DebugMenuCommand // ----------------------------------------------------------------------------- @@ -566,6 +568,14 @@ bool handleContextMenuAction(s32 cmdID) { ScriptingConsole* console = new ScriptingConsole(); } + else if (cmdID == DEBUG_START_RECORDING) + { + irr_driver->startRecording(); + } + else if (cmdID == DEBUG_STOP_RECORDING) + { + irr_driver->stopRecording(); + } return false; } @@ -642,6 +652,11 @@ bool onEvent(const SEvent &event) sub->addItem(L"Normal view (Ctrl + F2)", DEBUG_GUI_CAM_NORMAL); sub->addItem(L"Toggle smooth camera", DEBUG_GUI_CAM_SMOOTH); sub->addItem(L"Attach fps camera to kart", DEBUG_GUI_CAM_ATTACH); + + int idx = mnu->addItem(L"Recording >",-1,true, true); + sub = mnu->getSubMenu(idx); + sub->addItem(L"Start recording", DEBUG_START_RECORDING); + sub->addItem(L"Stop recording", DEBUG_STOP_RECORDING); mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);