From 156b799011bf8d6f1bee9db7dd6a3301e482ebac Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 9 Apr 2017 14:16:45 +0800 Subject: [PATCH] Remove STK headers in recorder and use c++11 thread library --- src/graphics/irr_driver.cpp | 68 ++- src/recorder/capture_library.cpp | 298 +++++++++++++ src/recorder/capture_library.hpp | 118 ++++++ src/recorder/jpg_writer.cpp | 84 ---- src/recorder/mjpeg_writer.cpp | 69 +++ .../{jpg_writer.hpp => mjpeg_writer.hpp} | 9 +- src/recorder/mkv_writer.cpp | 75 ++-- src/recorder/mkv_writer.hpp | 1 + src/recorder/openglrecorder.h | 211 +++++++++ src/recorder/pulseaudio_recorder.cpp | 153 +++---- src/recorder/pulseaudio_recorder.hpp | 5 +- src/recorder/recorder.cpp | 265 ++++++++++++ src/recorder/recorder_common.cpp | 401 ------------------ src/recorder/recorder_common.hpp | 60 --- src/recorder/recorder_private.hpp | 17 + src/recorder/vorbis_encoder.cpp | 62 ++- src/recorder/vorbis_encoder.hpp | 18 +- src/recorder/vpx_encoder.cpp | 158 ++----- src/recorder/vpx_encoder.hpp | 6 +- src/recorder/wasapi_recorder.cpp | 135 +++--- src/recorder/wasapi_recorder.hpp | 5 +- 21 files changed, 1288 insertions(+), 930 deletions(-) create mode 100644 src/recorder/capture_library.cpp create mode 100644 src/recorder/capture_library.hpp delete mode 100644 src/recorder/jpg_writer.cpp create mode 100644 src/recorder/mjpeg_writer.cpp rename src/recorder/{jpg_writer.hpp => mjpeg_writer.hpp} (87%) create mode 100644 src/recorder/openglrecorder.h create mode 100644 src/recorder/recorder.cpp delete mode 100644 src/recorder/recorder_common.cpp delete mode 100644 src/recorder/recorder_common.hpp create mode 100644 src/recorder/recorder_private.hpp diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index c88f2f034..a01acb61b 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -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 diff --git a/src/recorder/capture_library.cpp b/src/recorder/capture_library.cpp new file mode 100644 index 000000000..110cfead9 --- /dev/null +++ b/src/recorder/capture_library.cpp @@ -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 +} + +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 + >(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 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 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 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 diff --git a/src/recorder/capture_library.hpp b/src/recorder/capture_library.hpp new file mode 100644 index 000000000..6892b794c --- /dev/null +++ b/src/recorder/capture_library.hpp @@ -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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct AudioEncoderData +{ + enum AudioType { AT_FLOAT, AT_PCM }; + std::mutex* m_mutex; + std::condition_variable* m_cv; + std::list* 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 > 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 > 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 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 diff --git a/src/recorder/jpg_writer.cpp b/src/recorder/jpg_writer.cpp deleted file mode 100644 index 6b6c47213..000000000 --- a/src/recorder/jpg_writer.cpp +++ /dev/null @@ -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 - -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 > >* - jpg_data = (Synchronised > >*)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 diff --git a/src/recorder/mjpeg_writer.cpp b/src/recorder/mjpeg_writer.cpp new file mode 100644 index 000000000..93c78afed --- /dev/null +++ b/src/recorder/mjpeg_writer.cpp @@ -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 + +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 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 diff --git a/src/recorder/jpg_writer.hpp b/src/recorder/mjpeg_writer.hpp similarity index 87% rename from src/recorder/jpg_writer.hpp rename to src/recorder/mjpeg_writer.hpp index 25fef6b57..c7f261543 100644 --- a/src/recorder/jpg_writer.hpp +++ b/src/recorder/mjpeg_writer.hpp @@ -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 diff --git a/src/recorder/mkv_writer.cpp b/src/recorder/mkv_writer.cpp index b2ee9dcb0..004746072 100644 --- a/src/recorder/mkv_writer.cpp +++ b/src/recorder/mkv_writer.cpp @@ -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 #include #include #include @@ -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 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 (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( 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 diff --git a/src/recorder/mkv_writer.hpp b/src/recorder/mkv_writer.hpp index 033068de3..7a3c2b607 100644 --- a/src/recorder/mkv_writer.hpp +++ b/src/recorder/mkv_writer.hpp @@ -26,6 +26,7 @@ namespace Recorder { std::string writeMKV(const std::string& video, const std::string& audio); }; + #endif #endif diff --git a/src/recorder/openglrecorder.h b/src/recorder/openglrecorder.h new file mode 100644 index 000000000..c60374328 --- /dev/null +++ b/src/recorder/openglrecorder.h @@ -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 diff --git a/src/recorder/pulseaudio_recorder.cpp b/src/recorder/pulseaudio_recorder.cpp index 6abe955c7..6ba46c94f 100644 --- a/src/recorder/pulseaudio_recorder.cpp +++ b/src/recorder/pulseaudio_recorder.cpp @@ -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 -#include #include #include @@ -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* idle = (Synchronised*)obj; - Synchronised > 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 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 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 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 diff --git a/src/recorder/pulseaudio_recorder.hpp b/src/recorder/pulseaudio_recorder.hpp index 86abbc557..a16bf49ea 100644 --- a/src/recorder/pulseaudio_recorder.hpp +++ b/src/recorder/pulseaudio_recorder.hpp @@ -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 }; diff --git a/src/recorder/recorder.cpp b/src/recorder/recorder.cpp new file mode 100644 index 000000000..d0dd5cc60 --- /dev/null +++ b/src/recorder/recorder.cpp @@ -0,0 +1,265 @@ +#ifdef ENABLE_RECORDER + +#include "capture_library.hpp" +#include "recorder_private.hpp" + +#include +#include +#include +#include + +// ============================================================================ +std::unique_ptr g_recorder_config(nullptr); +// ============================================================================ +std::unique_ptr 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 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 + + 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 diff --git a/src/recorder/recorder_common.cpp b/src/recorder/recorder_common.cpp deleted file mode 100644 index d7c0a807d..000000000 --- a/src/recorder/recorder_common.cpp +++ /dev/null @@ -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 -#include -#include - -namespace Recorder -{ - // ======================================================================== - tjhandle g_compress_handle; - // ======================================================================== - Synchronised > > 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 g_video_thread(NULL); - // ======================================================================== - Synchronised g_idle(true); - // ======================================================================== - bool g_destroy; - // ======================================================================== - std::string g_recording_name; - // ======================================================================== - Synchronised g_display_progress(false); - // ======================================================================== - void* fbiConversion(void* obj) - { - VS::setThreadName("fbiConversion"); - ThreadData* td = (ThreadData*)obj; - Synchronised > >* fbi_queue = - (Synchronised > >*)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 > > 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 - >(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 diff --git a/src/recorder/recorder_common.hpp b/src/recorder/recorder_common.hpp deleted file mode 100644 index 4815ee1d1..000000000 --- a/src/recorder/recorder_common.hpp +++ /dev/null @@ -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 -#include -#include - -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 diff --git a/src/recorder/recorder_private.hpp b/src/recorder/recorder_private.hpp new file mode 100644 index 000000000..df0e1515e --- /dev/null +++ b/src/recorder/recorder_private.hpp @@ -0,0 +1,17 @@ +#ifdef ENABLE_RECORDER +#ifndef HEADER_RECORDER_PRIVATE_HPP +#define HEADER_RECORDER_PRIVATE_HPP + +#include "openglrecorder.h" + +#include + +RecorderConfig* getConfig(); +const std::string& getSavedName(); +void setCapturing(bool val); +void setThreadName(const char* name); +void runCallback(CallBackType cbt, const void* arg); + +#endif + +#endif diff --git a/src/recorder/vorbis_encoder.cpp b/src/recorder/vorbis_encoder.cpp index 23d081ab7..cacd641cf 100644 --- a/src/recorder/vorbis_encoder.cpp +++ b/src/recorder/vorbis_encoder.cpp @@ -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 #include 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 >* audio_data = - (Synchronised >*)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 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 diff --git a/src/recorder/vorbis_encoder.hpp b/src/recorder/vorbis_encoder.hpp index 6ac305536..9ba87602e 100644 --- a/src/recorder/vorbis_encoder.hpp +++ b/src/recorder/vorbis_encoder.hpp @@ -20,24 +20,10 @@ #ifndef HEADER_VORBIS_ENCODE_HPP #define HEADER_VORBIS_ENCODE_HPP -#include "utils/no_copy.hpp" -#include "utils/types.hpp" - -#include - +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 diff --git a/src/recorder/vpx_encoder.cpp b/src/recorder/vpx_encoder.cpp index 3870c6f09..601fefb77 100644 --- a/src/recorder/vpx_encoder.cpp +++ b/src/recorder/vpx_encoder.cpp @@ -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 -#include #include #include 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 > >* - jpg_data = (Synchronised > >*)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 >(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 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 diff --git a/src/recorder/vpx_encoder.hpp b/src/recorder/vpx_encoder.hpp index 61f1e8d6f..12c935c96 100644 --- a/src/recorder/vpx_encoder.hpp +++ b/src/recorder/vpx_encoder.hpp @@ -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 diff --git a/src/recorder/wasapi_recorder.cpp b/src/recorder/wasapi_recorder.cpp index 86d65bbf7..e81843852 100644 --- a/src/recorder/wasapi_recorder.cpp +++ b/src/recorder/wasapi_recorder.cpp @@ -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 +#include "capture_library.hpp" +#include "recorder_private.hpp" +#include "vorbis_encoder.hpp" #include #include @@ -31,7 +28,7 @@ #include #if defined (__MINGW32__) || defined(__CYGWIN__) - #include "utils/types.hpp" + #include 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* idle = (Synchronised*)obj; - Synchronised > 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 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 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 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 diff --git a/src/recorder/wasapi_recorder.hpp b/src/recorder/wasapi_recorder.hpp index ac5bc0599..401e08ff0 100644 --- a/src/recorder/wasapi_recorder.hpp +++ b/src/recorder/wasapi_recorder.hpp @@ -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 };