clock: Improve slaving regression.
authorJan Schmidt <jan@centricular.com>
Wed, 14 Jan 2015 23:05:32 +0000 (10:05 +1100)
committerJan Schmidt <jan@centricular.com>
Wed, 21 Jan 2015 11:27:18 +0000 (22:27 +1100)
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.

gst/Makefile.am
gst/gst_private.h
gst/gstclock-linreg.c [new file with mode: 0644]
gst/gstclock.c
tests/check/gst/gstclock.c

index 79dd67f..7216bad 100644 (file)
@@ -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 \
index aa77cb8..8c9cb4b 100644 (file)
@@ -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;
diff --git a/gst/gstclock-linreg.c b/gst/gstclock-linreg.c
new file mode 100644 (file)
index 0000000..9a7b3dd
--- /dev/null
@@ -0,0 +1,241 @@
+/* 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;
+  }
+}
index 12a9534..2d8de0f 100644 (file)
  * 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);
index a394cf8..cf971c6 100644 (file)
@@ -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;
+
 static Suite *
 gst_clock_suite (void)
 {
@@ -117,6 +315,7 @@ gst_clock_suite (void)
 
   suite_add_tcase (s, tc_chain);
   tcase_add_test (tc_chain, test_set_master_refcount);
+  tcase_add_test (tc_chain, test_regression);
 
   return s;
 }