Commit 81d61369 authored by Jan Schmidt's avatar Jan Schmidt

clock: Improve slaving regression.

Add domain checks for the input values, and a variable precision
calculation that loops if necessary to ensure we never overflow
accumulators and then silently produce garbage results.

Make the (non-public) linear regression function available for
unit testing by putting it in a separate source file the test
can include. Add a unit test that the new regression function
produces sensible results for several inputs taken from real-world
captures.
parent f9f45834
......@@ -67,6 +67,7 @@ libgstreamer_@GST_API_VERSION@_la_SOURCES = \
gstcapsfeatures.c \
gstchildproxy.c \
gstclock.c \
gstclock-linreg.c \
gstcontext.c \
gstcontrolbinding.c \
gstcontrolsource.c \
......
......@@ -180,6 +180,12 @@ gint __gst_date_time_compare (const GstDateTime * dt1, const GstDateTime * dt2);
G_GNUC_INTERNAL
gchar * __gst_date_time_serialize (GstDateTime * datetime, gboolean with_usecs);
/* Non-static, for access from the testsuite, but not for external use */
gboolean
_priv_gst_do_linear_regression (GstClockTime *times, guint n,
GstClockTime * m_num, GstClockTime * m_denom, GstClockTime * b,
GstClockTime * xbase, gdouble * r_squared);
#ifndef GST_DISABLE_REGISTRY
/* Secret variable to initialise gst without registry cache */
GST_EXPORT gboolean _gst_disable_registry_cache;
......
/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
* 2004 Wim Taymans <wim@fluendo.com>
* 2015 Jan Schmidt <jan@centricular.com>
*
* gstclock-linreg.c: Linear regression implementation, used in clock slaving
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gst_private.h"
#include <time.h>
#include "gstclock.h"
#include "gstinfo.h"
#include "gstutils.h"
#include "glib-compat-private.h"
/* Compute log2 of the passed 64-bit number by finding the highest set bit */
static guint
gst_log2 (GstClockTime in)
{
const guint64 b[] =
{ 0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000, 0xFFFFFFFF00000000LL };
const guint64 S[] = { 1, 2, 4, 8, 16, 32 };
int i;
guint count = 0;
for (i = 5; i >= 0; i--) {
if (in & b[i]) {
in >>= S[i];
count |= S[i];
}
}
return count;
}
/* http://mathworld.wolfram.com/LeastSquaresFitting.html
* with SLAVE_LOCK
*/
gboolean
_priv_gst_do_linear_regression (GstClockTime * times, guint n,
GstClockTime * m_num, GstClockTime * m_denom, GstClockTime * b,
GstClockTime * xbase, gdouble * r_squared)
{
GstClockTime *newx, *newy;
GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4;
GstClockTime xmax, ymax;
GstClockTimeDiff sxx, sxy, syy;
GstClockTime *x, *y;
gint i, j;
gint pshift = 0;
gint max_bits;
xbar = ybar = sxx = syy = sxy = 0;
x = times;
y = times + 2;
xmin = ymin = G_MAXUINT64;
xmax = ymax = 0;
for (i = j = 0; i < n; i++, j += 4) {
xmin = MIN (xmin, x[j]);
ymin = MIN (ymin, y[j]);
xmax = MAX (xmax, x[j]);
ymax = MAX (ymax, y[j]);
}
newx = times + 1;
newy = times + 3;
/* strip off unnecessary bits of precision */
for (i = j = 0; i < n; i++, j += 4) {
newx[j] = x[j] - xmin;
newy[j] = y[j] - ymin;
}
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "reduced numbers:");
for (i = j = 0; i < n; i++, j += 4)
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[j], newy[j]);
#endif
/* have to do this precisely otherwise the results are pretty much useless.
* should guarantee that none of these accumulators can overflow */
/* quantities on the order of 1e10 to 1e13 -> 30-35 bits;
* window size a max of 2^10, so
this addition could end up around 2^45 or so -- ample headroom */
for (i = j = 0; i < n; i++, j += 4) {
/* Just in case assumptions about headroom prove false, let's check */
if ((newx[j] > 0 && G_MAXUINT64 - xbar <= newx[j]) ||
(newy[j] > 0 && G_MAXUINT64 - ybar <= newy[j])) {
GST_CAT_WARNING_OBJECT (GST_CAT_CLOCK, clock,
"Regression overflowed in clock slaving! xbar %"
G_GUINT64_FORMAT " newx[j] %" G_GUINT64_FORMAT " ybar %"
G_GUINT64_FORMAT " newy[j] %" G_GUINT64_FORMAT, xbar, newx[j], ybar,
newy[j]);
return FALSE;
}
xbar += newx[j];
ybar += newy[j];
}
xbar /= n;
ybar /= n;
/* multiplying directly would give quantities on the order of 1e20-1e26 ->
* 60 bits to 70 bits times the window size that's 80 which is too much.
* Instead we (1) subtract off the xbar*ybar in the loop instead of after,
* to avoid accumulation; (2) shift off some estimated number of bits from
* each multiplicand to limit the expected ceiling. For strange
* distributions of input values, things can still overflow, in which
* case we drop precision and retry - at most a few times, in practice rarely
*/
/* Guess how many bits we might need for the usual distribution of input,
* with a fallback loop that drops precision if things go pear-shaped */
max_bits = gst_log2 (MAX (xmax - xmin, ymax - ymin)) * 7 / 8 + gst_log2 (n);
if (max_bits > 64)
pshift = max_bits - 64;
i = 0;
do {
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"Restarting regression with precision shift %u", pshift);
#endif
xbar4 = xbar >> pshift;
ybar4 = ybar >> pshift;
sxx = syy = sxy = 0;
for (i = j = 0; i < n; i++, j += 4) {
GstClockTime newx4, newy4;
GstClockTimeDiff tmp;
newx4 = newx[j] >> pshift;
newy4 = newy[j] >> pshift;
tmp = (newx4 + xbar4) * (newx4 - xbar4);
if (G_UNLIKELY (tmp > 0 && sxx > 0 && (G_MAXINT64 - sxx <= tmp))) {
do {
/* Drop some precision and restart */
pshift++;
sxx /= 4;
tmp /= 4;
} while (G_MAXINT64 - sxx <= tmp);
break;
} else if (G_UNLIKELY (tmp < 0 && sxx < 0 && (G_MAXINT64 - sxx >= tmp))) {
do {
/* Drop some precision and restart */
pshift++;
sxx /= 4;
tmp /= 4;
} while (G_MININT64 - sxx >= tmp);
break;
}
sxx += tmp;
tmp = newy4 * newy4 - ybar4 * ybar4;
if (G_UNLIKELY (tmp > 0 && syy > 0 && (G_MAXINT64 - syy <= tmp))) {
do {
pshift++;
syy /= 4;
tmp /= 4;
} while (G_MAXINT64 - syy <= tmp);
break;
} else if (G_UNLIKELY (tmp < 0 && syy < 0 && (G_MAXINT64 - syy >= tmp))) {
do {
pshift++;
syy /= 4;
tmp /= 4;
} while (G_MININT64 - syy >= tmp);
break;
}
syy += tmp;
tmp = newx4 * newy4 - xbar4 * ybar4;
if (G_UNLIKELY (tmp > 0 && sxy > 0 && (G_MAXINT64 - sxy <= tmp))) {
do {
pshift++;
sxy /= 4;
tmp /= 4;
} while (G_MAXINT64 - sxy <= tmp);
break;
} else if (G_UNLIKELY (tmp < 0 && sxy < 0 && (G_MININT64 - sxy >= tmp))) {
do {
pshift++;
sxy /= 4;
tmp /= 4;
} while (G_MININT64 - sxy >= tmp);
break;
}
sxy += tmp;
}
} while (i < n);
if (G_UNLIKELY (sxx == 0))
goto invalid;
*m_num = sxy;
*m_denom = sxx;
*xbase = xmin;
*b = (ybar + ymin) - gst_util_uint64_scale (xbar, *m_num, *m_denom);
*r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " m = %g",
((double) *m_num) / *m_denom);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " b = %" G_GUINT64_FORMAT,
*b);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbase = %" G_GUINT64_FORMAT,
*xbase);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " r2 = %g", *r_squared);
#endif
return TRUE;
invalid:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "sxx == 0, regression failed");
return FALSE;
}
}
......@@ -100,7 +100,6 @@
* defines the minimum number of samples before the calibration is performed.
*/
#include "gst_private.h"
#include <time.h>
......@@ -1262,132 +1261,6 @@ gst_clock_get_master (GstClock * clock)
return result;
}
/* http://mathworld.wolfram.com/LeastSquaresFitting.html
* with SLAVE_LOCK
*/
static gboolean
do_linear_regression (GstClock * clock, GstClockTime * m_num,
GstClockTime * m_denom, GstClockTime * b, GstClockTime * xbase,
gdouble * r_squared)
{
GstClockTime *newx, *newy;
GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4;
GstClockTimeDiff sxx, sxy, syy;
GstClockTime *x, *y;
gint i, j;
guint n;
GstClockPrivate *priv;
xbar = ybar = sxx = syy = sxy = 0;
priv = clock->priv;
x = priv->times;
y = priv->times + 2;
n = priv->filling ? priv->time_index : priv->window_size;
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "doing regression on:");
for (i = j = 0; i < n; i++, j += 4)
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[j], y[j]);
#endif
xmin = ymin = G_MAXUINT64;
for (i = j = 0; i < n; i++, j += 4) {
xmin = MIN (xmin, x[j]);
ymin = MIN (ymin, y[j]);
}
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min x: %" G_GUINT64_FORMAT,
xmin);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min y: %" G_GUINT64_FORMAT,
ymin);
#endif
newx = priv->times + 1;
newy = priv->times + 3;
/* strip off unnecessary bits of precision */
for (i = j = 0; i < n; i++, j += 4) {
newx[j] = x[j] - xmin;
newy[j] = y[j] - ymin;
}
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "reduced numbers:");
for (i = j = 0; i < n; i++, j += 4)
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[j], newy[j]);
#endif
/* have to do this precisely otherwise the results are pretty much useless.
* should guarantee that none of these accumulators can overflow */
/* quantities on the order of 1e10 -> 30 bits; window size a max of 2^10, so
this addition could end up around 2^40 or so -- ample headroom */
for (i = j = 0; i < n; i++, j += 4) {
xbar += newx[j];
ybar += newy[j];
}
xbar /= n;
ybar /= n;
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbar = %" G_GUINT64_FORMAT,
xbar);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " ybar = %" G_GUINT64_FORMAT,
ybar);
#endif
/* multiplying directly would give quantities on the order of 1e20 -> 60 bits;
times the window size that's 70 which is too much. Instead we (1) subtract
off the xbar*ybar in the loop instead of after, to avoid accumulation; (2)
shift off 4 bits from each multiplicand, giving an expected ceiling of 52
bits, which should be enough. Need to check the incoming range and domain
to ensure this is an appropriate loss of precision though. */
xbar4 = xbar >> 4;
ybar4 = ybar >> 4;
for (i = j = 0; i < n; i++, j += 4) {
GstClockTime newx4, newy4;
newx4 = newx[j] >> 4;
newy4 = newy[j] >> 4;
sxx += newx4 * newx4 - xbar4 * xbar4;
syy += newy4 * newy4 - ybar4 * ybar4;
sxy += newx4 * newy4 - xbar4 * ybar4;
}
if (G_UNLIKELY (sxx == 0))
goto invalid;
*m_num = sxy;
*m_denom = sxx;
*xbase = xmin;
*b = (ybar + ymin) - gst_util_uint64_scale (xbar, *m_num, *m_denom);
*r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
#ifdef DEBUGGING_ENABLED
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " m = %g",
((double) *m_num) / *m_denom);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " b = %" G_GUINT64_FORMAT,
*b);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbase = %" G_GUINT64_FORMAT,
*xbase);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " r2 = %g", *r_squared);
#endif
return TRUE;
invalid:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "sxx == 0, regression failed");
return FALSE;
}
}
/**
* gst_clock_add_observation:
* @clock: a #GstClock
......@@ -1455,6 +1328,7 @@ gst_clock_add_observation_unapplied (GstClock * clock, GstClockTime slave,
{
GstClockTime m_num, m_denom, b, xbase;
GstClockPrivate *priv;
guint n;
g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
g_return_val_if_fail (r_squared != NULL, FALSE);
......@@ -1479,7 +1353,9 @@ gst_clock_add_observation_unapplied (GstClock * clock, GstClockTime slave,
if (G_UNLIKELY (priv->filling && priv->time_index < priv->window_threshold))
goto filling;
if (!do_linear_regression (clock, &m_num, &m_denom, &b, &xbase, r_squared))
n = priv->filling ? priv->time_index : priv->window_size;
if (!_priv_gst_do_linear_regression (priv->times, n, &m_num, &m_denom, &b,
&xbase, r_squared))
goto invalid;
GST_CLOCK_SLAVE_UNLOCK (clock);
......
......@@ -17,6 +17,8 @@
* Boston, MA 02110-1301, USA.
*/
/* Include the non-public linear regression function */
#include "../../gst/gstclock-linreg.c"
#include <gst/check/gstcheck.h>
typedef struct
......@@ -109,6 +111,202 @@ GST_START_TEST (test_set_master_refcount)
GST_END_TEST;
GstClockTime times1[] = {
257116899087539, 0, 120632754291904, 0,
257117935914250, 0, 120633825367344, 0,
257119448289434, 0, 120635306141271, 0,
257120493671524, 0, 120636384357825, 0,
257121550784861, 0, 120637417438878, 0,
257123042669403, 0, 120638895344150, 0,
257124089184865, 0, 120639971729651, 0,
257125545836474, 0, 120641406788243, 0,
257127030618490, 0, 120642885914220, 0,
257128033712770, 0, 120643888843907, 0,
257129081768074, 0, 120644981892002, 0,
257130145383845, 0, 120646016376867, 0,
257131532530200, 0, 120647389850987, 0,
257132578136034, 0, 120648472767247, 0,
257134102475722, 0, 120649953785315, 0,
257135142994788, 0, 120651028858556, 0,
257136585079868, 0, 120652441303624, 0,
257137618260656, 0, 120653491627112, 0,
257139108694546, 0, 120654963978184, 0,
257140644022048, 0, 120656500233068, 0,
257141685671825, 0, 120657578510655, 0,
257142741238288, 0, 120658610889805, 0,
257144243633074, 0, 120660093098060, 0,
257145287962271, 0, 120661172901525, 0,
257146740596716, 0, 120662591572179, 0,
257147757607150, 0, 120663622822179, 0,
257149263992401, 0, 120665135578527, 0,
257150303719290, 0, 120666176166905, 0,
257151355569906, 0, 120667217304601, 0,
257152430578406, 0, 120668326099768, 0,
257153490501095, 0, 120669360554111, 0,
257154512360784, 0, 120670365497960, 0,
257155530610577, 0, 120671399006259, 0,
257156562091659, 0, 120672432728185, 0,
257157945388742, 0, 120673800312414, 0,
257159287547073, 0, 120675142444983, 0,
257160324912880, 0, 120676215076817, 0,
257345408328042, 0, 120861261738196, 0,
257346412270919, 0, 120862265613926, 0,
257347420532284, 0, 120863278644933, 0,
257348431187638, 0, 120864284412754, 0,
257349439018028, 0, 120865293110265, 0,
257351796217938, 0, 120867651111973, 0,
257352803038092, 0, 120868659107578, 0,
257354152688899, 0, 120870008594883, 0,
257355157088906, 0, 120871011097327, 0,
257356162439182, 0, 120872016346348, 0,
257357167872040, 0, 120873021656407, 0,
257358182440058, 0, 120874048633945, 0,
257359198881356, 0, 120875052265538, 0,
257100756525466, 0, 120616619282139, 0,
257101789337770, 0, 120617655475988, 0,
257102816323472, 0, 120618674000157, 0,
257103822485250, 0, 120619679005039, 0,
257104840760423, 0, 120620710743321, 0,
257105859459496, 0, 120621715351476, 0,
257106886662470, 0, 120622764942539, 0,
257108387497864, 0, 120624244221106, 0,
257109428859191, 0, 120625321461096, 0,
257110485892785, 0, 120626356892003, 0,
257111869872141, 0, 120627726459874, 0,
257112915903774, 0, 120628813190830, 0,
257114329982208, 0, 120630187061682, 0,
257115376666026, 0, 120631271992101, 0
};
GstClockTime times2[] = {
291678579009762, 0, 162107345029507, 0,
291679770464405, 0, 162108597684538, 0,
291680972924370, 0, 162109745816863, 0,
291682278949629, 0, 162111000577605, 0,
291683590706117, 0, 162112357724822, 0,
291684792322541, 0, 162113613156950, 0,
291685931362506, 0, 162114760556854, 0,
291687132156589, 0, 162115909238493, 0,
291688265012060, 0, 162117120603240, 0,
291689372183047, 0, 162118126279508, 0,
291705506022294, 0, 162134329373992, 0,
291667914301004, 0, 162096795553658, 0,
291668119537668, 0, 162096949051905, 0,
291668274671455, 0, 162097049238371, 0,
291668429435600, 0, 162097256356719, 0,
291668586128535, 0, 162097355689763, 0,
291668741306233, 0, 162097565678460, 0,
291668893789203, 0, 162097661044916, 0,
291669100256555, 0, 162097865694145, 0,
291669216417563, 0, 162098069214693, 0,
291669836394620, 0, 162098677275530, 0,
291669990447821, 0, 162098792601263, 0,
291670149426086, 0, 162098916899184, 0,
291670300232152, 0, 162099114225621, 0,
291670411261917, 0, 162099236784112, 0,
291670598483507, 0, 162099402158751, 0,
291671716582687, 0, 162100558744122, 0,
291672600759788, 0, 162101499326359, 0,
291673919988307, 0, 162102751981384, 0,
291675174441643, 0, 162104005551939, 0,
291676271562197, 0, 162105105252898, 0,
291677376345374, 0, 162106195737516, 0
};
GstClockTime times3[] = {
291881924291688, 0, 162223997578228, 0,
291883318122262, 0, 162224167198360, 0,
291884786394838, 0, 162224335172501, 0,
291886004374386, 0, 162224503695531, 0,
291887224353285, 0, 162224673560021, 0,
291888472403367, 0, 162224843760361, 0,
291889727977561, 0, 162225014479362, 0,
291890989982306, 0, 162225174554558, 0,
291892247875763, 0, 162225339753039, 0,
291893502163547, 0, 162225673230987, 0,
291894711382216, 0, 162225829494101, 0,
291895961021506, 0, 162225964530832, 0,
291897251690854, 0, 162226127287981, 0,
291898508630785, 0, 162226303710406, 0,
291899740172868, 0, 162226472478047, 0,
291900998878873, 0, 162226637402085, 0,
291902334919875, 0, 162226797873245, 0,
291903572196610, 0, 162226964352963, 0,
291904727342699, 0, 162227125312525, 0,
291906071189108, 0, 162228361337153, 0,
291907308146005, 0, 162229560625638, 0,
291908351925126, 0, 162230604986650, 0,
291909396411423, 0, 162231653690543, 0,
291910453965348, 0, 162232698550995, 0,
291912096870744, 0, 162233475264947, 0,
291913234148395, 0, 162233606516855, 0,
291915448096576, 0, 162233921145559, 0,
291916707748827, 0, 162234047154298, 0,
291918737451070, 0, 162234370837425, 0,
291919896016205, 0, 162234705504337, 0,
291921098663980, 0, 162234872320397, 0,
291922315691409, 0, 162235031023366, 0
};
struct test_entry
{
gint n;
GstClockTime *v;
GstClockTime expect_internal;
GstClockTime expect_external;
guint64 expect_num;
guint64 expect_denom;
} times[] = {
{
32, times1, 257116899087539, 120632768833649, 4052622913376634109,
4052799313904261962}, {
64, times1, 257100756525466, 120616627179145, 2011895759027682422,
2012014931360215503}, {
32, times2, 291667914301004, 162096729345562, 2319535707505209857,
2321009753483354451}, {
32, times3, 291881924291688, 162222328334994, 1370930728180888261,
4392719527011673456}
};
GST_START_TEST (test_regression)
{
GstClockTime m_num, m_den, internal, external;
gdouble r_squared, rate, expect_rate;
gint i;
for (i = 0; i < G_N_ELEMENTS (times); i++) {
fail_unless (_priv_gst_do_linear_regression (times[i].v, times[i].n,
&m_num, &m_den, &external, &internal, &r_squared));
GST_LOG ("xbase %" G_GUINT64_FORMAT " ybase %" G_GUINT64_FORMAT " rate = %"
G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT " = %.10f r_squared %f\n",
internal, external, m_num, m_den, (gdouble) (m_num) / (m_den),
r_squared);
/* Require high correlation */
fail_unless (r_squared >= 0.9);
fail_unless (internal == times[i].expect_internal,
"Regression params %d fail. internal %" G_GUINT64_FORMAT
" != expected %" G_GUINT64_FORMAT, i, internal,
times[i].expect_internal);
/* Rate must be within 1% tolerance */
expect_rate = ((gdouble) (times[i].expect_num) / times[i].expect_denom);
rate = ((gdouble) (m_num) / m_den);
fail_unless ((expect_rate - rate) >= -0.1 && (expect_rate - rate) <= 0.1,
"Regression params %d fail. Rate out of range. Expected %f, got %f",
i, expect_rate, rate);
fail_unless (external >= times[i].expect_external * 0.95 &&
external <= times[i].expect_internal * 1.05,
"Regression params %d fail. external %" G_GUINT64_FORMAT
" != expected %" G_GUINT64_FORMAT, i, external,
times[i].expect_external);
}
}
GST_END_TEST;