FrameWallClock was recently changed to nanoseconds, and all users of ClockRecovery use SensorTimestamp directly, which is also in nanoseconds. Thus addSample() should also use nanoseconds. Fix this. Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Reviewed-by: Naushir Patuck <naush@raspberrypi.com> Tested-by: Naushir Patuck <naush@raspberrypi.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
231 lines
7.1 KiB
C++
231 lines
7.1 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2024, Raspberry Pi Ltd
|
|
*
|
|
* Clock recovery algorithm
|
|
*/
|
|
|
|
#include "libcamera/internal/clock_recovery.h"
|
|
|
|
#include <time.h>
|
|
|
|
#include <libcamera/base/log.h>
|
|
|
|
/**
|
|
* \file clock_recovery.h
|
|
* \brief Clock recovery - deriving one clock from another independent clock
|
|
*/
|
|
|
|
namespace libcamera {
|
|
|
|
LOG_DEFINE_CATEGORY(ClockRec)
|
|
|
|
/**
|
|
* \class ClockRecovery
|
|
* \brief Recover an output clock from an input clock
|
|
*
|
|
* The ClockRecovery class derives an output clock from an input clock,
|
|
* modelling the output clock as being linearly related to the input clock.
|
|
* For example, we may use it to derive wall clock timestamps from timestamps
|
|
* measured by the internal system clock which counts local time since boot.
|
|
*
|
|
* When pairs of corresponding input and output timestamps are available,
|
|
* they should be submitted to the model with addSample(). The model will
|
|
* update, and output clock values for known input clock values can be
|
|
* obtained using getOutput().
|
|
*
|
|
* As a convenience, if the input clock is indeed the time since boot, and the
|
|
* output clock represents a real wallclock time, then addSample() can be
|
|
* called with no arguments, and a pair of timestamps will be captured at
|
|
* that moment.
|
|
*
|
|
* The configure() function accepts some configuration parameters to control
|
|
* the linear fitting process.
|
|
*/
|
|
|
|
/**
|
|
* \brief Construct a ClockRecovery
|
|
*/
|
|
ClockRecovery::ClockRecovery()
|
|
{
|
|
configure();
|
|
reset();
|
|
}
|
|
|
|
/**
|
|
* \brief Set configuration parameters
|
|
* \param[in] numSamples The approximate duration for which the state of the model
|
|
* is persistent
|
|
* \param[in] maxJitter New output samples are clamped to no more than this
|
|
* amount of jitter, to prevent sudden swings from having a large effect
|
|
* \param[in] minSamples The fitted clock model is not used to generate outputs
|
|
* until this many samples have been received
|
|
* \param[in] errorThreshold If the accumulated differences between input and
|
|
* output clocks reaches this amount over a few frames, the model is reset
|
|
*/
|
|
void ClockRecovery::configure(unsigned int numSamples, unsigned int maxJitter,
|
|
unsigned int minSamples, unsigned int errorThreshold)
|
|
{
|
|
LOG(ClockRec, Debug)
|
|
<< "configure " << numSamples << " " << maxJitter << " " << minSamples << " " << errorThreshold;
|
|
|
|
numSamples_ = numSamples;
|
|
maxJitter_ = maxJitter;
|
|
minSamples_ = minSamples;
|
|
errorThreshold_ = errorThreshold;
|
|
}
|
|
|
|
/**
|
|
* \brief Reset the clock recovery model and start again from scratch
|
|
*/
|
|
void ClockRecovery::reset()
|
|
{
|
|
LOG(ClockRec, Debug) << "reset";
|
|
|
|
lastInput_ = 0;
|
|
lastOutput_ = 0;
|
|
xAve_ = 0;
|
|
yAve_ = 0;
|
|
x2Ave_ = 0;
|
|
xyAve_ = 0;
|
|
count_ = 0;
|
|
error_ = 0.0;
|
|
/*
|
|
* Setting slope_ and offset_ to zero initially means that the clocks
|
|
* advance at exactly the same rate.
|
|
*/
|
|
slope_ = 0.0;
|
|
offset_ = 0.0;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a sample point to the clock recovery model, for recovering a wall
|
|
* clock value from the internal system time since boot
|
|
*
|
|
* This is a convenience function to make it easy to derive a wall clock value
|
|
* (using the Linux CLOCK_REALTIME) from the time since the system started
|
|
* (measured by CLOCK_BOOTTIME).
|
|
*/
|
|
void ClockRecovery::addSample()
|
|
{
|
|
LOG(ClockRec, Debug) << "addSample";
|
|
|
|
struct timespec bootTime1;
|
|
struct timespec bootTime2;
|
|
struct timespec wallTime;
|
|
|
|
/* Get boot and wall clocks in microseconds. */
|
|
clock_gettime(CLOCK_BOOTTIME, &bootTime1);
|
|
clock_gettime(CLOCK_REALTIME, &wallTime);
|
|
clock_gettime(CLOCK_BOOTTIME, &bootTime2);
|
|
uint64_t boot1 = bootTime1.tv_sec * 1000000000ULL + bootTime1.tv_nsec;
|
|
uint64_t boot2 = bootTime2.tv_sec * 1000000000ULL + bootTime2.tv_nsec;
|
|
uint64_t boot = (boot1 + boot2) / 2;
|
|
uint64_t wall = wallTime.tv_sec * 1000000000ULL + wallTime.tv_nsec;
|
|
|
|
addSample(boot, wall);
|
|
}
|
|
|
|
/**
|
|
* \brief Add a sample point to the clock recovery model, specifying the exact
|
|
* input and output clock values
|
|
* \param[in] input The input clock value
|
|
* \param[in] output The value of the output clock at the same moment, as far
|
|
* as possible, that the input clock was sampled
|
|
*
|
|
* This function should be used for corresponding clocks other than the Linux
|
|
* BOOTTIME and REALTIME clocks.
|
|
*/
|
|
void ClockRecovery::addSample(uint64_t input, uint64_t output)
|
|
{
|
|
LOG(ClockRec, Debug) << "addSample " << input << " " << output;
|
|
|
|
if (count_ == 0) {
|
|
inputBase_ = input;
|
|
outputBase_ = output;
|
|
}
|
|
|
|
/*
|
|
* We keep an eye on cumulative drift over the last several frames. If this exceeds a
|
|
* threshold, then probably the system clock has been updated and we're going to have to
|
|
* reset everything and start over.
|
|
*/
|
|
if (lastOutput_) {
|
|
int64_t inputDiff = getOutput(input) - getOutput(lastInput_);
|
|
int64_t outputDiff = output - lastOutput_;
|
|
error_ = error_ * 0.95 + (outputDiff - inputDiff);
|
|
if (std::abs(error_) > errorThreshold_) {
|
|
reset();
|
|
inputBase_ = input;
|
|
outputBase_ = output;
|
|
}
|
|
}
|
|
lastInput_ = input;
|
|
lastOutput_ = output;
|
|
|
|
/*
|
|
* Never let the new output value be more than maxJitter_ away from what
|
|
* we would have expected. This is just to reduce the effect of sudden
|
|
* large delays in the measured output.
|
|
*/
|
|
uint64_t expectedOutput = getOutput(input);
|
|
output = std::clamp(output, expectedOutput - maxJitter_, expectedOutput + maxJitter_);
|
|
|
|
/*
|
|
* We use x, y, x^2 and x*y sums to calculate the best fit line. Here we
|
|
* update them by pretending we have count_ samples at the previous fit,
|
|
* and now one new one. Gradually the effect of the older values gets
|
|
* lost. This is a very simple way of updating the fit (there are much
|
|
* more complicated ones!), but it works well enough. Using averages
|
|
* instead of sums makes the relative effect of old values and the new
|
|
* sample clearer.
|
|
*/
|
|
double x = static_cast<int64_t>(input - inputBase_);
|
|
double y = static_cast<int64_t>(output - outputBase_) - x;
|
|
unsigned int count1 = count_ + 1;
|
|
xAve_ = (count_ * xAve_ + x) / count1;
|
|
yAve_ = (count_ * yAve_ + y) / count1;
|
|
x2Ave_ = (count_ * x2Ave_ + x * x) / count1;
|
|
xyAve_ = (count_ * xyAve_ + x * y) / count1;
|
|
|
|
/*
|
|
* Don't update slope and offset until we've seen "enough" sample
|
|
* points. Note that the initial settings for slope_ and offset_
|
|
* ensures that the wallclock advances at the same rate as the realtime
|
|
* clock (but with their respective initial offsets).
|
|
*/
|
|
if (count_ > minSamples_) {
|
|
/* These are the standard equations for least squares linear regression. */
|
|
slope_ = (count1 * count1 * xyAve_ - count1 * xAve_ * count1 * yAve_) /
|
|
(count1 * count1 * x2Ave_ - count1 * xAve_ * count1 * xAve_);
|
|
offset_ = yAve_ - slope_ * xAve_;
|
|
}
|
|
|
|
/*
|
|
* Don't increase count_ above numSamples_, as this controls the long-term
|
|
* amount of the residual fit.
|
|
*/
|
|
if (count1 < numSamples_)
|
|
count_++;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculate the output clock value according to the model from an input
|
|
* clock value
|
|
* \param[in] input The input clock value
|
|
*
|
|
* \return Output clock value
|
|
*/
|
|
uint64_t ClockRecovery::getOutput(uint64_t input)
|
|
{
|
|
double x = static_cast<int64_t>(input - inputBase_);
|
|
double y = slope_ * x + offset_;
|
|
uint64_t output = y + x + outputBase_;
|
|
|
|
LOG(ClockRec, Debug) << "getOutput " << input << " " << output;
|
|
|
|
return output;
|
|
}
|
|
|
|
} /* namespace libcamera */
|