blob: 34d1e42472ee5af5ac3ba315f205a64e1c626ec6 [file] [log] [blame]
/*
**
** Copyright 2014, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "ClockEstimator"
#include <utils/Log.h>
#include <math.h>
#include <media/stagefright/ClockEstimator.h>
#include <media/stagefright/foundation/ADebug.h>
namespace android {
WindowedLinearFitEstimator::WindowedLinearFitEstimator(
size_t headLength, double headFactor, size_t mainLength, double tailFactor)
: mHeadFactorInv(1. / headFactor),
mTailFactor(tailFactor),
mHistoryLength(mainLength + headLength),
mHeadLength(headLength) {
reset();
mXHistory.resize(mHistoryLength);
mYHistory.resize(mHistoryLength);
mFirstWeight = pow(headFactor, mHeadLength);
}
WindowedLinearFitEstimator::LinearFit::LinearFit() {
reset();
}
void WindowedLinearFitEstimator::LinearFit::reset() {
mX = mXX = mY = mYY = mXY = mW = 0.;
}
double WindowedLinearFitEstimator::LinearFit::size() const {
double s = mW * mW + mX * mX + mY * mY + mXX * mXX + mXY * mXY + mYY * mYY;
if (s > 1e72) {
// 1e72 corresponds to clock monotonic time of about 8 years
ALOGW("estimator is overflowing: w=%g x=%g y=%g xx=%g xy=%g yy=%g",
mW, mX, mY, mXX, mXY, mYY);
}
return s;
}
void WindowedLinearFitEstimator::LinearFit::add(double x, double y, double w) {
mW += w;
mX += w * x;
mY += w * y;
mXX += w * x * x;
mXY += w * x * y;
mYY += w * y * y;
}
void WindowedLinearFitEstimator::LinearFit::combine(const LinearFit &lf) {
mW += lf.mW;
mX += lf.mX;
mY += lf.mY;
mXX += lf.mXX;
mXY += lf.mXY;
mYY += lf.mYY;
}
void WindowedLinearFitEstimator::LinearFit::scale(double w) {
mW *= w;
mX *= w;
mY *= w;
mXX *= w;
mXY *= w;
mYY *= w;
}
double WindowedLinearFitEstimator::LinearFit::interpolate(double x) {
double div = mW * mXX - mX * mX;
if (fabs(div) < 1e-5 * mW * mW) {
// this only should happen on the first value
return x;
// assuming a = 1, we could also return x + (mY - mX) / mW;
}
double a_div = (mW * mXY - mX * mY);
double b_div = (mXX * mY - mX * mXY);
ALOGV("a=%.4g b=%.4g in=%g out=%g",
a_div / div, b_div / div, x, (a_div * x + b_div) / div);
return (a_div * x + b_div) / div;
}
double WindowedLinearFitEstimator::estimate(double x, double y) {
/*
* TODO: We could update the head by adding the new sample to it
* and amplifying it, but this approach can lead to unbounded
* error. Instead, we recalculate the head at each step, which
* is computationally more expensive. We could balance the two
* methods by recalculating just before the error becomes
* significant.
*/
const bool update_head = false;
if (update_head) {
// add new sample to the head
mHead.scale(mHeadFactorInv); // amplify head
mHead.add(x, y, mFirstWeight);
}
/*
* TRICKY: place elements into the circular buffer at decreasing
* indices, so that we can access past elements by addition
* (thereby avoiding potentially negative indices.)
*/
if (mNumSamples >= mHeadLength) {
// move last head sample from head to the main window
size_t lastHeadIx = (mSampleIx + mHeadLength) % mHistoryLength;
if (update_head) {
mHead.add(mXHistory[lastHeadIx], mYHistory[lastHeadIx], -1.); // remove
}
mMain.add(mXHistory[lastHeadIx], mYHistory[lastHeadIx], 1.);
if (mNumSamples >= mHistoryLength) {
// move last main sample from main window to tail
mMain.add(mXHistory[mSampleIx], mYHistory[mSampleIx], -1.); // remove
mTail.add(mXHistory[mSampleIx], mYHistory[mSampleIx], 1.);
mTail.scale(mTailFactor); // attenuate tail
}
}
mXHistory.editItemAt(mSampleIx) = x;
mYHistory.editItemAt(mSampleIx) = y;
if (mNumSamples < mHistoryLength) {
++mNumSamples;
}
// recalculate head unless we were using the update method
if (!update_head) {
mHead.reset();
double w = mFirstWeight;
for (size_t headIx = 0; headIx < mHeadLength && headIx < mNumSamples; ++headIx) {
size_t ix = (mSampleIx + headIx) % mHistoryLength;
mHead.add(mXHistory[ix], mYHistory[ix], w);
w *= mHeadFactorInv;
}
}
if (mSampleIx > 0) {
--mSampleIx;
} else {
mSampleIx = mHistoryLength - 1;
}
// return estimation result
LinearFit total;
total.combine(mHead);
total.combine(mMain);
total.combine(mTail);
return total.interpolate(x);
}
void WindowedLinearFitEstimator::reset() {
mHead.reset();
mMain.reset();
mTail.reset();
mNumSamples = 0;
mSampleIx = mHistoryLength - 1;
}
}; // namespace android