Files
vimix/Timeline.cpp

708 lines
19 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <algorithm>
#include <cmath>
#include "defines.h"
#include "Log.h"
#include "Timeline.h"
static float empty_zeros[MAX_TIMELINE_ARRAY] = {};
static float empty_ones[MAX_TIMELINE_ARRAY] = {};
struct includesTime: public std::unary_function<TimeInterval, bool>
{
inline bool operator()(const TimeInterval s) const
{
return s.includes(_t);
}
explicit includesTime(GstClockTime t) : _t(t) { }
private:
GstClockTime _t;
};
Timeline::Timeline()
{
reset();
}
Timeline::~Timeline()
{
reset();
}
Timeline& Timeline::operator = (const Timeline& b)
{
if (this != &b) {
this->timing_ = b.timing_;
if (b.step_ != GST_CLOCK_TIME_NONE)
this->step_ = b.step_;
if (b.first_ != GST_CLOCK_TIME_NONE)
this->first_ = b.first_;
this->gaps_ = b.gaps_;
this->gaps_array_need_update_ = b.gaps_array_need_update_;
memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
}
return *this;
}
void Timeline::reset()
{
// reset timing
timing_.reset();
timing_.begin = 0;
first_ = GST_CLOCK_TIME_NONE;
step_ = GST_CLOCK_TIME_NONE;
clearGaps();
clearFading();
}
bool Timeline::is_valid()
{
return timing_.is_valid() && step_ != GST_CLOCK_TIME_NONE;
}
void Timeline::setFirst(GstClockTime first)
{
first_ = first;
}
void Timeline::setEnd(GstClockTime end)
{
timing_.end = end;
}
void Timeline::setStep(GstClockTime dt)
{
step_ = dt;
}
void Timeline::setTiming(TimeInterval interval, GstClockTime step)
{
timing_ = interval;
if (step != GST_CLOCK_TIME_NONE)
step_ = step;
}
GstClockTime Timeline::next(GstClockTime time) const
{
GstClockTime next_time = time;
TimeInterval gap;
if (getGapAt(time, gap) && gap.is_valid())
next_time = gap.end;
return next_time;
}
GstClockTime Timeline::previous(GstClockTime time) const
{
GstClockTime prev_time = time;
TimeInterval gap;
if (getGapAt(time, gap) && gap.is_valid())
prev_time = gap.begin;
return prev_time;
}
float *Timeline::gapsArray()
{
if (gaps_array_need_update_) {
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
}
return gapsArray_;
}
void Timeline::update()
{
updateGapsFromArray(gapsArray_, MAX_TIMELINE_ARRAY);
gaps_array_need_update_ = false;
}
void Timeline::refresh()
{
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
}
bool Timeline::gapAt(const GstClockTime t) const
{
TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
return ( g != gaps_.end() );
}
bool Timeline::getGapAt(const GstClockTime t, TimeInterval &gap) const
{
TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
if ( g != gaps_.end() ) {
gap = (*g);
return true;
}
return false;
}
bool Timeline::addGap(GstClockTime begin, GstClockTime end)
{
return addGap( TimeInterval(begin, end) );
}
bool Timeline::cut(GstClockTime t, bool left, bool join_extremity)
{
bool ret = false;
if (timing_.includes(t))
{
TimeIntervalSet::iterator gap = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t));
// cut left part
if (left) {
// cut a gap
if ( gap != gaps_.end() )
{
GstClockTime b = gap->begin;
gaps_.erase(gap);
ret = addGap(b, t);
}
// create a gap
else {
auto previous = gaps_.end();
for (auto g = gaps_.begin(); g != gaps_.end(); previous = g++) {
if ( g->begin > t)
break;
}
if (join_extremity) {
//TODO
}
else {
if (previous == gaps_.end())
ret = addGap( TimeInterval(timing_.begin, t) );
else {
GstClockTime b = previous->begin;
gaps_.erase(previous);
ret = addGap( TimeInterval(b, t) );
}
}
}
}
// cut right part
else {
// cut a gap
if ( gap != gaps_.end() )
{
GstClockTime e = gap->end;
gaps_.erase(gap);
ret = addGap(t, e);
}
// create a gap
else {
auto suivant = gaps_.rend();
for (auto g = gaps_.rbegin(); g != gaps_.rend(); suivant = g++) {
if ( g->end < t)
break;
}
if (join_extremity) {
if (suivant != gaps_.rend()) {
for (auto g = gaps_.find(*suivant); g != gaps_.end(); ) {
g = gaps_.erase(g);
}
}
ret = addGap( TimeInterval(t, timing_.end) );
}
else {
if (suivant == gaps_.rend())
ret = addGap( TimeInterval(t, timing_.end) );
else {
GstClockTime e = suivant->end;
gaps_.erase( gaps_.find(*suivant));
ret = addGap( TimeInterval(t, e) );
}
}
}
}
}
return ret;
}
bool Timeline::addGap(TimeInterval s)
{
if ( s.is_valid() ) {
gaps_array_need_update_ = true;
return gaps_.insert(s).second;
}
return false;
}
void Timeline::setGaps(const TimeIntervalSet &g)
{
gaps_array_need_update_ = true;
gaps_ = g;
}
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;
}
GstClockTime Timeline::sectionsDuration() const
{
// compute the sum of durations of gaps
GstClockTime d = 0;
for (auto g = gaps_.begin(); g != gaps_.end(); ++g)
d+= g->duration();
// remove sum of gaps from actual duration
return duration() - d;
}
GstClockTime Timeline::sectionsTimeAt(GstClockTime t) const
{
// loop over gaps
GstClockTime d = t;
for (auto g = gaps_.begin(); g != gaps_.end(); ++g) {
// gap before target?
if ( g->begin < d ) {
// increment target
d += g->duration();
}
else
// done
break;
}
// return updated target
return d;
}
size_t Timeline::fillSectionsArrays( float* const gaps, float* const fading)
{
size_t arraysize = MAX_TIMELINE_ARRAY;
float* gapsptr = gaps;
float* fadingptr = fading;
if (gaps_array_need_update_)
fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY);
if (gaps_.size() > 0) {
// indices to define [s e[] sections
size_t s = 0, e;
arraysize = 0;
auto it = gaps_.begin();
// cut at the beginning
if ((*it).begin == timing_.begin) {
for (size_t i = 0; i < 5; ++i)
gapsptr[ MAX(i, 0) ] = 1.f;
s = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end;
++it;
}
// loop
for (; it != gaps_.end(); ++it) {
// [s e] section
e = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end;
size_t n = e - s;
memcpy( gapsptr, gapsArray_ + s, n * sizeof(float));
memcpy( fadingptr, fadingArray_ + s, n * sizeof(float));
for (size_t i = -5; i > 0; ++i)
gapsptr[ MAX(n+i, 0) ] = 1.f;
gapsptr += n;
fadingptr += n;
arraysize += n;
// next section
s = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end;
}
// close last [s e] section
if (s != MAX_TIMELINE_ARRAY) {
// [s e] section
e = MAX_TIMELINE_ARRAY;
size_t n = e - s;
memcpy( gapsptr, gapsArray_ + s, n * sizeof(float));
memcpy( fadingptr, fadingArray_ + s, n * sizeof(float));
arraysize += n;
}
}
else {
memcpy( gaps, gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float));
memcpy( fading, fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float));
}
return arraysize;
}
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) const
{
double true_index = (static_cast<double>(MAX_TIMELINE_ARRAY) * static_cast<double>(t)) / static_cast<double>(timing_.end);
double previous_index = floor(true_index);
float percent = static_cast<float>(true_index - previous_index);
size_t keyframe_index = MINI( static_cast<size_t>(previous_index), MAX_TIMELINE_ARRAY-1);
size_t keyframe_next_index = MINI( keyframe_index+1, MAX_TIMELINE_ARRAY-1);
float v = fadingArray_[keyframe_index];
v += percent * (fadingArray_[keyframe_next_index] - fadingArray_[keyframe_index]);
return v;
}
size_t Timeline::fadingIndexAt(const GstClockTime t) const
{
double true_index = (static_cast<double>(MAX_TIMELINE_ARRAY) * static_cast<double>(t)) / static_cast<double>(timing_.end);
double previous_index = floor(true_index);
return MINI( static_cast<size_t>(previous_index), MAX_TIMELINE_ARRAY-1);
}
void Timeline::clearFading()
{
// fill static with 1 (only once)
if (empty_ones[0] < 1.f){
for(int i=0;i<MAX_TIMELINE_ARRAY;++i)
empty_ones[i] = 1.f;
}
// clear with static array
memcpy(fadingArray_, empty_ones, MAX_TIMELINE_ARRAY * sizeof(float));
}
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, FadingCurve curve)
{
// mow many index values of timeline array for this duration?
size_t N = MAX_TIMELINE_ARRAY -1;
GstClockTime d = static_cast<GstClockTime>(milisecond) * 1000000;
if ( d < timing_.end )
N = d / (timing_.end / MAX_TIMELINE_ARRAY);
// clear with static array
memcpy(fadingArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float));
// 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
const size_t s = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end;
// get index of ending of section
const size_t e = ( ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end ) -1;
// calculate size of the smooth transition in [s e] interval
const size_t n = MIN( (e-s)/2, N );
// linear fade in starting at s
size_t i = s;
for (; i < s+n; ++i){
const float x = static_cast<float>(i-s) / static_cast<float>(n);
if (curve==FADING_BILINEAR)
fadingArray_[i] = x * x;
else if (curve==FADING_BILINEAR_INV)
fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f));
else
fadingArray_[i] = x;
}
// plateau
for (; i < e-n; ++i)
fadingArray_[i] = 1.f;
// linear fade out ending at e
for (; i < e; ++i) {
const float x = static_cast<float>(e-i) / static_cast<float>(n);
if (curve==FADING_BILINEAR)
fadingArray_[i] = x * x;
else if (curve==FADING_BILINEAR_INV)
fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f));
else
fadingArray_[i] = x;
}
}
}
void Timeline::fadeIn(uint milisecond, FadingCurve curve)
{
// mow many index values of timeline array for this duration?
size_t N = MAX_TIMELINE_ARRAY -1;
GstClockTime d = static_cast<GstClockTime>(milisecond) * 1000000;
if ( d < timing_.end )
N = d / (timing_.end / MAX_TIMELINE_ARRAY);
// get sections (inverse of gaps) is never empty
TimeIntervalSet sec = sections();
auto it = sec.cbegin();
// get index of begining of section
const size_t s = ( it->begin * MAX_TIMELINE_ARRAY ) / timing_.end;
// get index of ending of section
const size_t e = ( it->end * MAX_TIMELINE_ARRAY ) / timing_.end;
// calculate size of the smooth transition in [s e] interval
const size_t n = MIN( e-s, N );
// linear fade in starting at s
size_t i = s;
float val = fadingArray_[s+n];
for (; i < s+n; ++i) {
const float x = static_cast<float>(i-s) / static_cast<float>(n);
if (curve==FADING_BILINEAR)
fadingArray_[i] = x * x;
else if (curve==FADING_BILINEAR_INV)
fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f));
else
fadingArray_[i] = x;
fadingArray_[i] *= val;
}
// add a bit of buffer to avoid jump to previous frame
size_t b = s - (step_ / 2) / (timing_.end / MAX_TIMELINE_ARRAY);
if (b > 0) {
for (size_t j = b; j < s; ++j)
fadingArray_[j] = 0.f;
}
}
void Timeline::fadeOut(uint milisecond, FadingCurve curve)
{
// mow many index values of timeline array for this duration?
size_t N = MAX_TIMELINE_ARRAY -1;
GstClockTime d = static_cast<GstClockTime>(milisecond) * 1000000;
if ( d < timing_.end )
N = d / (timing_.end / MAX_TIMELINE_ARRAY);
// get sections (inverse of gaps) is never empty
TimeIntervalSet sec = sections();
auto it = sec.end();
--it;
// get index of begining of section
const size_t s = ( it->begin * MAX_TIMELINE_ARRAY ) / timing_.end;
// get index of ending of section
const size_t e = ( it->end * MAX_TIMELINE_ARRAY ) / timing_.end;
// calculate size of the smooth transition in [s e] interval
const size_t n = MIN( e-s, N );
// linear fade out ending at e
size_t i = e-n;
float val = fadingArray_[i];
for (; i < e; ++i){
const float x = static_cast<float>(e-i) / static_cast<float>(n);
if (curve==FADING_BILINEAR)
fadingArray_[i] = x * x;
else if (curve==FADING_BILINEAR_INV)
fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f));
else
fadingArray_[i] = x;
fadingArray_[i] *= val;
}
// add a bit of buffer to avoid jump to next frame
size_t b = e + (1000 + step_) / (timing_.end / MAX_TIMELINE_ARRAY);
if (b < MAX_TIMELINE_ARRAY) {
for (size_t j = e; j < b; ++j)
fadingArray_[j] = 0.f;
}
}
bool Timeline::autoCut()
{
bool changed = false;
for (long i = 0; i < MAX_TIMELINE_ARRAY; ++i) {
if (fadingArray_[i] < EPSILON) {
if (gapsArray_[i] != 1.f)
changed = true;
gapsArray_[i] = 1.f;
}
}
updateGapsFromArray(gapsArray_, MAX_TIMELINE_ARRAY);
gaps_array_need_update_ = false;
return changed;
}
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-1);
// 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 {
TimeInterval d (begin_gap, t) ;
if (d.is_valid() && d.duration() > step_)
addGap(d);
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) {
TimeInterval d (begin_gap, timing_.end) ;
if (d.is_valid() && d.duration() > step_)
addGap(d);
}
}
}
void Timeline::fillArrayFromGaps(float *array, size_t array_size)
{
// fill the array from gaps
if (array != nullptr && array_size > 0 && timing_.is_valid()) {
// clear with static array
memcpy(gapsArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float));
// for each gap
GstClockTime d = timing_.duration();
for (auto it = gaps_.begin(); it != gaps_.end(); ++it)
{
size_t s = ( (*it).begin * array_size ) / d;
size_t e = ( (*it).end * array_size ) / d;
// 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;
// }
}