diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 008aa79..d1809c9 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -35,7 +35,8 @@ -FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), width_(0), height_(0), use_alpha_(0), caps_(NULL) +FrameGrabbing::FrameGrabbing(): pbo_index_(0), pbo_next_index_(0), size_(0), + width_(0), height_(0), use_alpha_(0), caps_(NULL) { pbo_[0] = 0; pbo_[1] = 0; @@ -267,9 +268,11 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer) -FrameGrabber::FrameGrabber(): finished_(false), initialized_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false), +FrameGrabber::FrameGrabber(): finished_(false), initialized_(false), active_(false), + endofstream_(false), accept_buffer_(false), buffering_full_(false), pause_(false), pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr), timer_firstframe_(0), - timestamp_(0), duration_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE), timestamp_on_clock_(true) + timer_pauseframe_(0), timestamp_(0), duration_(0), pause_duration_(0), frame_count_(0), + buffering_size_(MIN_BUFFER_SIZE), buffering_count_(0), timestamp_on_clock_(true) { // unique id id_ = BaseToolkit::uniqueId(); @@ -307,6 +310,23 @@ bool FrameGrabber::busy() const return false; } +bool FrameGrabber::paused() const +{ + return pause_; +} + +void FrameGrabber::setPaused(bool pause) +{ + // can pause only if already active + if (active_) { + // keep time of switch from not-paused to paused + if (pause && !pause_) + timer_pauseframe_ = gst_clock_get_time(timer_); + // set to paused + pause_ = pause; + } +} + uint64_t FrameGrabber::duration() const { return GST_TIME_AS_MSECONDS(duration_); @@ -422,7 +442,10 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps) // store a frame if recording is active and if the encoder accepts data if (active_) { - if (accept_buffer_) { + // how much buffer is used + buffering_count_ = gst_app_src_get_current_level_bytes(src_); + + if (accept_buffer_ && !pause_) { GstClockTime t = 0; // initialize timer on first occurence @@ -430,9 +453,18 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps) timer_ = gst_pipeline_get_clock ( GST_PIPELINE(pipeline_) ); timer_firstframe_ = gst_clock_get_time(timer_); } - else + else { + // if returning from pause, time of pause was stored + if (timer_pauseframe_ > 0) { + // compute duration of the pausing time and add to total pause duration + pause_duration_ += gst_clock_get_time(timer_) - timer_pauseframe_; + // reset pause frame time + timer_pauseframe_ = 0; + } // time since timer starts (first frame registered) - t = gst_clock_get_time(timer_) - timer_firstframe_; + // minus pause duration + t = gst_clock_get_time(timer_) - timer_firstframe_ - pause_duration_; + } // if time is zero (first frame) or if delta time is passed one frame duration (with a margin) if ( t == 0 || (t - duration_) > (frame_duration_ - 3000) ) { @@ -445,13 +477,14 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps) if (timestamp_on_clock_) // automatic frame presentation time stamp - // set time to actual time - // & round t to a multiples of frame duration + // Pipeline set to "do-timestamp"=TRUE + // set timestamp to actual time timestamp_ = duration_; else { - // monotonic time increment to keep fixed FPS + // monotonic timestamp increment to keep fixed FPS + // Pipeline set to "do-timestamp"=FALSE timestamp_ += frame_duration_; - // force frame presentation time stamp + // force frame presentation timestamp buffer->pts = timestamp_; // set frame duration buffer->duration = frame_duration_; @@ -464,10 +497,10 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps) { // enter buffering_full_ mode if the space left in buffering is for only few frames // (this prevents filling the buffer entirely) - if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < MIN_BUFFER_SIZE ) { + if ( buffering_size_ - buffering_count_ < MIN_BUFFER_SIZE ) { #ifndef NDEBUG Log::Info("Frame capture : Using %s of %s Buffer.", - BaseToolkit::byte_to_string(gst_app_src_get_current_level_bytes(src_)).c_str(), + BaseToolkit::byte_to_string(buffering_count_).c_str(), BaseToolkit::byte_to_string(buffering_size_).c_str()); #endif buffering_full_ = true; @@ -505,3 +538,15 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps) } } + + +uint FrameGrabber::buffering() const +{ + guint64 p = (100 * buffering_count_) / buffering_size_; + return (uint) p; +} + +guint64 FrameGrabber::frames() const +{ + return frame_count_; +} diff --git a/src/FrameGrabber.h b/src/FrameGrabber.h index 78d9648..90d4fa4 100644 --- a/src/FrameGrabber.h +++ b/src/FrameGrabber.h @@ -47,6 +47,12 @@ public: virtual bool finished() const; virtual bool busy() const; + virtual bool paused() const; + virtual void setPaused(bool pause); + + uint buffering() const; + guint64 frames() const; + protected: // only FrameGrabbing manager can add frame @@ -63,6 +69,7 @@ protected: std::atomic endofstream_; std::atomic accept_buffer_; std::atomic buffering_full_; + std::atomic pause_; // gstreamer pipeline GstElement *pipeline_; @@ -71,11 +78,14 @@ protected: GstClock *timer_; GstClockTime timer_firstframe_; + GstClockTime timer_pauseframe_; GstClockTime timestamp_; GstClockTime duration_; + GstClockTime pause_duration_; GstClockTime frame_duration_; guint64 frame_count_; guint64 buffering_size_; + guint64 buffering_count_; bool timestamp_on_clock_; // async threaded initializer diff --git a/src/OutputPreviewWindow.cpp b/src/OutputPreviewWindow.cpp index 8d8a843..7c55187 100644 --- a/src/OutputPreviewWindow.cpp +++ b/src/OutputPreviewWindow.cpp @@ -133,6 +133,13 @@ void OutputPreviewWindow::ToggleRecord(bool save_and_continue) } } +void OutputPreviewWindow::ToggleRecordPause() +{ + if (video_recorder_) { + video_recorder_->setPaused( !video_recorder_->paused() ); + } +} + void OutputPreviewWindow::ToggleVideoBroadcast() { if (video_broadcaster_) { @@ -255,12 +262,13 @@ void OutputPreviewWindow::Render() if (!_video_recorders.empty()) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); ImGui::MenuItem( MENU_RECORD, SHORTCUT_RECORD, false, false); + ImGui::MenuItem( MENU_RECORDPAUSE, SHORTCUT_RECORDPAUSE, false, false); ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false); ImGui::PopStyleColor(1); } // Stop recording menu (recorder already exists) else if (video_recorder_) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Record", SHORTCUT_RECORD) ) { // prepare for next time user open new source panel to show the recording if (Settings::application.recentRecordings.load_at_start) @@ -268,6 +276,14 @@ void OutputPreviewWindow::Render() // stop recorder video_recorder_->stop(); } + // offer the Pause recording + if (video_recorder_->paused()) { + if (ImGui::MenuItem(ICON_FA_PAUSE_CIRCLE " Resume Record", SHORTCUT_RECORDCONT)) + video_recorder_->setPaused(false); + } else { + if (ImGui::MenuItem(MENU_RECORDPAUSE, SHORTCUT_RECORDCONT)) + video_recorder_->setPaused(true); + } // offer the 'save & continue' recording if ( ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT) ) { // prepare for next time user open new source panel to show the recording @@ -289,73 +305,83 @@ void OutputPreviewWindow::Render() new VideoRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())), std::chrono::seconds(Settings::application.record.delay)) ); } + ImGui::MenuItem( MENU_RECORDPAUSE, SHORTCUT_RECORDPAUSE, false, false); ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false); ImGui::PopStyleColor(1); } - // Options menu + // Options menu if not recording ImGui::Separator(); - ImGui::MenuItem("Settings", nullptr, false, false); - float combo_width = ImGui::GetTextLineHeightWithSpacing() * 7.f; - - // offer to open config panel from here for more options - ImGui::SameLine(combo_width, IMGUI_SAME_LINE); - if (ImGuiToolkit::IconButton(13, 5, "Advanced settings")) - UserInterface::manager().navigator.showConfig(); - - // BASIC OPTIONS - 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)); - snprintf( name_path[1], 1024, "%s", ICON_FA_HOME " Home"); - snprintf( name_path[2], 1024, "%s", ICON_FA_FOLDER " Session location"); - snprintf( name_path[3], 1024, "%s", ICON_FA_FOLDER_PLUS " Select"); + if (video_recorder_) { + std::string info = "Recorded "; + info += std::to_string(video_recorder_->frames()) + " frames"; + ImGui::MenuItem(info.c_str(), nullptr, false, false); + info = std::to_string(video_recorder_->buffering()) + "% Buffer used"; + ImGui::MenuItem(info.c_str(), nullptr, false, false); } - if (Settings::application.record.path.empty()) - Settings::application.record.path = SystemToolkit::home_path(); - snprintf( name_path[0], 1024, "%s", Settings::application.record.path.c_str()); - int selected_path = 0; - ImGui::SetNextItemWidth(combo_width); - if (ImGui::Combo("##Path", &selected_path, name_path, 4) ) { - if (selected_path > 2) - recordFolderDialog->open(); - else if (selected_path > 1) - Settings::application.record.path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); - else if (selected_path > 0) + else { + ImGui::MenuItem("Settings", nullptr, false, false); + float combo_width = ImGui::GetTextLineHeightWithSpacing() * 7.f; + + // offer to open config panel from here for more options + ImGui::SameLine(combo_width, IMGUI_SAME_LINE); + if (ImGuiToolkit::IconButton(13, 5, "Advanced settings")) + UserInterface::manager().navigator.showConfig(); + + // BASIC OPTIONS + 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)); + snprintf( name_path[1], 1024, "%s", ICON_FA_HOME " Home"); + snprintf( name_path[2], 1024, "%s", ICON_FA_FOLDER " Session location"); + snprintf( name_path[3], 1024, "%s", ICON_FA_FOLDER_PLUS " Select"); + } + if (Settings::application.record.path.empty()) Settings::application.record.path = SystemToolkit::home_path(); + snprintf( name_path[0], 1024, "%s", Settings::application.record.path.c_str()); + int selected_path = 0; + ImGui::SetNextItemWidth(combo_width); + if (ImGui::Combo("##Path", &selected_path, name_path, 4) ) { + if (selected_path > 2) + recordFolderDialog->open(); + 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::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::TextButton("Path")) + Settings::application.record.path = SystemToolkit::home_path(); + + // offer to open folder location + ImVec2 draw_pos = ImGui::GetCursorPos(); + ImGui::SetCursorPos(draw_pos + ImVec2(combo_width + 3.f * ImGui::GetTextLineHeight(), -ImGui::GetFrameHeight()) ); + if (ImGuiToolkit::IconButton(3, 5, "Show in finder")) + SystemToolkit::open(Settings::application.record.path); + ImGui::SetCursorPos(draw_pos); + + // Naming menu selection + static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; + ImGui::SetNextItemWidth(combo_width); + ImGui::Combo("##Filename", &Settings::application.record.naming_mode, naming_style, IM_ARRAYSIZE(naming_style)); + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::TextButton("Filename")) + Settings::application.record.naming_mode = 1; + + ImGui::SetNextItemWidth(combo_width); + ImGuiToolkit::SliderTiming ("##Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped"); + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::TextButton("Duration")) + Settings::application.record.timeout = RECORD_MAX_TIMEOUT; + + ImGui::SetNextItemWidth(combo_width); + ImGui::SliderInt("##Trigger", &Settings::application.record.delay, 0, 5, + Settings::application.record.delay < 1 ? "Immediate" : "After %d s"); + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::TextButton("Trigger")) + Settings::application.record.delay = 0; } - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::TextButton("Path")) - Settings::application.record.path = SystemToolkit::home_path(); - - // offer to open folder location - ImVec2 draw_pos = ImGui::GetCursorPos(); - ImGui::SetCursorPos(draw_pos + ImVec2(combo_width + 3.f * ImGui::GetTextLineHeight(), -ImGui::GetFrameHeight()) ); - if (ImGuiToolkit::IconButton(3, 5, "Show in finder")) - SystemToolkit::open(Settings::application.record.path); - ImGui::SetCursorPos(draw_pos); - - // Naming menu selection - static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; - ImGui::SetNextItemWidth(combo_width); - ImGui::Combo("##Filename", &Settings::application.record.naming_mode, naming_style, IM_ARRAYSIZE(naming_style)); - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::TextButton("Filename")) - Settings::application.record.naming_mode = 1; - - ImGui::SetNextItemWidth(combo_width); - ImGuiToolkit::SliderTiming ("##Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped"); - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::TextButton("Duration")) - Settings::application.record.timeout = RECORD_MAX_TIMEOUT; - - ImGui::SetNextItemWidth(combo_width); - ImGui::SliderInt("##Trigger", &Settings::application.record.delay, 0, 5, - Settings::application.record.delay < 1 ? "Immediate" : "After %d s"); - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (ImGuiToolkit::TextButton("Trigger")) - Settings::application.record.delay = 0; ImGui::EndMenu(); } diff --git a/src/OutputPreviewWindow.h b/src/OutputPreviewWindow.h index 02f3787..3b5e26c 100644 --- a/src/OutputPreviewWindow.h +++ b/src/OutputPreviewWindow.h @@ -30,6 +30,7 @@ public: OutputPreviewWindow(); void ToggleRecord(bool save_and_continue = false); + void ToggleRecordPause(); inline bool isRecording() const { return video_recorder_ != nullptr; } void ToggleVideoBroadcast(); diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 6911ae9..76bf1a0 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -247,9 +247,9 @@ void SourceControlWindow::Render() // // Menu section for play control // - if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD "Space", nullptr, !selection_.empty())) + if (ImGui::MenuItem( MENU_PLAY_BEGIN, SHORTCUT_PLAY_BEGIN, nullptr, !selection_.empty())) replay_request_ = true; - if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space", nullptr, !selection_.empty())) + if (ImGui::MenuItem( MENU_PLAY_PAUSE, SHORTCUT_PLAY_PAUSE, nullptr, !selection_.empty())) play_toggle_request_ = true; ImGui::Separator(); diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index dd3b720..d8ed835 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -276,7 +276,7 @@ void UserInterface::handleKeyboard() // New Session Mixer::manager().close(); } - else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE, false )) { + else if (ImGui::IsKeyPressed( Control::layoutKey(GLFW_KEY_B), false )) { // restart media player sourcecontrol.Replay(); } @@ -323,6 +323,10 @@ void UserInterface::handleKeyboard() // toggle recording stop / start (or save and continue if + SHIFT modifier) outputcontrol.ToggleRecord(shift_modifier_active); } + else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE, false )) { + // toggle pause recorder + outputcontrol.ToggleRecordPause(); + } else if (ImGui::IsKeyPressed( Control::layoutKey(GLFW_KEY_Z), false )) { if (shift_modifier_active) Action::manager().redo(); @@ -2666,7 +2670,7 @@ void UserInterface::RenderHelp() ImGui::Text(SHORTCUT_PREVIEW_SRC); ImGui::NextColumn(); ImGuiToolkit::Icon(ICON_PREVIEW); ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::Text("Preview Source"); ImGui::NextColumn(); ImGui::NextColumn(); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); ImGui::Text("Press & hold key for temporary preview"); ImGui::PopFont(); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); ImGui::Text("Press & hold for momentary on/off"); ImGui::PopFont(); ImGui::NextColumn(); ImGui::Text(CTRL_MOD "TAB"); ImGui::NextColumn(); ImGui::Text("Switch view"); ImGui::NextColumn(); @@ -2684,7 +2688,10 @@ void UserInterface::RenderHelp() ImGui::Text(SHORTCUT_SHADEREDITOR); ImGui::NextColumn(); ImGui::Text(ICON_FA_CODE " " TOOLTIP_SHADEREDITOR "window"); ImGui::NextColumn(); ImGui::Text("ESC"); ImGui::NextColumn(); - ImGui::Text(" Hide / Show all windows (toggle or long press)"); ImGui::NextColumn(); + ImGui::Text(" Hide | Show all windows"); ImGui::NextColumn(); + ImGui::NextColumn(); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); ImGui::Text("Press & hold for momentary on/off"); ImGui::PopFont(); + ImGui::NextColumn(); ImGui::Separator(); ImGui::Text(SHORTCUT_NEW_FILE); ImGui::NextColumn(); ImGui::Text(MENU_NEW_FILE " session"); ImGui::NextColumn(); @@ -2721,10 +2728,10 @@ void UserInterface::RenderHelp() ImGui::Separator(); ImGui::Text(SHORTCUT_CAPTURE_PLAYER); ImGui::NextColumn(); ImGui::Text(MENU_CAPTUREFRAME " Player"); ImGui::NextColumn(); - ImGui::Text("Space"); ImGui::NextColumn(); - ImGui::Text("Toggle Play/Pause selected videos"); ImGui::NextColumn(); - ImGui::Text(CTRL_MOD "Space"); ImGui::NextColumn(); - ImGui::Text("Restart selected videos"); ImGui::NextColumn(); + ImGui::Text(SHORTCUT_PLAY_PAUSE); ImGui::NextColumn(); + ImGui::Text(MENU_PLAY_PAUSE " selected videos"); ImGui::NextColumn(); + ImGui::Text(SHORTCUT_PLAY_BEGIN); ImGui::NextColumn(); + ImGui::Text(MENU_PLAY_BEGIN " selected videos"); ImGui::NextColumn(); ImGui::Text(ICON_FA_ARROW_DOWN " " ICON_FA_ARROW_UP " " ICON_FA_ARROW_DOWN " " ICON_FA_ARROW_RIGHT ); ImGui::NextColumn(); ImGui::Text("Move the selection in the canvas"); ImGui::NextColumn(); ImGui::Separator(); diff --git a/src/defines.h b/src/defines.h index b1d51de..06cd107 100644 --- a/src/defines.h +++ b/src/defines.h @@ -176,6 +176,8 @@ #define SHORTCUT_RECORD CTRL_MOD "R" #define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue" #define SHORTCUT_RECORDCONT CTRL_MOD "Shift+R" +#define MENU_RECORDPAUSE ICON_FA_PAUSE_CIRCLE " Pause Record" +#define SHORTCUT_RECORDPAUSE CTRL_MOD "Space" #define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame" #define SHORTCUT_CAPTURE_DISPLAY "F11" #define SHORTCUT_CAPTURE_PLAYER "F10" @@ -222,6 +224,10 @@ #define TOOLTIP_PANEL_VISIBLE "Panel always visible " #define TOOLTIP_PANEL_AUTO "Panel auto hide " #define SHORTCUT_PANEL_MODE "HOME" +#define MENU_PLAY_PAUSE ICON_FA_PLAY " Play | Pause" +#define SHORTCUT_PLAY_PAUSE "Space" +#define MENU_PLAY_BEGIN ICON_FA_FAST_BACKWARD " Go to Beginning" +#define SHORTCUT_PLAY_BEGIN CTRL_MOD "B" #define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection" #define LABEL_STORE_SELECTION " Create batch"