mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-20 14:18:34 +00:00
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:
parent
59ac39499a
commit
cf925ebb6f
17 changed files with 469 additions and 396 deletions
49
ChangeLog
49
ChangeLog
|
@ -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>
|
2005-11-22 Michael Smith <msmith@fluendo.com>
|
||||||
|
|
||||||
* gst/gstutils.c: (gst_util_clock_time_scale):
|
* gst/gstutils.c: (gst_util_clock_time_scale):
|
||||||
|
|
|
@ -135,8 +135,6 @@ gst_base_sink_get_type (void)
|
||||||
return base_sink_type;
|
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,
|
static void gst_base_sink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec);
|
const GValue * value, GParamSpec * pspec);
|
||||||
static void gst_base_sink_get_property (GObject * object, guint prop_id,
|
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_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
|
||||||
G_PARAM_READWRITE));
|
G_PARAM_READWRITE));
|
||||||
|
|
||||||
gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_base_sink_set_clock);
|
|
||||||
gstelement_class->change_state =
|
gstelement_class->change_state =
|
||||||
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
|
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
|
||||||
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_sink_send_event);
|
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);
|
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
|
static void
|
||||||
gst_base_sink_set_property (GObject * object, guint prop_id,
|
gst_base_sink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec)
|
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 (basesink->clock_id == NULL);
|
||||||
g_assert (GST_CLOCK_TIME_IS_VALID (time));
|
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;
|
basesink->clock_id = id;
|
||||||
/* release the object lock while waiting */
|
/* release the object lock while waiting */
|
||||||
|
@ -956,7 +943,7 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now do clocking */
|
/* now do clocking */
|
||||||
if (basesink->clock) {
|
if (GST_ELEMENT_CLOCK (basesink)) {
|
||||||
GstClockTime base_time;
|
GstClockTime base_time;
|
||||||
GstClockTimeDiff stream_start, stream_end;
|
GstClockTimeDiff stream_start, stream_end;
|
||||||
|
|
||||||
|
@ -1016,7 +1003,7 @@ gst_base_sink_handle_event (GstBaseSink * basesink, GstEvent * event)
|
||||||
switch (GST_EVENT_TYPE (event)) {
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
case GST_EVENT_EOS:
|
case GST_EVENT_EOS:
|
||||||
GST_OBJECT_LOCK (basesink);
|
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 */
|
/* wait for last buffer to finish if we have a valid end time */
|
||||||
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
|
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
|
||||||
gst_base_sink_wait (basesink, basesink->end_time);
|
gst_base_sink_wait (basesink, basesink->end_time);
|
||||||
|
|
|
@ -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_IS_BASE_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_SINK))
|
||||||
#define GST_BASE_SINK_CAST(obj) ((GstBaseSink *) (obj))
|
#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:
|
* GST_BASE_SINK_PAD:
|
||||||
* @obj: base sink instance
|
* @obj: base sink instance
|
||||||
|
@ -83,7 +76,6 @@ struct _GstBaseSink {
|
||||||
GstSegment segment;
|
GstSegment segment;
|
||||||
|
|
||||||
/*< private >*/ /* with LOCK */
|
/*< private >*/ /* with LOCK */
|
||||||
GstClock *clock;
|
|
||||||
GstClockID clock_id;
|
GstClockID clock_id;
|
||||||
GstClockTime end_time;
|
GstClockTime end_time;
|
||||||
gboolean sync;
|
gboolean sync;
|
||||||
|
|
12
gst/gstbin.c
12
gst/gstbin.c
|
@ -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);
|
static void gst_bin_set_index_func (GstElement * element, GstIndex * index);
|
||||||
#endif
|
#endif
|
||||||
static GstClock *gst_bin_provide_clock_func (GstElement * element);
|
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 void gst_bin_handle_message_func (GstBin * bin, GstMessage * message);
|
||||||
static gboolean gst_bin_send_event (GstElement * element, GstEvent * event);
|
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
|
* MT safe
|
||||||
*/
|
*/
|
||||||
static void
|
static gboolean
|
||||||
gst_bin_set_clock_func (GstElement * element, GstClock * clock)
|
gst_bin_set_clock_func (GstElement * element, GstClock * clock)
|
||||||
{
|
{
|
||||||
GList *children;
|
GList *children;
|
||||||
GstBin *bin;
|
GstBin *bin;
|
||||||
|
gboolean res = TRUE;
|
||||||
|
|
||||||
bin = GST_BIN (element);
|
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)) {
|
for (children = bin->children; children; children = g_list_next (children)) {
|
||||||
GstElement *child = GST_ELEMENT (children->data);
|
GstElement *child = GST_ELEMENT (children->data);
|
||||||
|
|
||||||
gst_element_set_clock (child, clock);
|
res &= gst_element_set_clock (child, clock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GST_OBJECT_UNLOCK (bin);
|
GST_OBJECT_UNLOCK (bin);
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get the clock for this bin by asking all of the children in this bin
|
/* 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 */
|
/* propagate the current base time and clock */
|
||||||
gst_element_set_base_time (element, GST_ELEMENT (bin)->base_time);
|
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));
|
gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin));
|
||||||
bin->state_dirty = TRUE;
|
bin->state_dirty = TRUE;
|
||||||
GST_OBJECT_UNLOCK (bin);
|
GST_OBJECT_UNLOCK (bin);
|
||||||
|
|
343
gst/gstclock.c
343
gst/gstclock.c
|
@ -98,15 +98,26 @@
|
||||||
static GstAllocTrace *_gst_clock_entry_trace;
|
static GstAllocTrace *_gst_clock_entry_trace;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEFAULT_EVENT_DIFF (GST_SECOND)
|
/* #define DEBUGGING_ENABLED */
|
||||||
#define DEFAULT_MAX_DIFF (2 * GST_SECOND)
|
|
||||||
|
#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
|
enum
|
||||||
{
|
{
|
||||||
ARG_0,
|
PROP_0,
|
||||||
ARG_STATS,
|
PROP_STATS,
|
||||||
ARG_MAX_DIFF,
|
PROP_WINDOW_SIZE,
|
||||||
ARG_EVENT_DIFF
|
PROP_WINDOW_THRESHOLD,
|
||||||
|
PROP_TIMEOUT
|
||||||
};
|
};
|
||||||
|
|
||||||
static void gst_clock_class_init (GstClockClass * klass);
|
static void gst_clock_class_init (GstClockClass * klass);
|
||||||
|
@ -134,8 +145,8 @@ gst_clock_entry_new (GstClock * clock, GstClockTime time,
|
||||||
#ifndef GST_DISABLE_TRACE
|
#ifndef GST_DISABLE_TRACE
|
||||||
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
|
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
|
||||||
#endif
|
#endif
|
||||||
GST_CAT_DEBUG (GST_CAT_CLOCK, "created entry %p, time %" GST_TIME_FORMAT,
|
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
|
||||||
entry, GST_TIME_ARGS (time));
|
"created entry %p, time %" GST_TIME_FORMAT, entry, GST_TIME_ARGS (time));
|
||||||
|
|
||||||
gst_atomic_int_set (&entry->refcount, 1);
|
gst_atomic_int_set (&entry->refcount, 1);
|
||||||
entry->clock = clock;
|
entry->clock = clock;
|
||||||
|
@ -327,20 +338,22 @@ gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
|
||||||
entry = (GstClockEntry *) id;
|
entry = (GstClockEntry *) id;
|
||||||
requested = GST_CLOCK_ENTRY_TIME (entry);
|
requested = GST_CLOCK_ENTRY_TIME (entry);
|
||||||
|
|
||||||
|
clock = GST_CLOCK_ENTRY_CLOCK (entry);
|
||||||
|
|
||||||
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
|
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
|
||||||
goto invalid_time;
|
goto invalid_time;
|
||||||
|
|
||||||
if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED))
|
if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED))
|
||||||
goto unscheduled;
|
goto unscheduled;
|
||||||
|
|
||||||
clock = GST_CLOCK_ENTRY_CLOCK (entry);
|
|
||||||
cclass = GST_CLOCK_GET_CLASS (clock);
|
cclass = GST_CLOCK_GET_CLASS (clock);
|
||||||
|
|
||||||
if (G_LIKELY (cclass->wait)) {
|
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);
|
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) {
|
if (jitter) {
|
||||||
GstClockTime now = gst_clock_get_time (clock);
|
GstClockTime now = gst_clock_get_time (clock);
|
||||||
|
@ -362,12 +375,14 @@ gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
|
||||||
/* ERRORS */
|
/* ERRORS */
|
||||||
invalid_time:
|
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;
|
return GST_CLOCK_BADTIME;
|
||||||
}
|
}
|
||||||
unscheduled:
|
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;
|
return GST_CLOCK_UNSCHEDULED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,12 +442,14 @@ gst_clock_id_wait_async (GstClockID id,
|
||||||
invalid_time:
|
invalid_time:
|
||||||
{
|
{
|
||||||
(func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
|
(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;
|
return GST_CLOCK_BADTIME;
|
||||||
}
|
}
|
||||||
unscheduled:
|
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;
|
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->set_property = GST_DEBUG_FUNCPTR (gst_clock_set_property);
|
||||||
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_clock_get_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",
|
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
|
static void
|
||||||
|
@ -534,6 +564,12 @@ gst_clock_init (GstClock * clock)
|
||||||
clock->internal_calibration = 0;
|
clock->internal_calibration = 0;
|
||||||
clock->external_calibration = 0;
|
clock->external_calibration = 0;
|
||||||
clock->rate = 1.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
|
static void
|
||||||
|
@ -541,8 +577,19 @@ gst_clock_finalize (GObject * object)
|
||||||
{
|
{
|
||||||
GstClock *clock = GST_CLOCK (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_cond_free (clock->entries_changed);
|
||||||
|
|
||||||
|
g_free (clock->times);
|
||||||
|
clock->times = NULL;
|
||||||
|
GST_OBJECT_UNLOCK (clock);
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +699,7 @@ gst_clock_get_internal_time (GstClock * clock)
|
||||||
} else {
|
} else {
|
||||||
ret = G_GINT64_CONSTANT (0);
|
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));
|
GST_TIME_ARGS (ret));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -685,7 +732,7 @@ gst_clock_get_time (GstClock * clock)
|
||||||
ret = gst_clock_adjust_unlocked (clock, ret);
|
ret = gst_clock_adjust_unlocked (clock, ret);
|
||||||
GST_OBJECT_UNLOCK (clock);
|
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));
|
GST_TIME_ARGS (ret));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -766,6 +813,30 @@ gst_clock_get_calibration (GstClock * clock, GstClockTime * internal,
|
||||||
GST_OBJECT_UNLOCK (clock);
|
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
|
* gst_clock_set_master
|
||||||
* @clock: a #GstClock
|
* @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
|
* A clock provider that slaves its clock to a master can get the current
|
||||||
* calibration values with gst_clock_get_calibration().
|
* calibration values with gst_clock_get_calibration().
|
||||||
*
|
*
|
||||||
|
* Returns: TRUE if the clock is capable of being slaved to a master clock.
|
||||||
|
*
|
||||||
* MT safe.
|
* MT safe.
|
||||||
*/
|
*/
|
||||||
void
|
gboolean
|
||||||
gst_clock_set_master (GstClock * clock, GstClock * master)
|
gst_clock_set_master (GstClock * clock, GstClock * master)
|
||||||
{
|
{
|
||||||
|
g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
|
||||||
|
|
||||||
GST_OBJECT_LOCK (clock);
|
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);
|
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);
|
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;
|
GstClock *result = NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
|
||||||
|
|
||||||
GST_OBJECT_LOCK (clock);
|
GST_OBJECT_LOCK (clock);
|
||||||
if (clock->master)
|
if (clock->master)
|
||||||
result = gst_object_ref (clock->master);
|
result = gst_object_ref (clock->master);
|
||||||
|
@ -813,6 +919,160 @@ gst_clock_get_master (GstClock * clock)
|
||||||
return result;
|
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
|
static void
|
||||||
gst_clock_update_stats (GstClock * clock)
|
gst_clock_update_stats (GstClock * clock)
|
||||||
{
|
{
|
||||||
|
@ -827,10 +1087,32 @@ gst_clock_set_property (GObject * object, guint prop_id,
|
||||||
clock = GST_CLOCK (object);
|
clock = GST_CLOCK (object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
case ARG_STATS:
|
case PROP_STATS:
|
||||||
|
GST_OBJECT_LOCK (clock);
|
||||||
clock->stats = g_value_get_boolean (value);
|
clock->stats = g_value_get_boolean (value);
|
||||||
|
GST_OBJECT_UNLOCK (clock);
|
||||||
g_object_notify (object, "stats");
|
g_object_notify (object, "stats");
|
||||||
break;
|
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:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -846,8 +1128,25 @@ gst_clock_get_property (GObject * object, guint prop_id,
|
||||||
clock = GST_CLOCK (object);
|
clock = GST_CLOCK (object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
case ARG_STATS:
|
case PROP_STATS:
|
||||||
|
GST_OBJECT_LOCK (clock);
|
||||||
g_value_set_boolean (value, clock->stats);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
|
|
@ -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_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_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_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
|
* @GST_CLOCK_FLAG_LAST: subclasses can add additional flags starting from this flag
|
||||||
*
|
*
|
||||||
* The capabilities of this clock
|
* 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_SYNC = (GST_OBJECT_FLAG_LAST << 2),
|
||||||
GST_CLOCK_FLAG_CAN_DO_PERIODIC_ASYNC = (GST_OBJECT_FLAG_LAST << 3),
|
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_RESOLUTION = (GST_OBJECT_FLAG_LAST << 4),
|
||||||
|
GST_CLOCK_FLAG_CAN_SET_MASTER = (GST_OBJECT_FLAG_LAST << 5),
|
||||||
/* padding */
|
/* padding */
|
||||||
GST_CLOCK_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 8),
|
GST_CLOCK_FLAG_LAST = (GST_OBJECT_FLAG_LAST << 8),
|
||||||
} GstClockFlags;
|
} GstClockFlags;
|
||||||
|
@ -384,8 +386,17 @@ struct _GstClock {
|
||||||
GstClockTime resolution;
|
GstClockTime resolution;
|
||||||
gboolean stats;
|
gboolean stats;
|
||||||
|
|
||||||
|
/* for master/slave clocks */
|
||||||
GstClock *master;
|
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];
|
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,
|
void gst_clock_get_calibration (GstClock *clock, GstClockTime *internal,
|
||||||
GstClockTime *external, gdouble *rate);
|
GstClockTime *external, gdouble *rate);
|
||||||
/* master/slave clocks */
|
/* 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);
|
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_get_internal_time (GstClock *clock);
|
||||||
GstClockTime gst_clock_adjust_unlocked (GstClock *clock, GstClockTime internal);
|
GstClockTime gst_clock_adjust_unlocked (GstClock *clock, GstClockTime internal);
|
||||||
|
|
||||||
|
@ -451,6 +466,7 @@ GstClockReturn gst_clock_id_wait_async (GstClockID id,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
void gst_clock_id_unschedule (GstClockID id);
|
void gst_clock_id_unschedule (GstClockID id);
|
||||||
|
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __GST_CLOCK_H__ */
|
#endif /* __GST_CLOCK_H__ */
|
||||||
|
|
|
@ -387,25 +387,31 @@ gst_element_provide_clock (GstElement * element)
|
||||||
* refcount on the clock. Any previously set clock on the object
|
* refcount on the clock. Any previously set clock on the object
|
||||||
* is unreffed.
|
* is unreffed.
|
||||||
*
|
*
|
||||||
|
* Returns: TRUE if the element accepted the clock.
|
||||||
|
*
|
||||||
* MT safe.
|
* MT safe.
|
||||||
*/
|
*/
|
||||||
void
|
gboolean
|
||||||
gst_element_set_clock (GstElement * element, GstClock * clock)
|
gst_element_set_clock (GstElement * element, GstClock * clock)
|
||||||
{
|
{
|
||||||
GstElementClass *oclass;
|
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);
|
oclass = GST_ELEMENT_GET_CLASS (element);
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (element, "setting clock %p", clock);
|
GST_DEBUG_OBJECT (element, "setting clock %p", clock);
|
||||||
|
|
||||||
if (oclass->set_clock)
|
if (oclass->set_clock)
|
||||||
oclass->set_clock (element, clock);
|
res = oclass->set_clock (element, clock);
|
||||||
|
|
||||||
GST_OBJECT_LOCK (element);
|
if (res) {
|
||||||
gst_object_replace ((GstObject **) & element->clock, (GstObject *) clock);
|
GST_OBJECT_LOCK (element);
|
||||||
GST_OBJECT_UNLOCK (element);
|
gst_object_replace ((GstObject **) & element->clock, (GstObject *) clock);
|
||||||
|
GST_OBJECT_UNLOCK (element);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -462,7 +462,7 @@ struct _GstElementClass
|
||||||
|
|
||||||
/* set/get clocks */
|
/* set/get clocks */
|
||||||
GstClock* (*provide_clock) (GstElement *element);
|
GstClock* (*provide_clock) (GstElement *element);
|
||||||
void (*set_clock) (GstElement *element, GstClock *clock);
|
gboolean (*set_clock) (GstElement *element, GstClock *clock);
|
||||||
|
|
||||||
/* index */
|
/* index */
|
||||||
GstIndex* (*get_index) (GstElement *element);
|
GstIndex* (*get_index) (GstElement *element);
|
||||||
|
@ -529,7 +529,7 @@ gboolean gst_element_requires_clock (GstElement *element);
|
||||||
gboolean gst_element_provides_clock (GstElement *element);
|
gboolean gst_element_provides_clock (GstElement *element);
|
||||||
GstClock* gst_element_provide_clock (GstElement *element);
|
GstClock* gst_element_provide_clock (GstElement *element);
|
||||||
GstClock* gst_element_get_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);
|
void gst_element_set_base_time (GstElement *element, GstClockTime time);
|
||||||
GstClockTime gst_element_get_base_time (GstElement *element);
|
GstClockTime gst_element_get_base_time (GstElement *element);
|
||||||
|
|
||||||
|
|
|
@ -336,7 +336,8 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
|
||||||
|
|
||||||
if (new_clock) {
|
if (new_clock) {
|
||||||
/* now distribute the clock (which could be NULL I guess) */
|
/* 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 */
|
/* if we selected a new clock, let the app know about it */
|
||||||
gst_element_post_message (element,
|
gst_element_post_message (element,
|
||||||
|
@ -402,10 +403,11 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
|
||||||
/* store the current stream time */
|
/* store the current stream time */
|
||||||
if (pipeline->stream_time != GST_CLOCK_TIME_NONE)
|
if (pipeline->stream_time != GST_CLOCK_TIME_NONE)
|
||||||
pipeline->stream_time = now - element->base_time;
|
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,
|
", base time %" GST_TIME_FORMAT,
|
||||||
GST_TIME_ARGS (pipeline->stream_time),
|
GST_TIME_ARGS (pipeline->stream_time), GST_TIME_ARGS (now),
|
||||||
GST_TIME_ARGS (now), GST_TIME_ARGS (element->base_time));
|
GST_TIME_ARGS (element->base_time));
|
||||||
}
|
}
|
||||||
GST_OBJECT_UNLOCK (element);
|
GST_OBJECT_UNLOCK (element);
|
||||||
break;
|
break;
|
||||||
|
@ -420,6 +422,14 @@ gst_pipeline_change_state (GstElement * element, GstStateChange transition)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
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
|
* Set the clock for the pipeline. The clock will be distributed
|
||||||
* to all the elements managed by the pipeline.
|
* to all the elements managed by the pipeline.
|
||||||
*
|
*
|
||||||
|
* Returns: TRUE if the clock could be set on the pipeline.
|
||||||
|
*
|
||||||
* MT safe.
|
* MT safe.
|
||||||
*/
|
*/
|
||||||
void
|
gboolean
|
||||||
gst_pipeline_set_clock (GstPipeline * pipeline, GstClock * clock)
|
gst_pipeline_set_clock (GstPipeline * pipeline, GstClock * clock)
|
||||||
{
|
{
|
||||||
g_return_if_fail (pipeline != NULL);
|
g_return_val_if_fail (pipeline != NULL, FALSE);
|
||||||
g_return_if_fail (GST_IS_PIPELINE (pipeline));
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -79,7 +79,7 @@ void gst_pipeline_set_new_stream_time (GstPipeline *pipeline, GstClockTime tim
|
||||||
GstClockTime gst_pipeline_get_last_stream_time (GstPipeline *pipeline);
|
GstClockTime gst_pipeline_get_last_stream_time (GstPipeline *pipeline);
|
||||||
|
|
||||||
void gst_pipeline_use_clock (GstPipeline *pipeline, GstClock *clock);
|
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);
|
GstClock* gst_pipeline_get_clock (GstPipeline *pipeline);
|
||||||
void gst_pipeline_auto_clock (GstPipeline *pipeline);
|
void gst_pipeline_auto_clock (GstPipeline *pipeline);
|
||||||
|
|
||||||
|
|
|
@ -209,11 +209,11 @@ GstQuery * gst_query_new_application (GstQueryType type,
|
||||||
GstStructure * gst_query_get_structure (GstQuery *query);
|
GstStructure * gst_query_get_structure (GstQuery *query);
|
||||||
|
|
||||||
/* moved from old gstqueryutils.h */
|
/* moved from old gstqueryutils.h */
|
||||||
void gst_query_set_seeking (GstQuery *query, GstFormat format,
|
void gst_query_set_seeking (GstQuery *query, GstFormat format,
|
||||||
gboolean seekable,
|
gboolean seekable,
|
||||||
gint64 segment_start,
|
gint64 segment_start,
|
||||||
gint64 segment_end);
|
gint64 segment_end);
|
||||||
void gst_query_set_formats (GstQuery *query, gint n_formats, ...);
|
void gst_query_set_formats (GstQuery *query, gint n_formats, ...);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ G_STMT_START { \
|
||||||
|
|
||||||
#define DEFAULT_ADDRESS "127.0.0.1"
|
#define DEFAULT_ADDRESS "127.0.0.1"
|
||||||
#define DEFAULT_PORT 5637
|
#define DEFAULT_PORT 5637
|
||||||
#define DEFAULT_WINDOW_SIZE 32
|
|
||||||
#define DEFAULT_TIMEOUT GST_SECOND
|
#define DEFAULT_TIMEOUT GST_SECOND
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
@ -71,8 +70,6 @@ enum
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_ADDRESS,
|
PROP_ADDRESS,
|
||||||
PROP_PORT,
|
PROP_PORT,
|
||||||
PROP_WINDOW_SIZE,
|
|
||||||
PROP_TIMEOUT
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define _do_init(type) \
|
#define _do_init(type) \
|
||||||
|
@ -114,33 +111,22 @@ gst_net_client_clock_class_init (GstNetClientClockClass * klass)
|
||||||
g_param_spec_int ("port", "port",
|
g_param_spec_int ("port", "port",
|
||||||
"The port on which the remote server is listening", 0, G_MAXUINT16,
|
"The port on which the remote server is listening", 0, G_MAXUINT16,
|
||||||
DEFAULT_PORT, G_PARAM_READWRITE));
|
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
|
static void
|
||||||
gst_net_client_clock_init (GstNetClientClock * self,
|
gst_net_client_clock_init (GstNetClientClock * self,
|
||||||
GstNetClientClockClass * g_class)
|
GstNetClientClockClass * g_class)
|
||||||
{
|
{
|
||||||
|
GstClock *clock = GST_CLOCK_CAST (self);
|
||||||
|
|
||||||
self->port = DEFAULT_PORT;
|
self->port = DEFAULT_PORT;
|
||||||
self->address = g_strdup (DEFAULT_ADDRESS);
|
self->address = g_strdup (DEFAULT_ADDRESS);
|
||||||
self->window_size = DEFAULT_WINDOW_SIZE;
|
|
||||||
self->timeout = DEFAULT_TIMEOUT;
|
clock->timeout = DEFAULT_TIMEOUT;
|
||||||
|
|
||||||
self->sock = -1;
|
self->sock = -1;
|
||||||
self->thread = NULL;
|
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;
|
self->servaddr = NULL;
|
||||||
|
|
||||||
READ_SOCKET (self) = -1;
|
READ_SOCKET (self) = -1;
|
||||||
|
@ -170,12 +156,6 @@ gst_net_client_clock_finalize (GObject * object)
|
||||||
g_free (self->servaddr);
|
g_free (self->servaddr);
|
||||||
self->servaddr = NULL;
|
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);
|
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:
|
case PROP_PORT:
|
||||||
self->port = g_value_get_int (value);
|
self->port = g_value_get_int (value);
|
||||||
break;
|
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:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -221,146 +195,39 @@ gst_net_client_clock_get_property (GObject * object, guint prop_id,
|
||||||
case PROP_PORT:
|
case PROP_PORT:
|
||||||
g_value_set_int (value, self->port);
|
g_value_set_int (value, self->port);
|
||||||
break;
|
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:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
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
|
static void
|
||||||
gst_net_client_clock_observe_times (GstNetClientClock * self,
|
gst_net_client_clock_observe_times (GstNetClientClock * self,
|
||||||
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
|
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
|
||||||
{
|
{
|
||||||
GstClockTime local_avg;
|
GstClockTime local_avg;
|
||||||
GstClockTime b, xbase;
|
gdouble r_squared;
|
||||||
gdouble m, r_squared;
|
GstClock *clock;
|
||||||
|
|
||||||
if (local_2 < local_1)
|
if (local_2 < local_1)
|
||||||
goto bogus_observation;
|
goto bogus_observation;
|
||||||
|
|
||||||
local_avg = (local_2 + local_1) / 2;
|
local_avg = (local_2 + local_1) / 2;
|
||||||
|
|
||||||
self->local_times[self->time_index] = local_avg;
|
clock = GST_CLOCK_CAST (self);
|
||||||
self->remote_times[self->time_index] = remote;
|
|
||||||
|
|
||||||
self->time_index++;
|
GST_OBJECT_LOCK (self);
|
||||||
if (self->time_index == self->window_size) {
|
gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared);
|
||||||
self->filling = FALSE;
|
|
||||||
self->time_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self->filling || self->time_index >= 4) {
|
if (clock->filling) {
|
||||||
/* 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) {
|
|
||||||
self->current_timeout = 0;
|
self->current_timeout = 0;
|
||||||
} else {
|
} else {
|
||||||
/* geto formula */
|
/* geto formula */
|
||||||
self->current_timeout =
|
self->current_timeout =
|
||||||
(1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND;
|
(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;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -71,15 +71,11 @@ struct _GstNetClientClock {
|
||||||
|
|
||||||
GstClockTime current_timeout;
|
GstClockTime current_timeout;
|
||||||
|
|
||||||
gboolean filling;
|
|
||||||
gint time_index;
|
|
||||||
GstClockTime *local_times;
|
|
||||||
GstClockTime *remote_times;
|
|
||||||
|
|
||||||
struct sockaddr_id *servaddr;
|
struct sockaddr_id *servaddr;
|
||||||
|
|
||||||
GThread *thread;
|
GThread *thread;
|
||||||
|
|
||||||
|
/*< private >*/
|
||||||
gpointer _gst_reserved[GST_PADDING];
|
gpointer _gst_reserved[GST_PADDING];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -135,8 +135,6 @@ gst_base_sink_get_type (void)
|
||||||
return base_sink_type;
|
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,
|
static void gst_base_sink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec);
|
const GValue * value, GParamSpec * pspec);
|
||||||
static void gst_base_sink_get_property (GObject * object, guint prop_id,
|
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_spec_boolean ("sync", "Sync", "Sync on the clock", DEFAULT_SYNC,
|
||||||
G_PARAM_READWRITE));
|
G_PARAM_READWRITE));
|
||||||
|
|
||||||
gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_base_sink_set_clock);
|
|
||||||
gstelement_class->change_state =
|
gstelement_class->change_state =
|
||||||
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
|
GST_DEBUG_FUNCPTR (gst_base_sink_change_state);
|
||||||
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_sink_send_event);
|
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);
|
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
|
static void
|
||||||
gst_base_sink_set_property (GObject * object, guint prop_id,
|
gst_base_sink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec)
|
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 (basesink->clock_id == NULL);
|
||||||
g_assert (GST_CLOCK_TIME_IS_VALID (time));
|
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;
|
basesink->clock_id = id;
|
||||||
/* release the object lock while waiting */
|
/* release the object lock while waiting */
|
||||||
|
@ -956,7 +943,7 @@ gst_base_sink_do_sync (GstBaseSink * basesink, GstBuffer * buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* now do clocking */
|
/* now do clocking */
|
||||||
if (basesink->clock) {
|
if (GST_ELEMENT_CLOCK (basesink)) {
|
||||||
GstClockTime base_time;
|
GstClockTime base_time;
|
||||||
GstClockTimeDiff stream_start, stream_end;
|
GstClockTimeDiff stream_start, stream_end;
|
||||||
|
|
||||||
|
@ -1016,7 +1003,7 @@ gst_base_sink_handle_event (GstBaseSink * basesink, GstEvent * event)
|
||||||
switch (GST_EVENT_TYPE (event)) {
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
case GST_EVENT_EOS:
|
case GST_EVENT_EOS:
|
||||||
GST_OBJECT_LOCK (basesink);
|
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 */
|
/* wait for last buffer to finish if we have a valid end time */
|
||||||
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
|
if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) {
|
||||||
gst_base_sink_wait (basesink, basesink->end_time);
|
gst_base_sink_wait (basesink, basesink->end_time);
|
||||||
|
|
|
@ -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_IS_BASE_SINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASE_SINK))
|
||||||
#define GST_BASE_SINK_CAST(obj) ((GstBaseSink *) (obj))
|
#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:
|
* GST_BASE_SINK_PAD:
|
||||||
* @obj: base sink instance
|
* @obj: base sink instance
|
||||||
|
@ -83,7 +76,6 @@ struct _GstBaseSink {
|
||||||
GstSegment segment;
|
GstSegment segment;
|
||||||
|
|
||||||
/*< private >*/ /* with LOCK */
|
/*< private >*/ /* with LOCK */
|
||||||
GstClock *clock;
|
|
||||||
GstClockID clock_id;
|
GstClockID clock_id;
|
||||||
GstClockTime end_time;
|
GstClockTime end_time;
|
||||||
gboolean sync;
|
gboolean sync;
|
||||||
|
|
|
@ -63,7 +63,6 @@ G_STMT_START { \
|
||||||
|
|
||||||
#define DEFAULT_ADDRESS "127.0.0.1"
|
#define DEFAULT_ADDRESS "127.0.0.1"
|
||||||
#define DEFAULT_PORT 5637
|
#define DEFAULT_PORT 5637
|
||||||
#define DEFAULT_WINDOW_SIZE 32
|
|
||||||
#define DEFAULT_TIMEOUT GST_SECOND
|
#define DEFAULT_TIMEOUT GST_SECOND
|
||||||
|
|
||||||
enum
|
enum
|
||||||
|
@ -71,8 +70,6 @@ enum
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_ADDRESS,
|
PROP_ADDRESS,
|
||||||
PROP_PORT,
|
PROP_PORT,
|
||||||
PROP_WINDOW_SIZE,
|
|
||||||
PROP_TIMEOUT
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define _do_init(type) \
|
#define _do_init(type) \
|
||||||
|
@ -114,33 +111,22 @@ gst_net_client_clock_class_init (GstNetClientClockClass * klass)
|
||||||
g_param_spec_int ("port", "port",
|
g_param_spec_int ("port", "port",
|
||||||
"The port on which the remote server is listening", 0, G_MAXUINT16,
|
"The port on which the remote server is listening", 0, G_MAXUINT16,
|
||||||
DEFAULT_PORT, G_PARAM_READWRITE));
|
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
|
static void
|
||||||
gst_net_client_clock_init (GstNetClientClock * self,
|
gst_net_client_clock_init (GstNetClientClock * self,
|
||||||
GstNetClientClockClass * g_class)
|
GstNetClientClockClass * g_class)
|
||||||
{
|
{
|
||||||
|
GstClock *clock = GST_CLOCK_CAST (self);
|
||||||
|
|
||||||
self->port = DEFAULT_PORT;
|
self->port = DEFAULT_PORT;
|
||||||
self->address = g_strdup (DEFAULT_ADDRESS);
|
self->address = g_strdup (DEFAULT_ADDRESS);
|
||||||
self->window_size = DEFAULT_WINDOW_SIZE;
|
|
||||||
self->timeout = DEFAULT_TIMEOUT;
|
clock->timeout = DEFAULT_TIMEOUT;
|
||||||
|
|
||||||
self->sock = -1;
|
self->sock = -1;
|
||||||
self->thread = NULL;
|
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;
|
self->servaddr = NULL;
|
||||||
|
|
||||||
READ_SOCKET (self) = -1;
|
READ_SOCKET (self) = -1;
|
||||||
|
@ -170,12 +156,6 @@ gst_net_client_clock_finalize (GObject * object)
|
||||||
g_free (self->servaddr);
|
g_free (self->servaddr);
|
||||||
self->servaddr = NULL;
|
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);
|
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:
|
case PROP_PORT:
|
||||||
self->port = g_value_get_int (value);
|
self->port = g_value_get_int (value);
|
||||||
break;
|
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:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -221,146 +195,39 @@ gst_net_client_clock_get_property (GObject * object, guint prop_id,
|
||||||
case PROP_PORT:
|
case PROP_PORT:
|
||||||
g_value_set_int (value, self->port);
|
g_value_set_int (value, self->port);
|
||||||
break;
|
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:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
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
|
static void
|
||||||
gst_net_client_clock_observe_times (GstNetClientClock * self,
|
gst_net_client_clock_observe_times (GstNetClientClock * self,
|
||||||
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
|
GstClockTime local_1, GstClockTime remote, GstClockTime local_2)
|
||||||
{
|
{
|
||||||
GstClockTime local_avg;
|
GstClockTime local_avg;
|
||||||
GstClockTime b, xbase;
|
gdouble r_squared;
|
||||||
gdouble m, r_squared;
|
GstClock *clock;
|
||||||
|
|
||||||
if (local_2 < local_1)
|
if (local_2 < local_1)
|
||||||
goto bogus_observation;
|
goto bogus_observation;
|
||||||
|
|
||||||
local_avg = (local_2 + local_1) / 2;
|
local_avg = (local_2 + local_1) / 2;
|
||||||
|
|
||||||
self->local_times[self->time_index] = local_avg;
|
clock = GST_CLOCK_CAST (self);
|
||||||
self->remote_times[self->time_index] = remote;
|
|
||||||
|
|
||||||
self->time_index++;
|
GST_OBJECT_LOCK (self);
|
||||||
if (self->time_index == self->window_size) {
|
gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared);
|
||||||
self->filling = FALSE;
|
|
||||||
self->time_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self->filling || self->time_index >= 4) {
|
if (clock->filling) {
|
||||||
/* 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) {
|
|
||||||
self->current_timeout = 0;
|
self->current_timeout = 0;
|
||||||
} else {
|
} else {
|
||||||
/* geto formula */
|
/* geto formula */
|
||||||
self->current_timeout =
|
self->current_timeout =
|
||||||
(1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND;
|
(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;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -71,15 +71,11 @@ struct _GstNetClientClock {
|
||||||
|
|
||||||
GstClockTime current_timeout;
|
GstClockTime current_timeout;
|
||||||
|
|
||||||
gboolean filling;
|
|
||||||
gint time_index;
|
|
||||||
GstClockTime *local_times;
|
|
||||||
GstClockTime *remote_times;
|
|
||||||
|
|
||||||
struct sockaddr_id *servaddr;
|
struct sockaddr_id *servaddr;
|
||||||
|
|
||||||
GThread *thread;
|
GThread *thread;
|
||||||
|
|
||||||
|
/*< private >*/
|
||||||
gpointer _gst_reserved[GST_PADDING];
|
gpointer _gst_reserved[GST_PADDING];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue