Move all recording code into recorder folder
This commit is contained in:
parent
2d4bfa9fb0
commit
ea6719990a
@ -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",
|
||||
|
@ -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
|
||||
|
362
src/recorder/recorder_common.cpp
Normal file
362
src/recorder/recorder_common.cpp
Normal 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
|
58
src/recorder/recorder_common.hpp
Normal file
58
src/recorder/recorder_common.hpp
Normal 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
|
@ -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
|
||||
|
204
src/recorder/vpx_encoder.cpp
Normal file
204
src/recorder/vpx_encoder.cpp
Normal 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
|
34
src/recorder/vpx_encoder.hpp
Normal file
34
src/recorder/vpx_encoder.hpp
Normal 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
|
@ -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
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user