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

@ -16,9 +16,150 @@
// 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"
#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;
@ -49,8 +190,9 @@ bool AVIWriter::addJUNKChunk(std::string str, unsigned int min_size)
error:
closeFile(true);
return false;
}
} // addJUNKChunk
// ----------------------------------------------------------------------------
AVIErrCode AVIWriter::addImage(unsigned char* buffer, int buf_size)
{
if (m_file == NULL)
@ -111,8 +253,9 @@ error:
size_limit:
closeFile();
return AVI_SIZE_LIMIT_ERR;
}
} // addImage
// ----------------------------------------------------------------------------
bool AVIWriter::closeFile(bool delete_file)
{
if (m_file == NULL)
@ -164,8 +307,9 @@ bool AVIWriter::closeFile(bool delete_file)
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.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);
@ -201,22 +345,34 @@ error:
remove(m_filename.c_str());
m_file = NULL;
return false;
}
} // closeFile
bool AVIWriter::createFile(std::string filename, AVIFormat avi_format,
int width, int height, unsigned int msec_per_frame,
int bits, int quality)
// ----------------------------------------------------------------------------
bool AVIWriter::createFile(AVIFormat avi_format, int bits, int quality)
{
if (m_file != NULL)
return false;
if (width < 1 || height < 1)
return false;
int width = irr_driver->getActualScreenSize().Width;
int height = irr_driver->getActualScreenSize().Height;
m_filename = filename;
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 = msec_per_frame;
m_msec_per_frame = 42;
m_movi_start = 0;
m_last_junk_chunk = 0;
m_total_frames = 0;
@ -255,7 +411,8 @@ bool AVIWriter::createFile(std::string filename, AVIFormat avi_format,
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.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);
@ -295,7 +452,7 @@ bool AVIWriter::createFile(std::string filename, AVIFormat avi_format,
const uint32_t fcc_movi = FOURCC('m', 'o', 'v', 'i');
m_file = fopen(filename.c_str(), "wb");
m_file = fopen(m_filename.c_str(), "wb");
if (m_file == NULL)
return false;
@ -340,8 +497,9 @@ bool AVIWriter::createFile(std::string filename, AVIFormat avi_format,
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)
@ -375,4 +533,6 @@ int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output,
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)))
@ -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;
bool addJUNKChunk(std::string str, unsigned int min_size);
std::list<uint8_t*> m_fbi_queue;
public:
AVIWriter() {m_file = NULL;}
pthread_t m_thread;
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);
pthread_mutex_t m_fbi_mutex;
void updateMsecPerFrame(unsigned int value)
{m_msec_per_frame = (m_msec_per_frame + value) / 2;}
pthread_cond_t m_cond_request;
int bmpToJpg(unsigned char* image_data, unsigned char* image_output,
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();
// ------------------------------------------------------------------------
static void* startRoutine(void *obj);
// ------------------------------------------------------------------------
void captureFrameBufferImage(float dt);
// ------------------------------------------------------------------------
void stopRecording();
};
#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);