/*
* Copyright (C) 2011-2013 Team XBMC
* http://xbmc.org
*
* 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 2, 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 XBMC; see the file COPYING. If not, see
* .
*
*/
#include "TimeSmoother.h"
#include
#include
#include "utils/MathUtils.h"
using namespace std;
CTimeSmoother::CTimeSmoother()
: m_diffs(num_diffs),
m_periods(num_periods),
m_period(0),
m_lastFrameTime(0),
m_prevIn(num_stamps),
m_prevOut(num_stamps)
{
}
void CTimeSmoother::AddTimeStamp(unsigned int currentTime)
{
double diff = m_prevIn.size() ? currentTime - m_prevIn.back() : currentTime;
if (diff)
m_diffs.push_back(diff);
vector bins;
BinData(m_diffs, bins, 0.15, 2);
if (bins.size() && m_diffs.size() == num_diffs)
{
// have enough data to update our estimate
vector binMultipliers;
GetGCDMultipliers(bins, binMultipliers, 2);
assert(binMultipliers.size() == bins.size());
vector intRepresentation;
GetIntRepresentation(m_diffs, intRepresentation, bins, binMultipliers);
assert(intRepresentation.size() == m_diffs.size());
double period = EstimatePeriod(m_diffs, intRepresentation);
// update our mean period
if (fabs(period - m_period) > m_period*0.1)
{ // more than 10 % out - kill our previous running average
m_periods.clear();
m_period = 0;
}
if (m_periods.size() < m_periods.capacity())
m_period = (m_period * m_periods.size() + period) / (m_periods.size() + 1);
else
m_period += (period - m_periods[0]) / m_periods.size();
m_periods.push_back(period);
}
double frameTime = EstimateFrameTime(currentTime);
m_prevIn.push_back(currentTime);
m_prevOut.push_back(frameTime);
}
unsigned int CTimeSmoother::GetNextFrameTime(unsigned int currentTime)
{
if (m_period)
{
double frameTime = EstimateFrameTime(currentTime);
// ensure we jump at least 1 period ahead of the last time we were called
if (frameTime < m_lastFrameTime + m_period)
frameTime = m_lastFrameTime + m_period;
// Return an unsigned int in ms, so wrap into that, and round.
// Don't use MathUtils::round_int as that's restricted to -2^30..2^30
if (frameTime >= UINT_MAX)
frameTime = fmod(frameTime, UINT_MAX);
m_lastFrameTime = frameTime;
return (unsigned int)floor(frameTime + 0.5);
}
return currentTime;
}
void CTimeSmoother::BinData(const boost::circular_buffer &data, vector &bins, const double threshold, const unsigned int minbinsize)
{
if (!data.size())
return;
bins.clear();
vector counts;
for (boost::circular_buffer::const_iterator i = data.begin(); i != data.end(); ++i)
{
bool found = false;
for (unsigned int j = 0; j < bins.size(); ++j)
{
if (fabs(*i - bins[j]) < threshold*bins[j])
{
found = true;
// update our bin mean and count
bins[j] = (bins[j]*counts[j] + *i)/(counts[j]+1);
counts[j]++;
break;
}
}
if (!found)
{
bins.push_back(*i);
counts.push_back(1);
}
}
if (minbinsize)
{
assert(counts.size() == bins.size());
assert(counts.size());
// filter out any bins that are not large enough (and any bins that aren't positive)
for (unsigned int j = 0; j < counts.size(); )
{
if (counts[j] < minbinsize || bins[j] < 0.05)
{
bins.erase(bins.begin() + j);
counts.erase(counts.begin() + j);
}
else
j++;
}
}
}
void CTimeSmoother::GetConvergent(double value, unsigned int &num, unsigned int &denom, const unsigned int maxnumden)
{
assert(value >= 1);
unsigned int old_n = 1, old_d = 0;
num = 0; denom = 1;
// this while loop would typically be guaranteed to terminate as new_n, new_d are increasing non-negative
// integers as long as f >= 1. This in turn is guaranteed as f may never be zero as long as value > 1 and
// value - f < 1. Given that f = floor(value) this *should* always be true.
// However, as f is unsigned int and thus range restricted, we can not guarantee this, and hence
// break if value - f >= 1.
// In addition, just to be on the safe side we don't allow the loop to run forever ;)
unsigned int maxLoops = 3 * maxnumden;
while (maxLoops--)
{
unsigned int f = (unsigned int)floor(value);
if (value - f >= 1)
break; // value out of range of unsigned int
unsigned int new_n = f * num + old_n;
unsigned int new_d = f * denom + old_d;
if (min(new_n, new_d) > maxnumden)
break;
old_n = num; old_d = denom;
num = new_n; denom = new_d;
if ((double)f == value)
break;
value = 1/(value - f);
}
// ensure num, denom are positive
assert(num > 0 && denom > 0);
}
void CTimeSmoother::GetGCDMultipliers(const vector &data, vector &multipliers, const unsigned int maxminmult)
{
vector::const_iterator i = std::min_element(data.begin(), data.end());
multipliers.clear();
vector num, denom;
for (vector::const_iterator j = data.begin(); j != data.end(); ++j)
{
if (j != i)
{
unsigned int n, d;
GetConvergent(*j / *i, n, d, maxminmult);
num.push_back(n);
denom.push_back(d);
}
else
{
num.push_back(1);
denom.push_back(1);
}
}
vector::const_iterator k = std::max_element(num.begin(), num.end());
for (unsigned int i = 0; i < num.size(); ++i)
multipliers.push_back(denom[i] * (*k) / num[i]);
}
void CTimeSmoother::GetIntRepresentation(const boost::circular_buffer &data, vector &intData, const vector &bins, const vector &intBins)
{
intData.clear();
for (boost::circular_buffer::const_iterator i = data.begin(); i != data.end(); ++i)
{
double min_r2 = numeric_limits::max();
unsigned int min_j = 0;
for (unsigned int j = 0; j < bins.size(); ++j)
{
double d = MathUtils::round_int(*i/bins[j]);
double r2 = (*i - bins[j]*d)*(*i - bins[j]*d);
if (r2 < min_r2)
{
min_j = j;
min_r2 = r2;
}
}
intData.push_back(MathUtils::round_int(*i/bins[min_j])*intBins[min_j]);
}
}
double CTimeSmoother::EstimatePeriod(const boost::circular_buffer &data, const vector &intData)
{
double sxy = 0, sxx = 0;
for (unsigned int i = 0; i < data.size(); ++i)
{
sxy += intData[i] * data[i];
sxx += intData[i] * intData[i];
}
return sxy/sxx;
}
double CTimeSmoother::EstimateFrameTime(unsigned int currentTime)
{
assert(m_prevIn.size() == m_prevOut.size());
if (m_period)
{
vector outTimes;
for (unsigned int i = 0; i < m_prevIn.size(); ++i)
outTimes.push_back(m_prevOut[i] + m_period * MathUtils::round_int((currentTime - m_prevIn[i]) / m_period));
sort(outTimes.begin(), outTimes.end());
double outTime = outTimes[(outTimes.size()-1)/2];
if (outTime < m_prevOut.back() + m_period)
outTime = m_prevOut.back() + m_period;
return outTime;
}
return currentTime;
}