Improved FrameGrabber with clock duration and priority strategies

Keep track of actual FrameGrabber duration (different from timestamp). Two strategies for frame PTS: clock and framerate priorities. Implemented variable Framerate selection for VideoRecorder.  Integration of all this in UserInterface and Settings.
This commit is contained in:
Bruno
2021-08-11 00:20:28 +02:00
parent 7fb08e618f
commit b37d22ba47
12 changed files with 226 additions and 101 deletions

View File

@@ -134,7 +134,6 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
"format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB", "format", G_TYPE_STRING, use_alpha_ ? "RGBA" : "RGB",
"width", G_TYPE_INT, width_, "width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_, "height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL); NULL);
} }
@@ -215,12 +214,13 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt)
FrameGrabber::FrameGrabber(): finished_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false), FrameGrabber::FrameGrabber(): finished_(false), active_(false), endofstream_(false), accept_buffer_(false), buffering_full_(false),
pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr), timestamp_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE) pipeline_(nullptr), src_(nullptr), caps_(nullptr), timer_(nullptr),
timestamp_(0), duration_(0), frame_count_(0), buffering_size_(MIN_BUFFER_SIZE), timestamp_on_clock_(false)
{ {
// unique id // unique id
id_ = BaseToolkit::uniqueId(); id_ = BaseToolkit::uniqueId();
// configure fix parameter // configure default parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // 25 FPS by default
} }
FrameGrabber::~FrameGrabber() FrameGrabber::~FrameGrabber()
@@ -250,7 +250,7 @@ bool FrameGrabber::busy() const
uint64_t FrameGrabber::duration() const uint64_t FrameGrabber::duration() const
{ {
return GST_TIME_AS_MSECONDS(timestamp_); return GST_TIME_AS_MSECONDS(duration_);
} }
void FrameGrabber::stop () void FrameGrabber::stop ()
@@ -265,7 +265,7 @@ void FrameGrabber::stop ()
std::string FrameGrabber::info() const std::string FrameGrabber::info() const
{ {
if (active_) if (active_)
return GstToolkit::time_to_string(timestamp_); return GstToolkit::time_to_string(duration_);
else else
return "Inactive"; return "Inactive";
} }
@@ -315,7 +315,7 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
gst_object_unref (pad); gst_object_unref (pad);
} }
// stop if an incompatilble frame buffer given // stop if an incompatilble frame buffer given
else if ( !gst_caps_is_equal( caps_, caps )) else if ( !gst_caps_is_subset( caps_, caps ))
{ {
stop(); stop();
Log::Warning("Frame capture interrupted because the resolution changed."); Log::Warning("Frame capture interrupted because the resolution changed.");
@@ -339,23 +339,32 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
// if time is zero (first frame) or if delta time is passed one frame duration (with a margin) // if time is zero (first frame) or if delta time is passed one frame duration (with a margin)
if ( t == 0 || (t - timestamp_) > (frame_duration_ - 3000) ) { if ( t == 0 || (t - timestamp_) > (frame_duration_ - 3000) ) {
// round time to a multiples of frame duration // count frames
t = ( t / frame_duration_) * frame_duration_; frame_count_++;
// set duration to an exact multiples of frame duration
duration_ = ( t / frame_duration_) * frame_duration_;
if (timestamp_on_clock_)
// set time to actual time
// & round t to a multiples of frame duration
t = duration_;
else
// monotonic time increment to keep fixed FPS
t = timestamp_ + frame_duration_;
// set frame presentation time stamp // set frame presentation time stamp
buffer->pts = t; buffer->pts = t;
buffer->duration = frame_duration_;
// if time since last timestamp is more than 1 frame // if time since last timestamp is more than 1 frame
if (t - timestamp_ > frame_duration_) { if (t - timestamp_ > frame_duration_) {
// compute duration // timestamp for next addFrame will be one frame later (skip a frame)
buffer->duration = t - timestamp_;
// keep timestamp for next addFrame to one frame later
timestamp_ = t + frame_duration_; timestamp_ = t + frame_duration_;
} }
// normal case (not delayed) // normal case (not delayed)
else { else
// normal frame duration {
buffer->duration = frame_duration_;
// keep timestamp for next addFrame // keep timestamp for next addFrame
timestamp_ = t; timestamp_ = t;
} }
@@ -367,9 +376,14 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
{ {
// enter buffering_full_ mode if the space left in buffering is for only few frames // enter buffering_full_ mode if the space left in buffering is for only few frames
// (this prevents filling the buffer entirely) // (this prevents filling the buffer entirely)
// if ( (double) gst_app_src_get_current_level_bytes(src_) / (double) buffering_size_ > 0.8) // 80% test if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < 3 * gst_buffer_get_size(buffer)) {
if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < 4 * gst_buffer_get_size(buffer)) #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_size_).c_str());
#endif
buffering_full_ = true; buffering_full_ = true;
}
} }
// increment ref counter to make sure the frame remains available // increment ref counter to make sure the frame remains available
@@ -379,9 +393,6 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
gst_app_src_push_buffer (src_, buffer); gst_app_src_push_buffer (src_, buffer);
// NB: buffer will be unrefed by the appsrc // NB: buffer will be unrefed by the appsrc
// count frames
frame_count_++;
} }
} }
@@ -395,7 +406,7 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
// de-activate and re-send EOS // de-activate and re-send EOS
stop(); stop();
// inform // inform
Log::Warning("Frame capture : interrupted after %s.", GstToolkit::time_to_string(timestamp_).c_str()); Log::Warning("Frame capture : interrupted after %s.", GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str());
Log::Info("Frame capture: not space left on drive / encoding buffer full."); Log::Info("Frame capture: not space left on drive / encoding buffer full.");
} }
// terminate properly if finished // terminate properly if finished

View File

@@ -13,7 +13,7 @@
// read pixels & pbo should be the fastest // read pixels & pbo should be the fastest
// https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage // https://stackoverflow.com/questions/38140527/glreadpixels-vs-glgetteximage
#define USE_GLREADPIXEL #define USE_GLREADPIXEL
#define DEFAULT_GRABBER_FPS 30
#define MIN_BUFFER_SIZE 33177600 // 33177600 bytes = 1 frames 4K, 9 frames 720p #define MIN_BUFFER_SIZE 33177600 // 33177600 bytes = 1 frames 4K, 9 frames 720p
class FrameBuffer; class FrameBuffer;
@@ -68,9 +68,11 @@ protected:
GstClock *timer_; GstClock *timer_;
GstClockTime timestamp_; GstClockTime timestamp_;
GstClockTime duration_;
GstClockTime frame_duration_; GstClockTime frame_duration_;
guint64 frame_count_; guint64 frame_count_;
guint64 buffering_size_; guint64 buffering_size_;
bool timestamp_on_clock_;
GstClockTime timer_firstframe_; GstClockTime timer_firstframe_;

View File

@@ -151,7 +151,7 @@ bool Loopback::systemLoopbackInitialized()
Loopback::Loopback() : FrameGrabber() Loopback::Loopback() : FrameGrabber()
{ {
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60); frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // fixed 30 FPS
} }
void Loopback::init(GstCaps *caps) void Loopback::init(GstCaps *caps)
@@ -199,9 +199,18 @@ void Loopback::init(GstCaps *caps)
// Set buffer size // Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ ); gst_app_src_set_max_bytes( src_, buffering_size_ );
// instruct src to use the required caps // specify streaming framerate in the given caps
caps_ = gst_caps_copy( caps ); GstCaps *tmp = gst_caps_copy( caps );
gst_app_src_set_caps( src_, caps_); GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, 30, 1); // fixed 30 FPS
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks // setup callbacks
GstAppSrcCallbacks callbacks; GstAppSrcCallbacks callbacks;

View File

@@ -12,7 +12,7 @@
#define OSC_STREAM_REJECT "/reject" #define OSC_STREAM_REJECT "/reject"
#define OSC_STREAM_DISCONNECT "/disconnect" #define OSC_STREAM_DISCONNECT "/disconnect"
#define STREAMING_FPS 30
#define MAX_HANDSHAKE 20 #define MAX_HANDSHAKE 20
#define HANDSHAKE_PORT 71310 #define HANDSHAKE_PORT 71310
#define STREAM_REQUEST_PORT 71510 #define STREAM_REQUEST_PORT 71510

View File

@@ -198,13 +198,16 @@ const std::vector<std::string> VideoRecorder::profile_description {
// "qtmux ! filesink name=sink"; // "qtmux ! filesink name=sink";
const char* VideoRecorder::buffering_preset_name[VIDEO_RECORDER_BUFFERING_NUM_PRESET] = { "30 MB", "100 MB", "200 MB", "500 MB", "1 GB", "2 GB" }; const char* VideoRecorder::buffering_preset_name[6] = { "30 MB", "100 MB", "200 MB", "500 MB", "1 GB", "2 GB" };
const guint64 VideoRecorder::buffering_preset_value[VIDEO_RECORDER_BUFFERING_NUM_PRESET] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648}; const guint64 VideoRecorder::buffering_preset_value[6] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648 };
const char* VideoRecorder::framerate_preset_name[3] = { "15 FPS", "25 FPS", "30 FPS" };
const gint VideoRecorder::framerate_preset_value[3] = { 15, 25, 30 };
VideoRecorder::VideoRecorder(guint64 buffersize) : FrameGrabber() VideoRecorder::VideoRecorder() : FrameGrabber()
{ {
buffering_size_ = MAX( MIN_BUFFER_SIZE, buffersize);
} }
void VideoRecorder::init(GstCaps *caps) void VideoRecorder::init(GstCaps *caps)
@@ -213,6 +216,11 @@ void VideoRecorder::init(GstCaps *caps)
if (caps == nullptr) if (caps == nullptr)
return; return;
// apply settings
buffering_size_ = MAX( MIN_BUFFER_SIZE, buffering_preset_value[Settings::application.record.buffering_mode]);
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, framerate_preset_value[Settings::application.record.framerate_mode]);
timestamp_on_clock_ = Settings::application.record.priority_mode < 1;
// create a gstreamer pipeline // create a gstreamer pipeline
std::string description = "appsrc name=src ! videoconvert ! "; std::string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT) if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
@@ -273,9 +281,18 @@ void VideoRecorder::init(GstCaps *caps)
// Set buffer size // Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_); gst_app_src_set_max_bytes( src_, buffering_size_);
// instruct src to use the required caps // specify recorder framerate in the given caps
caps_ = gst_caps_copy( caps ); GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, framerate_preset_value[Settings::application.record.framerate_mode], 1);
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_); gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks // setup callbacks
GstAppSrcCallbacks callbacks; GstAppSrcCallbacks callbacks;
@@ -308,15 +325,24 @@ void VideoRecorder::init(GstCaps *caps)
void VideoRecorder::terminate() void VideoRecorder::terminate()
{ {
// stop the pipeline // stop the pipeline (again)
gst_element_set_state (pipeline_, GST_STATE_NULL); gst_element_set_state (pipeline_, GST_STATE_NULL);
guint64 N = MAX( (guint64) timestamp_ / (guint64) frame_duration_, frame_count_); // statistics on expected number of frames
guint64 N = MAX( (guint64) duration_ / (guint64) frame_duration_, frame_count_);
float loss = 100.f * ((float) (N - frame_count_) ) / (float) N; float loss = 100.f * ((float) (N - frame_count_) ) / (float) N;
Log::Info("Video Recording : %ld frames in %s (aming for %ld, %.0f%% lost)", frame_count_, GstToolkit::time_to_string(timestamp_).c_str(), N, loss); Log::Info("Video Recording : %ld frames captured in %s (aming for %ld, %.0f%% lost)",
Log::Info("Video Recording : try with a lower resolution / a larger buffer size / a faster codec."); frame_count_, GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str(), N, loss);
if (loss > 20.f)
Log::Warning("Video Recording lost %.0f%% of frames.", loss); // warn user if more than 10% lost
if (loss > 10.f) {
if (timestamp_on_clock_)
Log::Warning("Video Recording lost %.0f%% of frames: framerate could not be maintained at %ld FPS.", loss, GST_SECOND / frame_duration_);
else
Log::Warning("Video Recording lost %.0f%% of frames: video is only %s long.",
loss, GstToolkit::time_to_string(timestamp_, GstToolkit::TIME_STRING_READABLE).c_str());
Log::Info("Video Recording : try a lower resolution / a lower framerate / a larger buffer size / a faster codec.");
}
Log::Notify("Video Recording %s is ready.", filename_.c_str()); Log::Notify("Video Recording %s is ready.", filename_.c_str());
} }
@@ -324,7 +350,7 @@ void VideoRecorder::terminate()
std::string VideoRecorder::info() const std::string VideoRecorder::info() const
{ {
if (active_) if (active_)
return GstToolkit::time_to_string(timestamp_); return FrameGrabber::info();
else if (!endofstream_) else if (!endofstream_)
return "Saving file..."; return "Saving file...";
else else

View File

@@ -24,9 +24,6 @@ protected:
}; };
#define VIDEO_RECORDER_BUFFERING_NUM_PRESET 6
class VideoRecorder : public FrameGrabber class VideoRecorder : public FrameGrabber
{ {
std::string filename_; std::string filename_;
@@ -50,10 +47,12 @@ public:
static const char* profile_name[DEFAULT]; static const char* profile_name[DEFAULT];
static const std::vector<std::string> profile_description; static const std::vector<std::string> profile_description;
static const char* buffering_preset_name[VIDEO_RECORDER_BUFFERING_NUM_PRESET]; static const char* buffering_preset_name[6];
static const guint64 buffering_preset_value[VIDEO_RECORDER_BUFFERING_NUM_PRESET]; static const guint64 buffering_preset_value[6];
static const char* framerate_preset_name[3];
static const int framerate_preset_value[3];
VideoRecorder(guint64 buffersize = 0); VideoRecorder();
std::string info() const override; std::string info() const override;
}; };

View File

@@ -102,7 +102,10 @@ void Settings::Save()
RecordNode->SetAttribute("profile", application.record.profile); RecordNode->SetAttribute("profile", application.record.profile);
RecordNode->SetAttribute("timeout", application.record.timeout); RecordNode->SetAttribute("timeout", application.record.timeout);
RecordNode->SetAttribute("delay", application.record.delay); RecordNode->SetAttribute("delay", application.record.delay);
RecordNode->SetAttribute("resolution_mode", application.record.resolution_mode);
RecordNode->SetAttribute("framerate_mode", application.record.framerate_mode);
RecordNode->SetAttribute("buffering_mode", application.record.buffering_mode); RecordNode->SetAttribute("buffering_mode", application.record.buffering_mode);
RecordNode->SetAttribute("priority_mode", application.record.priority_mode);
pRoot->InsertEndChild(RecordNode); pRoot->InsertEndChild(RecordNode);
// Transition // Transition
@@ -315,7 +318,10 @@ void Settings::Load()
recordnode->QueryIntAttribute("profile", &application.record.profile); recordnode->QueryIntAttribute("profile", &application.record.profile);
recordnode->QueryUnsignedAttribute("timeout", &application.record.timeout); recordnode->QueryUnsignedAttribute("timeout", &application.record.timeout);
recordnode->QueryIntAttribute("delay", &application.record.delay); recordnode->QueryIntAttribute("delay", &application.record.delay);
recordnode->QueryIntAttribute("resolution_mode", &application.record.resolution_mode);
recordnode->QueryIntAttribute("framerate_mode", &application.record.framerate_mode);
recordnode->QueryIntAttribute("buffering_mode", &application.record.buffering_mode); recordnode->QueryIntAttribute("buffering_mode", &application.record.buffering_mode);
recordnode->QueryIntAttribute("priority_mode", &application.record.priority_mode);
const char *path_ = recordnode->Attribute("path"); const char *path_ = recordnode->Attribute("path");
if (path_) if (path_)

View File

@@ -77,13 +77,19 @@ struct RecordConfig
int profile; int profile;
uint timeout; uint timeout;
int delay; int delay;
int resolution_mode;
int framerate_mode;
int buffering_mode; int buffering_mode;
int priority_mode;
RecordConfig() : path("") { RecordConfig() : path("") {
profile = 0; profile = 0;
timeout = RECORD_MAX_TIMEOUT; timeout = RECORD_MAX_TIMEOUT;
delay = 0; delay = 0;
buffering_mode = 0; resolution_mode = 1;
framerate_mode = 1;
buffering_mode = 2;
priority_mode = 1;
} }
}; };

View File

@@ -287,6 +287,7 @@ void Streaming::addStream(const std::string &sender, int reply_to, const std::st
VideoStreamer::VideoStreamer(const NetworkToolkit::StreamConfig &conf): FrameGrabber(), config_(conf), stopped_(false) VideoStreamer::VideoStreamer(const NetworkToolkit::StreamConfig &conf): FrameGrabber(), config_(conf), stopped_(false)
{ {
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, STREAMING_FPS); // fixed 30 FPS
} }
void VideoStreamer::init(GstCaps *caps) void VideoStreamer::init(GstCaps *caps)
@@ -357,9 +358,18 @@ void VideoStreamer::init(GstCaps *caps)
// Set buffer size // Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ ); gst_app_src_set_max_bytes( src_, buffering_size_ );
// instruct src to use the required caps // specify streaming framerate in the given caps
caps_ = gst_caps_copy( caps ); GstCaps *tmp = gst_caps_copy( caps );
GValue v = { 0, };
g_value_init (&v, GST_TYPE_FRACTION);
gst_value_set_fraction (&v, STREAMING_FPS, 1); // fixed 30 FPS
gst_caps_set_value(tmp, "framerate", &v);
g_value_unset (&v);
// instruct src to use the caps
caps_ = gst_caps_copy( tmp );
gst_app_src_set_caps (src_, caps_); gst_app_src_set_caps (src_, caps_);
gst_caps_unref (tmp);
// setup callbacks // setup callbacks
GstAppSrcCallbacks callbacks; GstAppSrcCallbacks callbacks;

View File

@@ -13,6 +13,7 @@
#include "NetworkToolkit.h" #include "NetworkToolkit.h"
#include "FrameGrabber.h" #include "FrameGrabber.h"
class Session; class Session;
class VideoStreamer; class VideoStreamer;

View File

@@ -275,8 +275,7 @@ void UserInterface::handleKeyboard()
// video_recorder_ = nullptr; // video_recorder_ = nullptr;
} }
else { else {
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
new VideoRecorder(VideoRecorder::buffering_preset_value[Settings::application.record.buffering_mode]),
std::chrono::seconds(Settings::application.record.delay)) ); std::chrono::seconds(Settings::application.record.delay)) );
} }
} }
@@ -1102,8 +1101,7 @@ void UserInterface::RenderPreview()
else { else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f));
if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record", CTRL_MOD "R") ) { if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record", CTRL_MOD "R") ) {
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
new VideoRecorder(VideoRecorder::buffering_preset_value[Settings::application.record.buffering_mode]),
std::chrono::seconds(Settings::application.record.delay)) ); std::chrono::seconds(Settings::application.record.delay)) );
} }
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
@@ -1114,40 +1112,46 @@ void UserInterface::RenderPreview()
// Options menu // Options menu
ImGui::Separator(); ImGui::Separator();
ImGui::MenuItem("Options", nullptr, false, false); ImGui::MenuItem("Options", nullptr, false, false);
{ // offer to open config panel from here for more options
static char* name_path[4] = { nullptr }; ImGui::SameLine(ImGui::GetContentRegionAvailWidth() + 1.2f * IMGUI_RIGHT_ALIGN);
if ( name_path[0] == nullptr ) { if (ImGuiToolkit::IconButton(13, 5))
for (int i = 0; i < 4; ++i) navigator.showConfig();
name_path[i] = (char *) malloc( 1024 * sizeof(char)); ImGui::SameLine(0);
sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); ImGui::Text("Settings");
sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location"); // BASIC OPTIONS
sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); static char* name_path[4] = { nullptr };
} if ( name_path[0] == nullptr ) {
if (Settings::application.record.path.empty()) for (int i = 0; i < 4; ++i)
Settings::application.record.path = SystemToolkit::home_path(); name_path[i] = (char *) malloc( 1024 * sizeof(char));
sprintf( name_path[0], "%s", Settings::application.record.path.c_str()); sprintf( name_path[1], "%s", ICON_FA_HOME " Home");
sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location");
int selected_path = 0; sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
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::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGuiToolkit::SliderTiming ("Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Trigger", &Settings::application.record.delay, 0, 5,
Settings::application.record.delay < 1 ? "Immediate" : "After %d s");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Buffer", &Settings::application.record.buffering_mode, 0, VIDEO_RECORDER_BUFFERING_NUM_PRESET-1,
VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode]);
} }
if (Settings::application.record.path.empty())
Settings::application.record.path = SystemToolkit::home_path();
sprintf( name_path[0], "%s", Settings::application.record.path.c_str());
int selected_path = 0;
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
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::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGuiToolkit::SliderTiming ("Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped");
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Trigger", &Settings::application.record.delay, 0, 5,
Settings::application.record.delay < 1 ? "Immediate" : "After %d s");
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
// ImGui::SliderInt("Buffer", &Settings::application.record.buffering_mode, 0, VIDEO_RECORDER_BUFFERING_NUM_PRESET-1,
// VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode]);
//
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Share stream")) if (ImGui::BeginMenu("Share stream"))
@@ -3343,6 +3347,13 @@ void Navigator::showPannelSource(int index)
} }
} }
void Navigator::showConfig()
{
selected_button[NAV_MENU] = true;
applyButtonSelection(NAV_MENU);
show_config_ = true;
}
void Navigator::togglePannelMenu() void Navigator::togglePannelMenu()
{ {
selected_button[NAV_MENU] = !selected_button[NAV_MENU]; selected_button[NAV_MENU] = !selected_button[NAV_MENU];
@@ -4148,20 +4159,33 @@ void Navigator::RenderMainPannelVimix()
// get parameters to edit resolution // get parameters to edit resolution
glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution()); glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution());
// Basic information on session ImGuiTextBuffer info;
ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO ); info.appendf("%d x %d px", output->width(), output->height());
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.75);
if (sessionfilename.empty())
ImGui::Text(" <unsaved>");
else
ImGui::Text(" %s", SystemToolkit::filename(sessionfilename).c_str());
if (p.x > -1) if (p.x > -1)
ImGui::Text(" %dx%dpx, %s", output->width(), output->height(), FrameBuffer::aspect_ratio_name[p.x]); info.appendf(", %s", FrameBuffer::aspect_ratio_name[p.x]);
else
ImGui::Text(" %dx%dpx", output->width(), output->height()); // Show info text bloc (dark background)
ImGui::PopStyleVar(); ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO );
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::InputText("##Info", (char *)info.c_str(), info.size(), ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor(1);
ImGui::PopFont(); ImGui::PopFont();
// Alternative // Basic information on session
// ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO );
// ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.75);
// if (sessionfilename.empty())
// ImGui::Text(" <unsaved>");
// else
// ImGui::Text(" %s", SystemToolkit::filename(sessionfilename).c_str());
// if (p.x > -1)
// ImGui::Text(" %dx%dpx, %s", output->width(), output->height(), FrameBuffer::aspect_ratio_name[p.x]);
// else
// ImGui::Text(" %dx%dpx", output->width(), output->height());
// ImGui::PopStyleVar();
// ImGui::PopFont();
// Kept for later? Larger info box with more details on the session file... // Kept for later? Larger info box with more details on the session file...
// ImGuiTextBuffer info; // ImGuiTextBuffer info;
// if (!sessionfilename.empty()) // if (!sessionfilename.empty())
@@ -4472,10 +4496,10 @@ void Navigator::RenderMainPannelVimix()
ImGuiToolkit::ToolTip("Take Snapshot ", CTRL_MOD "Y"); ImGuiToolkit::ToolTip("Take Snapshot ", CTRL_MOD "Y");
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing())); ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()));
ImGuiToolkit::HelpMarker("Snapshots keeps a list of favorite\n" ImGuiToolkit::HelpMarker("Snapshots keep a list of favorite\n"
"status of the current session.\n" "status of the current session.\n\n"
"Clic an item to preview or edit.\n" "Clic an item to preview or edit.\n"
"Double-clic to restore immediately.\n"); "Double-clic to apply.\n");
// ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); // ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing()));
// ImGuiToolkit::HelpMarker("Snapshots capture the state of the session.\n" // ImGuiToolkit::HelpMarker("Snapshots capture the state of the session.\n"
@@ -4580,6 +4604,37 @@ void Navigator::RenderMainPannelSettings()
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T"); ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T");
ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L"); ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
#endif #endif
// Recording preferences
ImGui::Spacing();
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Text("Recording");
ImGuiToolkit::HelpMarker(ICON_FA_CARET_RIGHT " Capture 15, 25 or 30 frames per seconds.\n"
// ICON_FA_CARET_RIGHT " Downscale captured frames if larger than Height.\n"
ICON_FA_CARET_RIGHT " Size of RAM Buffer storing frames before recording.\n"
ICON_FA_CARET_RIGHT " Priority when buffer is full and recorder skips frames;\n "
ICON_FA_ANGLE_RIGHT " Clock : variable framerate, correct duration.\n "
ICON_FA_ANGLE_RIGHT " Framerate: correct framerate, shorter duration.");
ImGui::SameLine(0);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Framerate", &Settings::application.record.framerate_mode, VideoRecorder::framerate_preset_name, IM_ARRAYSIZE(VideoRecorder::framerate_preset_name) );
// ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
// ImGui::Combo("Height", &Settings::application.record.resolution_mode, FrameBuffer::resolution_name, IM_ARRAYSIZE(FrameBuffer::resolution_name) );
// TODO: compute number of frames in buffer and show warning sign if too low
// ImGuiToolkit::HelpMarker("Buffer to store captured frames before recording", ICON_FA_EXCLAMATION_TRIANGLE);
// ImGui::SameLine(0);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::SliderInt("Buffer", &Settings::application.record.buffering_mode, 0, IM_ARRAYSIZE(VideoRecorder::buffering_preset_name)-1,
VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode]);
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Clock\0Framerate\0");
// system preferences // system preferences
ImGui::Spacing(); ImGui::Spacing();

View File

@@ -88,7 +88,7 @@ public:
void showPannelSource(int index); void showPannelSource(int index);
void togglePannelMenu(); void togglePannelMenu();
void togglePannelNew(); void togglePannelNew();
void showConfig();
void Render(); void Render();
}; };