mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-06 16:00:00 +01:00
Saving and loading of timeline, with fading and gaps. Applying fading to
MediaSource. Playing with timeline options to facilitate its use.
This commit is contained in:
@@ -636,7 +636,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
|
||||
|
||||
// read user input and activate widget
|
||||
const bool left_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right) | (ImGui::GetIO().KeyAlt & left_mouse_press) ;
|
||||
const bool mouse_press = left_mouse_press | right_mouse_press;
|
||||
const bool hovered = ImGui::ItemHoverable(bbox, id);
|
||||
bool temp_input_is_active = ImGui::TempInputIsActive(id);
|
||||
@@ -653,7 +653,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
|
||||
else
|
||||
return false;
|
||||
|
||||
ImVec4* colors = ImGui::GetStyle().Colors;
|
||||
static ImVec4* colors = ImGui::GetStyle().Colors;
|
||||
ImVec4 bg_color = hovered ? colors[ImGuiCol_FrameBgHovered] : colors[ImGuiCol_FrameBg];
|
||||
|
||||
// enter edit if widget is active
|
||||
@@ -674,12 +674,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
|
||||
if (previous_index == UINT32_MAX)
|
||||
previous_index = index;
|
||||
|
||||
if (left_mouse_press) {
|
||||
lines_array[index] = values_max - y;
|
||||
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
|
||||
lines_array[i] = values_max - y;
|
||||
}
|
||||
else {
|
||||
if (right_mouse_press){
|
||||
static float target_value = values_min;
|
||||
|
||||
// toggle value histo
|
||||
@@ -691,6 +686,11 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array,
|
||||
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
|
||||
histogram_array[i] = target_value;
|
||||
}
|
||||
else {
|
||||
lines_array[index] = values_max - y;
|
||||
for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i)
|
||||
lines_array[i] = values_max - y;
|
||||
}
|
||||
|
||||
previous_index = index;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ MediaPlayer::MediaPlayer()
|
||||
|
||||
uri_ = "undefined";
|
||||
pipeline_ = nullptr;
|
||||
converter_ = nullptr;
|
||||
|
||||
ready_ = false;
|
||||
failed_ = false;
|
||||
@@ -209,15 +210,22 @@ void MediaPlayer::execute_open()
|
||||
// equivalent to gst-launch-1.0 uridecodebin uri=file:///path_to_file/filename.mp4 ! videoconvert ! ximagesink
|
||||
|
||||
// Build string describing pipeline
|
||||
// NB: video convertion chroma-resampler
|
||||
// Duplicates the samples when upsampling and drops when downsampling 0
|
||||
// Uses linear interpolation 1 (default)
|
||||
// Uses cubic interpolation 2
|
||||
// Uses sinc interpolation 3
|
||||
// video deinterlacing method
|
||||
// tomsmocomp (0) – Motion Adaptive: Motion Search
|
||||
// greedyh (1) – Motion Adaptive: Advanced Detection
|
||||
// greedyl (2) – Motion Adaptive: Simple Detection
|
||||
// vfir (3) – Blur Vertical
|
||||
// linear (4) – Linear
|
||||
// scalerbob (6) – Double lines
|
||||
// video convertion chroma-resampler
|
||||
// Duplicates the samples when upsampling and drops when downsampling 0
|
||||
// Uses linear interpolation 1 (default)
|
||||
// Uses cubic interpolation 2
|
||||
// Uses sinc interpolation 3
|
||||
string description = "uridecodebin uri=" + uri_ + " ! ";
|
||||
if (media_.interlaced)
|
||||
description += "deinterlace ! ";
|
||||
description += "videoconvert chroma-resampler=2 ! appsink name=sink";
|
||||
description += "deinterlace method=2 ! ";
|
||||
description += "videoconvert chroma-resampler=2 n-threads=2 ! appsink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
@@ -336,6 +344,10 @@ void MediaPlayer::close()
|
||||
// un-ready the media player
|
||||
ready_ = false;
|
||||
|
||||
// no more need to reference converter
|
||||
if (converter_ != nullptr)
|
||||
gst_object_unref (converter_);
|
||||
|
||||
// clean up GST
|
||||
if (pipeline_ != nullptr) {
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
@@ -697,12 +709,7 @@ void MediaPlayer::update()
|
||||
// try to get info from discoverer
|
||||
if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready )
|
||||
{
|
||||
// remember loaded gaps
|
||||
TimeIntervalSet gaps = media_.timeline.gaps();
|
||||
// ok, discovering thread is finished ! Get the media info
|
||||
media_ = discoverer_.get();
|
||||
// restore loaded gaps (overwriten above)
|
||||
media_.timeline.setGaps( gaps );
|
||||
// if its ok, open the media
|
||||
if (media_.valid)
|
||||
execute_open();
|
||||
@@ -715,13 +722,6 @@ void MediaPlayer::update()
|
||||
if (!enabled_ || (media_.isimage && textureindex_>0 ) )
|
||||
return;
|
||||
|
||||
// if (seeking_) {
|
||||
// GstState state;
|
||||
// gst_element_get_state (pipeline_, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
// seeking_ = false;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// local variables before trying to update
|
||||
guint read_index = 0;
|
||||
bool need_loop = false;
|
||||
@@ -895,11 +895,15 @@ double MediaPlayer::playSpeed() const
|
||||
return rate_;
|
||||
}
|
||||
|
||||
Timeline MediaPlayer::timeline()
|
||||
Timeline *MediaPlayer::timeline()
|
||||
{
|
||||
return media_.timeline;
|
||||
return &media_.timeline;
|
||||
}
|
||||
|
||||
float MediaPlayer::currentTimelineFading()
|
||||
{
|
||||
return media_.timeline.fadingAt(position_);
|
||||
}
|
||||
|
||||
void MediaPlayer::setTimeline(Timeline tl)
|
||||
{
|
||||
@@ -1040,6 +1044,7 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p)
|
||||
// send frames to media player only if ready
|
||||
MediaPlayer *m = (MediaPlayer *)p;
|
||||
if (m && m->ready_) {
|
||||
|
||||
// fill frame from buffer
|
||||
if ( !m->fill_frame(buf, MediaPlayer::PREROLL) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
|
||||
@@ -49,7 +49,9 @@ struct MediaInfo {
|
||||
inline MediaInfo& operator = (const MediaInfo& b)
|
||||
{
|
||||
if (this != &b) {
|
||||
this->timeline = b.timeline;
|
||||
this->timeline.setEnd( b.timeline.end() );
|
||||
this->timeline.setStep( b.timeline.step() );
|
||||
this->timeline.setFirst( b.timeline.first() );
|
||||
this->width = b.width;
|
||||
this->par_width = b.par_width;
|
||||
this->height = b.height;
|
||||
@@ -194,11 +196,10 @@ public:
|
||||
* - duration : timeline.duration()
|
||||
* - frame duration : timeline.step()
|
||||
*/
|
||||
Timeline timeline();
|
||||
|
||||
Timeline *timeline();
|
||||
void setTimeline(Timeline tl);
|
||||
// void toggleGapInTimeline(GstClockTime from, GstClockTime to);
|
||||
|
||||
float currentTimelineFading();
|
||||
/**
|
||||
* Get position time
|
||||
* */
|
||||
@@ -261,6 +262,7 @@ private:
|
||||
LoopMode loop_;
|
||||
GstState desired_state_;
|
||||
GstElement *pipeline_;
|
||||
GstElement *converter_;
|
||||
GstVideoInfo v_frame_video_info_;
|
||||
std::atomic<bool> ready_;
|
||||
std::atomic<bool> failed_;
|
||||
|
||||
@@ -135,6 +135,7 @@ void MediaSource::render()
|
||||
// render the media player into frame buffer
|
||||
static glm::mat4 projection = glm::ortho(-1.f, 1.f, 1.f, -1.f, -1.f, 1.f);
|
||||
renderbuffer_->begin();
|
||||
mediasurface_->shader()->color.a = mediaplayer_->currentTimelineFading();
|
||||
mediasurface_->draw(glm::identity<glm::mat4>(), projection);
|
||||
renderbuffer_->end();
|
||||
}
|
||||
|
||||
@@ -184,17 +184,25 @@ void SessionCreator::visit(MediaPlayer &n)
|
||||
XMLElement* mediaplayerNode = xmlCurrent_->FirstChildElement("MediaPlayer");
|
||||
if (mediaplayerNode) {
|
||||
// timeline
|
||||
XMLElement *gapselement = mediaplayerNode->FirstChildElement("Gaps");
|
||||
if (gapselement) {
|
||||
XMLElement *timelineelement = mediaplayerNode->FirstChildElement("Timeline");
|
||||
if (timelineelement) {
|
||||
Timeline tl;
|
||||
XMLElement* gap = gapselement->FirstChildElement("Interval");
|
||||
for( ; gap ; gap = gap->NextSiblingElement())
|
||||
{
|
||||
GstClockTime a = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime b = GST_CLOCK_TIME_NONE;
|
||||
gap->QueryUnsigned64Attribute("begin", &a);
|
||||
gap->QueryUnsigned64Attribute("end", &b);
|
||||
tl.addGap( a, b );
|
||||
XMLElement *gapselement = timelineelement->FirstChildElement("Gaps");
|
||||
if (gapselement) {
|
||||
XMLElement* gap = gapselement->FirstChildElement("Interval");
|
||||
for( ; gap ; gap = gap->NextSiblingElement())
|
||||
{
|
||||
GstClockTime a = GST_CLOCK_TIME_NONE;
|
||||
GstClockTime b = GST_CLOCK_TIME_NONE;
|
||||
gap->QueryUnsigned64Attribute("begin", &a);
|
||||
gap->QueryUnsigned64Attribute("end", &b);
|
||||
tl.addGap( a, b );
|
||||
}
|
||||
}
|
||||
XMLElement *fadingselement = timelineelement->FirstChildElement("Fading");
|
||||
if (fadingselement) {
|
||||
XMLElement* array = fadingselement->FirstChildElement("array");
|
||||
XMLElementDecodeArray(array, tl.fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
n.setTimeline(tl);
|
||||
}
|
||||
|
||||
@@ -147,17 +147,27 @@ void SessionVisitor::visit(MediaPlayer &n)
|
||||
newelement->SetAttribute("loop", (int) n.loop());
|
||||
newelement->SetAttribute("speed", n.playSpeed());
|
||||
|
||||
// timeline
|
||||
XMLElement *timelineelement = xmlDoc_->NewElement("Timeline");
|
||||
|
||||
// gaps in timeline
|
||||
XMLElement *gapselement = xmlDoc_->NewElement("Gaps");
|
||||
TimeIntervalSet gaps = n.timeline().gaps();
|
||||
TimeIntervalSet gaps = n.timeline()->gaps();
|
||||
for( auto it = gaps.begin(); it!= gaps.end(); it++) {
|
||||
XMLElement *g = xmlDoc_->NewElement("Interval");
|
||||
g->SetAttribute("begin", (*it).begin);
|
||||
g->SetAttribute("end", (*it).end);
|
||||
gapselement->InsertEndChild(g);
|
||||
}
|
||||
newelement->InsertEndChild(gapselement);
|
||||
timelineelement->InsertEndChild(gapselement);
|
||||
|
||||
// fading in timeline
|
||||
XMLElement *fadingelement = xmlDoc_->NewElement("Fading");
|
||||
XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
fadingelement->InsertEndChild(array);
|
||||
timelineelement->InsertEndChild(fadingelement);
|
||||
|
||||
newelement->InsertEndChild(timelineelement);
|
||||
xmlCurrent_->InsertEndChild(newelement);
|
||||
}
|
||||
|
||||
|
||||
283
Timeline.cpp
283
Timeline.cpp
@@ -6,8 +6,6 @@
|
||||
#include "Log.h"
|
||||
#include "Timeline.h"
|
||||
|
||||
#define SEGMENT_ARRAY_MAX_SIZE 1000
|
||||
|
||||
struct includesTime: public std::unary_function<TimeInterval, bool>
|
||||
{
|
||||
inline bool operator()(const TimeInterval s) const
|
||||
@@ -36,6 +34,8 @@ Timeline& Timeline::operator = (const Timeline& b)
|
||||
this->timing_ = b.timing_;
|
||||
this->step_ = b.step_;
|
||||
this->gaps_ = b.gaps_;
|
||||
memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -47,8 +47,8 @@ void Timeline::reset()
|
||||
first_ = GST_CLOCK_TIME_NONE;
|
||||
step_ = GST_CLOCK_TIME_NONE;
|
||||
|
||||
// clear gaps
|
||||
gaps_.clear();
|
||||
clearGaps();
|
||||
clearFading();
|
||||
}
|
||||
|
||||
bool Timeline::is_valid()
|
||||
@@ -58,8 +58,10 @@ bool Timeline::is_valid()
|
||||
|
||||
void Timeline::setFirst(GstClockTime first)
|
||||
{
|
||||
timing_.begin = 0;
|
||||
first_ = first;
|
||||
// validate timing
|
||||
if (first_ != GST_CLOCK_TIME_NONE)
|
||||
timing_.begin = 0;
|
||||
}
|
||||
|
||||
void Timeline::setEnd(GstClockTime end)
|
||||
@@ -72,7 +74,6 @@ void Timeline::setStep(GstClockTime dt)
|
||||
step_ = dt;
|
||||
}
|
||||
|
||||
|
||||
GstClockTime Timeline::next(GstClockTime time) const
|
||||
{
|
||||
GstClockTime next_time = time;
|
||||
@@ -94,52 +95,18 @@ GstClockTime Timeline::previous(GstClockTime time) const
|
||||
return prev_time;
|
||||
}
|
||||
|
||||
void Timeline::updateGapsFromArray(float *array_, size_t array_size_)
|
||||
float *Timeline::gapsArray()
|
||||
{
|
||||
// reset gaps
|
||||
gaps_.clear();
|
||||
|
||||
// loop over the array to detect gaps
|
||||
float status = 1.f;
|
||||
GstClockTime begin_gap = GST_CLOCK_TIME_NONE;
|
||||
for (size_t i = 0; i < array_size_; ++i) {
|
||||
// detect a change of value between two slots
|
||||
if ( array_[i] != status) {
|
||||
// compute time of the event in array
|
||||
GstClockTime t = (timing_.duration() * i) / array_size_;
|
||||
// change from 1.f to 0.f : begin of a gap
|
||||
if (status) {
|
||||
begin_gap = t;
|
||||
}
|
||||
// change from 0.f to 1.f : end of a gap
|
||||
else {
|
||||
addGap( begin_gap, t );
|
||||
begin_gap = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
// swap
|
||||
status = array_[i];
|
||||
}
|
||||
if (gaps_array_need_update_) {
|
||||
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
|
||||
}
|
||||
// end a potentially pending gap if reached end of array with no end of gap
|
||||
if (begin_gap != GST_CLOCK_TIME_NONE)
|
||||
addGap( begin_gap, timing_.end );
|
||||
|
||||
return gapsArray_;
|
||||
}
|
||||
|
||||
void Timeline::fillArrayFromGaps(float *array_, size_t array_size_)
|
||||
void Timeline::update()
|
||||
{
|
||||
if (array_ != nullptr && array_size_ > 0 && timing_.is_valid()) {
|
||||
|
||||
// fill the array from gaps
|
||||
// NB: this is the less efficient algorithm possible! but we should not keep arrays anyway
|
||||
// TODO : implement an ImGui widget to plot a timeline instead of an array
|
||||
TimeInterval gap;
|
||||
for (size_t i = 0; i < array_size_; ++i) {
|
||||
GstClockTime t = (timing_.duration() * i) / array_size_;
|
||||
array_[i] = gapAt(t, gap) ? 0.f : 1.f;
|
||||
}
|
||||
|
||||
}
|
||||
updateGapsFromArray(gapsArray_, MAX_TIMELINE_ARRAY);
|
||||
gaps_array_need_update_ = false;
|
||||
}
|
||||
|
||||
bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) const
|
||||
@@ -161,64 +128,220 @@ bool Timeline::addGap(GstClockTime begin, GstClockTime end)
|
||||
|
||||
bool Timeline::addGap(TimeInterval s)
|
||||
{
|
||||
if ( s.is_valid() )
|
||||
if ( s.is_valid() ) {
|
||||
gaps_array_need_update_ = true;
|
||||
return gaps_.insert(s).second;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Timeline::setGaps(TimeIntervalSet g)
|
||||
{
|
||||
gaps_array_need_update_ = true;
|
||||
gaps_ = g;
|
||||
}
|
||||
|
||||
//void Timeline::toggleGaps(GstClockTime from, GstClockTime to)
|
||||
//{
|
||||
// TimeInterval interval(from, to);
|
||||
// TimeInterval gap;
|
||||
// bool is_a_gap_ = gapAt(from, gap);
|
||||
|
||||
// if (interval.is_valid())
|
||||
// {
|
||||
// // fill gap
|
||||
// if (is_a_gap_) {
|
||||
|
||||
// }
|
||||
// // else create gap (from time is not in a gap)
|
||||
// else {
|
||||
|
||||
// TimeIntervalSet::iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(to));
|
||||
// // if there is a gap overlap with [from to] (or [to from]), expand the gap
|
||||
// if ( g != gaps_.end() ) {
|
||||
// interval.begin = MIN( (*g).begin, interval.begin);
|
||||
// interval.end = MAX( (*g).end , interval.end);
|
||||
// gaps_.erase(g);
|
||||
// }
|
||||
// // add the new gap
|
||||
// addGap(interval);
|
||||
// Log::Info("add gap [ %ld %ld ]", interval.begin, interval.end);
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
// Log::Info("%d gaps in timeline", numGaps());
|
||||
//}
|
||||
|
||||
bool Timeline::removeGaptAt(GstClockTime t)
|
||||
{
|
||||
TimeIntervalSet::const_iterator s = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
|
||||
|
||||
if ( s != gaps_.end() ) {
|
||||
gaps_.erase(s);
|
||||
gaps_array_need_update_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
TimeIntervalSet Timeline::sections() const
|
||||
{
|
||||
TimeIntervalSet sec;
|
||||
|
||||
GstClockTime begin_sec = timing_.begin;
|
||||
|
||||
if (gaps_.size() > 0) {
|
||||
|
||||
auto it = gaps_.begin();
|
||||
if ((*it).begin == begin_sec) {
|
||||
begin_sec = (*it).end;
|
||||
++it;
|
||||
}
|
||||
|
||||
for (; it != gaps_.end(); ++it)
|
||||
{
|
||||
sec.insert( TimeInterval(begin_sec, (*it).begin) );
|
||||
begin_sec = (*it).end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (begin_sec != timing_.end)
|
||||
sec.insert( TimeInterval(begin_sec, timing_.end) );
|
||||
|
||||
return sec;
|
||||
}
|
||||
|
||||
void Timeline::clearGaps()
|
||||
{
|
||||
gaps_.clear();
|
||||
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
gapsArray_[i] = 0.f;
|
||||
|
||||
gaps_array_need_update_ = true;
|
||||
}
|
||||
|
||||
float Timeline::fadingAt(const GstClockTime t)
|
||||
{
|
||||
GstClockTime modulo = t % step_;
|
||||
GstClockTime keyframe = t - modulo;
|
||||
size_t keyframe_index = CLAMP( (MAX_TIMELINE_ARRAY * keyframe) / timing_.end, 0, MAX_TIMELINE_ARRAY-1);
|
||||
size_t keyframe_next_index = CLAMP( keyframe_index+1, 0, MAX_TIMELINE_ARRAY-1);
|
||||
|
||||
float interpol = static_cast<float>( static_cast<double>(modulo) / static_cast<double>(step_) );
|
||||
float v = fadingArray_[keyframe_index];
|
||||
v += interpol * (fadingArray_[keyframe_next_index] - fadingArray_[keyframe_index]);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void Timeline::clearFading()
|
||||
{
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
fadingArray_[i] = 1.f;
|
||||
}
|
||||
|
||||
void Timeline::smoothFading(uint N)
|
||||
{
|
||||
const float kernel[7] = { 2.f, 22.f, 97.f, 159.f, 97.f, 22.f, 2.f};
|
||||
float tmparray[MAX_TIMELINE_ARRAY];
|
||||
|
||||
for (uint n = 0; n < N; ++n) {
|
||||
|
||||
for (long i = 0; i < MAX_TIMELINE_ARRAY; ++i) {
|
||||
tmparray[i] = 0.f;
|
||||
float divider = 0.f;
|
||||
for( long j = 0; j < 7; ++j) {
|
||||
long k = i - 3 + j;
|
||||
if (k > -1 && k < MAX_TIMELINE_ARRAY-1) {
|
||||
tmparray[i] += fadingArray_[k] * kernel[j];
|
||||
divider += kernel[j];
|
||||
}
|
||||
}
|
||||
tmparray[i] *= 1.f / divider;
|
||||
}
|
||||
|
||||
memcpy( fadingArray_, tmparray, MAX_TIMELINE_ARRAY * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Timeline::autoFading(uint milisecond)
|
||||
{
|
||||
|
||||
GstClockTime stepduration = timing_.end / MAX_TIMELINE_ARRAY;
|
||||
stepduration = GST_TIME_AS_MSECONDS(stepduration);
|
||||
uint N = milisecond / stepduration;
|
||||
|
||||
// reset all to zero
|
||||
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
|
||||
fadingArray_[i] = 0.f;
|
||||
|
||||
// get sections (inverse of gaps)
|
||||
TimeIntervalSet sec = sections();
|
||||
|
||||
// fading for each section
|
||||
// NB : there is at least one
|
||||
for (auto it = sec.begin(); it != sec.end(); ++it)
|
||||
{
|
||||
// get index of begining of section
|
||||
size_t s = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end;
|
||||
// get index of ending of section
|
||||
size_t e = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end;
|
||||
|
||||
// calculate size of the smooth transition in [s e] interval
|
||||
uint n = MIN( (e-s)/3, N );
|
||||
|
||||
// linear fade in starting at s
|
||||
size_t i = s;
|
||||
for (; i < s+n; ++i)
|
||||
fadingArray_[i] = static_cast<float>(i-s) / static_cast<float>(n);
|
||||
// plateau
|
||||
for (; i < e-n; ++i)
|
||||
fadingArray_[i] = 1.f;
|
||||
// linear fade out ending at e
|
||||
for (; i < e; ++i)
|
||||
fadingArray_[i] = static_cast<float>(e-i) / static_cast<float>(n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Timeline::updateGapsFromArray(float *array, size_t array_size)
|
||||
{
|
||||
// reset gaps
|
||||
gaps_.clear();
|
||||
|
||||
// fill the gaps from array
|
||||
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
|
||||
|
||||
// loop over the array to detect gaps
|
||||
float status = 0.f;
|
||||
GstClockTime begin_gap = GST_CLOCK_TIME_NONE;
|
||||
for (size_t i = 0; i < array_size; ++i) {
|
||||
// detect a change of value between two slots
|
||||
if ( array[i] != status) {
|
||||
// compute time of the event in array
|
||||
GstClockTime t = (timing_.duration() * i) / array_size;
|
||||
// change from 0.f to 1.f : begin of a gap
|
||||
if (array[i] > 0.f) {
|
||||
begin_gap = t;
|
||||
}
|
||||
// change from 1.f to 0.f : end of a gap
|
||||
else {
|
||||
addGap( begin_gap, t );
|
||||
begin_gap = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
// swap
|
||||
status = array[i];
|
||||
}
|
||||
}
|
||||
// end a potentially pending gap if reached end of array with no end of gap
|
||||
if (begin_gap != GST_CLOCK_TIME_NONE)
|
||||
addGap( begin_gap, timing_.end );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Timeline::fillArrayFromGaps(float *array, size_t array_size)
|
||||
{
|
||||
// fill the array from gaps
|
||||
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
|
||||
|
||||
for(int i=0;i<array_size;++i)
|
||||
gapsArray_[i] = 0.f;
|
||||
|
||||
// for each gap
|
||||
for (auto it = gaps_.begin(); it != gaps_.end(); ++it)
|
||||
{
|
||||
size_t s = ( (*it).begin * array_size ) / timing_.end;
|
||||
size_t e = ( (*it).end * array_size ) / timing_.end;
|
||||
|
||||
// fill with 1 where there is a gap
|
||||
for (size_t i = s; i < e; ++i) {
|
||||
gapsArray_[i] = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
gaps_array_need_update_ = false;
|
||||
}
|
||||
|
||||
// // NB: less efficient algorithm :
|
||||
// TimeInterval gap;
|
||||
// for (size_t i = 0; i < array_size; ++i) {
|
||||
// GstClockTime t = (timing_.duration() * i) / array_size;
|
||||
// array[i] = gapAt(t, gap) ? 1.f : 0.f;
|
||||
// }
|
||||
}
|
||||
|
||||
38
Timeline.h
38
Timeline.h
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#define MAX_TIMELINE_ARRAY 2000
|
||||
|
||||
struct TimeInterval
|
||||
{
|
||||
GstClockTime begin;
|
||||
@@ -81,16 +83,16 @@ public:
|
||||
~Timeline();
|
||||
Timeline& operator = (const Timeline& b);
|
||||
|
||||
void reset();
|
||||
bool is_valid();
|
||||
void update();
|
||||
|
||||
// global properties of the timeline
|
||||
// timeline is invalid untill all 3 are set
|
||||
// timeline is valid only if all 3 are set
|
||||
void setFirst(GstClockTime first);
|
||||
void setEnd(GstClockTime end);
|
||||
void setStep(GstClockTime dt);
|
||||
|
||||
// get properties
|
||||
// Timing manipulation
|
||||
inline GstClockTime start() const { return timing_.begin; }
|
||||
inline GstClockTime end() const { return timing_.end; }
|
||||
inline GstClockTime first() const { return first_; }
|
||||
@@ -98,33 +100,45 @@ public:
|
||||
inline GstClockTime step() const { return step_; }
|
||||
inline GstClockTime duration() const { return timing_.duration(); }
|
||||
inline size_t numFrames() const { return duration() / step_; }
|
||||
inline TimeIntervalSet gaps() const { return gaps_; }
|
||||
inline size_t numGaps() const { return gaps_.size(); }
|
||||
|
||||
GstClockTime next(GstClockTime time) const;
|
||||
GstClockTime previous(GstClockTime time) const;
|
||||
|
||||
// Add / remove / get gaps in the timeline
|
||||
// Manipulation of gaps in the timeline
|
||||
inline TimeIntervalSet gaps() const { return gaps_; }
|
||||
inline TimeIntervalSet sections() const;
|
||||
inline size_t numGaps() const { return gaps_.size(); }
|
||||
float *gapsArray();
|
||||
void clearGaps();
|
||||
void setGaps(TimeIntervalSet g);
|
||||
bool addGap(TimeInterval s);
|
||||
bool addGap(GstClockTime begin, GstClockTime end);
|
||||
bool removeGaptAt(GstClockTime t);
|
||||
bool gapAt(const GstClockTime t, TimeInterval &gap) const;
|
||||
void setGaps(TimeIntervalSet g);
|
||||
|
||||
// synchronize data structures
|
||||
void updateGapsFromArray(float *array_, size_t array_size_);
|
||||
void fillArrayFromGaps(float *array_, size_t array_size_);
|
||||
float fadingAt(const GstClockTime t);
|
||||
inline float *fadingArray() { return fadingArray_; }
|
||||
void clearFading();
|
||||
void smoothFading(uint N = 1);
|
||||
void autoFading(uint milisecond = 100);
|
||||
|
||||
private:
|
||||
|
||||
void reset();
|
||||
|
||||
// global information on the timeline
|
||||
TimeInterval timing_;
|
||||
GstClockTime first_;
|
||||
GstClockTime step_;
|
||||
|
||||
// main data structure containing list of gaps in the timeline
|
||||
TimeIntervalSet gaps_;
|
||||
TimeIntervalSet gaps_;
|
||||
float gapsArray_[MAX_TIMELINE_ARRAY];
|
||||
bool gaps_array_need_update_;
|
||||
// synchronize data structures
|
||||
void updateGapsFromArray(float *array, size_t array_size);
|
||||
void fillArrayFromGaps(float *array, size_t array_size);
|
||||
|
||||
float fadingArray_[MAX_TIMELINE_ARRAY];
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1181,8 +1181,8 @@ void MediaController::Render()
|
||||
static float timeline_zoom = 1.f;
|
||||
const float width = ImGui::GetContentRegionAvail().x;
|
||||
const float timeline_height = 2.f * (ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y);
|
||||
const float segments_height = ImGui::GetFontSize();
|
||||
const float slider_zoom_width = segments_height;
|
||||
const float segments_height = timeline_height;
|
||||
const float slider_zoom_width = segments_height / 2.f;
|
||||
|
||||
if (Settings::application.widget.media_player_view)
|
||||
{
|
||||
@@ -1267,45 +1267,58 @@ void MediaController::Render()
|
||||
ImGui::PopButtonRepeat();
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, MAX(spacing * 4.f, width - 400.f) );
|
||||
|
||||
// loop modes button
|
||||
ImGui::SameLine(0, spacing);
|
||||
static int current_loop = 0;
|
||||
static std::vector< std::pair<int, int> > iconsloop = { {0,15}, {1,15}, {19,14} };
|
||||
current_loop = (int) mp_->loop();
|
||||
if ( ImGuiToolkit::ButtonIconMultistate(iconsloop, ¤t_loop) )
|
||||
mp_->setLoop( (MediaPlayer::LoopMode) current_loop );
|
||||
|
||||
// speed slider
|
||||
ImGui::SameLine(0, MAX(spacing * 4.f, width - 400.f) );
|
||||
float speed = static_cast<float>(mp_->playSpeed());
|
||||
ImGui::SameLine(0, spacing);
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 40.0);
|
||||
if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, "Speed x %.1f", 2.f))
|
||||
mp_->setPlaySpeed( static_cast<double>(speed) );
|
||||
|
||||
// reset
|
||||
// Timeline popup menu
|
||||
ImGui::SameLine(0, spacing);
|
||||
if (ImGuiToolkit::ButtonIcon(11, 14)) {
|
||||
timeline_zoom = 1.f;
|
||||
speed = 1.f;
|
||||
mp_->setPlaySpeed( static_cast<double>(speed) );
|
||||
mp_->setLoop( MediaPlayer::LOOP_REWIND );
|
||||
if (ImGuiToolkit::ButtonIcon(5, 8))
|
||||
ImGui::OpenPopup("MenuTimeline");
|
||||
if (ImGui::BeginPopup("MenuTimeline")) {
|
||||
if (ImGui::Selectable( "Reset Speed" )){
|
||||
speed = 1.f;
|
||||
mp_->setPlaySpeed( static_cast<double>(speed) );
|
||||
}
|
||||
if (ImGui::Selectable( "Reset Timeline" )){
|
||||
timeline_zoom = 1.f;
|
||||
mp_->timeline()->clearFading();
|
||||
mp_->timeline()->clearGaps();
|
||||
}
|
||||
ImGui::Separator();
|
||||
static int smoothfactor = 10;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::Button( "Smooth Curve" )){
|
||||
mp_->timeline()->smoothFading(smoothfactor);
|
||||
}
|
||||
ImGui::SameLine(0);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::DragInt("##smoothfactor", &smoothfactor, 5.f, 5, 50, "x %d");
|
||||
static int milisec = 500;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::Button( "Auto Fading" )){
|
||||
mp_->timeline()->autoFading(milisec);
|
||||
mp_->timeline()->smoothFading(10);
|
||||
}
|
||||
ImGui::SameLine(0);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::DragInt("##milisecfading", &milisec, 100.f, 100, 2000, "%d ms");
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3.f, 3.f));
|
||||
|
||||
// For controlling the current media player with timeline
|
||||
static Timeline working_timeline;
|
||||
static MediaPlayer *timeline_mp = nullptr;
|
||||
static size_t array_size = 1200;
|
||||
static float *array = (float *) malloc( sizeof(float) * array_size);
|
||||
|
||||
// detect change of media player to update the timeline array
|
||||
if (timeline_mp != mp_ || !working_timeline.is_valid()) {
|
||||
timeline_mp = mp_;
|
||||
working_timeline = timeline_mp->timeline();
|
||||
working_timeline.fillArrayFromGaps(array, array_size);
|
||||
}
|
||||
|
||||
guint64 current_t = mp_->position();
|
||||
guint64 seek_t = current_t;
|
||||
|
||||
@@ -1320,43 +1333,15 @@ void MediaController::Render()
|
||||
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), segments_height -1);
|
||||
size.x *= timeline_zoom;
|
||||
|
||||
// draw position when entering
|
||||
ImVec2 draw_pos = ImGui::GetCursorPos();
|
||||
|
||||
// start slider at a position which forces to change value
|
||||
uint press_index = array_size;
|
||||
bool pressed = ImGuiToolkit::InvisibleSliderInt("##TimelinePicking", &press_index, 0, array_size, size);
|
||||
|
||||
// behavior on action on array of segments
|
||||
static bool active = false;
|
||||
static float target_value = 0.f;
|
||||
static uint starting_index = array_size;
|
||||
if (pressed != active) {
|
||||
active = pressed;
|
||||
if (pressed) {
|
||||
starting_index = press_index;
|
||||
target_value = array[starting_index] > 0.f ? 0.f : 1.f;
|
||||
}
|
||||
else // action on released
|
||||
{
|
||||
// update the timeline
|
||||
working_timeline.updateGapsFromArray(array, array_size);
|
||||
// apply it to the media player
|
||||
timeline_mp->setTimeline(working_timeline);
|
||||
}
|
||||
if ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", mp_->timeline()->gapsArray(),
|
||||
mp_->timeline()->fadingArray(), MAX_TIMELINE_ARRAY, 0.f, 1.f, size) )
|
||||
{
|
||||
mp_->timeline()->update();
|
||||
}
|
||||
if (active) {
|
||||
for (int i = MIN(starting_index, press_index); i < MAX(starting_index, press_index); ++i)
|
||||
array[i] = target_value;
|
||||
}
|
||||
|
||||
// back to drawing position to draw the segments data with historgram
|
||||
ImGui::SetCursorPos(draw_pos);
|
||||
ImGui::PlotHistogram("##TimelineHistogram", array, array_size, 0, NULL, 0.0f, 1.0f, size);
|
||||
|
||||
// custom timeline slider
|
||||
slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp_->timeline().first(),
|
||||
mp_->timeline().end(), mp_->timeline().step(), size.x);
|
||||
slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp_->timeline()->first(),
|
||||
mp_->timeline()->end(), mp_->timeline()->step(), size.x);
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
}
|
||||
@@ -2377,7 +2362,7 @@ void ShowSandbox(bool* p_open)
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f);
|
||||
|
||||
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), 40);
|
||||
size.x *= 2.f;
|
||||
size.x *= 1.f;
|
||||
|
||||
// // draw position when entering
|
||||
// ImVec2 draw_pos = ImGui::GetCursorPos();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "SystemToolkit.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
#include "tinyxml2Toolkit.h"
|
||||
using namespace tinyxml2;
|
||||
@@ -81,6 +84,39 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
XMLElement *tinyxml2::XMLElementEncodeArray(XMLDocument *doc, void *array, uint64_t arraysize)
|
||||
{
|
||||
gchar *encoded_string = g_base64_encode( (guchar *) array, arraysize);
|
||||
|
||||
XMLElement *newelement = doc->NewElement( "array" );
|
||||
newelement->SetAttribute("len", arraysize);
|
||||
|
||||
XMLText *text = doc->NewText( encoded_string );
|
||||
newelement->InsertEndChild( text );
|
||||
|
||||
g_free(encoded_string);
|
||||
return newelement;
|
||||
}
|
||||
|
||||
bool tinyxml2::XMLElementDecodeArray(XMLElement *elem, void *array, uint64_t arraysize)
|
||||
{
|
||||
if ( !elem || std::string(elem->Name()).find("array") == std::string::npos )
|
||||
return false;
|
||||
|
||||
uint64_t len = 0;
|
||||
elem->QueryUnsigned64Attribute("len", &len);
|
||||
if ( arraysize != len )
|
||||
return false;
|
||||
|
||||
guchar *decoded_array = g_base64_decode(elem->GetText(), &len);
|
||||
if ( arraysize != len )
|
||||
return false;
|
||||
|
||||
memcpy(array, decoded_array, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tinyxml2::XMLSaveDoc(XMLDocument * const doc, std::string filename)
|
||||
{
|
||||
XMLDeclaration *pDec = doc->NewDeclaration();
|
||||
|
||||
@@ -17,6 +17,8 @@ void XMLElementToGLM(XMLElement *elem, glm::vec3 &vector);
|
||||
void XMLElementToGLM(XMLElement *elem, glm::vec4 &vector);
|
||||
void XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix);
|
||||
|
||||
XMLElement *XMLElementEncodeArray(XMLDocument *doc, void *array, uint64_t arraysize);
|
||||
bool XMLElementDecodeArray(XMLElement *elem, void *array, uint64_t arraysize);
|
||||
|
||||
bool XMLSaveDoc(tinyxml2::XMLDocument * const doc, std::string filename);
|
||||
bool XMLResultError(int result);
|
||||
|
||||
Reference in New Issue
Block a user