mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
17
Loopback.cpp
17
Loopback.cpp
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
52
Recorder.cpp
52
Recorder.cpp
@@ -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
|
||||||
|
|||||||
11
Recorder.h
11
Recorder.h
@@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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_)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
14
Streamer.cpp
14
Streamer.cpp
@@ -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;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "NetworkToolkit.h"
|
#include "NetworkToolkit.h"
|
||||||
#include "FrameGrabber.h"
|
#include "FrameGrabber.h"
|
||||||
|
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
class VideoStreamer;
|
class VideoStreamer;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user