Move all recording code into recorder folder

This commit is contained in:
Benau 2017-04-04 10:04:55 +08:00
parent 2d4bfa9fb0
commit ea6719990a
9 changed files with 683 additions and 359 deletions

View File

@ -560,16 +560,18 @@ namespace UserConfigParams
// ---- Recording
PARAM_PREFIX GroupUserConfigParam m_recording_group
PARAM_DEFAULT( GroupUserConfigParam("Recording", "Recording Settings") );
PARAM_DEFAULT(GroupUserConfigParam("Recording",
"Recording Settings"));
PARAM_PREFIX BoolUserConfigParam m_limit_game_fps
PARAM_DEFAULT(BoolUserConfigParam(true, "limit_game_fps",
&m_recording_group, "Limit game framerate not beyond the fps of"
" recording video."));
" recording video."));
PARAM_PREFIX IntUserConfigParam m_vp_codec
PARAM_DEFAULT(IntUserConfigParam(0, "vp_codec",
&m_recording_group, "Specify the codec for libvpx (VP8 / VP9)"));
PARAM_PREFIX IntUserConfigParam m_record_format
PARAM_DEFAULT(IntUserConfigParam(0, "record_format",
&m_recording_group, "Specify the format for record (VP8, VP9, mjpeg,"
" h264)"));
PARAM_PREFIX IntUserConfigParam m_vp_end_usage
PARAM_DEFAULT(IntUserConfigParam(0, "vp_end_usage",

View File

@ -57,11 +57,11 @@
#include "modes/profile_world.hpp"
#include "modes/world.hpp"
#include "physics/physics.hpp"
#include "recorder/recorder_common.hpp"
#include "scriptengine/property_animator.hpp"
#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"
@ -170,7 +170,7 @@ IrrDriver::~IrrDriver()
delete m_wind;
delete m_renderer;
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
AVIWriter::kill();
Recorder::destroyRecorder();
#endif
} // ~IrrDriver
@ -927,7 +927,8 @@ void IrrDriver::applyResolutionSettings()
VAOManager::getInstance()->kill();
STKTexManager::getInstance()->kill();
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
AVIWriter::kill();
Recorder::destroyRecorder();
m_recording = false;
#endif
// initDevice will drop the current device.
if (CVS->isGLSL())
@ -1895,7 +1896,7 @@ void IrrDriver::update(float dt)
// printRenderStats();
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
if (m_recording)
AVIWriter::getInstance()->captureFrameBufferImage();
Recorder::captureFrameBufferImage();
#endif
} // update
@ -1910,20 +1911,23 @@ void IrrDriver::setRecording(bool val)
}
if (m_recording == val)
return;
m_recording = val;
if (m_recording == true)
if (val == true)
{
if (!Recorder::isRecording())
return;
m_recording = val;
std::string track_name = World::getWorld() != NULL ?
race_manager->getTrackName() : "menu";
AVIWriter::setRecordingTarget(file_manager->getScreenshotDir() +
Recorder::setRecordingName(file_manager->getScreenshotDir() +
track_name);
AVIWriter::getInstance()->resetFrameBufferImage();
Recorder::prepareCapture();
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video recording started."));
}
else
{
AVIWriter::getInstance()->stopRecording();
m_recording = val;
Recorder::stopRecording();
}
#endif
} // setRecording

View File

@ -0,0 +1,362 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// 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)) && !defined(WIN32)
#include "recorder/recorder_common.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/gl_headers.hpp"
#include "guiengine/message_queue.hpp"
#include "recorder/pulseaudio_recorder.hpp"
#include "recorder/wasapi_recorder.hpp"
#include "recorder/vpx_encoder.hpp"
#include "recorder/webm_writer.hpp"
#include "utils/synchronised.hpp"
#include "utils/translation.hpp"
#include "utils/vs.hpp"
#include <chrono>
#include <cassert>
#include <turbojpeg.h>
namespace Recorder
{
// ========================================================================
tjhandle g_compress_handle;
// ========================================================================
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > > g_jpg_list;
// ========================================================================
pthread_cond_t g_jpg_request;
// ========================================================================
ThreadData g_jpg_thread_data;
// ========================================================================
int bmpToJPG(uint8_t* raw, unsigned width, unsigned height,
uint8_t** jpeg_buffer, unsigned long* jpeg_size)
{
int ret = 0;
#ifdef TJFLAG_FASTDCT
ret = tjCompress2(g_compress_handle, raw, width, 0, height, TJPF_BGR,
jpeg_buffer, jpeg_size, TJSAMP_420,
UserConfigParams::m_recorder_jpg_quality, TJFLAG_FASTDCT);
#else
ret = tjCompress2(g_compress_handle, raw, width, 0, height, TJPF_BGR,
jpeg_buffer, jpeg_size, TJSAMP_420,
UserConfigParams::m_recorder_jpg_quality, 0);
#endif
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("RecorderCommon", "Jpeg encode error: %s.", err);
return ret;
}
return ret;
} // bmpToJPG
// ========================================================================
pthread_t g_audio_thread;
// ========================================================================
Synchronised<pthread_t*> g_video_thread(NULL);
// ========================================================================
Synchronised<bool> g_idle(true);
// ========================================================================
bool g_destroy;
// ========================================================================
std::string g_recording_name;
// ========================================================================
void* fbiConversion(void* obj)
{
VS::setThreadName("fbiConversion");
ThreadData* td = (ThreadData*)obj;
Synchronised<std::list<std::pair<uint8_t*, int> > >* fbi_queue =
(Synchronised<std::list<std::pair<uint8_t*, int> > >*)td->m_data;
pthread_cond_t* cond_request = td->m_request;
while (true)
{
fbi_queue->lock();
bool waiting = fbi_queue->getData().empty();
while (waiting)
{
pthread_cond_wait(cond_request, fbi_queue->getMutex());
waiting = fbi_queue->getData().empty();
}
auto& p = fbi_queue->getData().front();
uint8_t* fbi = p.first;
int frame_count = p.second;
if (frame_count == -1)
{
fbi_queue->getData().clear();
fbi_queue->unlock();
g_idle.setAtomic(true);
pthread_join(g_audio_thread, NULL);
g_jpg_list.lock();
g_jpg_list.getData().emplace_back((uint8_t*)NULL, 0, 0);
pthread_cond_signal(&g_jpg_request);
g_jpg_list.unlock();
pthread_join(*g_video_thread.getAtomic(), NULL);
g_video_thread.setAtomic(NULL);
Recorder::writeWebm(g_recording_name + ".video",
g_recording_name + ".audio");
if (g_destroy)
{
return NULL;
}
continue;
}
else if (fbi == NULL)
{
fbi_queue->getData().clear();
fbi_queue->unlock();
if (g_destroy)
{
return NULL;
}
continue;
}
const bool too_slow = fbi_queue->getData().size() > 50;
if (too_slow)
{
MessageQueue::add(MessageQueue::MT_ERROR,
_("Encoding is too slow, dropping frames."));
delete [] fbi;
fbi_queue->getData().pop_front();
for (auto& p : fbi_queue->getData())
delete [] p.first;
fbi_queue->getData().clear();
fbi_queue->unlock();
continue;
}
fbi_queue->getData().pop_front();
fbi_queue->unlock();
uint8_t* orig_fbi = fbi;
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().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;
uint8_t* jpg = NULL;
unsigned long jpg_size = 0;
bmpToJPG(orig_fbi + area, width, height, &jpg, &jpg_size);
delete[] orig_fbi;
g_jpg_list.lock();
g_jpg_list.getData().emplace_back(jpg, jpg_size, frame_count);
pthread_cond_signal(&g_jpg_request);
g_jpg_list.unlock();
}
return NULL;
} // fbiConversion
// ========================================================================
struct CommonData : public NoCopy
{
GLuint m_pbo[3];
unsigned m_pbo_use;
bool m_loaded;
Synchronised<std::list<std::pair<uint8_t*, int> > > m_fbi_queue;
pthread_cond_t m_fbi_request;
pthread_t m_fbi_thread;
ThreadData m_common_thread_data;
// --------------------------------------------------------------------
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_fbi_request);
m_fbi_queue.unlock();
} // addFrameBufferImage
// --------------------------------------------------------------------
CommonData()
{
m_loaded = false;
m_pbo_use = 0;
g_compress_handle = tjInitCompress();
} // CommonData
// --------------------------------------------------------------------
~CommonData()
{
destroy();
tjDestroy(g_compress_handle);
} // ~CommonData
// --------------------------------------------------------------------
void destroy()
{
if (m_loaded)
{
glDeleteBuffers(3, m_pbo);
addFrameBufferImage(NULL, 0);
pthread_join(m_fbi_thread, NULL);
pthread_cond_destroy(&m_fbi_request);
pthread_cond_destroy(&g_jpg_request);
g_destroy = false;
}
m_loaded = false;
} // destroy
// --------------------------------------------------------------------
void load()
{
if (m_loaded) return;
m_loaded = true;
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().Width *
irr_driver->getActualScreenSize().Height * 4, NULL,
GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
pthread_cond_init(&m_fbi_request, NULL);
pthread_cond_init(&g_jpg_request, NULL);
m_common_thread_data.m_data = &m_fbi_queue;
m_common_thread_data.m_request = &m_fbi_request;
g_jpg_thread_data.m_data = &g_jpg_list;
g_jpg_thread_data.m_request = &g_jpg_request;
pthread_create(&m_fbi_thread, NULL, &fbiConversion,
&m_common_thread_data);
} // load
};
// ========================================================================
std::chrono::high_resolution_clock::time_point g_framerate_timer;
// ========================================================================
double g_accumulated_time;
// ========================================================================
CommonData g_common_data;
// ========================================================================
void setRecordingName(const std::string& name)
{
g_recording_name = name;
} // setRecordingName
// ------------------------------------------------------------------------
const std::string& getRecordingName()
{
return g_recording_name;
} // getRecordingName
// ------------------------------------------------------------------------
void prepareCapture()
{
g_common_data.load();
g_common_data.m_pbo_use = 0;
g_accumulated_time = 0.;
assert(g_idle.getAtomic() && g_video_thread.getAtomic() == NULL);
g_idle.setAtomic(false);
pthread_create(&g_audio_thread, NULL, &Recorder::audioRecorder,
&g_idle);
g_video_thread.setAtomic(new pthread_t());
pthread_create(g_video_thread.getAtomic(), NULL, &Recorder::vpxEncoder,
&g_jpg_thread_data);
} // prepareCapture
// ------------------------------------------------------------------------
int getFrameCount(double rate)
{
const double frame_rate = 1. / double(UserConfigParams::m_record_fps);
g_accumulated_time += rate;
if (g_accumulated_time < frame_rate)
{
return 0;
}
int frame_count = 0;
while (g_accumulated_time >= frame_rate)
{
frame_count++;
g_accumulated_time = g_accumulated_time - frame_rate;
}
return frame_count;
} // getFrameCount
// ------------------------------------------------------------------------
void captureFrameBufferImage()
{
assert(g_common_data.m_loaded);
int pbo_read = -1;
if (g_common_data.m_pbo_use > 3 && g_common_data.m_pbo_use % 3 == 0)
g_common_data.m_pbo_use = 3;
auto rate = std::chrono::high_resolution_clock::now() -
g_framerate_timer;
g_framerate_timer = std::chrono::high_resolution_clock::now();
glReadBuffer(GL_BACK);
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().Height;
if (g_common_data.m_pbo_use >= 3)
{
int frame_count = getFrameCount(std::chrono::duration_cast
<std::chrono::duration<double> >(rate).count());
if (frame_count != 0)
{
pbo_read = g_common_data.m_pbo_use % 3;
glBindBuffer(GL_PIXEL_PACK_BUFFER,
g_common_data.m_pbo[pbo_read]);
void* ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
const unsigned size = width * height * 4;
uint8_t* fbi = new uint8_t[size];
memcpy(fbi, ptr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
g_common_data.addFrameBufferImage(fbi, frame_count);
}
}
int pbo_use = g_common_data.m_pbo_use++ % 3;
assert(pbo_read == -1 || pbo_use == pbo_read);
glBindBuffer(GL_PIXEL_PACK_BUFFER, g_common_data.m_pbo[pbo_use]);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
} // captureFrameBufferImage
// ------------------------------------------------------------------------
void stopRecording()
{
if (!isRecording())
{
g_common_data.addFrameBufferImage(NULL, -1);
}
} // stopRecording
// ------------------------------------------------------------------------
bool isRecording()
{
return g_video_thread.getAtomic() == NULL;
} // isRecording
// ------------------------------------------------------------------------
void destroyRecorder()
{
g_destroy = true;
stopRecording();
g_common_data.destroy();
} // destroyRecorder
}
#endif

View File

@ -0,0 +1,58 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// 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))
#ifndef HEADER_RECORDER_COMMON_HPP
#define HEADER_RECORDER_COMMON_HPP
#include "utils/no_copy.hpp"
#include <string>
#include <list>
#include <pthread.h>
namespace Recorder
{
// ------------------------------------------------------------------------
enum VideoFormat { VF_VP8, VF_VP9, VF_MJPEG, VF_H264 };
// ------------------------------------------------------------------------
struct ThreadData : public NoCopy
{
void* m_data;
pthread_cond_t* m_request;
};
// ------------------------------------------------------------------------
void setRecordingName(const std::string& name);
// ------------------------------------------------------------------------
const std::string& getRecordingName();
// ------------------------------------------------------------------------
void prepareCapture();
// ------------------------------------------------------------------------
void captureFrameBufferImage();
// ------------------------------------------------------------------------
void stopRecording();
// ------------------------------------------------------------------------
bool isRecording();
// ------------------------------------------------------------------------
void destroyRecorder();
};
#endif
#endif

View File

@ -18,8 +18,9 @@
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
#include "recorder/vorbis_encoder.hpp"
#include "utils/avi_writer.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/synchronised.hpp"
#include "utils/vs.hpp"
#include <ogg/ogg.h>
@ -52,8 +53,7 @@ namespace Recorder
Log::error("vorbisEncoder", "Header is too long.");
return NULL;
}
FILE* vb_data = fopen((AVIWriter::getRecordingTarget() + ".vb_data")
.c_str(), "wb");
FILE* vb_data = fopen((getRecordingName() + ".audio").c_str(), "wb");
if (vb_data == NULL)
{
Log::error("vorbisEncoder", "Failed to open file for encoding"
@ -154,6 +154,6 @@ namespace Recorder
fclose(vb_data);
return NULL;
} // vorbisEncode
} // vorbisEncoder
}
#endif

View File

@ -0,0 +1,204 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// 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 "recorder/vpx_encoder.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/synchronised.hpp"
#include "utils/vs.hpp"
#include <turbojpeg.h>
#include <vpx/vpx_encoder.h>
#include <vpx/vp8cx.h>
namespace Recorder
{
// ========================================================================
struct JPGDecoder
{
tjhandle m_handle;
// --------------------------------------------------------------------
JPGDecoder()
{
m_handle = tjInitDecompress();
} // JPGDecoder
// --------------------------------------------------------------------
~JPGDecoder()
{
tjDestroy(m_handle);
} // ~JPGDecoder
// --------------------------------------------------------------------
int yuvConversion(uint8_t* jpeg_buffer, unsigned jpeg_size,
uint8_t** yuv_buffer, unsigned* yuv_size)
{
int width, height;
TJSAMP subsample;
int ret = 0;
ret = tjDecompressHeader2(m_handle, jpeg_buffer, jpeg_size, &width,
&height, (int*)&subsample);
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("vpxEncoder", "Jpeg decode error: %s.", err);
return ret;
}
*yuv_size = tjBufSizeYUV(width, height, subsample);
*yuv_buffer = new uint8_t[*yuv_size];
ret = tjDecompressToYUV(m_handle, jpeg_buffer, jpeg_size,
*yuv_buffer, 0);
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("vpxEncoder", "YUV conversion error: %s.", err);
return ret;
}
return ret;
} // yuvConversion
};
// ========================================================================
JPGDecoder g_jpg_decoder;
// ========================================================================
int vpxEncodeFrame(vpx_codec_ctx_t *codec, vpx_image_t *img,
int frame_index, FILE *out)
{
int got_pkts = 0;
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt = NULL;
const vpx_codec_err_t res = vpx_codec_encode(codec, img, frame_index,
1, 0, VPX_DL_REALTIME);
if (res != VPX_CODEC_OK)
{
Log::error("vpxEncoder", "Failed to encode frame");
return -1;
}
while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL)
{
got_pkts = 1;
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT)
{
fwrite(&pkt->data.frame.sz, 1, sizeof(uint32_t), out);
fwrite(&pkt->data.frame.pts, 1, sizeof(int64_t), out);
fwrite(&pkt->data.frame.flags, 1,
sizeof(vpx_codec_frame_flags_t), out);
fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out);
}
}
return got_pkts;
} // vpxEncodeFrame
// ------------------------------------------------------------------------
void* vpxEncoder(void *obj)
{
VS::setThreadName("vpxEncoder");
FILE* vpx_data = fopen((getRecordingName() + ".video").c_str(), "wb");
if (vpx_data == NULL)
{
Log::error("vorbisEncoder", "Failed to encode ogg file");
return NULL;
}
ThreadData* td = (ThreadData*)obj;
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > >*
jpg_data = (Synchronised<std::list<std::tuple<uint8_t*,
unsigned, int> > >*)td->m_data;
pthread_cond_t* cond_request = td->m_request;
vpx_codec_ctx_t codec;
vpx_codec_enc_cfg_t cfg;
vpx_codec_err_t res = vpx_codec_enc_config_default(vpx_codec_vp8_cx(),
&cfg, 0);
if (res > 0)
{
Log::error("vpxEncoder", "Failed to get default codec config.");
return NULL;
}
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().Height;
int frames_encoded = 0;
cfg.g_w = width;
cfg.g_h = height;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = UserConfigParams::m_record_fps;
int end_usage = UserConfigParams::m_vp_end_usage;
cfg.rc_end_usage = (vpx_rc_mode)end_usage;
cfg.rc_target_bitrate = UserConfigParams::m_vp_bitrate;
if (vpx_codec_enc_init(&codec, vpx_codec_vp8_cx(), &cfg, 0) > 0)
{
Log::error("vpxEncoder", "Failed to initialize encoder");
fclose(vpx_data);
return NULL;
}
while (true)
{
jpg_data->lock();
bool waiting = jpg_data->getData().empty();
while (waiting)
{
pthread_cond_wait(cond_request, jpg_data->getMutex());
waiting = jpg_data->getData().empty();
}
auto& p = jpg_data->getData().front();
uint8_t* jpg = std::get<0>(p);
unsigned jpg_size = std::get<1>(p);
int frame_count = std::get<2>(p);
if (jpg == NULL)
{
jpg_data->getData().clear();
jpg_data->unlock();
break;
}
jpg_data->getData().pop_front();
jpg_data->unlock();
uint8_t* yuv = NULL;
unsigned yuv_size;
int ret = g_jpg_decoder.yuvConversion(jpg, jpg_size, &yuv,
&yuv_size);
if (ret < 0)
{
delete [] yuv;
tjFree(jpg);
continue;
}
assert(yuv_size != 0);
tjFree(jpg);
vpx_image_t each_frame;
vpx_img_wrap(&each_frame, VPX_IMG_FMT_I420, width, height, 1, yuv);
while (frame_count != 0)
{
vpxEncodeFrame(&codec, &each_frame, frames_encoded++, vpx_data);
frame_count--;
}
delete [] yuv;
}
while (vpxEncodeFrame(&codec, NULL, -1, vpx_data));
if (vpx_codec_destroy(&codec))
{
Log::error("vpxEncoder", "Failed to destroy codec.");
return NULL;
}
fclose(vpx_data);
return NULL;
} // vpxEncoder
}
#endif

View File

@ -0,0 +1,34 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// 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))
#ifndef HEADER_VPX_ENCODER_HPP
#define HEADER_VPX_ENCODER_HPP
#include "utils/no_copy.hpp"
#include <pthread.h>
namespace Recorder
{
void* vpxEncoder(void *obj);
};
#endif
#endif

View File

@ -24,6 +24,7 @@
#include "guiengine/message_queue.hpp"
#include "recorder/pulseaudio_recorder.hpp"
#include "recorder/vorbis_encoder.hpp"
#include "recorder/vpx_encoder.hpp"
#include "recorder/wasapi_recorder.hpp"
#include "recorder/webm_writer.hpp"
#include "utils/translation.hpp"
@ -60,7 +61,6 @@ AVIWriter::AVIWriter() : m_idle(true)
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
pthread_cond_init(&m_cond_request, NULL);
pthread_create(&m_record_thread, NULL, &videoRecord, this);
} // AVIWriter
// ----------------------------------------------------------------------------
@ -70,7 +70,6 @@ AVIWriter::~AVIWriter()
addFrameBufferImage(NULL, 0);
if (!waitForReadyToDeleted(2.0f))
Log::info("AVIWriter", "AVIWriter not stopping, exiting anyway.");
pthread_join(m_record_thread, NULL);
pthread_cond_destroy(&m_cond_request);
} // ~AVIWriter
@ -81,93 +80,6 @@ void AVIWriter::resetFrameBufferImage()
m_accumulated_time = 0.0f;
} // resetFrameBufferImage
// ----------------------------------------------------------------------------
int bmpToJPG(uint8_t* raw, unsigned width, unsigned height,
uint8_t** jpeg_buffer, unsigned long* jpeg_size)
{
tjhandle handle = NULL;
int ret = 0;
handle = tjInitCompress();
#ifdef TJFLAG_FASTDCT
ret = tjCompress2(handle, raw, width, 0, height, TJPF_BGR, jpeg_buffer,
jpeg_size, TJSAMP_420, UserConfigParams::m_recorder_jpg_quality,
TJFLAG_FASTDCT);
#else
ret = tjCompress2(handle, raw, width, 0, height, TJPF_BGR, jpeg_buffer,
jpeg_size, TJSAMP_420, UserConfigParams::m_recorder_jpg_quality, 0);
#endif
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("vpxEncoder", "Jpeg encode error: %s.", err);
return ret;
}
tjDestroy(handle);
return ret;
} // bmpToJPG
// ----------------------------------------------------------------------------
int jpgToYuv(uint8_t* jpeg_buffer, unsigned jpeg_size, uint8_t** yuv_buffer,
TJSAMP* yuv_type, unsigned* yuv_size)
{
tjhandle handle = NULL;
int width, height;
TJSAMP subsample;
int ret = 0;
handle = tjInitDecompress();
ret = tjDecompressHeader2(handle, jpeg_buffer, jpeg_size, &width, &height,
(int*)&subsample);
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("vpxEncoder", "Jpeg decode error: %s.", err);
return ret;
}
*yuv_type = subsample;
*yuv_size = tjBufSizeYUV(width, height, subsample);
*yuv_buffer = new uint8_t[*yuv_size];
ret = tjDecompressToYUV(handle, jpeg_buffer, jpeg_size, *yuv_buffer, 0);
if (ret != 0)
{
char* err = tjGetErrorStr();
Log::error("vpxEncoder", "YUV conversion error: %s.", err);
return ret;
}
tjDestroy(handle);
return ret;
} // jpgToYuv
// ----------------------------------------------------------------------------
int vpxEncodeFrame(vpx_codec_ctx_t *codec, vpx_image_t *img, int frame_index,
FILE *out)
{
int got_pkts = 0;
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt = NULL;
const vpx_codec_err_t res = vpx_codec_encode(codec, img, frame_index, 1, 0,
VPX_DL_REALTIME);
if (res != VPX_CODEC_OK)
{
Log::error("vpxEncoder", "Failed to encode frame");
return -1;
}
while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL)
{
got_pkts = 1;
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT)
{
fwrite(&pkt->data.frame.sz, 1, sizeof(uint32_t), out);
fwrite(&pkt->data.frame.pts, 1, sizeof(int64_t), out);
fwrite(&pkt->data.frame.flags, 1, sizeof(vpx_codec_frame_flags_t),
out);
fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out);
}
}
return got_pkts;
} // vpxEncodeFrame
// ----------------------------------------------------------------------------
void AVIWriter::resetCaptureFormat()
{
@ -176,220 +88,6 @@ void AVIWriter::resetCaptureFormat()
m_avi_format = AVI_FORMAT_JPG;
} // resetCaptureFormat
// ----------------------------------------------------------------------------
struct EncoderInfo
{
void* m_data;
pthread_cond_t* m_enc_request;
}; // EncoderInfo
// ----------------------------------------------------------------------------
void* AVIWriter::vpxEncoder(void *obj)
{
VS::setThreadName("vpxEncoder");
FILE* vpx_data = fopen((m_recording_target.getAtomic() + ".vp_data")
.c_str(), "wb");
if (vpx_data == NULL)
{
Log::error("vorbisEncoder", "Failed to encode ogg file");
return NULL;
}
EncoderInfo* ei = (EncoderInfo*)obj;
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > >* jpg_data =
(Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > >*)
ei->m_data;
pthread_cond_t* cond_request = ei->m_enc_request;
vpx_codec_ctx_t codec;
vpx_codec_enc_cfg_t cfg;
vpx_codec_err_t res = vpx_codec_enc_config_default(vpx_codec_vp8_cx(),
&cfg, 0);
if (res > 0)
{
Log::error("vpxEncoder", "Failed to get default codec config.");
return NULL;
}
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().Height;
int frames_encoded = 0;
cfg.g_w = width;
cfg.g_h = height;
cfg.g_timebase.num = 1;
cfg.g_timebase.den = UserConfigParams::m_record_fps;
int end_usage = UserConfigParams::m_vp_end_usage;
cfg.rc_end_usage = (vpx_rc_mode)end_usage;
cfg.rc_target_bitrate = UserConfigParams::m_vp_bitrate;
if (vpx_codec_enc_init(&codec, vpx_codec_vp8_cx(), &cfg, 0) > 0)
{
Log::error("vpxEncoder", "Failed to initialize encoder");
fclose(vpx_data);
return NULL;
}
while (true)
{
jpg_data->lock();
bool waiting = jpg_data->getData().empty();
while (waiting)
{
pthread_cond_wait(cond_request, jpg_data->getMutex());
waiting = jpg_data->getData().empty();
}
auto& p = jpg_data->getData().front();
uint8_t* jpg = std::get<0>(p);
unsigned jpg_size = std::get<1>(p);
int frame_count = std::get<2>(p);
if (jpg == NULL)
{
jpg_data->getData().clear();
jpg_data->unlock();
break;
}
jpg_data->getData().pop_front();
jpg_data->unlock();
uint8_t* yuv = NULL;
TJSAMP yuv_type;
unsigned yuv_size;
int ret = jpgToYuv(jpg, jpg_size, &yuv, &yuv_type, &yuv_size);
if (ret < 0)
{
delete [] yuv;
tjFree(jpg);
continue;
}
assert(yuv_type == TJSAMP_420 && yuv_size != 0);
tjFree(jpg);
vpx_image_t each_frame;
vpx_img_wrap(&each_frame, VPX_IMG_FMT_I420, width, height, 1, yuv);
while (frame_count != 0)
{
vpxEncodeFrame(&codec, &each_frame, frames_encoded++, vpx_data);
frame_count--;
}
delete [] yuv;
}
while (vpxEncodeFrame(&codec, NULL, -1, vpx_data));
if (vpx_codec_destroy(&codec))
{
Log::error("vpxEncoder", "Failed to destroy codec.");
return NULL;
}
fclose(vpx_data);
return NULL;
} // vpxEncoder
// ----------------------------------------------------------------------------
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > > jpg_data;
pthread_cond_t vpx_enc_request;
pthread_t audio_thread, vpx_enc_thread;
EncoderInfo vpx_ei;
// ----------------------------------------------------------------------------
void* AVIWriter::videoRecord(void *obj)
{
VS::setThreadName("videoRecord");
vpx_ei.m_data = &jpg_data;
vpx_ei.m_enc_request = &vpx_enc_request;
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->m_idle.setAtomic(true);
jpg_data.lock();
jpg_data.getData().emplace_back((uint8_t*)NULL, 0, 0);
pthread_cond_signal(vpx_ei.m_enc_request);
jpg_data.unlock();
pthread_join(audio_thread, NULL);
pthread_join(vpx_enc_thread, NULL);
Recorder::writeWebm(m_recording_target.getAtomic() + ".vp_data",
m_recording_target.getAtomic() + ".vb_data");
avi_writer->m_fbi_queue.getData().clear();
avi_writer->m_fbi_queue.unlock();
continue;
}
else if (fbi == NULL)
{
avi_writer->m_idle.setAtomic(true);
jpg_data.lock();
jpg_data.getData().emplace_back((uint8_t*)NULL, 0, 0);
pthread_cond_signal(vpx_ei.m_enc_request);
jpg_data.unlock();
//pthread_join(audio_thread, NULL);
//pthread_join(vpx_enc_thread, NULL);
avi_writer->setCanBeDeleted();
avi_writer->m_fbi_queue.getData().clear();
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;
}
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;
uint8_t* jpg = NULL;
unsigned long jpg_size = 0;
bmpToJPG(orig_fbi + area, width, height, &jpg, &jpg_size);
delete[] orig_fbi;
jpg_data.lock();
jpg_data.getData().emplace_back(jpg, jpg_size, frame_count);
pthread_cond_signal(vpx_ei.m_enc_request);
jpg_data.unlock();
}
return NULL;
} // videoRecord
// ----------------------------------------------------------------------------
int AVIWriter::getFrameCount(double rate)
{
@ -411,40 +109,6 @@ int AVIWriter::getFrameCount(double rate)
// ----------------------------------------------------------------------------
void AVIWriter::captureFrameBufferImage()
{
if (m_idle.getAtomic())
{
m_idle.setAtomic(false);
pthread_create(&audio_thread, NULL, &Recorder::audioRecorder, &m_idle);
pthread_cond_init(vpx_ei.m_enc_request, NULL);
pthread_create(&vpx_enc_thread, NULL, &vpxEncoder, &vpx_ei);
}
int pbo_read = -1;
if (m_pbo_use > 3 && m_pbo_use % 3 == 0)
m_pbo_use = 3;
auto rate = std::chrono::high_resolution_clock::now() - m_framerate_timer;
m_framerate_timer = std::chrono::high_resolution_clock::now();
glReadBuffer(GL_BACK);
if (m_pbo_use >= 3)
{
int frame_count = getFrameCount(std::chrono::duration_cast
<std::chrono::duration<double> >(rate).count());
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, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
} // captureFrameBufferImage
// ----------------------------------------------------------------------------

View File

@ -242,10 +242,6 @@ public:
// ------------------------------------------------------------------------
~AVIWriter();
// ------------------------------------------------------------------------
static void* videoRecord(void *obj);
// ------------------------------------------------------------------------
static void* vpxEncoder(void *obj);
// ------------------------------------------------------------------------
static void setRecordingTarget(const std::string& name)
{
m_recording_target.setAtomic(name);