gst/base/gstbasesink.*: No need to store the clock, the parent element class already has it.

Original commit message from CVS:
* gst/base/gstbasesink.c: (gst_base_sink_class_init),
(gst_base_sink_wait), (gst_base_sink_do_sync),
(gst_base_sink_handle_event):
* gst/base/gstbasesink.h:
No need to store the clock, the parent element class already
has it.

* gst/gstbin.c: (gst_bin_set_clock_func), (gst_bin_add_func):
Updates for clock_set returning a gboolean

* gst/gstclock.c: (gst_clock_entry_new), (gst_clock_id_wait),
(gst_clock_id_wait_async), (gst_clock_class_init),
(gst_clock_init), (gst_clock_finalize),
(gst_clock_get_internal_time), (gst_clock_get_time),
(gst_clock_slave_callback), (gst_clock_set_master),
(gst_clock_get_master), (do_linear_regression),
(gst_clock_add_observation), (gst_clock_set_property),
(gst_clock_get_property):
* gst/gstclock.h:
Implement master/slave. When setting a clock as a slave, a
periodic timeout is scheduled to sample master and slave times.
Then the slave clock is recalibrated to match offset and rate
of the master clock.
Update logging a bit.
Add flag so that a clock can state that is cannot be slaved to
another clock.

* gst/gstelement.c: (gst_element_set_clock):
* gst/gstelement.h:
The set_clock returns a gboolean for when an element cannot
deal with the selected clock in the pipeline.

* gst/gstpipeline.c: (gst_pipeline_change_state),
(gst_pipeline_set_clock):
* gst/gstpipeline.h:
Handle the case where the selected clock cannot be set on
the pipeline.

* gst/net/gstnetclientclock.c: (gst_net_client_clock_class_init),
(gst_net_client_clock_init), (gst_net_client_clock_finalize),
(gst_net_client_clock_set_property),
(gst_net_client_clock_get_property),
(gst_net_client_clock_observe_times):
* gst/net/gstnetclientclock.h:
Use regression code in GstClock parent, remove duplicated
functionality.
This commit is contained in:
Wim Taymans 2005-11-22 18:28:44 +00:00
parent 59ac39499a
commit cf925ebb6f
17 changed files with 469 additions and 396 deletions

View file

@ -1,3 +1,52 @@
2005-11-22 Wim Taymans <wim@fluendo.com>
* gst/base/gstbasesink.c: (gst_base_sink_class_init),
(gst_base_sink_wait), (gst_base_sink_do_sync),
(gst_base_sink_handle_event):
* gst/base/gstbasesink.h:
No need to store the clock, the parent element class already
has it.
* gst/gstbin.c: (gst_bin_set_clock_func), (gst_bin_add_func):
Updates for clock_set returning a gboolean
* gst/gstclock.c: (gst_clock_entry_new), (gst_clock_id_wait),
(gst_clock_id_wait_async), (gst_clock_class_init),
(gst_clock_init), (gst_clock_finalize),
(gst_clock_get_internal_time), (gst_clock_get_time),
(gst_clock_slave_callback), (gst_clock_set_master),
(gst_clock_get_master), (do_linear_regression),
(gst_clock_add_observation), (gst_clock_set_property),
(gst_clock_get_property):
* gst/gstclock.h:
Implement master/slave. When setting a clock as a slave, a
periodic timeout is scheduled to sample master and slave times.
Then the slave clock is recalibrated to match offset and rate
of the master clock.
Update logging a bit.
Add flag so that a clock can state that is cannot be slaved to
another clock.
* gst/gstelement.c: (gst_element_set_clock):
* gst/gstelement.h:
The set clock returns a gboolean for when an element cannot
deal with the selected clock in the pipeline.
* gst/gstpipeline.c: (gst_pipeline_change_state),
(gst_pipeline_set_clock):
* gst/gstpipeline.h:
Handle the case where the selected clock cannot be set on
the pipeline.
* gst/net/gstnetclientclock.c: (gst_net_client_clock_class_init),
(gst_net_client_clock_init), (gst_net_client_clock_finalize),
(gst_net_client_clock_set_property),
(gst_net_client_clock_get_property),
(gst_net_client_clock_observe_times):
* gst/net/gstnetclientclock.h:
Use regression code in GstClock parent, remove duplicated
functionality.
2005-11-22 Michael Smith <msmith@fluendo.com>
* gst/gstutils.c: (gst_util_clock_time_scale):

View file

@ -135,8 +135,6 @@ gst_base_sink_get_type (void)
return base_sink_type;
}
static void gst_base_sink_set_clock (GstElement * element, GstClock * clock);
static void gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_base_sink_get_property (GObject * object, guint prop_id,
@ -200,7 +198,6 @@ gst_base_sink_class_init (GstBaseSinkClass * klass)
g_param_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
G_PARAM_READWRITE));
gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_base_sink_set_clock);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_sink_send_event);
@ -330,16 +327,6 @@ gst_base_sink_finalize (GObject * object)
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_base_sink_set_clock (GstElement * element, GstClock * clock)
{
GstBaseSink *sink;
sink = GST_BASE_SINK (element);
sink->clock = clock;
}
static void
gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
@ -890,7 +877,7 @@ gst_base_sink_wait (GstBaseSink * basesink, GstClockTime time)
g_assert (basesink->clock_id == NULL);
g_assert (GST_CLOCK_TIME_IS_VALID (time));
id = gst_clock_new_single_shot_id (basesink->clock, time);
id = gst_clock_new_single_shot_id (GST_ELEMENT_CLOCK (basesink), time);
basesink->clock_id = id;
/* release the object lock while waiting */
@ -956,7 +943,7 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
}
/* now do clocking */
if (basesink->clock) {
if (GST_ELEMENT_CLOCK (basesink)) {
GstClockTime base_time;
GstClockTimeDiff stream_start, stream_end;
@ -1016,7 +1003,7 @@ gst_base_sink_handle_event (GstBaseSink * basesink, GstEvent * event)
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
GST_OBJECT_LOCK (basesink);
if (basesink->clock) {
if (GST_ELEMENT_CLOCK (basesink)) {
/* wait for last buffer to finish if we have a valid end time */
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
gst_base_sink_wait (basesink, basesink->end_time);

View file

@ -36,13 +36,6 @@ G_BEGIN_DECLS
#define GST_IS_BASE_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_SINK))
#define GST_BASE_SINK_CAST(obj) ((GstBaseSink *) (obj))
/**
* GST_BASE_SINK_CLOCK:
* @obj: base sink instance
*
* Gives the pointer to the #GstClock object of the element.
*/
#define GST_BASE_SINK_CLOCK(obj) (GST_BASE_SINK_CAST (obj)->clock)
/**
* GST_BASE_SINK_PAD:
* @obj: base sink instance
@ -83,7 +76,6 @@ struct _GstBaseSink {
GstSegment segment;
/*< private >*/ /* with LOCK */
GstClock *clock;
GstClockID clock_id;
GstClockTime end_time;
gboolean sync;

View file

@ -145,7 +145,7 @@ static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element);
static void gst_bin_set_index_func (GstElement * element, GstIndex * index);
#endif
static GstClock *gst_bin_provide_clock_func (GstElement * element);
static void gst_bin_set_clock_func (GstElement * element, GstClock * clock);
static gboolean gst_bin_set_clock_func (GstElement * element, GstClock * clock);
static void gst_bin_handle_message_func (GstBin * bin, GstMessage * message);
static gboolean gst_bin_send_event (GstElement * element, GstEvent * event);
@ -432,11 +432,12 @@ gst_bin_set_index_func (GstElement * element, GstIndex * index)
*
* MT safe
*/
static void
static gboolean
gst_bin_set_clock_func (GstElement * element, GstClock * clock)
{
GList *children;
GstBin *bin;
gboolean res = TRUE;
bin = GST_BIN (element);
@ -445,10 +446,12 @@ gst_bin_set_clock_func (GstElement * element, GstClock * clock)
for (children = bin->children; children; children = g_list_next (children)) {
GstElement *child = GST_ELEMENT (children->data);
gst_element_set_clock (child, clock);
res &= gst_element_set_clock (child, clock);
}
}
GST_OBJECT_UNLOCK (bin);
return res;
}
/* get the clock for this bin by asking all of the children in this bin
@ -726,6 +729,9 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
/* propagate the current base time and clock */
gst_element_set_base_time (element, GST_ELEMENT (bin)->base_time);
/* it's possible that the element did not accept the clock but
* that is not important right now. When the pipeline goes to PLAYING,
* a new clock will be selected */
gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin));
bin->state_dirty = TRUE;
GST_OBJECT_UNLOCK (bin);

View file

@ -98,15 +98,26 @@
static GstAllocTrace *_gst_clock_entry_trace;
#endif
#define DEFAULT_EVENT_DIFF (GST_SECOND)
#define DEFAULT_MAX_DIFF (2 * GST_SECOND)
/* #define DEBUGGING_ENABLED */
#ifdef DEBUGGING_ENABLED
#define DEBUG(x, args...) g_print (x "\n", ##args)
#else
#define DEBUG(x, args...) /* nop */
#endif
#define DEFAULT_STATS FALSE
#define DEFAULT_WINDOW_SIZE 32
#define DEFAULT_WINDOW_THRESHOLD 4
#define DEFAULT_TIMEOUT GST_SECOND / 10
enum
{
ARG_0,
ARG_STATS,
ARG_MAX_DIFF,
ARG_EVENT_DIFF
PROP_0,
PROP_STATS,
PROP_WINDOW_SIZE,
PROP_WINDOW_THRESHOLD,
PROP_TIMEOUT
};
static void gst_clock_class_init (GstClockClass * klass);
@ -134,8 +145,8 @@ gst_clock_entry_new (GstClock * clock, GstClockTime time,
#ifndef GST_DISABLE_TRACE
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
#endif
GST_CAT_DEBUG (GST_CAT_CLOCK, "created entry %p, time %" GST_TIME_FORMAT,
entry, GST_TIME_ARGS (time));
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"created entry %p, time %" GST_TIME_FORMAT, entry, GST_TIME_ARGS (time));
gst_atomic_int_set (&entry->refcount, 1);
entry->clock = clock;
@ -327,20 +338,22 @@ gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
clock = GST_CLOCK_ENTRY_CLOCK (entry);
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
goto invalid_time;
if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED))
goto unscheduled;
clock = GST_CLOCK_ENTRY_CLOCK (entry);
cclass = GST_CLOCK_GET_CLASS (clock);
if (G_LIKELY (cclass->wait)) {
GST_CAT_DEBUG (GST_CAT_CLOCK, "waiting on clock entry %p", id);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "waiting on clock entry %p",
id);
res = cclass->wait (clock, entry);
GST_CAT_DEBUG (GST_CAT_CLOCK, "done waiting entry %p", id);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "done waiting entry %p", id);
if (jitter) {
GstClockTime now = gst_clock_get_time (clock);
@ -362,12 +375,14 @@ gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
/* ERRORS */
invalid_time:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME");
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"invalid time requested, returning _BADTIME");
return GST_CLOCK_BADTIME;
}
unscheduled:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED");
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"entry was unscheduled return _UNSCHEDULED");
return GST_CLOCK_UNSCHEDULED;
}
}
@ -427,12 +442,14 @@ gst_clock_id_wait_async (GstClockID id,
invalid_time:
{
(func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME");
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"invalid time requested, returning _BADTIME");
return GST_CLOCK_BADTIME;
}
unscheduled:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED");
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"entry was unscheduled return _UNSCHEDULED");
return GST_CLOCK_UNSCHEDULED;
}
}
@ -518,9 +535,22 @@ gst_clock_class_init (GstClockClass * klass)
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_clock_set_property);
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_clock_get_property);
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATS,
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_STATS,
g_param_spec_boolean ("stats", "Stats", "Enable clock stats",
FALSE, G_PARAM_READWRITE));
DEFAULT_STATS, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_SIZE,
g_param_spec_int ("window-size", "Window size",
"The size of the window used to calculate rate and offset", 2, 1024,
DEFAULT_WINDOW_SIZE, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_WINDOW_THRESHOLD, g_param_spec_int ("window-threshold",
"Window threshold",
"The threshold to start calculating rate and offset", 2, 1024,
DEFAULT_WINDOW_THRESHOLD, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMEOUT,
g_param_spec_uint64 ("timeout", "Timeout",
"The amount of time, in nanoseconds, to sample master and slave clocks",
0, G_MAXUINT64, DEFAULT_TIMEOUT, G_PARAM_READWRITE));
}
static void
@ -534,6 +564,12 @@ gst_clock_init (GstClock * clock)
clock->internal_calibration = 0;
clock->external_calibration = 0;
clock->rate = 1.0;
clock->filling = TRUE;
clock->window_size = DEFAULT_WINDOW_SIZE;
clock->window_threshold = DEFAULT_WINDOW_THRESHOLD;
clock->time_index = 0;
clock->timeout = DEFAULT_TIMEOUT;
clock->times = g_new0 (GstClockTime, 4 * clock->window_size);
}
static void
@ -541,8 +577,19 @@ gst_clock_finalize (GObject * object)
{
GstClock *clock = GST_CLOCK (object);
GST_OBJECT_LOCK (clock);
if (clock->clockid) {
gst_clock_id_unschedule (clock->clockid);
gst_clock_id_unref (clock->clockid);
clock->clockid = NULL;
}
g_cond_free (clock->entries_changed);
g_free (clock->times);
clock->times = NULL;
GST_OBJECT_UNLOCK (clock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -652,7 +699,7 @@ gst_clock_get_internal_time (GstClock * clock)
} else {
ret = G_GINT64_CONSTANT (0);
}
GST_CAT_DEBUG (GST_CAT_CLOCK, "internal time %" GST_TIME_FORMAT,
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "internal time %" GST_TIME_FORMAT,
GST_TIME_ARGS (ret));
return ret;
@ -685,7 +732,7 @@ gst_clock_get_time (GstClock * clock)
ret = gst_clock_adjust_unlocked (clock, ret);
GST_OBJECT_UNLOCK (clock);
GST_CAT_DEBUG (GST_CAT_CLOCK, "adjusted time %" GST_TIME_FORMAT,
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "adjusted time %" GST_TIME_FORMAT,
GST_TIME_ARGS (ret));
return ret;
@ -766,6 +813,30 @@ gst_clock_get_calibration (GstClock * clock, GstClockTime * internal,
GST_OBJECT_UNLOCK (clock);
}
/* will be called repeadedly to sample the master and slave clock
* to recalibrate the clock */
static gboolean
gst_clock_slave_callback (GstClock * master, GstClockTime time,
GstClockID id, GstClock * clock)
{
GstClockTime stime, mtime;
gdouble r_squared;
stime = gst_clock_get_internal_time (clock);
mtime = gst_clock_get_time (master);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"master %" GST_TIME_FORMAT ", slave %" GST_TIME_FORMAT,
GST_TIME_ARGS (mtime), GST_TIME_ARGS (stime));
gst_clock_add_observation (clock, stime, mtime, &r_squared);
/* FIXME, we can use the r_squared value to adjust the timeout
* value of the clockid */
return TRUE;
}
/**
* gst_clock_set_master
* @clock: a #GstClock
@ -778,14 +849,47 @@ gst_clock_get_calibration (GstClock * clock, GstClockTime * internal,
* A clock provider that slaves its clock to a master can get the current
* calibration values with gst_clock_get_calibration().
*
* Returns: TRUE if the clock is capable of being slaved to a master clock.
*
* MT safe.
*/
void
gboolean
gst_clock_set_master (GstClock * clock, GstClock * master)
{
g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
GST_OBJECT_LOCK (clock);
/* we always allow setting the master to NULL */
if (master && !GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER))
goto not_supported;
GST_DEBUG_OBJECT (clock, "slaving to master clock %p", master);
gst_object_replace ((GstObject **) & clock->master, (GstObject *) master);
if (clock->clockid) {
gst_clock_id_unschedule (clock->clockid);
gst_clock_id_unref (clock->clockid);
clock->clockid = NULL;
}
if (master) {
clock->filling = TRUE;
clock->time_index = 0;
/* use the master periodic id to schedule sampling and
* clock calibration. */
clock->clockid = gst_clock_new_periodic_id (master,
gst_clock_get_time (master), clock->timeout);
gst_clock_id_wait_async (clock->clockid,
(GstClockCallback) gst_clock_slave_callback, clock);
}
GST_OBJECT_UNLOCK (clock);
return TRUE;
not_supported:
{
GST_DEBUG_OBJECT (clock, "cannot be slaved to a master clock");
return FALSE;
}
}
/**
@ -805,6 +909,8 @@ gst_clock_get_master (GstClock * clock)
{
GstClock *result = NULL;
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
GST_OBJECT_LOCK (clock);
if (clock->master)
result = gst_object_ref (clock->master);
@ -813,6 +919,160 @@ gst_clock_get_master (GstClock * clock)
return result;
}
/* http://mathworld.wolfram.com/LeastSquaresFitting.html */
static gboolean
do_linear_regression (GstClock * clock, gdouble * m,
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;
xbar = ybar = sxx = syy = sxy = 0;
x = clock->times;
y = clock->times + 2,
n = clock->filling ? clock->time_index : clock->window_size;
#ifdef DEBUGGING_ENABLED
DEBUG ("doing regression on:");
for (i = 0; i < n; i++)
DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[i], y[i]);
#endif
xmin = ymin = G_MAXUINT64;
for (i = j = 0; i < n; i++, j += 4) {
xmin = MIN (xmin, x[j]);
ymin = MIN (ymin, y[j]);
}
DEBUG ("min x: %" G_GUINT64_FORMAT, xmin);
DEBUG ("min y: %" G_GUINT64_FORMAT, ymin);
newx = clock->times + 1;
newy = clock->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
DEBUG ("reduced numbers:");
for (i = j = 0; i < n; i++, j += 4)
DEBUG (" %" 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;
DEBUG (" xbar = %" G_GUINT64_FORMAT, xbar);
DEBUG (" ybar = %" G_GUINT64_FORMAT, ybar);
/* 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;
}
*m = ((double) sxy) / sxx;
*xbase = xmin;
*b = (ybar + ymin) - (GstClockTime) (xbar * *m);
*r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
DEBUG (" m = %g", *m);
DEBUG (" b = %" G_GUINT64_FORMAT, *b);
DEBUG (" xbase = %" G_GUINT64_FORMAT, *xbase);
DEBUG (" r2 = %g", *r_squared);
return TRUE;
}
/**
* gst_clock_add_observation
* @clock: a #GstClock
* @slave: an time on the slave
* @master: an time on the master
* @r_squared: a pointer to hold the result
*
* The time @master of the master clock and the time @slave of the slave
* clock are added to the list of observations. If enough observations
* are available, a linear regression algorithm is run on the
* observations and @clock is recalibrated.
*
* This function should be called with @clock OBJECT_LOCK.
*
* Returns: TRUE if enough observations were added to run the
* regression algorithm.
*
* MT safe.
*/
gboolean
gst_clock_add_observation (GstClock * clock, GstClockTime slave,
GstClockTime master, gdouble * r_squared)
{
GstClockTime b, xbase;
gdouble m;
g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
g_return_val_if_fail (r_squared != NULL, FALSE);
clock->times[(4 * clock->time_index)] = slave;
clock->times[(4 * clock->time_index) + 2] = master;
clock->time_index++;
if (clock->time_index == clock->window_size) {
clock->filling = FALSE;
clock->time_index = 0;
}
if (clock->filling && clock->time_index < clock->window_threshold)
goto filling;
do_linear_regression (clock, &m, &b, &xbase, r_squared);
GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock,
"adjusting clock to m=%g, b=%" G_GINT64_FORMAT " (rsquared=%g)", m, b,
*r_squared);
clock->internal_calibration = xbase;
clock->external_calibration = b;
clock->rate = m;
return TRUE;
filling:
{
return FALSE;
}
}
static void
gst_clock_update_stats (GstClock * clock)
{
@ -827,10 +1087,32 @@ gst_clock_set_property (GObject * object, guint prop_id,
clock = GST_CLOCK (object);
switch (prop_id) {
case ARG_STATS:
case PROP_STATS:
GST_OBJECT_LOCK (clock);
clock->stats = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (clock);
g_object_notify (object, "stats");
break;
case PROP_WINDOW_SIZE:
GST_OBJECT_LOCK (clock);
clock->window_size = g_value_get_int (value);
clock->window_threshold =
MIN (clock->window_threshold, clock->window_size);
clock->times =
g_renew (GstClockTime, clock->times, 4 * clock->window_size);
GST_OBJECT_UNLOCK (clock);
break;
case PROP_WINDOW_THRESHOLD:
GST_OBJECT_LOCK (clock);
clock->window_threshold =
MIN (g_value_get_int (value), clock->window_size);
GST_OBJECT_UNLOCK (clock);
break;
case PROP_TIMEOUT:
GST_OBJECT_LOCK (clock);
clock->timeout = g_value_get_uint64 (value);
GST_OBJECT_UNLOCK (clock);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -846,8 +1128,25 @@ gst_clock_get_property (GObject * object, guint prop_id,
clock = GST_CLOCK (object);
switch (prop_id) {
case ARG_STATS:
case PROP_STATS:
GST_OBJECT_LOCK (clock);
g_value_set_boolean (value, clock->stats);
GST_OBJECT_UNLOCK (clock);
break;
case PROP_WINDOW_SIZE:
GST_OBJECT_LOCK (clock);
g_value_set_int (value, clock->window_size);
GST_OBJECT_UNLOCK (clock);
break;
case PROP_WINDOW_THRESHOLD:
GST_OBJECT_LOCK (clock);
g_value_set_int (value, clock->window_threshold);
GST_OBJECT_UNLOCK (clock);
break;
case PROP_TIMEOUT:
GST_OBJECT_LOCK (clock);
g_value_set_uint64 (value, clock->timeout);
GST_OBJECT_UNLOCK (clock);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);

View file

@ -308,6 +308,7 @@ struct _GstClockEntry {
* @GST_CLOCK_FLAG_CAN_DO_PERIODIC_SYNC: clock can do sync periodic timeout requests
* @GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC: clock can do async periodic timeout callbacks
* @GST_CLOCK_FLAG_CAN_SET_RESOLUTION: clock's resolution can be changed
* @GST_CLOCK_FLAG_CAN_SET_MASTER: clock can be slaved to a master clock
* @GST_CLOCK_FLAG_LAST: subclasses can add additional flags starting from this flag
*
* The capabilities of this clock
@ -318,6 +319,7 @@ typedef enum {
GST_CLOCK_FLAG_CAN_DO_PERIODIC_SYNC = (GST_OBJECT_FLAG_LAST << 2),
GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC = (GST_OBJECT_FLAG_LAST << 3),
GST_CLOCK_FLAG_CAN_SET_RESOLUTION = (GST_OBJECT_FLAG_LAST << 4),
GST_CLOCK_FLAG_CAN_SET_MASTER = (GST_OBJECT_FLAG_LAST << 5),
/* padding */
GST_CLOCK_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 8),
} GstClockFlags;
@ -384,8 +386,17 @@ struct _GstClock {
GstClockTime resolution;
gboolean stats;
/* for master/slave clocks */
GstClock *master;
gboolean filling;
gint window_size;
gint window_threshold;
gint time_index;
GstClockTime timeout;
GstClockTime *times;
GstClockID clockid;
/*< private >*/
GstClockTime _gst_reserved[GST_PADDING];
};
@ -422,9 +433,13 @@ void gst_clock_set_calibration (GstClock *clock, GstClockTime internal,
void gst_clock_get_calibration (GstClock *clock, GstClockTime *internal,
GstClockTime *external, gdouble *rate);
/* master/slave clocks */
void gst_clock_set_master (GstClock *clock, GstClock *master);
gboolean gst_clock_set_master (GstClock *clock, GstClock *master);
GstClock* gst_clock_get_master (GstClock *clock);
gboolean gst_clock_add_observation (GstClock *clock, GstClockTime slave,
GstClockTime master, gdouble *r_squared);
/* getting and adjusting internal time */
GstClockTime gst_clock_get_internal_time (GstClock *clock);
GstClockTime gst_clock_adjust_unlocked (GstClock *clock, GstClockTime internal);
@ -451,6 +466,7 @@ GstClockReturn gst_clock_id_wait_async (GstClockID id,
gpointer user_data);
void gst_clock_id_unschedule (GstClockID id);
G_END_DECLS
#endif /* __GST_CLOCK_H__ */

View file

@ -387,25 +387,31 @@ gst_element_provide_clock (GstElement * element)
* refcount on the clock. Any previously set clock on the object
* is unreffed.
*
* Returns: TRUE if the element accepted the clock.
*
* MT safe.
*/
void
gboolean
gst_element_set_clock (GstElement * element, GstClock * clock)
{
GstElementClass *oclass;
gboolean res = TRUE;
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
oclass = GST_ELEMENT_GET_CLASS (element);
GST_DEBUG_OBJECT (element, "setting clock %p", clock);
if (oclass->set_clock)
oclass->set_clock (element, clock);
res = oclass->set_clock (element, clock);
GST_OBJECT_LOCK (element);
gst_object_replace ((GstObject **) & element->clock, (GstObject *) clock);
GST_OBJECT_UNLOCK (element);
if (res) {
GST_OBJECT_LOCK (element);
gst_object_replace ((GstObject **) & element->clock, (GstObject *) clock);
GST_OBJECT_UNLOCK (element);
}
return res;
}
/**

View file

@ -462,7 +462,7 @@ struct _GstElementClass
/* set/get clocks */
GstClock* (*provide_clock) (GstElement *element);
void (*set_clock) (GstElement *element, GstClock *clock);
gboolean (*set_clock) (GstElement *element, GstClock *clock);
/* index */
GstIndex* (*get_index) (GstElement *element);
@ -529,7 +529,7 @@ gboolean gst_element_requires_clock (GstElement *element);
gboolean gst_element_provides_clock (GstElement *element);
GstClock* gst_element_provide_clock (GstElement *element);
GstClock* gst_element_get_clock (GstElement *element);
void gst_element_set_clock (GstElement *element, GstClock *clock);
gboolean gst_element_set_clock (GstElement *element, GstClock *clock);
void gst_element_set_base_time (GstElement *element, GstClockTime time);
GstClockTime gst_element_get_base_time (GstElement *element);

View file

@ -336,7 +336,8 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
if (new_clock) {
/* now distribute the clock (which could be NULL I guess) */
gst_element_set_clock (element, clock);
if (!gst_element_set_clock (element, clock))
goto invalid_clock;
/* if we selected a new clock, let the app know about it */
gst_element_post_message (element,
@ -402,10 +403,11 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
/* store the current stream time */
if (pipeline->stream_time != GST_CLOCK_TIME_NONE)
pipeline->stream_time = now - element->base_time;
GST_DEBUG ("stream_time=%" GST_TIME_FORMAT ", now=%" GST_TIME_FORMAT
GST_DEBUG_OBJECT (element,
"stream_time=%" GST_TIME_FORMAT ", now=%" GST_TIME_FORMAT
", base time %" GST_TIME_FORMAT,
GST_TIME_ARGS (pipeline->stream_time),
GST_TIME_ARGS (now), GST_TIME_ARGS (element->base_time));
GST_TIME_ARGS (pipeline->stream_time), GST_TIME_ARGS (now),
GST_TIME_ARGS (element->base_time));
}
GST_OBJECT_UNLOCK (element);
break;
@ -420,6 +422,14 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
break;
}
return result;
invalid_clock:
{
/* FIXME, post error message */
GST_DEBUG_OBJECT (pipeline,
"Pipeline cannot operate with selected clock %p", clock);
return GST_STATE_CHANGE_FAILURE;
}
}
/**
@ -584,15 +594,18 @@ gst_pipeline_use_clock (GstPipeline * pipeline, GstClock * clock)
* Set the clock for the pipeline. The clock will be distributed
* to all the elements managed by the pipeline.
*
* Returns: TRUE if the clock could be set on the pipeline.
*
* MT safe.
*/
void
gboolean
gst_pipeline_set_clock (GstPipeline * pipeline, GstClock * clock)
{
g_return_if_fail (pipeline != NULL);
g_return_if_fail (GST_IS_PIPELINE (pipeline));
g_return_val_if_fail (pipeline != NULL, FALSE);
g_return_val_if_fail (GST_IS_PIPELINE (pipeline), FALSE);
GST_ELEMENT_CLASS (parent_class)->set_clock (GST_ELEMENT (pipeline), clock);
return GST_ELEMENT_CLASS (parent_class)->set_clock (GST_ELEMENT (pipeline),
clock);
}
/**

View file

@ -79,7 +79,7 @@ void gst_pipeline_set_new_stream_time (GstPipeline *pipeline, GstClockTime tim
GstClockTime gst_pipeline_get_last_stream_time (GstPipeline *pipeline);
void gst_pipeline_use_clock (GstPipeline *pipeline, GstClock *clock);
void gst_pipeline_set_clock (GstPipeline *pipeline, GstClock *clock);
gboolean gst_pipeline_set_clock (GstPipeline *pipeline, GstClock *clock);
GstClock* gst_pipeline_get_clock (GstPipeline *pipeline);
void gst_pipeline_auto_clock (GstPipeline *pipeline);

View file

@ -209,11 +209,11 @@ GstQuery * gst_query_new_application (GstQueryType type,
GstStructure * gst_query_get_structure (GstQuery *query);
/* moved from old gstqueryutils.h */
void gst_query_set_seeking (GstQuery *query, GstFormat format,
gboolean seekable,
gint64 segment_start,
gint64 segment_end);
void gst_query_set_formats (GstQuery *query, gint n_formats, ...);
void gst_query_set_seeking (GstQuery *query, GstFormat format,
gboolean seekable,
gint64 segment_start,
gint64 segment_end);
void gst_query_set_formats (GstQuery *query, gint n_formats, ...);
G_END_DECLS

View file

@ -63,7 +63,6 @@ G_STMT_START { \
#define DEFAULT_ADDRESS "127.0.0.1"
#define DEFAULT_PORT 5637
#define DEFAULT_WINDOW_SIZE 32
#define DEFAULT_TIMEOUT GST_SECOND
enum
@ -71,8 +70,6 @@ enum
PROP_0,
PROP_ADDRESS,
PROP_PORT,
PROP_WINDOW_SIZE,
PROP_TIMEOUT
};
#define _do_init(type) \
@ -114,33 +111,22 @@ gst_net_client_clock_class_init (GstNetClientClockClass * klass)
g_param_spec_int ("port", "port",
"The port on which the remote server is listening", 0, G_MAXUINT16,
DEFAULT_PORT, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_SIZE,
g_param_spec_int ("window-size", "Window size",
"The size of the window used to calculate rate and offset", 2, 1024,
DEFAULT_WINDOW_SIZE, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMEOUT,
g_param_spec_uint64 ("timeout", "Timeout",
"The amount of time, in nanoseconds, to wait for replies", 0,
G_MAXUINT64, DEFAULT_TIMEOUT, G_PARAM_READWRITE));
}
static void
gst_net_client_clock_init (GstNetClientClock * self,
GstNetClientClockClass * g_class)
{
GstClock *clock = GST_CLOCK_CAST (self);
self->port = DEFAULT_PORT;
self->address = g_strdup (DEFAULT_ADDRESS);
self->window_size = DEFAULT_WINDOW_SIZE;
self->timeout = DEFAULT_TIMEOUT;
clock->timeout = DEFAULT_TIMEOUT;
self->sock = -1;
self->thread = NULL;
self->filling = TRUE;
self->time_index = 0;
self->local_times = g_new0 (GstClockTime, self->window_size);
self->remote_times = g_new0 (GstClockTime, self->window_size);
self->servaddr = NULL;
READ_SOCKET (self) = -1;
@ -170,12 +156,6 @@ gst_net_client_clock_finalize (GObject * object)
g_free (self->servaddr);
self->servaddr = NULL;
g_free (self->local_times);
self->local_times = NULL;
g_free (self->remote_times);
self->remote_times = NULL;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -196,12 +176,6 @@ gst_net_client_clock_set_property (GObject * object, guint prop_id,
case PROP_PORT:
self->port = g_value_get_int (value);
break;
case PROP_WINDOW_SIZE:
self->window_size = g_value_get_int (value);
break;
case PROP_TIMEOUT:
self->timeout = g_value_get_uint64 (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -221,146 +195,39 @@ gst_net_client_clock_get_property (GObject * object, guint prop_id,
case PROP_PORT:
g_value_set_int (value, self->port);
break;
case PROP_WINDOW_SIZE:
g_value_set_int (value, self->window_size);
break;
case PROP_TIMEOUT:
g_value_set_uint64 (value, self->timeout);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* http://mathworld.wolfram.com/LeastSquaresFitting.html */
static gboolean
do_linear_regression (GstClockTime * x, GstClockTime * y, gint n, gdouble * m,
GstClockTime * b, GstClockTime * xbase, gdouble * r_squared)
{
GstClockTime *newx, *newy;
GstClockTime xmin, ymin, xbar, ybar;
GstClockTimeDiff sxx, sxy, syy;
gint i;
xbar = ybar = sxx = syy = sxy = 0;
#ifdef DEBUGGING_ENABLED
DEBUG ("doing regression on:");
for (i = 0; i < n; i++)
DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[i], y[i]);
#endif
xmin = ymin = G_MAXUINT64;
for (i = 0; i < n; i++) {
xmin = MIN (xmin, x[i]);
ymin = MIN (ymin, y[i]);
}
DEBUG ("min x: %" G_GUINT64_FORMAT, xmin);
DEBUG ("min y: %" G_GUINT64_FORMAT, ymin);
newx = g_new (GstClockTime, n);
newy = g_new (GstClockTime, n);
/* strip off unnecessary bits of precision */
for (i = 0; i < n; i++) {
newx[i] = x[i] - xmin;
newy[i] = y[i] - ymin;
}
#ifdef DEBUGGING_ENABLED
DEBUG ("reduced numbers:");
for (i = 0; i < n; i++)
DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[i], newy[i]);
#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 = 0; i < n; i++) {
xbar += newx[i];
ybar += newy[i];
}
xbar /= n;
ybar /= n;
DEBUG (" xbar = %" G_GUINT64_FORMAT, xbar);
DEBUG (" ybar = %" G_GUINT64_FORMAT, ybar);
/* 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. */
for (i = 0; i < n; i++) {
sxx += (newx[i] >> 4) * (newx[i] >> 4) - (xbar >> 4) * (xbar >> 4);
syy += (newy[i] >> 4) * (newy[i] >> 4) - (ybar >> 4) * (ybar >> 4);
sxy += (newx[i] >> 4) * (newy[i] >> 4) - (xbar >> 4) * (ybar >> 4);
}
*m = ((double) sxy) / sxx;
*xbase = xmin;
*b = (ybar + ymin) - (GstClockTime) (xbar * *m);
*r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
DEBUG (" m = %g", *m);
DEBUG (" b = %" G_GUINT64_FORMAT, *b);
DEBUG (" xbase = %" G_GUINT64_FORMAT, *xbase);
DEBUG (" r2 = %g", *r_squared);
g_free (newx);
g_free (newy);
return TRUE;
}
static void
gst_net_client_clock_observe_times (GstNetClientClock * self,
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
{
GstClockTime local_avg;
GstClockTime b, xbase;
gdouble m, r_squared;
gdouble r_squared;
GstClock *clock;
if (local_2 < local_1)
goto bogus_observation;
local_avg = (local_2 + local_1) / 2;
self->local_times[self->time_index] = local_avg;
self->remote_times[self->time_index] = remote;
clock = GST_CLOCK_CAST (self);
self->time_index++;
if (self->time_index == self->window_size) {
self->filling = FALSE;
self->time_index = 0;
}
GST_OBJECT_LOCK (self);
gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared);
if (!self->filling || self->time_index >= 4) {
/* need to allow tuning of the "4" parameter -- means that we need 4 samples
* before beginning to adjust the clock */
do_linear_regression (self->local_times, self->remote_times,
self->filling ? self->time_index : self->window_size, &m, &b,
&xbase, &r_squared);
GST_LOG_OBJECT (self, "adjusting clock to m=%g, b=%" G_GINT64_FORMAT
" (rsquared=%g)", m, b, r_squared);
gst_clock_set_calibration (GST_CLOCK (self), xbase, b, m);
}
if (self->filling) {
if (clock->filling) {
self->current_timeout = 0;
} else {
/* geto formula */
self->current_timeout =
(1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND;
self->current_timeout = MIN (self->current_timeout, self->timeout);
self->current_timeout = MIN (self->current_timeout, clock->timeout);
}
GST_OBJECT_UNLOCK (clock);
return;

View file

@ -71,15 +71,11 @@ struct _GstNetClientClock {
GstClockTime current_timeout;
gboolean filling;
gint time_index;
GstClockTime *local_times;
GstClockTime *remote_times;
struct sockaddr_id *servaddr;
GThread *thread;
/*< private >*/
gpointer _gst_reserved[GST_PADDING];
};

View file

@ -135,8 +135,6 @@ gst_base_sink_get_type (void)
return base_sink_type;
}
static void gst_base_sink_set_clock (GstElement * element, GstClock * clock);
static void gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_base_sink_get_property (GObject * object, guint prop_id,
@ -200,7 +198,6 @@ gst_base_sink_class_init (GstBaseSinkClass * klass)
g_param_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
G_PARAM_READWRITE));
gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_base_sink_set_clock);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_sink_send_event);
@ -330,16 +327,6 @@ gst_base_sink_finalize (GObject * object)
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_base_sink_set_clock (GstElement * element, GstClock * clock)
{
GstBaseSink *sink;
sink = GST_BASE_SINK (element);
sink->clock = clock;
}
static void
gst_base_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
@ -890,7 +877,7 @@ gst_base_sink_wait (GstBaseSink * basesink, GstClockTime time)
g_assert (basesink->clock_id == NULL);
g_assert (GST_CLOCK_TIME_IS_VALID (time));
id = gst_clock_new_single_shot_id (basesink->clock, time);
id = gst_clock_new_single_shot_id (GST_ELEMENT_CLOCK (basesink), time);
basesink->clock_id = id;
/* release the object lock while waiting */
@ -956,7 +943,7 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
}
/* now do clocking */
if (basesink->clock) {
if (GST_ELEMENT_CLOCK (basesink)) {
GstClockTime base_time;
GstClockTimeDiff stream_start, stream_end;
@ -1016,7 +1003,7 @@ gst_base_sink_handle_event (GstBaseSink * basesink, GstEvent * event)
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
GST_OBJECT_LOCK (basesink);
if (basesink->clock) {
if (GST_ELEMENT_CLOCK (basesink)) {
/* wait for last buffer to finish if we have a valid end time */
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
gst_base_sink_wait (basesink, basesink->end_time);

View file

@ -36,13 +36,6 @@ G_BEGIN_DECLS
#define GST_IS_BASE_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_SINK))
#define GST_BASE_SINK_CAST(obj) ((GstBaseSink *) (obj))
/**
* GST_BASE_SINK_CLOCK:
* @obj: base sink instance
*
* Gives the pointer to the #GstClock object of the element.
*/
#define GST_BASE_SINK_CLOCK(obj) (GST_BASE_SINK_CAST (obj)->clock)
/**
* GST_BASE_SINK_PAD:
* @obj: base sink instance
@ -83,7 +76,6 @@ struct _GstBaseSink {
GstSegment segment;
/*< private >*/ /* with LOCK */
GstClock *clock;
GstClockID clock_id;
GstClockTime end_time;
gboolean sync;

View file

@ -63,7 +63,6 @@ G_STMT_START { \
#define DEFAULT_ADDRESS "127.0.0.1"
#define DEFAULT_PORT 5637
#define DEFAULT_WINDOW_SIZE 32
#define DEFAULT_TIMEOUT GST_SECOND
enum
@ -71,8 +70,6 @@ enum
PROP_0,
PROP_ADDRESS,
PROP_PORT,
PROP_WINDOW_SIZE,
PROP_TIMEOUT
};
#define _do_init(type) \
@ -114,33 +111,22 @@ gst_net_client_clock_class_init (GstNetClientClockClass * klass)
g_param_spec_int ("port", "port",
"The port on which the remote server is listening", 0, G_MAXUINT16,
DEFAULT_PORT, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_SIZE,
g_param_spec_int ("window-size", "Window size",
"The size of the window used to calculate rate and offset", 2, 1024,
DEFAULT_WINDOW_SIZE, G_PARAM_READWRITE));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMEOUT,
g_param_spec_uint64 ("timeout", "Timeout",
"The amount of time, in nanoseconds, to wait for replies", 0,
G_MAXUINT64, DEFAULT_TIMEOUT, G_PARAM_READWRITE));
}
static void
gst_net_client_clock_init (GstNetClientClock * self,
GstNetClientClockClass * g_class)
{
GstClock *clock = GST_CLOCK_CAST (self);
self->port = DEFAULT_PORT;
self->address = g_strdup (DEFAULT_ADDRESS);
self->window_size = DEFAULT_WINDOW_SIZE;
self->timeout = DEFAULT_TIMEOUT;
clock->timeout = DEFAULT_TIMEOUT;
self->sock = -1;
self->thread = NULL;
self->filling = TRUE;
self->time_index = 0;
self->local_times = g_new0 (GstClockTime, self->window_size);
self->remote_times = g_new0 (GstClockTime, self->window_size);
self->servaddr = NULL;
READ_SOCKET (self) = -1;
@ -170,12 +156,6 @@ gst_net_client_clock_finalize (GObject * object)
g_free (self->servaddr);
self->servaddr = NULL;
g_free (self->local_times);
self->local_times = NULL;
g_free (self->remote_times);
self->remote_times = NULL;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@ -196,12 +176,6 @@ gst_net_client_clock_set_property (GObject * object, guint prop_id,
case PROP_PORT:
self->port = g_value_get_int (value);
break;
case PROP_WINDOW_SIZE:
self->window_size = g_value_get_int (value);
break;
case PROP_TIMEOUT:
self->timeout = g_value_get_uint64 (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -221,146 +195,39 @@ gst_net_client_clock_get_property (GObject * object, guint prop_id,
case PROP_PORT:
g_value_set_int (value, self->port);
break;
case PROP_WINDOW_SIZE:
g_value_set_int (value, self->window_size);
break;
case PROP_TIMEOUT:
g_value_set_uint64 (value, self->timeout);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* http://mathworld.wolfram.com/LeastSquaresFitting.html */
static gboolean
do_linear_regression (GstClockTime * x, GstClockTime * y, gint n, gdouble * m,
GstClockTime * b, GstClockTime * xbase, gdouble * r_squared)
{
GstClockTime *newx, *newy;
GstClockTime xmin, ymin, xbar, ybar;
GstClockTimeDiff sxx, sxy, syy;
gint i;
xbar = ybar = sxx = syy = sxy = 0;
#ifdef DEBUGGING_ENABLED
DEBUG ("doing regression on:");
for (i = 0; i < n; i++)
DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[i], y[i]);
#endif
xmin = ymin = G_MAXUINT64;
for (i = 0; i < n; i++) {
xmin = MIN (xmin, x[i]);
ymin = MIN (ymin, y[i]);
}
DEBUG ("min x: %" G_GUINT64_FORMAT, xmin);
DEBUG ("min y: %" G_GUINT64_FORMAT, ymin);
newx = g_new (GstClockTime, n);
newy = g_new (GstClockTime, n);
/* strip off unnecessary bits of precision */
for (i = 0; i < n; i++) {
newx[i] = x[i] - xmin;
newy[i] = y[i] - ymin;
}
#ifdef DEBUGGING_ENABLED
DEBUG ("reduced numbers:");
for (i = 0; i < n; i++)
DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[i], newy[i]);
#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 = 0; i < n; i++) {
xbar += newx[i];
ybar += newy[i];
}
xbar /= n;
ybar /= n;
DEBUG (" xbar = %" G_GUINT64_FORMAT, xbar);
DEBUG (" ybar = %" G_GUINT64_FORMAT, ybar);
/* 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. */
for (i = 0; i < n; i++) {
sxx += (newx[i] >> 4) * (newx[i] >> 4) - (xbar >> 4) * (xbar >> 4);
syy += (newy[i] >> 4) * (newy[i] >> 4) - (ybar >> 4) * (ybar >> 4);
sxy += (newx[i] >> 4) * (newy[i] >> 4) - (xbar >> 4) * (ybar >> 4);
}
*m = ((double) sxy) / sxx;
*xbase = xmin;
*b = (ybar + ymin) - (GstClockTime) (xbar * *m);
*r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
DEBUG (" m = %g", *m);
DEBUG (" b = %" G_GUINT64_FORMAT, *b);
DEBUG (" xbase = %" G_GUINT64_FORMAT, *xbase);
DEBUG (" r2 = %g", *r_squared);
g_free (newx);
g_free (newy);
return TRUE;
}
static void
gst_net_client_clock_observe_times (GstNetClientClock * self,
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
{
GstClockTime local_avg;
GstClockTime b, xbase;
gdouble m, r_squared;
gdouble r_squared;
GstClock *clock;
if (local_2 < local_1)
goto bogus_observation;
local_avg = (local_2 + local_1) / 2;
self->local_times[self->time_index] = local_avg;
self->remote_times[self->time_index] = remote;
clock = GST_CLOCK_CAST (self);
self->time_index++;
if (self->time_index == self->window_size) {
self->filling = FALSE;
self->time_index = 0;
}
GST_OBJECT_LOCK (self);
gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared);
if (!self->filling || self->time_index >= 4) {
/* need to allow tuning of the "4" parameter -- means that we need 4 samples
* before beginning to adjust the clock */
do_linear_regression (self->local_times, self->remote_times,
self->filling ? self->time_index : self->window_size, &m, &b,
&xbase, &r_squared);
GST_LOG_OBJECT (self, "adjusting clock to m=%g, b=%" G_GINT64_FORMAT
" (rsquared=%g)", m, b, r_squared);
gst_clock_set_calibration (GST_CLOCK (self), xbase, b, m);
}
if (self->filling) {
if (clock->filling) {
self->current_timeout = 0;
} else {
/* geto formula */
self->current_timeout =
(1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND;
self->current_timeout = MIN (self->current_timeout, self->timeout);
self->current_timeout = MIN (self->current_timeout, clock->timeout);
}
GST_OBJECT_UNLOCK (clock);
return;

View file

@ -71,15 +71,11 @@ struct _GstNetClientClock {
GstClockTime current_timeout;
gboolean filling;
gint time_index;
GstClockTime *local_times;
GstClockTime *remote_times;
struct sockaddr_id *servaddr;
GThread *thread;
/*< private >*/
gpointer _gst_reserved[GST_PADDING];
};