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",
"width", G_TYPE_INT, width_,
"height", G_TYPE_INT, height_,
"framerate", GST_TYPE_FRACTION, 30, 1,
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),
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
id_ = BaseToolkit::uniqueId();
// configure fix parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
// configure default parameter
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, DEFAULT_GRABBER_FPS); // 25 FPS by default
}
FrameGrabber::~FrameGrabber()
@@ -250,7 +250,7 @@ bool FrameGrabber::busy() const
uint64_t FrameGrabber::duration() const
{
return GST_TIME_AS_MSECONDS(timestamp_);
return GST_TIME_AS_MSECONDS(duration_);
}
void FrameGrabber::stop ()
@@ -265,7 +265,7 @@ void FrameGrabber::stop ()
std::string FrameGrabber::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
return GstToolkit::time_to_string(duration_);
else
return "Inactive";
}
@@ -315,7 +315,7 @@ void FrameGrabber::addFrame (GstBuffer *buffer, GstCaps *caps, float dt)
gst_object_unref (pad);
}
// stop if an incompatilble frame buffer given
else if ( !gst_caps_is_equal( caps_, caps ))
else if ( !gst_caps_is_subset( caps_, caps ))
{
stop();
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 ( t == 0 || (t - timestamp_) > (frame_duration_ - 3000) ) {
// round time to a multiples of frame duration
t = ( t / frame_duration_) * frame_duration_;
// count frames
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
buffer->pts = t;
buffer->duration = frame_duration_;
// if time since last timestamp is more than 1 frame
if (t - timestamp_ > frame_duration_) {
// compute duration
buffer->duration = t - timestamp_;
// keep timestamp for next addFrame to one frame later
// timestamp for next addFrame will be one frame later (skip a frame)
timestamp_ = t + frame_duration_;
}
// normal case (not delayed)
else {
// normal frame duration
buffer->duration = frame_duration_;
else
{
// keep timestamp for next addFrame
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
// (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_) < 4 * gst_buffer_get_size(buffer))
if ( buffering_size_ - gst_app_src_get_current_level_bytes(src_) < 3 * 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;
}
}
// 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);
// 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
stop();
// 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.");
}
// terminate properly if finished

View File

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

View File

@@ -151,7 +151,7 @@ bool Loopback::systemLoopbackInitialized()
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)
@@ -199,9 +199,18 @@ void Loopback::init(GstCaps *caps)
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ );
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
gst_app_src_set_caps( src_, caps_);
// specify streaming framerate in the given caps
GstCaps *tmp = gst_caps_copy( 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
GstAppSrcCallbacks callbacks;

View File

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

View File

@@ -198,13 +198,16 @@ const std::vector<std::string> VideoRecorder::profile_description {
// "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 guint64 VideoRecorder::buffering_preset_value[VIDEO_RECORDER_BUFFERING_NUM_PRESET] = { MIN_BUFFER_SIZE, 104857600, 209715200, 524288000, 1073741824, 2147483648};
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[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)
@@ -213,6 +216,11 @@ void VideoRecorder::init(GstCaps *caps)
if (caps == nullptr)
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
std::string description = "appsrc name=src ! videoconvert ! ";
if (Settings::application.record.profile < 0 || Settings::application.record.profile >= DEFAULT)
@@ -273,9 +281,18 @@ void VideoRecorder::init(GstCaps *caps)
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_);
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// specify recorder framerate in the given 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_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;
@@ -308,15 +325,24 @@ void VideoRecorder::init(GstCaps *caps)
void VideoRecorder::terminate()
{
// stop the pipeline
// stop the pipeline (again)
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;
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 : try with a lower resolution / a larger buffer size / a faster codec.");
if (loss > 20.f)
Log::Warning("Video Recording lost %.0f%% of frames.", loss);
Log::Info("Video Recording : %ld frames captured in %s (aming for %ld, %.0f%% lost)",
frame_count_, GstToolkit::time_to_string(duration_, GstToolkit::TIME_STRING_READABLE).c_str(), N, 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());
}
@@ -324,7 +350,7 @@ void VideoRecorder::terminate()
std::string VideoRecorder::info() const
{
if (active_)
return GstToolkit::time_to_string(timestamp_);
return FrameGrabber::info();
else if (!endofstream_)
return "Saving file...";
else

View File

@@ -24,9 +24,6 @@ protected:
};
#define VIDEO_RECORDER_BUFFERING_NUM_PRESET 6
class VideoRecorder : public FrameGrabber
{
std::string filename_;
@@ -50,10 +47,12 @@ public:
static const char* profile_name[DEFAULT];
static const std::vector<std::string> profile_description;
static const char* buffering_preset_name[VIDEO_RECORDER_BUFFERING_NUM_PRESET];
static const guint64 buffering_preset_value[VIDEO_RECORDER_BUFFERING_NUM_PRESET];
static const char* buffering_preset_name[6];
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;
};

View File

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

View File

@@ -77,13 +77,19 @@ struct RecordConfig
int profile;
uint timeout;
int delay;
int resolution_mode;
int framerate_mode;
int buffering_mode;
int priority_mode;
RecordConfig() : path("") {
profile = 0;
timeout = RECORD_MAX_TIMEOUT;
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)
{
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, STREAMING_FPS); // fixed 30 FPS
}
void VideoStreamer::init(GstCaps *caps)
@@ -357,9 +358,18 @@ void VideoStreamer::init(GstCaps *caps)
// Set buffer size
gst_app_src_set_max_bytes( src_, buffering_size_ );
// instruct src to use the required caps
caps_ = gst_caps_copy( caps );
// specify streaming framerate in the given 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_caps_unref (tmp);
// setup callbacks
GstAppSrcCallbacks callbacks;

View File

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

View File

@@ -275,8 +275,7 @@ void UserInterface::handleKeyboard()
// video_recorder_ = nullptr;
}
else {
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger,
new VideoRecorder(VideoRecorder::buffering_preset_value[Settings::application.record.buffering_mode]),
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
std::chrono::seconds(Settings::application.record.delay)) );
}
}
@@ -1102,8 +1101,7 @@ void UserInterface::RenderPreview()
else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f));
if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record", CTRL_MOD "R") ) {
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger,
new VideoRecorder(VideoRecorder::buffering_preset_value[Settings::application.record.buffering_mode]),
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
std::chrono::seconds(Settings::application.record.delay)) );
}
ImGui::PopStyleColor(1);
@@ -1114,40 +1112,46 @@ void UserInterface::RenderPreview()
// Options menu
ImGui::Separator();
ImGui::MenuItem("Options", 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());
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]);
// offer to open config panel from here for more options
ImGui::SameLine(ImGui::GetContentRegionAvailWidth() + 1.2f * IMGUI_RIGHT_ALIGN);
if (ImGuiToolkit::IconButton(13, 5))
navigator.showConfig();
ImGui::SameLine(0);
ImGui::Text("Settings");
// 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));
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());
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();
}
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()
{
selected_button[NAV_MENU] = !selected_button[NAV_MENU];
@@ -4148,20 +4159,33 @@ void Navigator::RenderMainPannelVimix()
// get parameters to edit resolution
glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution());
// 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());
ImGuiTextBuffer info;
info.appendf("%d x %d px", output->width(), output->height());
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();
info.appendf(", %s", FrameBuffer::aspect_ratio_name[p.x]);
// Show info text bloc (dark background)
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();
// 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...
// ImGuiTextBuffer info;
// if (!sessionfilename.empty())
@@ -4472,10 +4496,10 @@ void Navigator::RenderMainPannelVimix()
ImGuiToolkit::ToolTip("Take Snapshot ", CTRL_MOD "Y");
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()));
ImGuiToolkit::HelpMarker("Snapshots keeps a list of favorite\n"
"status of the current session.\n"
ImGuiToolkit::HelpMarker("Snapshots keep a list of favorite\n"
"status of the current session.\n\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()));
// 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_LOGS, &Settings::application.widget.logs, CTRL_MOD "L");
#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
ImGui::Spacing();

View File

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