/* * 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; }