Remove STK headers in recorder and use c++11 thread library

This commit is contained in:
Benau 2017-04-09 14:16:45 +08:00
parent c00c35e59a
commit 156b799011
21 changed files with 1288 additions and 930 deletions

View File

@ -57,7 +57,7 @@
#include "modes/profile_world.hpp"
#include "modes/world.hpp"
#include "physics/physics.hpp"
#include "recorder/recorder_common.hpp"
#include "recorder/openglrecorder.h"
#include "scriptengine/property_animator.hpp"
#include "states_screens/dialogs/confirm_resolution_dialog.hpp"
#include "states_screens/state_manager.hpp"
@ -170,7 +170,7 @@ IrrDriver::~IrrDriver()
delete m_wind;
delete m_renderer;
#ifdef ENABLE_RECORDER
Recorder::destroyRecorder();
ogrDestroy();
#endif
} // ~IrrDriver
@ -603,6 +603,46 @@ void IrrDriver::initDevice()
sml->drop();
m_actual_screen_size = m_video_driver->getCurrentRenderTargetSize();
#ifdef ENABLE_RECORDER
RecorderConfig cfg;
cfg.m_triple_buffering = 1;
cfg.m_record_audio = 1;
cfg.m_width = m_actual_screen_size.Width;
cfg.m_height = m_actual_screen_size.Height;
int vf = UserConfigParams::m_record_format;
cfg.m_video_format = (VideoFormat)vf;
cfg.m_audio_format = REC_AF_VORBIS;
cfg.m_audio_bitrate = 112000;
cfg.m_video_bitrate = UserConfigParams::m_vp_bitrate;
cfg.m_record_fps = UserConfigParams::m_record_fps;
cfg.m_record_jpg_quality = UserConfigParams::m_recorder_jpg_quality;
ogrInitConfig(&cfg);
ogrRegGeneralCallback(REC_CBT_START_RECORDING,
[] (void* user_data) { MessageQueue::add
(MessageQueue::MT_GENERIC, _("Video recording started.")); }, NULL);
ogrRegGeneralCallback(REC_CBT_ERROR_RECORDING,
[] (void* user_data) { MessageQueue::add
(MessageQueue::MT_ERROR, _("Error when saving video.")); }, NULL);
ogrRegGeneralCallback(REC_CBT_SLOW_RECORDING,
[] (void* user_data) { MessageQueue::add
(MessageQueue::MT_ERROR, _("Encoding is too slow, dropping frames."));
}, NULL);
ogrRegGeneralCallback(REC_CBT_WAIT_RECORDING,
[] (void* user_data) { MessageQueue::add
(MessageQueue::MT_GENERIC, _("Please wait while encoding is finished."
)); }, NULL);
ogrRegStringCallback(REC_CBT_SAVED_RECORDING,
[] (const char* s, void* user_data) { MessageQueue::add
(MessageQueue::MT_GENERIC, _("Video saved in \"%s\".", s));
}, NULL);
ogrRegIntCallback(REC_CBT_PROGRESS_RECORDING,
[] (const int i, void* user_data)
{ Log::info("Recorder", "%d%% of video encoding finished", i);}, NULL);
#endif
#ifndef SERVER_ONLY
if(CVS->isGLSL())
m_renderer = new ShaderBasedRenderer();
@ -927,7 +967,7 @@ void IrrDriver::applyResolutionSettings()
VAOManager::getInstance()->kill();
STKTexManager::getInstance()->kill();
#ifdef ENABLE_RECORDER
Recorder::destroyRecorder();
ogrDestroy();
m_recording = false;
#endif
// initDevice will drop the current device.
@ -1896,7 +1936,7 @@ void IrrDriver::update(float dt)
// printRenderStats();
#ifdef ENABLE_RECORDER
if (m_recording)
Recorder::captureFrameBufferImage();
ogrCapture();
#endif
} // update
@ -1913,21 +1953,27 @@ void IrrDriver::setRecording(bool val)
return;
if (val == true)
{
if (!Recorder::isRecording())
if (ogrCapturing() > 0)
return;
m_recording = val;
std::string track_name = World::getWorld() != NULL ?
race_manager->getTrackName() : "menu";
Recorder::setRecordingName(file_manager->getScreenshotDir() +
track_name);
Recorder::prepareCapture();
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video recording started."));
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);
ogrSetSavedName((file_manager->getScreenshotDir() +
track_name + "_" + time_buffer).c_str());
ogrPrepareCapture();
}
else
{
m_recording = val;
Recorder::stopRecording();
ogrStopCapture();
}
#endif
} // setRecording

View File

@ -0,0 +1,298 @@
#ifdef ENABLE_RECORDER
#include "capture_library.hpp"
#include "mjpeg_writer.hpp"
#include "mkv_writer.hpp"
#include "recorder_private.hpp"
#include "pulseaudio_recorder.hpp"
#include "vpx_encoder.hpp"
#include "wasapi_recorder.hpp"
extern "C"
{
#include <GL/glew.h>
}
const uint32_t E_GL_PIXEL_PACK_BUFFER = 0x88EB;
const uint32_t E_GL_STREAM_READ = 0x88E1;
const uint32_t E_GL_READ_ONLY = 0x88B8;
const uint32_t E_GL_RGB = 0x1907;
const uint32_t E_GL_UNSIGNED_BYTE = 0x1401;
// ----------------------------------------------------------------------------
CaptureLibrary::CaptureLibrary(RecorderConfig* rc)
{
m_recorder_cfg = rc;
m_destroy.store(false);
m_sound_stop.store(true);
m_display_progress.store(false);
m_compress_handle = tjInitCompress();
m_decompress_handle = tjInitDecompress();
if (m_recorder_cfg->m_triple_buffering > 0)
{
glGenBuffers(3, m_pbo);
for (int i = 0; i < 3; i++)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[i]);
glBufferData(GL_PIXEL_PACK_BUFFER, m_recorder_cfg->m_width *
m_recorder_cfg->m_height * 4, NULL, GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}
m_capture_thread = std::thread(CaptureLibrary::captureConversion, this);
} // CaptureLibrary
// ----------------------------------------------------------------------------
CaptureLibrary::~CaptureLibrary()
{
m_destroy.store(true);
addFrameBufferImage(NULL, ogrCapturing() > 0 ? -1 : 0);
m_capture_thread.join();
tjDestroy(m_compress_handle);
tjDestroy(m_decompress_handle);
if (m_recorder_cfg->m_triple_buffering > 0)
{
glDeleteBuffers(3, m_pbo);
}
} // ~CaptureLibrary
// ----------------------------------------------------------------------------
void CaptureLibrary::reset()
{
runCallback(REC_CBT_START_RECORDING, NULL);
m_pbo_use = 0;
m_accumulated_time = 0.;
assert(m_sound_stop.load() && ogrCapturing() == 0);
if (m_recorder_cfg->m_record_audio > 0)
{
m_sound_stop.store(false);
m_audio_enc_thread = std::thread(Recorder::audioRecorder, this);
}
setCapturing(true);
switch (m_recorder_cfg->m_video_format)
{
case REC_VF_VP8:
case REC_VF_VP9:
m_video_enc_thread = std::thread(Recorder::vpxEncoder, this);
break;
case REC_VF_MJPEG:
m_video_enc_thread = std::thread(Recorder::mjpegWriter, this);
break;
case REC_VF_H264:
break;
}
} // reset
// ----------------------------------------------------------------------------
int CaptureLibrary::bmpToJPG(uint8_t* raw, unsigned width, unsigned height,
uint8_t** jpeg_buffer, unsigned long* jpeg_size)
{
int ret = 0;
#ifdef TJFLAG_FASTDCT
ret = tjCompress2(m_compress_handle, raw, width, 0, height, TJPF_RGB,
jpeg_buffer, jpeg_size, TJSAMP_420,
m_recorder_cfg->m_record_jpg_quality, TJFLAG_FASTDCT);
#else
ret = tjCompress2(m_compress_handle, raw, width, 0, height, TJPF_RGB,
jpeg_buffer, jpeg_size, TJSAMP_420,
m_recorder_cfg->m_record_jpg_quality, 0);
#endif
if (ret != 0)
{
char* err = tjGetErrorStr();
printf("Jpeg encode error: %s.", err);
return ret;
}
return ret;
} // bmpToJPG
// ----------------------------------------------------------------------------
int CaptureLibrary::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_decompress_handle, jpeg_buffer, jpeg_size,
&width, &height, (int*)&subsample);
if (ret != 0)
{
char* err = tjGetErrorStr();
printf("Jpeg decode error: %s.", err);
return ret;
}
*yuv_size = tjBufSizeYUV(width, height, subsample);
*yuv_buffer = new uint8_t[*yuv_size];
ret = tjDecompressToYUV(m_decompress_handle, jpeg_buffer, jpeg_size,
*yuv_buffer, 0);
if (ret != 0)
{
char* err = tjGetErrorStr();
printf("YUV conversion error: %s.", err);
return ret;
}
return ret;
} // yuvConversion
// ----------------------------------------------------------------------------
int CaptureLibrary::getFrameCount(double rate)
{
const double frame_rate = 1. / double(m_recorder_cfg->m_record_fps);
m_accumulated_time += rate;
if (m_accumulated_time < frame_rate)
{
return 0;
}
int frame_count = 0;
while (m_accumulated_time >= frame_rate)
{
frame_count++;
m_accumulated_time = m_accumulated_time - frame_rate;
}
return frame_count;
} // getFrameCount
// ----------------------------------------------------------------------------
void CaptureLibrary::capture()
{
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();
const unsigned width = m_recorder_cfg->m_width;
const unsigned height = m_recorder_cfg->m_height;
const bool use_pbo = m_recorder_cfg->m_triple_buffering > 0;
if (m_pbo_use >= 3)
{
int frame_count = getFrameCount(std::chrono::duration_cast
<std::chrono::duration<double> >(rate).count());
if (frame_count != 0)
{
const unsigned size = width * height * 4;
uint8_t* fbi = new uint8_t[size];
if (use_pbo)
{
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);
memcpy(fbi, ptr, size);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
}
else
{
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE,
fbi);
}
addFrameBufferImage(fbi, frame_count);
}
}
int pbo_use = m_pbo_use++ % 3;
if (!use_pbo)
return;
assert(pbo_read == -1 || pbo_use == pbo_read);
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[pbo_use]);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
} // capture
// ----------------------------------------------------------------------------
void CaptureLibrary::captureConversion(CaptureLibrary* cl)
{
setThreadName("captureConvert");
while (true)
{
std::unique_lock<std::mutex> ul(cl->m_fbi_list_mutex);
cl->m_fbi_list_ready.wait(ul, [&cl]
{ return !cl->m_fbi_list.empty(); });
auto& p = cl->m_fbi_list.front();
uint8_t* fbi = p.first;
int frame_count = p.second;
if (frame_count == -1)
{
cl->m_fbi_list.clear();
ul.unlock();
if (cl->m_recorder_cfg->m_record_audio > 0)
{
cl->m_sound_stop.store(true);
cl->m_audio_enc_thread.join();
}
std::unique_lock<std::mutex> ulj(cl->m_jpg_list_mutex);
if (!cl->m_destroy.load() && cl->m_jpg_list.size() > 100)
{
runCallback(REC_CBT_WAIT_RECORDING, NULL);
}
cl->m_display_progress.store(true);
cl->m_jpg_list.emplace_back((uint8_t*)NULL, 0, 0);
cl->m_jpg_list_ready.notify_one();
ulj.unlock();
cl->m_video_enc_thread.join();
cl->m_display_progress.store(false);
std::string f = Recorder::writeMKV(getSavedName() + ".video",
getSavedName() + ".audio");
if (cl->m_destroy.load())
{
return;
}
if (f.empty())
{
runCallback(REC_CBT_ERROR_RECORDING, NULL);
}
else
{
runCallback(REC_CBT_SAVED_RECORDING, f.c_str());
}
setCapturing(false);
continue;
}
else if (fbi == NULL)
{
cl->m_fbi_list.clear();
ul.unlock();
return;
}
const bool too_slow = cl->m_fbi_list.size() > 50;
if (too_slow)
{
runCallback(REC_CBT_SLOW_RECORDING, NULL);
delete [] fbi;
cl->m_fbi_list.pop_front();
for (auto& p : cl->m_fbi_list)
delete [] p.first;
cl->m_fbi_list.clear();
ul.unlock();
continue;
}
cl->m_fbi_list.pop_front();
ul.unlock();
uint8_t* orig_fbi = fbi;
const unsigned width = cl->m_recorder_cfg->m_width;
const unsigned height = cl->m_recorder_cfg->m_height;
const unsigned area = width * height;
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;
cl->bmpToJPG(orig_fbi, width, height, &jpg, &jpg_size);
delete[] orig_fbi;
std::lock_guard<std::mutex> lg(cl->m_jpg_list_mutex);
cl->m_jpg_list.emplace_back(jpg, jpg_size, frame_count);
cl->m_jpg_list_ready.notify_one();
}
} // captureConversion
#endif

View File

@ -0,0 +1,118 @@
#ifdef ENABLE_RECORDER
#ifndef HEADER_CAPTURE_LIBRARY_HPP
#define HEADER_CAPTURE_LIBRARY_HPP
#if defined(_MSC_VER) && _MSC_VER < 1700
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif
#include <atomic>
#include <cassert>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <list>
#include <memory>
#include <mutex>
#include <thread>
#include <turbojpeg.h>
struct AudioEncoderData
{
enum AudioType { AT_FLOAT, AT_PCM };
std::mutex* m_mutex;
std::condition_variable* m_cv;
std::list<int8_t*>* m_buf_list;
uint32_t m_sample_rate;
uint32_t m_channels;
uint32_t m_audio_bitrate;
AudioType m_audio_type;
};
struct RecorderConfig;
typedef std::list<std::tuple<uint8_t*, unsigned, int> > JPGList;
class CaptureLibrary
{
private:
RecorderConfig* m_recorder_cfg;
std::atomic_bool m_destroy, m_display_progress, m_sound_stop;
tjhandle m_compress_handle, m_decompress_handle;
JPGList m_jpg_list;
std::mutex m_jpg_list_mutex;
std::condition_variable m_jpg_list_ready;
std::list<std::pair<uint8_t*, int> > m_fbi_list;
std::mutex m_fbi_list_mutex;
std::condition_variable m_fbi_list_ready;
std::thread m_capture_thread, m_audio_enc_thread, m_video_enc_thread;
uint32_t m_pbo[3];
unsigned m_pbo_use;
std::chrono::high_resolution_clock::time_point m_framerate_timer;
double m_accumulated_time;
// ------------------------------------------------------------------------
int getFrameCount(double rate);
// ------------------------------------------------------------------------
void addFrameBufferImage(uint8_t* fbi, int frame_count)
{
std::lock_guard<std::mutex> lock(m_fbi_list_mutex);
m_fbi_list.emplace_back(fbi, frame_count);
m_fbi_list_ready.notify_one();
}
public:
// ------------------------------------------------------------------------
CaptureLibrary(RecorderConfig* rc);
// ------------------------------------------------------------------------
~CaptureLibrary();
// ------------------------------------------------------------------------
void capture();
// ------------------------------------------------------------------------
void stopCapture() { addFrameBufferImage(NULL, -1); }
// ------------------------------------------------------------------------
void reset();
// ------------------------------------------------------------------------
int bmpToJPG(uint8_t* raw, unsigned width, unsigned height,
uint8_t** jpeg_buffer, unsigned long* jpeg_size);
// ------------------------------------------------------------------------
int yuvConversion(uint8_t* jpeg_buffer, unsigned jpeg_size,
uint8_t** yuv_buffer, unsigned* yuv_size);
// ------------------------------------------------------------------------
JPGList* getJPGList() { return &m_jpg_list; }
// ------------------------------------------------------------------------
std::mutex* getJPGListMutex() { return &m_jpg_list_mutex; }
// ------------------------------------------------------------------------
std::condition_variable* getJPGListCV() { return &m_jpg_list_ready; }
// ------------------------------------------------------------------------
bool displayingProgress() const { return m_display_progress.load(); }
// ------------------------------------------------------------------------
bool getSoundStop() const { return m_sound_stop.load(); }
// ------------------------------------------------------------------------
bool getDestroy() const { return m_destroy.load(); }
// ------------------------------------------------------------------------
const RecorderConfig& getRecorderConfig() const { return *m_recorder_cfg; }
// ------------------------------------------------------------------------
static void captureConversion(CaptureLibrary* cl);
};
#endif
#endif

View File

@ -1,84 +0,0 @@
// 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.
#ifdef ENABLE_RECORDER
#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>
namespace Recorder
{
// ------------------------------------------------------------------------
void* jpgWriter(void *obj)
{
VS::setThreadName("jpgWriter");
FILE* jpg_writer = fopen((getRecordingName() + ".video").c_str(), "wb");
if (jpg_writer == NULL)
{
Log::error("jpgWriter", "Failed to open file for writing");
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;
int64_t frames_encoded = 0;
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);
uint32_t 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();
while (frame_count != 0)
{
fwrite(&jpg_size, 1, sizeof(uint32_t), jpg_writer);
fwrite(&frames_encoded, 1, sizeof(int64_t), jpg_writer);
fwrite(&jpg_size, 1, sizeof(uint32_t), jpg_writer);
fwrite(jpg, 1, jpg_size, jpg_writer);
frame_count--;
frames_encoded++;
}
tjFree(jpg);
}
fclose(jpg_writer);
return NULL;
} // jpgWriter
}
#endif

View File

@ -0,0 +1,69 @@
// 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.
#ifdef ENABLE_RECORDER
#include "capture_library.hpp"
#include "recorder_private.hpp"
#include <turbojpeg.h>
namespace Recorder
{
// ------------------------------------------------------------------------
void mjpegWriter(CaptureLibrary* cl)
{
setThreadName("mjpegWriter");
FILE* mjpeg_writer = fopen((getSavedName() + ".video").c_str(), "wb");
if (mjpeg_writer == NULL)
{
printf("Failed to open file for writing mjpeg.\n");
return;
}
int64_t frames_encoded = 0;
while (true)
{
std::unique_lock<std::mutex> ul(*cl->getJPGListMutex());
cl->getJPGListCV()->wait(ul, [&cl]
{ return !cl->getJPGList()->empty(); });
auto& p = cl->getJPGList()->front();
uint8_t* jpg = std::get<0>(p);
uint32_t jpg_size = std::get<1>(p);
int frame_count = std::get<2>(p);
if (jpg == NULL)
{
cl->getJPGList()->clear();
ul.unlock();
break;
}
cl->getJPGList()->pop_front();
ul.unlock();
while (frame_count != 0)
{
fwrite(&jpg_size, 1, sizeof(uint32_t), mjpeg_writer);
fwrite(&frames_encoded, 1, sizeof(int64_t), mjpeg_writer);
fwrite(&jpg_size, 1, sizeof(uint32_t), mjpeg_writer);
fwrite(jpg, 1, jpg_size, mjpeg_writer);
frame_count--;
frames_encoded++;
}
tjFree(jpg);
}
fclose(mjpeg_writer);
} // mjpegWriter
};
#endif

View File

@ -18,13 +18,16 @@
#ifdef ENABLE_RECORDER
#ifndef HEADER_JPG_WRITER_HPP
#define HEADER_JPG_WRITER_HPP
#ifndef HEADER_MJPEG_WRITER_HPP
#define HEADER_MJPEG_WRITER_HPP
class CaptureLibrary;
namespace Recorder
{
void* jpgWriter(void *obj);
void mjpegWriter(CaptureLibrary* cl);
};
#endif
#endif

View File

@ -17,12 +17,9 @@
#ifdef ENABLE_RECORDER
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#include "recorder_private.hpp"
#include <cstring>
#include <list>
#include <mkvmuxer/mkvmuxer.h>
#include <mkvmuxer/mkvwriter.h>
@ -35,28 +32,20 @@ namespace Recorder
// ------------------------------------------------------------------------
std::string writeMKV(const std::string& video, const std::string& audio)
{
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 no_ext = StringUtils::removeExtension(video);
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
std::string file_name = no_ext + "-" + time_buffer +
(vf == VF_VP8 || vf == VF_VP9 ? ".webm" : ".mkv");
std::string no_ext = video.substr(0, video.find_last_of("."));
VideoFormat vf = getConfig()->m_video_format;
std::string file_name = no_ext +
(vf == REC_VF_VP8 || vf == REC_VF_VP9 ? ".webm" : ".mkv");
mkvmuxer::MkvWriter writer;
if (!writer.Open(file_name.c_str()))
{
Log::error("writeMKV", "Error while opening output file.");
printf("Error while opening output file.\n");
return "";
}
mkvmuxer::Segment muxer_segment;
if (!muxer_segment.Init(&writer))
{
Log::error("writeMKV", "Could not initialize muxer segment.");
printf("Could not initialize muxer segment.\n");;
return "";
}
std::list<mkvmuxer::Frame*> audio_frames;
@ -70,18 +59,18 @@ namespace Recorder
uint32_t sample_rate, channels;
fread(&sample_rate, 1, sizeof(uint32_t), input);
fread(&channels, 1, sizeof(uint32_t), input);
uint64_t aud_track = muxer_segment.AddAudioTrack(sample_rate,
channels, 0);
uint64_t aud_track = muxer_segment.AddAudioTrack(sample_rate, channels,
0);
if (!aud_track)
{
Log::error("writeMKV", "Could not add audio track.");
printf("Could not add audio track.\n");
return "";
}
mkvmuxer::AudioTrack* const at = static_cast<mkvmuxer::AudioTrack*>
(muxer_segment.GetTrackByNumber(aud_track));
if (!at)
{
Log::error("writeMKV", "Could not get audio track.");
printf("Could not get audio track.\n");
return "";
}
uint32_t codec_private_size;
@ -89,7 +78,7 @@ namespace Recorder
fread(buf, 1, codec_private_size, input);
if (!at->SetCodecPrivate(buf, codec_private_size))
{
Log::warn("writeMKV", "Could not add audio private data.");
printf("Could not add audio private data.\n");
return "";
}
while (fread(buf, 1, 12, input) == 12)
@ -102,7 +91,7 @@ namespace Recorder
mkvmuxer::Frame* audio_frame = new mkvmuxer::Frame();
if (!audio_frame->Init(buf, frame_size))
{
Log::error("writeMKV", "Failed to construct a frame.");
printf("Failed to construct a frame.\n");
return "";
}
audio_frame->set_track_number(aud_track);
@ -113,37 +102,36 @@ namespace Recorder
fclose(input);
if (remove(audio.c_str()) != 0)
{
Log::warn("writeMKV", "Failed to remove audio data file");
printf("Failed to remove audio data file\n");
}
}
uint64_t vid_track = muxer_segment.AddVideoTrack(
irr_driver->getActualScreenSize().Width,
irr_driver->getActualScreenSize().Height, 0);
uint64_t vid_track = muxer_segment.AddVideoTrack(getConfig()->m_width,
getConfig()->m_height, 0);
if (!vid_track)
{
Log::error("writeMKV", "Could not add video track.");
printf("Could not add video track.\n");
return "";
}
mkvmuxer::VideoTrack* const vt = static_cast<mkvmuxer::VideoTrack*>(
muxer_segment.GetTrackByNumber(vid_track));
if (!vt)
{
Log::error("writeMKV", "Could not get video track.");
printf("Could not get video track.\n");
return "";
}
vt->set_frame_rate(UserConfigParams::m_record_fps);
vt->set_frame_rate(getConfig()->m_record_fps);
switch (vf)
{
case VF_VP8:
case REC_VF_VP8:
vt->set_codec_id("V_VP8");
break;
case VF_VP9:
case REC_VF_VP9:
vt->set_codec_id("V_VP9");
break;
case VF_MJPEG:
case REC_VF_MJPEG:
vt->set_codec_id("V_MJPEG");
break;
case VF_H264:
case REC_VF_H264:
vt->set_codec_id("V_MPEG4/ISO/AVC");
break;
}
@ -156,17 +144,17 @@ namespace Recorder
memcpy(&timestamp, buf + sizeof(uint32_t), sizeof(int64_t));
memcpy(&flag, buf + sizeof(uint32_t) + sizeof(int64_t),
sizeof(uint32_t));
timestamp *= 1000000000ll / UserConfigParams::m_record_fps;
timestamp *= 1000000000ll / getConfig()->m_record_fps;
fread(buf, 1, frame_size, input);
mkvmuxer::Frame muxer_frame;
if (!muxer_frame.Init(buf, frame_size))
{
Log::error("writeMKV", "Failed to construct a frame.");
printf("Failed to construct a frame.\n");
return "";
}
muxer_frame.set_track_number(vid_track);
muxer_frame.set_timestamp(timestamp);
if (vf == VF_VP8 || vf == VF_VP9)
if (vf == REC_VF_VP8 || vf == REC_VF_VP9)
{
muxer_frame.set_is_key((flag & VPX_FRAME_IS_KEY) != 0);
}
@ -182,7 +170,7 @@ namespace Recorder
{
if (!muxer_segment.AddGenericFrame(cur_aud_frame))
{
Log::error("writeMKV", "Could not add audio frame.");
printf("Could not add audio frame.\n");
return "";
}
delete cur_aud_frame;
@ -197,7 +185,7 @@ namespace Recorder
}
if (!muxer_segment.AddGenericFrame(&muxer_frame))
{
Log::error("writeMKV", "Could not add video frame.");
printf("Could not add video frame.\n");
return "";
}
}
@ -209,15 +197,16 @@ namespace Recorder
}
if (remove(video.c_str()) != 0)
{
Log::warn("writeMKV", "Failed to remove video data file");
printf("Failed to remove video data file\n");
}
if (!muxer_segment.Finalize())
{
Log::error("writeMKV", "Finalization of segment failed.");
printf("Finalization of segment failed.\n");
return "";
}
writer.Close();
return file_name;
} // writeMKV
};
#endif

View File

@ -26,6 +26,7 @@ namespace Recorder
{
std::string writeMKV(const std::string& video, const std::string& audio);
};
#endif
#endif

View File

@ -0,0 +1,211 @@
#ifdef ENABLE_RECORDER
#ifndef HEADER_OPENGLRECORDER_H
#define HEADER_OPENGLRECORDER_H
/**
* \mainpage libopenglrecorder
*
* libopenglrecorder is a library allowing (optional) async readback opengl
* framebuffer with audio recording. It will do video and audio encoding
* together. The user of this library has to setup opengl context himself
* and load suitable callback. All function here should be called by the same
* thread which created the opengl context.
*/
/**
* List of audio encoder supported by libopenglrecorder.
*/
enum AudioFormat
{
/**
* Vorbis encoder by libvorbisenc.
*/
REC_AF_VORBIS,
};
/**
* List of video encoder supported by libopenglrecorder
*/
enum VideoFormat
{
/**
* VP8 encoder by libvpx.
*/
REC_VF_VP8,
/**
* VP9 encoder by libvpx. Notice: this is very slow.
*/
REC_VF_VP9,
/**
* MJPEG encoder, it's provided by turbojpeg and will always present.
*/
REC_VF_MJPEG,
/**
* H264 encoder by openh264.
*/
REC_VF_H264
};
/**
* Callback which takes a string pointer to work with.
*/
typedef void(*StringCallback)(const char* s, void* user_data);
/**
* Callback which takes a int to work with.
*/
typedef void(*IntCallback)(const int i, void* user_data);
/**
* Callback which takes nothing (void) to work with.
*/
typedef void(*GeneralCallback)(void* user_data);
/**
* List of callbacks currently using.
*/
enum CallBackType
{
/**
* A \ref GeneralCallback which notify the starting of recording.
*/
REC_CBT_START_RECORDING = 0,
/**
* A \ref StringCallback which notify the saved filename of recorded file.
*/
REC_CBT_SAVED_RECORDING,
/**
* A \ref GeneralCallback which notify error when recording.
*/
REC_CBT_ERROR_RECORDING,
/**
* A \ref IntCallback which the tells the progress percentage for video
* encoding after the issue of \ref ogrStopCapture.
*/
REC_CBT_PROGRESS_RECORDING,
/**
* A \ref GeneralCallback which notify user if there is still video
* encoding happening after the issue of \ref ogrStopCapture.
*/
REC_CBT_WAIT_RECORDING,
/**
* A \ref GeneralCallback which notify user if the coversion to jpeg
* from opengl frame buffer image is too slow, so libopenglrecorder will
* drop frames.
*/
REC_CBT_SLOW_RECORDING,
/**
* Total callback numbers.
*/
REC_CBT_COUNT
};
/**
* Settings for libopenglrecorder
*/
struct RecorderConfig
{
/**
* 1 if triple buffering is used when capture the opengl buffer.
* It will create 3 pixel buffer objects for async reading, recommend on.
* 0 otherwise.
*/
int m_triple_buffering;
/**
* 1 if audio is recorded together, it will use wasapi in windows,
* pulseaudio in linux. 0 otherwise.
*/
int m_record_audio;
/**
* Width of the capture, it will be floored down to the closest integer divisble
* by 8 if needed.
*/
unsigned int m_width;
/**
* Height of the capture, it will be floored down to the closest integer divisble
* by 2 if needed.
*/
unsigned int m_height;
/**
* Encoder for video, see \ref VideoFormat.
*/
VideoFormat m_video_format;
/**
* Encoder for video, see \ref AudioFormat.
*/
AudioFormat m_audio_format;
/**
* Bitrate for audio encoding.
*/
unsigned int m_video_bitrate;
/**
* Bitrate for video encoding.
*/
unsigned int m_audio_bitrate;
/**
* Framerate for the video, 30 is recommended.
*/
unsigned int m_record_fps;
/**
* Jpeg quality for the captured image, from 0 to 100.
*/
unsigned int m_record_jpg_quality;
};
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Initialize the configuration, call this first before using the library.
*/
void ogrInitConfig(RecorderConfig*);
/**
* Set the full path with filename for saving the recorded video, excluding
* extension, libopenglrecorder will automatically add .webm or .mkv as needed.
*/
void ogrSetSavedName(const char*);
/**
* Reset libopenglrecorder, call this before first \ref ogrCapture.
*/
void ogrPrepareCapture(void);
/**
* Capture the current frame buffer image as frame, make sure you have called
* \ref ogrPrepareCapture first.
*/
void ogrCapture(void);
/**
* Stop the recorder of libopenglrecorder.
*/
void ogrStopCapture(void);
/**
* Destroy the recorder of libopenglrecorder.
*/
void ogrDestroy(void);
/**
* (Optional) Register the callback(s) for \ref GeneralCallback, you have to
* make sure the enum CallBackType matches the callback type, see
* \ref CallBackType.
*/
void ogrRegGeneralCallback(CallBackType, GeneralCallback, void*);
/**
* (Optional) Register the callback(s) for \ref StringCallback, you have to
* make sure the enum CallBackType matches the callback type, see
* \ref CallBackType.
*/
void ogrRegStringCallback(CallBackType, StringCallback, void*);
/**
* (Optional) Register the callback(s) for \ref IntCallback, you have to
* make sure the enum CallBackType matches the callback type, see
* \ref CallBackType.
*/
void ogrRegIntCallback(CallBackType, IntCallback, void*);
/**
* Return 1 if recording is happening in libopenglrecorder, 0 otherwise.
*/
int ogrCapturing(void);
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@ -17,13 +17,10 @@
#if defined(ENABLE_REC_SOUND) && !defined(WIN32)
#include "recorder/vorbis_encoder.hpp"
#include "utils/synchronised.hpp"
#include "utils/log.hpp"
#include "utils/vs.hpp"
#include "capture_library.hpp"
#include "recorder_private.hpp"
#include "vorbis_encoder.hpp"
#include <cstring>
#include <list>
#include <pulse/pulseaudio.h>
#include <string>
@ -153,168 +150,147 @@ namespace Recorder
m_dl_handle = dlopen("libpulse.so", RTLD_LAZY);
if (m_dl_handle == NULL)
{
Log::error("PulseAudioRecorder", "Failed to open PulseAudio"
" library");
printf("Failed to open PulseAudio library\n");
return false;
}
pa_stream_new = (pa_stream_new_t)dlsym(m_dl_handle,
"pa_stream_new");
if (pa_stream_new == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_new'");
printf("Cannot load function 'pa_stream_new'\n");
return false;
}
pa_stream_connect_record = (pa_stream_connect_record_t)dlsym
(m_dl_handle, "pa_stream_connect_record");
if (pa_stream_connect_record == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_connect_record'");
printf("Cannot load function 'pa_stream_connect_record'\n");
return false;
}
pa_stream_get_state = (pa_stream_get_state_t)dlsym(m_dl_handle,
"pa_stream_get_state");
if (pa_stream_get_state == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_get_state'");
printf("Cannot load function 'pa_stream_get_state'\n");
return false;
}
pa_stream_readable_size = (pa_stream_readable_size_t)dlsym
(m_dl_handle, "pa_stream_readable_size");
if (pa_stream_readable_size == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_readable_size'");
printf("Cannot load function 'pa_stream_readable_size'\n");
return false;
}
pa_stream_peek = (pa_stream_peek_t)dlsym(m_dl_handle,
"pa_stream_peek");
if (pa_stream_peek == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_peek'");
printf("Cannot load function 'pa_stream_peek'\n");
return false;
}
pa_stream_drop = (pa_stream_drop_t)dlsym(m_dl_handle,
"pa_stream_drop");
if (pa_stream_drop == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_drop'");
printf("Cannot load function 'pa_stream_drop'\n");
return false;
}
pa_stream_disconnect = (pa_stream_disconnect_t)dlsym(m_dl_handle,
"pa_stream_disconnect");
if (pa_stream_disconnect == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_disconnect'");
printf("Cannot load function 'pa_stream_disconnect'\n");
return false;
}
pa_stream_unref = (pa_stream_unref_t)dlsym(m_dl_handle,
"pa_stream_unref");
if (pa_stream_unref == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_stream_unref'");
printf("Cannot load function 'pa_stream_unref'\n");
return false;
}
pa_mainloop_new = (pa_mainloop_new_t)dlsym(m_dl_handle,
"pa_mainloop_new");
if (pa_mainloop_new == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_mainloop_new'");
printf("Cannot load function 'pa_mainloop_new'\n");
return false;
}
pa_mainloop_get_api = (pa_mainloop_get_api_t)dlsym(m_dl_handle,
"pa_mainloop_get_api");
if (pa_mainloop_get_api == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_mainloop_get_api'");
printf("Cannot load function 'pa_mainloop_get_api'\n");
return false;
}
pa_context_new = (pa_context_new_t)dlsym(m_dl_handle,
"pa_context_new");
if (pa_context_new == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_new'");
printf("Cannot load function 'pa_context_new'\n");
return false;
}
pa_context_connect = (pa_context_connect_t)dlsym(m_dl_handle,
"pa_context_connect");
if (pa_context_connect == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_connect'");
printf("Cannot load function 'pa_context_connect'\n");
return false;
}
pa_mainloop_iterate = (pa_mainloop_iterate_t)dlsym(m_dl_handle,
"pa_mainloop_iterate");
if (pa_mainloop_iterate == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_mainloop_iterate'");
printf("Cannot load function 'pa_mainloop_iterate'\n");
return false;
}
pa_context_get_state = (pa_context_get_state_t)dlsym(m_dl_handle,
"pa_context_get_state");
if (pa_context_get_state == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_get_state'");
printf("Cannot load function 'pa_context_get_state'\n");
return false;
}
pa_context_get_server_info = (pa_context_get_server_info_t)dlsym
(m_dl_handle, "pa_context_get_server_info");
if (pa_context_get_server_info == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_get_server_info'");
printf("Cannot load function 'pa_context_get_server_info'\n");
return false;
}
pa_operation_get_state = (pa_operation_get_state_t)dlsym
(m_dl_handle, "pa_operation_get_state");
if (pa_operation_get_state == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_operation_get_state'");
printf("Cannot load function 'pa_operation_get_state'\n");
return false;
}
pa_operation_unref = (pa_operation_unref_t)dlsym(m_dl_handle,
"pa_operation_unref");
if (pa_operation_unref == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_operation_unref'");
printf("Cannot load function 'pa_operation_unref'\n");
return false;
}
pa_context_disconnect = (pa_context_disconnect_t)dlsym(m_dl_handle,
"pa_context_disconnect");
if (pa_context_disconnect == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_disconnect'");
printf("Cannot load function 'pa_context_disconnect'\n");
return false;
}
pa_context_unref = (pa_context_unref_t)dlsym(m_dl_handle,
"pa_context_unref");
if (pa_context_unref == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_context_unref'");
printf("Cannot load function 'pa_context_unref'\n");
return false;
}
pa_mainloop_free = (pa_mainloop_free_t)dlsym(m_dl_handle,
"pa_mainloop_free");
if (pa_mainloop_free == NULL)
{
Log::error("PulseAudioRecorder", "Cannot load function"
" 'pa_mainloop_free'");
printf("Cannot load function 'pa_mainloop_free'\n");
return false;
}
return true;
@ -337,14 +313,14 @@ namespace Recorder
m_loop = pa_mainloop_new();
if (m_loop == NULL)
{
Log::error("PulseAudioRecorder", "Failed to create mainloop");
printf("Failed to create mainloop\n");
return false;
}
m_context = pa_context_new(pa_mainloop_get_api(m_loop),
"audioRecord");
if (m_context == NULL)
{
Log::error("PulseAudioRecorder", "Failed to create context");
printf("Failed to create context\n");
return false;
}
pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN , NULL);
@ -356,8 +332,7 @@ namespace Recorder
break;
if (!PA_CONTEXT_IS_GOOD(state))
{
Log::error("PulseAudioRecorder", "Failed to connect to"
" context");
printf("Failed to connect to context\n");
return false;
}
}
@ -370,7 +345,7 @@ namespace Recorder
pa_operation_unref(pa_op);
if (m_default_sink.empty())
{
Log::error("PulseAudioRecorder", "Failed to get default sink");
printf("Failed to get default sink\n");
return false;
}
m_default_sink += ".monitor";
@ -382,11 +357,11 @@ namespace Recorder
return true;
} // load
// --------------------------------------------------------------------
void configAudioType(Recorder::VorbisEncoderData* ved)
void configAudioType(AudioEncoderData* aed)
{
ved->m_sample_rate = m_sample_spec.rate;
ved->m_channels = m_sample_spec.channels;
ved->m_audio_type = Recorder::VorbisEncoderData::AT_PCM;
aed->m_sample_rate = m_sample_spec.rate;
aed->m_channels = m_sample_spec.channels;
aed->m_audio_type = AudioEncoderData::AT_PCM;
} // configAudioType
// --------------------------------------------------------------------
inline void mainLoopIterate()
@ -478,51 +453,59 @@ namespace Recorder
// ========================================================================
PulseAudioData g_pa_data;
// ========================================================================
void* audioRecorder(void *obj)
void audioRecorder(CaptureLibrary* cl)
{
VS::setThreadName("audioRecorder");
setThreadName("audioRecorder");
if (!g_pa_data.m_loaded)
{
if (!g_pa_data.load())
{
Log::error("PulseAudioRecord", "Cannot pulseaudio data");
return NULL;
printf("Cannot load pulseaudio data.\n");
return;
}
}
if (g_pa_data.createRecordStream() == false)
{
Log::error("PulseAudioRecorder", "Failed to create stream");
printf("Failed to create audio record stream.\n");
if (g_pa_data.m_stream != NULL)
{
g_pa_data.removeRecordStream();
}
return NULL;
return;
}
Synchronised<bool>* idle = (Synchronised<bool>*)obj;
Synchronised<std::list<int8_t*> > pcm_data;
pthread_cond_t enc_request;
pthread_cond_init(&enc_request, NULL);
pthread_t vorbis_enc;
Recorder::VorbisEncoderData ved;
g_pa_data.configAudioType(&ved);
ved.m_data = &pcm_data;
ved.m_enc_request = &enc_request;
std::list<int8_t*> pcm_data;
std::mutex pcm_mutex;
std::condition_variable pcm_cv;
std::thread audio_enc_thread;
AudioEncoderData aed;
g_pa_data.configAudioType(&aed);
aed.m_buf_list = &pcm_data;
aed.m_mutex = &pcm_mutex;
aed.m_cv = &pcm_cv;
aed.m_audio_bitrate = cl->getRecorderConfig().m_audio_bitrate;
const unsigned frag_size = 1024 * g_pa_data.m_sample_spec.channels *
sizeof(int16_t);
pthread_create(&vorbis_enc, NULL, &Recorder::vorbisEncoder, &ved);
switch (cl->getRecorderConfig().m_audio_format)
{
case REC_AF_VORBIS:
audio_enc_thread = std::thread(vorbisEncoder, &aed);
break;
}
int8_t* each_pcm_buf = new int8_t[frag_size]();
unsigned readed = 0;
while (true)
{
if (idle->getAtomic())
if (cl->getSoundStop())
{
pcm_data.lock();
pcm_data.getData().push_back(each_pcm_buf);
pcm_data.getData().push_back(NULL);
pthread_cond_signal(&enc_request);
pcm_data.unlock();
std::lock_guard<std::mutex> lock(pcm_mutex);
pcm_data.push_back(each_pcm_buf);
pcm_data.push_back(NULL);
pcm_cv.notify_one();
break;
}
g_pa_data.mainLoopIterate();
@ -544,10 +527,10 @@ namespace Recorder
memcpy(each_pcm_buf + readed, data, copy_size);
if (buf_full)
{
pcm_data.lock();
pcm_data.getData().push_back(each_pcm_buf);
pthread_cond_signal(&enc_request);
pcm_data.unlock();
std::unique_lock<std::mutex> ul(pcm_mutex);
pcm_data.push_back(each_pcm_buf);
pcm_cv.notify_one();
ul.unlock();
each_pcm_buf = new int8_t[frag_size]();
readed = (unsigned)bytes - copy_size;
memcpy(each_pcm_buf, (uint8_t*)data + copy_size, readed);
@ -558,10 +541,8 @@ namespace Recorder
}
g_pa_data.dropStream();
}
pthread_join(vorbis_enc, NULL);
pthread_cond_destroy(&enc_request);
audio_enc_thread.join();
g_pa_data.removeRecordStream();
return NULL;
} // audioRecorder
}
#endif

View File

@ -20,12 +20,13 @@
#ifndef HEADER_PULSEAUDIO_RECORD_HPP
#define HEADER_PULSEAUDIO_RECORD_HPP
class CaptureLibrary;
namespace Recorder
{
#ifdef ENABLE_REC_SOUND
void* audioRecorder(void *obj);
void audioRecorder(CaptureLibrary* cl);
#else
inline void* audioRecorder(void *obj) { return NULL; }
inline void audioRecorder(CaptureLibrary* cl) {}
#endif
};

265
src/recorder/recorder.cpp Normal file
View File

@ -0,0 +1,265 @@
#ifdef ENABLE_RECORDER
#include "capture_library.hpp"
#include "recorder_private.hpp"
#include <array>
#include <cassert>
#include <memory>
#include <cstring>
// ============================================================================
std::unique_ptr<RecorderConfig> g_recorder_config(nullptr);
// ============================================================================
std::unique_ptr<CaptureLibrary> g_capture_library(nullptr);
// ============================================================================
std::atomic_bool g_capturing(false);
// ============================================================================
std::string g_saved_name;
// ============================================================================
StringCallback g_cb_saved_rec = NULL;
// ============================================================================
IntCallback g_cb_progress_rec = NULL;
// ============================================================================
GeneralCallback g_cb_wait_rec = NULL;
// ============================================================================
GeneralCallback g_cb_start_rec = NULL;
// ============================================================================
GeneralCallback g_cb_error_rec = NULL;
// ============================================================================
GeneralCallback g_cb_slow_rec = NULL;
// ============================================================================
std::array<void*, REC_CBT_COUNT> g_all_user_data;
// ============================================================================
void ogrInitConfig(RecorderConfig* rc)
{
RecorderConfig* new_rc = new RecorderConfig;
memcpy(new_rc, rc, sizeof(RecorderConfig));
while (new_rc->m_width % 8 != 0)
{
new_rc->m_width--;
}
while (new_rc->m_height % 2 != 0)
{
new_rc->m_height--;
}
g_recorder_config.reset(new_rc);
} // ogrInitConfig
// ----------------------------------------------------------------------------
RecorderConfig* getConfig()
{
assert(g_recorder_config.get() != nullptr);
return g_recorder_config.get();
} // getConfig
// ----------------------------------------------------------------------------
void ogrSetSavedName(const char* name)
{
g_saved_name = name;
} // ogrSetSavedName
// ----------------------------------------------------------------------------
const std::string& getSavedName()
{
return g_saved_name;
} // getSavedName
// ----------------------------------------------------------------------------
void ogrPrepareCapture(void)
{
assert(g_recorder_config.get() != nullptr && !g_saved_name.empty());
if (g_capture_library.get() == nullptr)
{
assert(g_recorder_config.get() != nullptr);
g_capture_library.reset(new CaptureLibrary(getConfig()));
}
g_capture_library.get()->reset();
} // ogrPrepareCapture
// ----------------------------------------------------------------------------
void ogrCapture(void)
{
g_capture_library.get()->capture();
} // ogrCapture
// ----------------------------------------------------------------------------
void ogrStopCapture(void)
{
g_capture_library.get()->stopCapture();
} // ogrStopCapture
// ----------------------------------------------------------------------------
void ogrDestroy(void)
{
delete g_capture_library.release();
} // ogrDestroy
// ----------------------------------------------------------------------------
void ogrRegGeneralCallback(CallBackType cbt, GeneralCallback cb, void* data)
{
switch (cbt)
{
case REC_CBT_ERROR_RECORDING:
g_cb_error_rec = cb;
g_all_user_data[REC_CBT_ERROR_RECORDING] = data;
break;
case REC_CBT_START_RECORDING:
g_cb_start_rec = cb;
g_all_user_data[REC_CBT_START_RECORDING] = data;
break;
case REC_CBT_SLOW_RECORDING:
g_cb_slow_rec = cb;
g_all_user_data[REC_CBT_SLOW_RECORDING] = data;
break;
case REC_CBT_WAIT_RECORDING:
g_cb_wait_rec = cb;
g_all_user_data[REC_CBT_WAIT_RECORDING] = data;
break;
default:
assert(false && "Wrong callback enum");
break;
}
} // ogrRegGeneralCallback
// ----------------------------------------------------------------------------
void ogrRegStringCallback(CallBackType cbt, StringCallback cb, void* data)
{
switch (cbt)
{
case REC_CBT_SAVED_RECORDING:
g_cb_saved_rec = cb;
g_all_user_data[REC_CBT_SAVED_RECORDING] = data;
break;
default:
assert(false && "Wrong callback enum");
break;
}
} // ogrRegStringCallback
// ----------------------------------------------------------------------------
void ogrRegIntCallback(CallBackType cbt, IntCallback cb, void* data)
{
switch (cbt)
{
case REC_CBT_PROGRESS_RECORDING:
g_cb_progress_rec = cb;
g_all_user_data[REC_CBT_PROGRESS_RECORDING] = data;
break;
default:
assert(false && "Wrong callback enum");
break;
}
} // ogrRegIntCallback
// ----------------------------------------------------------------------------
void runCallback(CallBackType cbt, const void* arg)
{
switch (cbt)
{
case REC_CBT_START_RECORDING:
{
if (g_cb_start_rec == NULL) return;
g_cb_start_rec(g_all_user_data[REC_CBT_START_RECORDING]);
break;
}
case REC_CBT_SAVED_RECORDING:
{
if (g_cb_saved_rec == NULL) return;
const char* s = (const char*)arg;
g_cb_saved_rec(s, g_all_user_data[REC_CBT_SAVED_RECORDING]);
break;
}
case REC_CBT_ERROR_RECORDING:
{
if (g_cb_error_rec == NULL) return;
g_cb_error_rec(g_all_user_data[REC_CBT_ERROR_RECORDING]);
break;
}
case REC_CBT_PROGRESS_RECORDING:
{
if (g_cb_progress_rec == NULL) return;
const int* i = (const int*)arg;
g_cb_progress_rec(*i, g_all_user_data[REC_CBT_PROGRESS_RECORDING]);
break;
}
case REC_CBT_WAIT_RECORDING:
{
if (g_cb_wait_rec == NULL) return;
g_cb_wait_rec(g_all_user_data[REC_CBT_WAIT_RECORDING]);
break;
}
case REC_CBT_SLOW_RECORDING:
{
if (g_cb_slow_rec == NULL) return;
g_cb_slow_rec(g_all_user_data[REC_CBT_SLOW_RECORDING]);
break;
}
default:
break;
}
} // runCallback
// ----------------------------------------------------------------------------
int ogrCapturing(void)
{
return g_capturing.load() ? 1 : 0;
} // ogrCapturing
// ----------------------------------------------------------------------------
void setCapturing(bool val)
{
g_capturing.store(val);
} // setCapturing
// ----------------------------------------------------------------------------
/** This function sets the name of this thread in the debugger.
* \param name Name of the thread.
*/
#if defined(_MSC_VER) && defined(DEBUG)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
void setThreadName(const char *name)
{
const DWORD MS_VC_EXCEPTION=0x406D1388;
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = -1;
info.dwFlags = 0;
__try
{
RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR),
(ULONG_PTR*)&info );
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
} // setThreadName
#elif defined(__linux__) && defined(__GLIBC__) && defined(__GLIBC_MINOR__)
void setThreadName(const char* name)
{
#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 11
pthread_setname_np(pthread_self(), name);
#endif
} // setThreadName
#else
void setThreadName(const char* name)
{
} // setThreadName
#endif
#endif

View File

@ -1,401 +0,0 @@
// 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.
#ifdef ENABLE_RECORDER
#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/jpg_writer.hpp"
#include "recorder/mkv_writer.hpp"
#include "recorder/pulseaudio_recorder.hpp"
#include "recorder/vpx_encoder.hpp"
#include "recorder/wasapi_recorder.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;
// ========================================================================
Synchronised<bool> g_display_progress(false);
// ========================================================================
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();
if (!g_destroy && g_jpg_list.getData().size() > 100)
{
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Please wait while encoding is finished."));
}
g_jpg_list.getData().emplace_back((uint8_t*)NULL, 0, 0);
pthread_cond_signal(&g_jpg_request);
g_jpg_list.unlock();
g_display_progress.setAtomic(true);
pthread_join(*g_video_thread.getData(), NULL);
delete g_video_thread.getData();
g_video_thread.setAtomic(NULL);
std::string f = Recorder::writeMKV(g_recording_name + ".video",
g_recording_name + ".audio");
g_display_progress.setAtomic(false);
if (g_destroy)
{
return NULL;
}
if (f.empty())
{
MessageQueue::add(MessageQueue::MT_ERROR,
_("Error when saving video."));
}
else
{
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video saved in \"%s\".", f.c_str()));
}
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());
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
switch (vf)
{
case VF_VP8:
case VF_VP9:
pthread_create(g_video_thread.getAtomic(), NULL,
&Recorder::vpxEncoder, &g_jpg_thread_data);
break;
case VF_MJPEG:
pthread_create(g_video_thread.getAtomic(), NULL,
&Recorder::jpgWriter, &g_jpg_thread_data);
break;
case VF_H264:
break;
}
} // 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
// ------------------------------------------------------------------------
bool displayProgress()
{
return g_display_progress.getAtomic();
} // displayProgress
}
#endif

View File

@ -1,60 +0,0 @@
// 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.
#ifdef ENABLE_RECORDER
#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();
// ------------------------------------------------------------------------
bool displayProgress();
};
#endif
#endif

View File

@ -0,0 +1,17 @@
#ifdef ENABLE_RECORDER
#ifndef HEADER_RECORDER_PRIVATE_HPP
#define HEADER_RECORDER_PRIVATE_HPP
#include "openglrecorder.h"
#include <string>
RecorderConfig* getConfig();
const std::string& getSavedName();
void setCapturing(bool val);
void setThreadName(const char* name);
void runCallback(CallBackType cbt, const void* arg);
#endif
#endif

View File

@ -17,32 +17,29 @@
#ifdef ENABLE_RECORDER
#include "recorder/vorbis_encoder.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/synchronised.hpp"
#include "utils/vs.hpp"
#include "capture_library.hpp"
#include "recorder_private.hpp"
#include <ogg/ogg.h>
#include <vorbis/vorbisenc.h>
namespace Recorder
{
void* vorbisEncoder(void *obj)
void vorbisEncoder(AudioEncoderData* aed)
{
VS::setThreadName("vorbisEncoder");
VorbisEncoderData* ved = (VorbisEncoderData*)obj;
setThreadName("vorbisEncoder");
vorbis_info vi;
vorbis_dsp_state vd;
vorbis_block vb;
vorbis_info_init(&vi);
vorbis_encode_init(&vi, ved->m_channels, ved->m_sample_rate, -1,
112000, -1);
vorbis_encode_init(&vi, aed->m_channels, aed->m_sample_rate, -1,
aed->m_audio_bitrate, -1);
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
vorbis_comment vc;
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc, "ENCODER", "STK vorbis encoder");
vorbis_comment_add_tag(&vc, "Encoder",
"vorbis encoder by libopenglrecorder");
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
@ -50,18 +47,17 @@ namespace Recorder
&header_code);
if (header.bytes > 255 || header_comm.bytes > 255)
{
Log::error("vorbisEncoder", "Header is too long.");
return NULL;
printf("Header is too long for vorbis.\n");
return;
}
FILE* vb_data = fopen((getRecordingName() + ".audio").c_str(), "wb");
FILE* vb_data = fopen((getSavedName() + ".audio").c_str(), "wb");
if (vb_data == NULL)
{
Log::error("vorbisEncoder", "Failed to open file for encoding"
" vorbis.");
return NULL;
printf("Failed to open file for encoding vorbis.\n");
return;
}
fwrite(&ved->m_sample_rate, 1, sizeof(uint32_t), vb_data);
fwrite(&ved->m_channels, 1, sizeof(uint32_t), vb_data);
fwrite(&aed->m_sample_rate, 1, sizeof(uint32_t), vb_data);
fwrite(&aed->m_channels, 1, sizeof(uint32_t), vb_data);
const uint32_t all = header.bytes + header_comm.bytes +
header_code.bytes + 3;
fwrite(&all, 1, sizeof(uint32_t), vb_data);
@ -74,24 +70,16 @@ namespace Recorder
fwrite(header.packet, 1, header.bytes, vb_data);
fwrite(header_comm.packet, 1, header_comm.bytes, vb_data);
fwrite(header_code.packet, 1, header_code.bytes, vb_data);
Synchronised<std::list<int8_t*> >* audio_data =
(Synchronised<std::list<int8_t*> >*)ved->m_data;
pthread_cond_t* cond_request = ved->m_enc_request;
ogg_packet op;
int64_t last_timestamp = 0;
bool eos = false;
while (eos == false)
{
audio_data->lock();
bool waiting = audio_data->getData().empty();
while (waiting)
{
pthread_cond_wait(cond_request, audio_data->getMutex());
waiting = audio_data->getData().empty();
}
int8_t* audio_buf = audio_data->getData().front();
audio_data->getData().pop_front();
audio_data->unlock();
std::unique_lock<std::mutex> ul(*aed->m_mutex);
aed->m_cv->wait(ul, [&aed] { return !aed->m_buf_list->empty(); });
int8_t* audio_buf = aed->m_buf_list->front();
aed->m_buf_list->pop_front();
ul.unlock();
if (audio_buf == NULL)
{
vorbis_analysis_wrote(&vd, 0);
@ -100,8 +88,8 @@ namespace Recorder
else
{
float **buffer = vorbis_analysis_buffer(&vd, 1024);
const unsigned channels = ved->m_channels;
if (ved->m_audio_type == VorbisEncoderData::AT_PCM)
const unsigned channels = aed->m_channels;
if (aed->m_audio_type == AudioEncoderData::AT_PCM)
{
for (unsigned j = 0; j < channels; j++)
{
@ -110,7 +98,7 @@ namespace Recorder
int8_t* each_channel =
&audio_buf[i * channels * 2 + j * 2];
buffer[j][i] = float((each_channel[1] << 8) |
(0x00ff & (int)each_channel[0])) / 32768.0f;
(0x00ff & (int)each_channel[0])) / 32768.0f;
}
}
}
@ -140,7 +128,7 @@ namespace Recorder
fwrite(&last_timestamp, 1, sizeof(int64_t), vb_data);
fwrite(op.packet, 1, frame_size, vb_data);
double s = (double)op.granulepos /
(double)ved->m_sample_rate * 1000000000.;
(double)aed->m_sample_rate * 1000000000.;
last_timestamp = (int64_t)s;
}
}
@ -152,8 +140,6 @@ namespace Recorder
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
fclose(vb_data);
return NULL;
} // vorbisEncoder
}
#endif

View File

@ -20,24 +20,10 @@
#ifndef HEADER_VORBIS_ENCODE_HPP
#define HEADER_VORBIS_ENCODE_HPP
#include "utils/no_copy.hpp"
#include "utils/types.hpp"
#include <pthread.h>
struct AudioEncoderData;
namespace Recorder
{
struct VorbisEncoderData : public NoCopy
{
enum AudioType { AT_FLOAT, AT_PCM };
void* m_data;
pthread_cond_t* m_enc_request;
uint32_t m_sample_rate;
uint32_t m_channels;
AudioType m_audio_type;
};
void* vorbisEncoder(void *obj);
void vorbisEncoder(AudioEncoderData* aed);
};
#endif

View File

@ -17,65 +17,15 @@
#if defined(ENABLE_RECORDER) && !defined(NO_VPX)
#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 "capture_library.hpp"
#include "recorder_private.hpp"
#include <chrono>
#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)
{
@ -86,7 +36,7 @@ namespace Recorder
1, 0, VPX_DL_REALTIME);
if (res != VPX_CODEC_OK)
{
Log::error("vpxEncoder", "Failed to encode frame");
printf("Failed to encode frame\n");
return -1;
}
while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL)
@ -104,101 +54,85 @@ namespace Recorder
return got_pkts;
} // vpxEncodeFrame
// ------------------------------------------------------------------------
void* vpxEncoder(void *obj)
void vpxEncoder(CaptureLibrary* cl)
{
VS::setThreadName("vpxEncoder");
FILE* vpx_data = fopen((getRecordingName() + ".video").c_str(), "wb");
setThreadName("vpxEncoder");
FILE* vpx_data = fopen((getSavedName() + ".video").c_str(), "wb");
if (vpx_data == NULL)
{
Log::error("vpxEncoder", "Failed to open file for writing");
return NULL;
printf("Failed to open file for writing vpx.\n");
return;
}
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_iface_t* codec_if = NULL;
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
switch (vf)
switch (cl->getRecorderConfig().m_video_format)
{
case VF_VP8:
case REC_VF_VP8:
codec_if = vpx_codec_vp8_cx();
break;
case VF_VP9:
case REC_VF_VP9:
codec_if = vpx_codec_vp9_cx();
break;
case VF_MJPEG:
case VF_H264:
case REC_VF_MJPEG:
case REC_VF_H264:
assert(false);
break;
}
vpx_codec_err_t res = vpx_codec_enc_config_default(codec_if, &cfg, 0);
if (res > 0)
{
Log::error("vpxEncoder", "Failed to get default codec config.");
return NULL;
printf("Failed to get default vpx codec config.\n");
return;
}
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().Height;
const unsigned width = cl->getRecorderConfig().m_width;
const unsigned height = cl->getRecorderConfig().m_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;
cfg.g_timebase.den = cl->getRecorderConfig().m_record_fps;
cfg.rc_end_usage = VPX_VBR;
cfg.rc_target_bitrate = cl->getRecorderConfig().m_video_bitrate;
if (vpx_codec_enc_init(&codec, codec_if, &cfg, 0) > 0)
{
Log::error("vpxEncoder", "Failed to initialize encoder");
printf("Failed to initialize vpx encoder\n");
fclose(vpx_data);
return NULL;
return;
}
std::chrono::high_resolution_clock::time_point tp;
float last_size = -1.0f;
int cur_finished_count = 0;
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();
}
if (displayProgress())
{
auto rate = std::chrono::high_resolution_clock::now() -
tp;
double t = std::chrono::duration_cast<std::chrono::
duration<double> >(rate).count();
if (t > 3.)
{
tp = std::chrono::high_resolution_clock::now();
Log::info("vpxEncoder", "%d frames remaining.",
jpg_data->getData().size());
}
}
auto& p = jpg_data->getData().front();
std::unique_lock<std::mutex> ul(*cl->getJPGListMutex());
cl->getJPGListCV()->wait(ul, [&cl]
{ return !cl->getJPGList()->empty(); });
auto& p = cl->getJPGList()->front();
uint8_t* jpg = std::get<0>(p);
unsigned jpg_size = std::get<1>(p);
uint32_t jpg_size = std::get<1>(p);
int frame_count = std::get<2>(p);
if (jpg == NULL)
{
jpg_data->getData().clear();
jpg_data->unlock();
cl->getJPGList()->clear();
ul.unlock();
break;
}
jpg_data->getData().pop_front();
jpg_data->unlock();
cl->getJPGList()->pop_front();
ul.unlock();
if (!cl->getDestroy() && cl->displayingProgress())
{
if (last_size == -1.0f)
last_size = (float)(cl->getJPGList()->size());
cur_finished_count += frame_count;
int rate = (int)(cur_finished_count / last_size * 100.0f);
runCallback(REC_CBT_PROGRESS_RECORDING, &rate);
}
uint8_t* yuv = NULL;
unsigned yuv_size;
int ret = g_jpg_decoder.yuvConversion(jpg, jpg_size, &yuv,
int ret = cl->yuvConversion(jpg, jpg_size, &yuv,
&yuv_size);
if (ret < 0)
{
@ -221,12 +155,10 @@ namespace Recorder
while (vpxEncodeFrame(&codec, NULL, -1, vpx_data));
if (vpx_codec_destroy(&codec))
{
Log::error("vpxEncoder", "Failed to destroy codec.");
return NULL;
printf("Failed to destroy vpx codec.\n");
return;
}
fclose(vpx_data);
return NULL;
} // vpxEncoder
}
#endif

View File

@ -21,12 +21,14 @@
#ifndef HEADER_VPX_ENCODER_HPP
#define HEADER_VPX_ENCODER_HPP
class CaptureLibrary;
namespace Recorder
{
#ifdef NO_VPX
inline void* vpxEncoder(void *obj) { return NULL; }
inline void vpxEncoder(CaptureLibrary* cl) {}
#else
void* vpxEncoder(void *obj);
void vpxEncoder(CaptureLibrary* cl);
#endif
};
#endif

View File

@ -17,12 +17,9 @@
#if defined(ENABLE_REC_SOUND) && defined(WIN32)
#include "recorder/vorbis_encoder.hpp"
#include "utils/synchronised.hpp"
#include "utils/log.hpp"
#include "utils/vs.hpp"
#include <list>
#include "capture_library.hpp"
#include "recorder_private.hpp"
#include "vorbis_encoder.hpp"
#include <audioclient.h>
#include <mmsystem.h>
@ -31,7 +28,7 @@
#include <windows.h>
#if defined (__MINGW32__) || defined(__CYGWIN__)
#include "utils/types.hpp"
#include <stdint.h>
inline GUID uuidFromString(const char* s)
{
unsigned long p0;
@ -136,111 +133,114 @@ namespace Recorder
// ========================================================================
WasapiData g_wasapi_data;
// ========================================================================
void* audioRecorder(void *obj)
void audioRecorder(CaptureLibrary* cl)
{
VS::setThreadName("audioRecorder");
setThreadName("audioRecorder");
if (!g_wasapi_data.m_loaded)
{
if (!g_wasapi_data.load())
{
Log::error("WasapiRecorder", "Failed to load wasapi data");
return NULL;
printf("Failed to load wasapi data.\n");
return;
}
}
VorbisEncoderData ved = {};
AudioEncoderData aed = {};
if (g_wasapi_data.m_wav_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
WAVEFORMATEXTENSIBLE* wav_for_ext =
(WAVEFORMATEXTENSIBLE*)g_wasapi_data.m_wav_format;
ved.m_channels = wav_for_ext->Format.nChannels;
ved.m_sample_rate = wav_for_ext->Format.nSamplesPerSec;
aed.m_channels = wav_for_ext->Format.nChannels;
aed.m_sample_rate = wav_for_ext->Format.nSamplesPerSec;
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_PCM, wav_for_ext->SubFormat))
{
ved.m_audio_type = VorbisEncoderData::AT_PCM;
aed.m_audio_type = AudioEncoderData::AT_PCM;
if (wav_for_ext->Format.wBitsPerSample != 16)
{
Log::error("WasapiRecorder", "Only 16bit PCM is"
" supported.");
return NULL;
printf("Only 16bit PCM is supported.\n");
return;
}
}
else if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wav_for_ext
->SubFormat))
{
ved.m_audio_type = VorbisEncoderData::AT_FLOAT;
aed.m_audio_type = AudioEncoderData::AT_FLOAT;
if (wav_for_ext->Format.wBitsPerSample != 32)
{
Log::error("WasapiRecorder", "Only 32bit float is"
" supported.");
return NULL;
printf("Only 32bit float is supported.\n");
return;
}
}
else
{
Log::error("WasapiRecorder", "Unsupported audio input"
" format.");
return NULL;
printf("Unsupported audio input format.\n");
return;
}
}
else if (g_wasapi_data.m_wav_format->wFormatTag == WAVE_FORMAT_PCM)
{
ved.m_channels = g_wasapi_data.m_wav_format->nChannels;
ved.m_sample_rate = g_wasapi_data.m_wav_format->nSamplesPerSec;
ved.m_audio_type = VorbisEncoderData::AT_PCM;
aed.m_channels = g_wasapi_data.m_wav_format->nChannels;
aed.m_sample_rate = g_wasapi_data.m_wav_format->nSamplesPerSec;
aed.m_audio_type = AudioEncoderData::AT_PCM;
if (g_wasapi_data.m_wav_format->wBitsPerSample != 16)
{
Log::error("WasapiRecorder", "Only 16bit PCM is supported.");
return NULL;
printf("Only 16bit PCM is supported.\n");
return;
}
}
else
{
Log::error("WasapiRecorder", "Unsupported audio input format");
return NULL;
printf("Unsupported audio input format\n");
return;
}
if (ved.m_sample_rate > 48000)
if (aed.m_sample_rate > 48000)
{
Log::error("WasapiRecorder", "Only support maximum 48000hz sample "
"rate audio.");
return NULL;
printf("Only support maximum 48000hz sample rate audio.\n");
return;
}
HRESULT hr = g_wasapi_data.m_client->Reset();
if (FAILED(hr))
{
Log::error("WasapiRecorder", "Failed to reset recorder");
return NULL;
printf("Failed to reset audio recorder.\n");
return;
}
hr = g_wasapi_data.m_client->Start();
if (FAILED(hr))
{
Log::error("WasapiRecorder", "Failed to start recorder");
return NULL;
printf("Failed to start audio recorder.\n");
return;
}
REFERENCE_TIME duration = REFTIMES_PER_SEC *
g_wasapi_data.m_buffer_size / g_wasapi_data.m_wav_format
->nSamplesPerSec;
Synchronised<bool>* idle = (Synchronised<bool>*)obj;
Synchronised<std::list<int8_t*> > audio_data;
pthread_cond_t enc_request;
pthread_cond_init(&enc_request, NULL);
pthread_t vorbis_enc;
ved.m_data = &audio_data;
ved.m_enc_request = &enc_request;
pthread_create(&vorbis_enc, NULL, &Recorder::vorbisEncoder, &ved);
const unsigned frag_size = 1024 * ved.m_channels *
std::list<int8_t*> audio_data;
std::mutex audio_mutex;
std::condition_variable audio_cv;
std::thread audio_enc_thread;
aed.m_buf_list = &audio_data;
aed.m_mutex = &audio_mutex;
aed.m_cv = &audio_cv;
aed.m_audio_bitrate = cl->getRecorderConfig().m_audio_bitrate;
switch (cl->getRecorderConfig().m_audio_format)
{
case REC_AF_VORBIS:
audio_enc_thread = std::thread(vorbisEncoder, &aed);
break;
}
const unsigned frag_size = 1024 * aed.m_channels *
(g_wasapi_data.m_wav_format->wBitsPerSample / 8);
int8_t* each_audio_buf = new int8_t[frag_size]();
unsigned readed = 0;
while (true)
{
if (idle->getAtomic())
if (cl->getSoundStop())
{
audio_data.lock();
audio_data.getData().push_back(each_audio_buf);
audio_data.getData().push_back(NULL);
pthread_cond_signal(&enc_request);
audio_data.unlock();
std::lock_guard<std::mutex> lock(audio_mutex);
audio_data.push_back(each_audio_buf);
audio_data.push_back(NULL);
audio_cv.notify_one();
break;
}
uint32_t packet_length = 0;
@ -248,7 +248,7 @@ namespace Recorder
&packet_length);
if (FAILED(hr))
{
Log::warn("WasapiRecorder", "Failed to get next packet size");
printf("Failed to get next audio packet size\n");
}
if (packet_length == 0)
{
@ -262,9 +262,9 @@ namespace Recorder
&packet_length, &flags, NULL, NULL);
if (FAILED(hr))
{
Log::warn("WasapiRecorder", "Failed to get buffer");
printf("Failed to get audio buffer\n");
}
const unsigned bytes = ved.m_channels * (g_wasapi_data.m_wav_format
const unsigned bytes = aed.m_channels * (g_wasapi_data.m_wav_format
->wBitsPerSample / 8) * packet_length;
bool buf_full = readed + bytes > frag_size;
unsigned copy_size = buf_full ? frag_size - readed : bytes;
@ -274,12 +274,12 @@ namespace Recorder
}
if (buf_full)
{
audio_data.lock();
audio_data.getData().push_back(each_audio_buf);
pthread_cond_signal(&enc_request);
audio_data.unlock();
std::unique_lock<std::mutex> ul(audio_mutex);
audio_data.push_back(each_audio_buf);
audio_cv.notify_one();
ul.unlock();
each_audio_buf = new int8_t[frag_size]();
readed = bytes - copy_size;
readed = (unsigned)bytes - copy_size;
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
{
memcpy(each_audio_buf, (uint8_t*)data + copy_size, readed);
@ -292,18 +292,15 @@ namespace Recorder
hr = g_wasapi_data.m_capture_client->ReleaseBuffer(packet_length);
if (FAILED(hr))
{
Log::warn("WasapiRecorder", "Failed to release buffer");
printf("Failed to release audio buffer\n");
}
}
hr = g_wasapi_data.m_client->Stop();
if (FAILED(hr))
{
Log::warn("WasapiRecorder", "Failed to stop recorder");
printf("Failed to stop audio recorder\n");
}
pthread_join(vorbis_enc, NULL);
pthread_cond_destroy(&enc_request);
return NULL;
audio_enc_thread.join();
} // audioRecorder
}
#endif

View File

@ -20,12 +20,13 @@
#ifndef HEADER_WASAPI_RECORD_HPP
#define HEADER_WASAPI_RECORD_HPP
class CaptureLibrary;
namespace Recorder
{
#ifdef ENABLE_REC_SOUND
void* audioRecorder(void *obj);
void audioRecorder(CaptureLibrary* cl);
#else
inline void* audioRecorder(void *obj) { return NULL; }
inline void audioRecorder(CaptureLibrary* cl) {}
#endif
};