From 3f782736ac7bc5337c3842e80002ae4a6015dfe1 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Mon, 27 Jul 2020 11:45:15 +0200 Subject: [PATCH] Fixed Recorder and Session to allow transfer of recorders when changing session. --- Mixer.cpp | 3 ++ Recorder.cpp | 82 +++++++++++----------------------------- Recorder.h | 2 +- Session.cpp | 25 +++++++++++- Session.h | 4 +- UserInterfaceManager.cpp | 28 +++++++++++--- 6 files changed, 76 insertions(+), 68 deletions(-) diff --git a/Mixer.cpp b/Mixer.cpp index d66d3dc..83adf73 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -672,6 +672,9 @@ void Mixer::swap() session_ = back_session_; back_session_ = tmp; + // swap recorders + back_session_->transferRecorders(session_); + // attach new session's nodes to views for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++) { diff --git a/Recorder.cpp b/Recorder.cpp index 929f757..a1cc396 100644 --- a/Recorder.cpp +++ b/Recorder.cpp @@ -69,7 +69,7 @@ void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float) finished_ = true; } -const char* VideoRecorder::profile_name[4] = { "H264 (high)", "H264 (low)", "Apple ProRes 4444", "WebM VP9" }; +const char* VideoRecorder::profile_name[4] = { "H264 (low)", "H264 (high)", "Apple ProRes 4444", "WebM VP9" }; const std::vector VideoRecorder::profile_description { // Control x264 encoder quality : // pass=4 @@ -82,8 +82,8 @@ const std::vector VideoRecorder::profile_description { // veryfast (3) // faster (4) // fast (5) - "x264enc pass=4 quantizer=20 speed-preset=3 ! video/x-h264, profile=high ! h264parse ! ", "x264enc pass=4 quantizer=23 speed-preset=3 ! video/x-h264, profile=baseline ! h264parse ! ", + "x264enc pass=5 quantizer=18 speed-preset=4 ! video/x-h264, profile=high ! h264parse ! ", // Apple ProRes encoding parameters // pass=2 // cbr (0) – Constant Bitrate Encoding @@ -97,9 +97,15 @@ const std::vector VideoRecorder::profile_description { }; +// FAILED +// x265 encoder quality +// string description = "appsrc name=src ! videoconvert ! " +// "x265enc tune=4 speed-preset=2 option-string='crf=28' ! h265parse ! " +// "qtmux ! filesink name=sink"; + VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0), buf_size_(0), - recording_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0), time_(0), accept_buffer_(false) + recording_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0), timeframe_(0), accept_buffer_(false) { // auto filename std::string path = SystemToolkit::path_directory(Settings::application.record.path); @@ -110,7 +116,7 @@ VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), // configure fix parameter frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS - time_ = 2 * frame_duration_; + timeframe_ = 2 * frame_duration_; } VideoRecorder::~VideoRecorder() @@ -134,7 +140,7 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) // first frame for initialization if (frame_buffer_ == nullptr) { - // accepting a new frame buffer as input + // set frame buffer as input frame_buffer_ = frame_buffer; // define stream properties @@ -147,45 +153,6 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) description += profile_description[Settings::application.record.profile]; description += "qtmux ! filesink name=sink"; - // Control x264 encoder quality : - // pass=4 - // quant (4) – Constant Quantizer - // qual (5) – Constant Quality - // quantizer=23 - // The total range is from 0 to 51, where 0 is lossless, 18 can be considered ‘visually lossless’, - // and 51 is terrible quality. A sane range is 18-26, and the default is 23. - // speed-preset=3 - // veryfast (3) - // faster (4) - // fast (5) -// string description = "appsrc name=src ! videoconvert ! " -// "x264enc pass=4 quantizer=23 speed-preset=3 ! video/x-h264, profile=high ! h264parse ! " -// "qtmux ! filesink name=sink"; - - // WebM VP9 encoding parameters - // https://www.webmproject.org/docs/encoder-parameters/ - // https://developers.google.com/media/vp9/settings/vod/ -// string description = "appsrc name=src ! videoconvert ! " -// "vp9enc end-usage=vbr end-usage=vbr cpu-used=3 max-quantizer=35 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=1000 ! " -// "webmmux ! filesink name=sink"; - - // string description = "appsrc name=src ! videoconvert ! avenc_prores ! qtmux ! filesink name=sink"; - - // x265 encoder quality -// string description = "appsrc name=src ! videoconvert ! " -// "x265enc tune=4 speed-preset=2 option-string='crf=28' ! h265parse ! " -// "qtmux ! filesink name=sink"; - - - // Apple ProRes encoding parameters - // pass=2 - // cbr (0) – Constant Bitrate Encoding - // quant (2) – Constant Quantizer - // pass1 (512) – VBR Encoding - Pass 1 -// string description = "appsrc name=src ! videoconvert ! " -// "avenc_prores bitrate=60000 pass=2 ! " -// "qtmux ! filesink name=sink"; - // parse pipeline descriptor GError *error = NULL; pipeline_ = gst_parse_launch (description.c_str(), &error); @@ -221,8 +188,8 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) // "do-timestamp", TRUE, NULL); - // 2 sec buffer - gst_app_src_set_max_bytes( src_, 0 ); + // Direct encoding (no buffering) + gst_app_src_set_max_bytes( src_, 0 ); // gst_app_src_set_max_bytes( src_, 2 * buf_size_); // instruct src to use the required caps @@ -271,9 +238,12 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) frame_buffer->height() != height_ || frame_buffer->use_alpha() != frame_buffer_->use_alpha()) { - // end stream and stop - gst_app_src_end_of_stream (src_); - recording_ = false; + stop(); + Log::Info("Recording interrupted: new session (%d x %d) incompatible with recording (%d x %d)", frame_buffer->width(), frame_buffer->height(), width_, height_); + } + else { + // accepting a new frame buffer as input + frame_buffer_ = frame_buffer; } } @@ -283,11 +253,11 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) if (recording_ && buf_size_ > 0) { // calculate dt in ns - time_ += gst_gdouble_to_guint64( dt * 1000000.f); + timeframe_ += gst_gdouble_to_guint64( dt * 1000000.f); // if time is passed one frame duration (with 10% margin) // and if the encoder accepts data - if ( time_ > frame_duration_ - 3000000 && accept_buffer_) { + if ( timeframe_ > frame_duration_ - 3000000 && accept_buffer_) { GstBuffer *buffer = gst_buffer_new_and_alloc (buf_size_); GLenum format = frame_buffer_->use_alpha() ? GL_RGBA : GL_RGB; @@ -307,18 +277,10 @@ void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt) gst_app_src_push_buffer (src_, buffer); // NB: buffer will be unrefed by the appsrc -// // TODO : detect user request for end -// count++; -// if (count > 120) -// { -// stop(); -// } - // restart counter - time_ = 0; + timeframe_ = 0; // next timestamp timestamp_ += frame_duration_; - } } diff --git a/Recorder.h b/Recorder.h index 403de19..5874bfc 100644 --- a/Recorder.h +++ b/Recorder.h @@ -62,7 +62,7 @@ class VideoRecorder : public Recorder GstElement *pipeline_; GstAppSrc *src_; GstBaseSink *sink_; - GstClockTime time_; + GstClockTime timeframe_; GstClockTime timestamp_; GstClockTime frame_duration_; diff --git a/Session.cpp b/Session.cpp index 8b6f90d..055ef43 100644 --- a/Session.cpp +++ b/Session.cpp @@ -30,12 +30,14 @@ Session::Session() : filename_(""), failedSource_(nullptr), active_(true) Session::~Session() { + // delete all recorders + clearRecorders(); + // delete all sources for(auto it = sources_.begin(); it != sources_.end(); ) { // erase this source from the list it = deleteSource(*it); } - } void Session::setActive (bool on) @@ -241,14 +243,35 @@ Recorder *Session::frontRecorder() return recorders_.front(); } +void Session::stopRecorders() +{ + std::list::iterator iter; + for (iter=recorders_.begin(); iter != recorders_.end(); ) + (*iter)->stop(); +} + void Session::clearRecorders() { std::list::iterator iter; for (iter=recorders_.begin(); iter != recorders_.end(); ) { Recorder *rec = *iter; + rec->stop(); iter = recorders_.erase(iter); delete rec; } } +void Session::transferRecorders(Session *dest) +{ + if (dest == nullptr) + return; + + std::list::iterator iter; + for (iter=recorders_.begin(); iter != recorders_.end(); ) + { + dest->recorders_.push_back(*iter); + iter = recorders_.erase(iter); + } +} + diff --git a/Session.h b/Session.h index 42959bb..742d580 100644 --- a/Session.h +++ b/Session.h @@ -47,10 +47,12 @@ public: // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } - // add recorders + // Recorders void addRecorder(Recorder *rec); Recorder *frontRecorder(); + void stopRecorders(); void clearRecorders(); + void transferRecorders(Session *dest); // configure rendering resolution void setResolution(glm::vec3 resolution); diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 6811886..f5049af 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -889,7 +889,7 @@ void UserInterface::RenderPreview() } if (ImGui::BeginMenu("Record")) { - if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Save frame") ) + if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame") ) Mixer::manager().session()->addRecorder(new PNGRecorder); ImGui::Separator(); @@ -902,19 +902,37 @@ void UserInterface::RenderPreview() } // start recording menu else { - // start rec if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record") ) Mixer::manager().session()->addRecorder(new VideoRecorder); + ImGui::SetNextItemWidth(300); ImGui::Combo("##RecProfile", &Settings::application.record.profile, VideoRecorder::profile_name, IM_ARRAYSIZE(VideoRecorder::profile_name) ); + } + // Options menu + ImGui::MenuItem("Destination", nullptr, false, false); + { + static char* name_path[4] = { nullptr }; + if ( name_path[0] == nullptr ) { + for (int i = 0; i < 4; ++i) + name_path[i] = (char *) malloc( 1024 * sizeof(char)); + sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); + sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location"); + sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); + } if (Settings::application.record.path.empty()) Settings::application.record.path = SystemToolkit::home_path(); + sprintf( name_path[0], "%s", Settings::application.record.path.c_str()); - if (ImGui::MenuItem( Settings::application.record.path.c_str() ) ){ + int selected_path = 0; + ImGui::SetNextItemWidth(300); + ImGui::Combo("##RecDestination", &selected_path, name_path, 4); + if (selected_path > 2) std::thread (FolderDialogOpen, record_browser_path_, &record_path_selected, Settings::application.record.path).detach(); - } - + else if (selected_path > 1) + Settings::application.record.path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); + else if (selected_path > 0) + Settings::application.record.path = SystemToolkit::home_path(); } ImGui::EndMenu();