2 * Copyright (C) 2011-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "TimeSmoother.h"
25 #include "utils/MathUtils.h"
29 CTimeSmoother::CTimeSmoother()
31 m_periods(num_periods),
39 void CTimeSmoother::AddTimeStamp(unsigned int currentTime)
41 double diff = m_prevIn.size() ? currentTime - m_prevIn.back() : currentTime;
43 m_diffs.push_back(diff);
46 BinData(m_diffs, bins, 0.15, 2);
48 if (bins.size() && m_diffs.size() == num_diffs)
50 // have enough data to update our estimate
51 vector<unsigned int> binMultipliers;
52 GetGCDMultipliers(bins, binMultipliers, 2);
53 assert(binMultipliers.size() == bins.size());
55 vector<unsigned int> intRepresentation;
56 GetIntRepresentation(m_diffs, intRepresentation, bins, binMultipliers);
57 assert(intRepresentation.size() == m_diffs.size());
59 double period = EstimatePeriod(m_diffs, intRepresentation);
61 // update our mean period
62 if (fabs(period - m_period) > m_period*0.1)
63 { // more than 10 % out - kill our previous running average
67 if (m_periods.size() < m_periods.capacity())
68 m_period = (m_period * m_periods.size() + period) / (m_periods.size() + 1);
70 m_period += (period - m_periods[0]) / m_periods.size();
71 m_periods.push_back(period);
73 double frameTime = EstimateFrameTime(currentTime);
74 m_prevIn.push_back(currentTime);
75 m_prevOut.push_back(frameTime);
78 unsigned int CTimeSmoother::GetNextFrameTime(unsigned int currentTime)
82 double frameTime = EstimateFrameTime(currentTime);
83 // ensure we jump at least 1 period ahead of the last time we were called
84 if (frameTime < m_lastFrameTime + m_period)
85 frameTime = m_lastFrameTime + m_period;
86 // Return an unsigned int in ms, so wrap into that, and round.
87 // Don't use MathUtils::round_int as that's restricted to -2^30..2^30
88 if (frameTime >= UINT_MAX)
89 frameTime = fmod(frameTime, UINT_MAX);
90 m_lastFrameTime = frameTime;
91 return (unsigned int)floor(frameTime + 0.5);
96 void CTimeSmoother::BinData(const boost::circular_buffer<double> &data, vector<double> &bins, const double threshold, const unsigned int minbinsize)
102 vector<unsigned int> counts;
104 for (boost::circular_buffer<double>::const_iterator i = data.begin(); i != data.end(); ++i)
107 for (unsigned int j = 0; j < bins.size(); ++j)
109 if (fabs(*i - bins[j]) < threshold*bins[j])
112 // update our bin mean and count
113 bins[j] = (bins[j]*counts[j] + *i)/(counts[j]+1);
126 assert(counts.size() == bins.size());
127 assert(counts.size());
128 // filter out any bins that are not large enough (and any bins that aren't positive)
129 for (unsigned int j = 0; j < counts.size(); )
131 if (counts[j] < minbinsize || bins[j] < 0.05)
133 bins.erase(bins.begin() + j);
134 counts.erase(counts.begin() + j);
142 void CTimeSmoother::GetConvergent(double value, unsigned int &num, unsigned int &denom, const unsigned int maxnumden)
146 unsigned int old_n = 1, old_d = 0;
149 // this while loop would typically be guaranteed to terminate as new_n, new_d are increasing non-negative
150 // integers as long as f >= 1. This in turn is guaranteed as f may never be zero as long as value > 1 and
151 // value - f < 1. Given that f = floor(value) this *should* always be true.
152 // However, as f is unsigned int and thus range restricted, we can not guarantee this, and hence
153 // break if value - f >= 1.
155 // In addition, just to be on the safe side we don't allow the loop to run forever ;)
156 unsigned int maxLoops = 3 * maxnumden;
159 unsigned int f = (unsigned int)floor(value);
161 break; // value out of range of unsigned int
162 unsigned int new_n = f * num + old_n;
163 unsigned int new_d = f * denom + old_d;
164 if (min(new_n, new_d) > maxnumden)
166 old_n = num; old_d = denom;
167 num = new_n; denom = new_d;
168 if ((double)f == value)
170 value = 1/(value - f);
172 // ensure num, denom are positive
173 assert(num > 0 && denom > 0);
176 void CTimeSmoother::GetGCDMultipliers(const vector<double> &data, vector<unsigned int> &multipliers, const unsigned int maxminmult)
178 vector<double>::const_iterator i = std::min_element(data.begin(), data.end());
182 vector<unsigned int> num, denom;
183 for (vector<double>::const_iterator j = data.begin(); j != data.end(); ++j)
188 GetConvergent(*j / *i, n, d, maxminmult);
198 vector<unsigned int>::const_iterator k = std::max_element(num.begin(), num.end());
199 for (unsigned int i = 0; i < num.size(); ++i)
200 multipliers.push_back(denom[i] * (*k) / num[i]);
203 void CTimeSmoother::GetIntRepresentation(const boost::circular_buffer<double> &data, vector<unsigned int> &intData, const vector<double> &bins, const vector<unsigned int> &intBins)
206 for (boost::circular_buffer<double>::const_iterator i = data.begin(); i != data.end(); ++i)
208 double min_r2 = numeric_limits<double>::max();
209 unsigned int min_j = 0;
210 for (unsigned int j = 0; j < bins.size(); ++j)
212 double d = MathUtils::round_int(*i/bins[j]);
213 double r2 = (*i - bins[j]*d)*(*i - bins[j]*d);
220 intData.push_back(MathUtils::round_int(*i/bins[min_j])*intBins[min_j]);
224 double CTimeSmoother::EstimatePeriod(const boost::circular_buffer<double> &data, const vector<unsigned int> &intData)
226 double sxy = 0, sxx = 0;
227 for (unsigned int i = 0; i < data.size(); ++i)
229 sxy += intData[i] * data[i];
230 sxx += intData[i] * intData[i];
235 double CTimeSmoother::EstimateFrameTime(unsigned int currentTime)
237 assert(m_prevIn.size() == m_prevOut.size());
240 vector<double> outTimes;
241 for (unsigned int i = 0; i < m_prevIn.size(); ++i)
242 outTimes.push_back(m_prevOut[i] + m_period * MathUtils::round_int((currentTime - m_prevIn[i]) / m_period));
243 sort(outTimes.begin(), outTimes.end());
244 double outTime = outTimes[(outTimes.size()-1)/2];
245 if (outTime < m_prevOut.back() + m_period)
246 outTime = m_prevOut.back() + m_period;