Make AVIWriter threaded and get pixel with 3 async PBOs

Also try to record at a fixed 24fps
This commit is contained in:
Benau 2017-03-18 15:33:16 +08:00
parent d3d7c95b4d
commit 8259026ac1
5 changed files with 643 additions and 408 deletions

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
@ -1885,8 +1887,22 @@ void IrrDriver::update(float dt)
// menu.
//if(World::getWorld() && World::getWorld()->isRacePhase())
// printRenderStats();
#ifndef SERVER_ONLY
if (m_recording)
AVIWriter::getInstance()->captureFrameBufferImage(dt);
#endif
} // update
// ----------------------------------------------------------------------------
void IrrDriver::setRecording(bool val)
{
if (val == false && m_recording == false)
return;
m_recording = val;
if (val == false)
AVIWriter::getInstance()->stopRecording();
} // setRecording
// ----------------------------------------------------------------------------
void IrrDriver::requestScreenshot()

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

@ -1,378 +1,538 @@
//
// 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 = (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;
}
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:
closeFile();
return AVI_SIZE_LIMIT_ERR;
}
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 * 1000;
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 * 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 = 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;
}
//
// 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.
#ifndef SERVER_ONLY
#include "utils/avi_writer.hpp"
#include "io/file_manager.hpp"
#include "graphics/irr_driver.hpp"
#include "modes/world.hpp"
#include "race/race_manager.hpp"
#include "utils/string_utils.hpp"
#include "utils/vs.hpp"
#include <cstring>
// ----------------------------------------------------------------------------
AVIWriter::AVIWriter()
{
m_file = NULL;
m_pbo_use = 0;
m_dt = 0.0f;
glGenBuffers(3, m_pbo);
for (int i = 0; i < 3; i++)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[i]);
glBufferData(GL_PIXEL_PACK_BUFFER,
irr_driver->getActualScreenSize().getArea() * 4, NULL,
GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
pthread_mutex_init(&m_fbi_mutex, NULL);
pthread_cond_init(&m_cond_request, NULL);
pthread_create(&m_thread, NULL, &startRoutine, this);
} // AVIWriter
// ----------------------------------------------------------------------------
void* AVIWriter::startRoutine(void *obj)
{
VS::setThreadName("AVIWriter");
AVIWriter* avi_writer = (AVIWriter*)obj;
while (true)
{
pthread_mutex_lock(&avi_writer->m_fbi_mutex);
bool waiting = avi_writer->m_fbi_queue.empty();
while (waiting)
{
pthread_cond_wait(&avi_writer->m_cond_request,
&avi_writer->m_fbi_mutex);
waiting = avi_writer->m_fbi_queue.empty();
}
uint8_t* fbi = avi_writer->m_fbi_queue.front();
if (fbi == NULL)
{
avi_writer->setCanBeDeleted();
avi_writer->m_fbi_queue.pop_front();
pthread_mutex_unlock(&avi_writer->m_fbi_mutex);
return NULL;
}
if (fbi == (uint8_t*)0xAAAB1E5D)
{
avi_writer->closeFile();
avi_writer->m_file = NULL;
avi_writer->m_fbi_queue.pop_front();
pthread_mutex_unlock(&avi_writer->m_fbi_mutex);
continue;
}
avi_writer->m_fbi_queue.pop_front();
pthread_mutex_unlock(&avi_writer->m_fbi_mutex);
video::IImage* image = irr_driver->getVideoDriver()
->createImageFromData(video::ECF_A8R8G8B8,
irr_driver->getActualScreenSize(), fbi,
true/*ownForeignMemory*/);
video::IImage* rgb = irr_driver->getVideoDriver()->createImage
(video::ECF_R8G8B8, irr_driver->getActualScreenSize());
image->copyTo(rgb);
image->drop();
rgb->setDeleteMemory(false);
fbi = (uint8_t*)rgb->lock();
rgb->unlock();
rgb->drop();
uint8_t* orig_fbi = fbi;
const int pitch = irr_driver->getActualScreenSize().Width * 3;
uint8_t* p2 = fbi + (irr_driver->getActualScreenSize().Height - 1) *
pitch;
uint8_t* tmp_buf = new uint8_t[pitch];
for (unsigned i = 0; i < irr_driver->getActualScreenSize().Height;
i += 2)
{
memcpy(tmp_buf, fbi, pitch);
memcpy(fbi, p2, pitch);
memcpy(p2, tmp_buf, pitch);
fbi += pitch;
p2 -= pitch;
}
delete [] tmp_buf;
const unsigned size = irr_driver->getActualScreenSize().getArea() * 3;
uint8_t* jpg = new uint8_t[size];
int new_len = bmpToJpg(orig_fbi, jpg, size,
irr_driver->getActualScreenSize().Width,
irr_driver->getActualScreenSize().Height, 3, 70);
delete[] orig_fbi;
avi_writer->addImage(jpg, new_len);
delete[] jpg;
}
return NULL;
} // startRoutine
// ----------------------------------------------------------------------------
void AVIWriter::captureFrameBufferImage(float dt)
{
if (m_file == NULL)
{
createFile(AVI_FORMAT_JPG, 24, 70);
}
m_dt += dt;
glReadBuffer(GL_BACK);
int pbo_read = -1;
if (m_pbo_use > 2 && m_dt >= 0.0416666667f)
{
m_dt = 0.0f;
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 = irr_driver->getActualScreenSize().getArea() * 4;
uint8_t* fbi = new uint8_t[size];
memcpy(fbi, ptr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
addFrameBufferImage(fbi);
}
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, irr_driver->getActualScreenSize().Width,
irr_driver->getActualScreenSize().Height, GL_BGRA, GL_UNSIGNED_BYTE,
0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
} // captureFrameBufferImage
// ----------------------------------------------------------------------------
void AVIWriter::stopRecording()
{
assert(m_file != NULL);
addFrameBufferImage((uint8_t*)0xAAAB1E5D);
} // stopRecording
// ----------------------------------------------------------------------------
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:
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.dwMicroSecPerFrame = m_msec_per_frame * 1000;
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;
} // closeFile
// ----------------------------------------------------------------------------
bool AVIWriter::createFile(AVIFormat avi_format, int bits, int quality)
{
if (m_file != NULL)
return false;
int width = irr_driver->getActualScreenSize().Width;
int height = irr_driver->getActualScreenSize().Height;
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";
m_filename = file_manager->getScreenshotDir() + track_name + "-"
+ time_buffer + ".avi";
m_stream_bytes = 0;
m_total_frames = 0;
m_msec_per_frame = 42;
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 * 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 = 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(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, 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;
} // bmpToJpg
#endif

View File

@ -16,9 +16,17 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <cstring>
#include <string>
#ifndef SERVER_ONLY
#include "graphics/gl_headers.hpp"
#include "utils/can_be_deleted.hpp"
#include "utils/no_copy.hpp"
#include "utils/singleton.hpp"
#include <string>
#include <list>
#include <pthread.h>
#include <jpeglib.h>
#define FOURCC(a,b,c,d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)))
@ -45,17 +53,17 @@ const uint32_t AVIIF_MIDPART = 0x00000060;
const uint32_t AVIIF_NOTIME = 0x00000100;
const uint32_t AVIIF_COMPUSE = 0x0FFF0000;
enum AVIFormat
enum AVIFormat
{
AVI_FORMAT_BMP,
AVI_FORMAT_JPG
AVI_FORMAT_BMP,
AVI_FORMAT_JPG
};
enum AVIErrCode
{
AVI_SUCCESS,
AVI_SIZE_LIMIT_ERR,
AVI_IO_ERR
AVI_SUCCESS,
AVI_SIZE_LIMIT_ERR,
AVI_IO_ERR
};
const int MAX_FRAMES = 1000000;
@ -153,38 +161,71 @@ struct IndexTable
};
class AVIWriter
class AVIWriter : public CanBeDeleted, public NoCopy,
public Singleton<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;
int m_last_junk_chunk, m_end_of_header, m_movi_start;
unsigned int m_msec_per_frame, m_stream_bytes, m_total_frames, m_pbo_use;
float m_dt;
AVIHeader m_avi_hdr;
CHUNK m_movi_chunk;
FormatHeader m_format_hdr;
IndexTable m_index_table[MAX_FRAMES];
uint32_t m_chunk_fcc;
std::list<uint8_t*> m_fbi_queue;
pthread_t m_thread;
pthread_mutex_t m_fbi_mutex;
pthread_cond_t m_cond_request;
GLuint m_pbo[3];
// ------------------------------------------------------------------------
static 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);
// ------------------------------------------------------------------------
AVIErrCode addImage(unsigned char* buffer, int size);
// ------------------------------------------------------------------------
bool closeFile(bool delete_file = false);
// ------------------------------------------------------------------------
bool createFile(AVIFormat avi_format, int bits, int quality);
// ------------------------------------------------------------------------
bool addJUNKChunk(std::string str, unsigned int min_size);
// ------------------------------------------------------------------------
void addFrameBufferImage(uint8_t* fbi)
{
pthread_mutex_lock(&m_fbi_mutex);
m_fbi_queue.push_back(fbi);
pthread_cond_signal(&m_cond_request);
pthread_mutex_unlock(&m_fbi_mutex);
}
public:
AVIWriter() {m_file = NULL;}
// ------------------------------------------------------------------------
AVIWriter();
// ------------------------------------------------------------------------
static void* startRoutine(void *obj);
// ------------------------------------------------------------------------
void captureFrameBufferImage(float dt);
// ------------------------------------------------------------------------
void stopRecording();
AVIErrCode 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);
};
#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);