Remove STK headers in recorder and use c++11 thread library
This commit is contained in:
parent
c00c35e59a
commit
156b799011
@ -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
|
||||
|
298
src/recorder/capture_library.cpp
Normal file
298
src/recorder/capture_library.cpp
Normal 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
|
118
src/recorder/capture_library.hpp
Normal file
118
src/recorder/capture_library.hpp
Normal 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
|
@ -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
|
69
src/recorder/mjpeg_writer.cpp
Normal file
69
src/recorder/mjpeg_writer.cpp
Normal 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
|
@ -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
|
@ -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(×tamp, 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
|
||||
|
@ -26,6 +26,7 @@ namespace Recorder
|
||||
{
|
||||
std::string writeMKV(const std::string& video, const std::string& audio);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
211
src/recorder/openglrecorder.h
Normal file
211
src/recorder/openglrecorder.h
Normal 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
|
@ -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
|
||||
|
@ -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
265
src/recorder/recorder.cpp
Normal 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
|
@ -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
|
@ -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
|
17
src/recorder/recorder_private.hpp
Normal file
17
src/recorder/recorder_private.hpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user