diff --git a/pinos/Makefile.am b/pinos/Makefile.am index 0eb9abb99..ee0eb4e9d 100644 --- a/pinos/Makefile.am +++ b/pinos/Makefile.am @@ -268,6 +268,7 @@ plugin_LTLIBRARIES = libgstpinos.la libgstpinos_la_SOURCES = \ gst/gstburstcache.c \ gst/gstpinos.c \ + gst/gstpinosclock.c \ gst/gstpinosformat.c \ gst/gstpinosdeviceprovider.c \ gst/gstpinossrc.c \ @@ -281,7 +282,7 @@ libgstpinos_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(GLIB_LIBS) $(LIBM) -lgst libgstpinos_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) noinst_HEADERS = gst/gstburstcache.h gst/gstpinossrc.h \ - gst/gstpinosformat.h \ + gst/gstpinosclock.h gst/gstpinosformat.h \ gst/gstpinossink.h gst/gstpinosdeviceprovider.h ################################### diff --git a/pinos/client/stream.c b/pinos/client/stream.c index cc1d67204..2d9e7f4a8 100644 --- a/pinos/client/stream.c +++ b/pinos/client/stream.c @@ -95,7 +95,8 @@ struct _PinosStreamPrivate GArray *buffer_ids; gboolean in_order; - gint64 last_timestamp; + gint64 last_ticks; + gint32 last_rate; gint64 last_monotonic; }; @@ -637,6 +638,23 @@ send_need_input (PinosStream *stream, uint32_t port_id) spa_control_clear (&control); } +static void +add_request_clock_update (PinosStream *stream, SpaControlBuilder *builder) +{ + SpaControlCmdNodeEvent cne; + SpaNodeEvent ne; + SpaNodeEventRequestClockUpdate rcu; + + cne.event = ≠ + ne.type = SPA_NODE_EVENT_TYPE_REQUEST_CLOCK_UPDATE; + ne.data = &rcu; + ne.size = sizeof (rcu); + rcu.update_mask = SPA_NODE_EVENT_REQUEST_CLOCK_UPDATE_TIME; + rcu.timestamp = 0; + rcu.offset = 0; + spa_control_builder_add_cmd (builder, SPA_CONTROL_CMD_NODE_EVENT, &cne); +} + static void send_reuse_buffer (PinosStream *stream, uint32_t port_id, uint32_t buffer_id) { @@ -794,6 +812,7 @@ handle_node_command (PinosStream *stream, g_debug ("stream %p: start", stream); control_builder_init (stream, &builder); + add_request_clock_update (stream, &builder); if (priv->direction == PINOS_DIRECTION_INPUT) add_need_input (stream, &builder, 0); add_state_change (stream, &builder, SPA_NODE_STATE_STREAMING); @@ -816,8 +835,8 @@ handle_node_command (PinosStream *stream, case SPA_NODE_COMMAND_CLOCK_UPDATE: { SpaNodeCommandClockUpdate *cu = command->data; - g_debug ("got clock update %"PRId64", %"PRId64, cu->timestamp, cu->monotonic_time); - priv->last_timestamp = cu->timestamp; + priv->last_ticks = cu->ticks; + priv->last_rate = cu->rate; priv->last_monotonic = cu->monotonic_time; break; } @@ -1050,20 +1069,9 @@ on_timeout (gpointer user_data) PinosStreamPrivate *priv = stream->priv; SpaControlBuilder builder; SpaControl control; - SpaControlCmdNodeEvent cne; - SpaNodeEvent ne; - SpaNodeEventRequestClockUpdate rcu; - - cne.event = ≠ - ne.type = SPA_NODE_EVENT_TYPE_REQUEST_CLOCK_UPDATE; - ne.data = &rcu; - ne.size = sizeof (rcu); - rcu.update_mask = SPA_NODE_EVENT_REQUEST_CLOCK_UPDATE_TIME; - rcu.timestamp = 0; - rcu.offset = 0; control_builder_init (stream, &builder); - spa_control_builder_add_cmd (&builder, SPA_CONTROL_CMD_NODE_EVENT, &cne); + add_request_clock_update (stream, &builder); spa_control_builder_end (&builder, &control); if (spa_control_write (&control, priv->fd) < 0) @@ -1540,6 +1548,26 @@ pinos_stream_disconnect (PinosStream *stream) return TRUE; } +gboolean +pinos_stream_get_time (PinosStream *stream, + PinosTime *time) +{ + PinosStreamPrivate *priv; + gint64 now, elapsed; + + g_return_val_if_fail (PINOS_IS_STREAM (stream), FALSE); + priv = stream->priv; + g_return_val_if_fail (time, FALSE); + + now = g_get_monotonic_time (); + elapsed = now - (priv->last_monotonic / 1000); + + time->ticks = priv->last_ticks + (elapsed * priv->last_rate) / G_USEC_PER_SEC; + time->rate = priv->last_rate; + + return TRUE; +} + /** * pinos_stream_get_empty_buffer: * @stream: a #PinosStream diff --git a/pinos/client/stream.h b/pinos/client/stream.h index 748aef906..3e2481b3a 100644 --- a/pinos/client/stream.h +++ b/pinos/client/stream.h @@ -64,6 +64,11 @@ typedef enum { PINOS_STREAM_MODE_RINGBUFFER = 1, } PinosStreamMode; +typedef struct { + gint64 ticks; + gint32 rate; +} PinosTime; + /** * PinosStream: * @@ -110,6 +115,9 @@ gboolean pinos_stream_start_allocation (PinosStream *stream, gboolean pinos_stream_start (PinosStream *stream); gboolean pinos_stream_stop (PinosStream *stream); +gboolean pinos_stream_get_time (PinosStream *stream, + PinosTime *time); + guint pinos_stream_get_empty_buffer (PinosStream *stream); gboolean pinos_stream_recycle_buffer (PinosStream *stream, guint id); diff --git a/pinos/gst/gstpinosclock.c b/pinos/gst/gstpinosclock.c new file mode 100644 index 000000000..0a03992e9 --- /dev/null +++ b/pinos/gst/gstpinosclock.c @@ -0,0 +1,88 @@ +/* GStreamer + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gstpinosclock.h" + +GST_DEBUG_CATEGORY_STATIC (gst_pinos_clock_debug_category); +#define GST_CAT_DEFAULT gst_pinos_clock_debug_category + +G_DEFINE_TYPE (GstPinosClock, gst_pinos_clock, GST_TYPE_SYSTEM_CLOCK); + +GstClock * +gst_pinos_clock_new (PinosStream *stream) +{ + GstPinosClock *clock; + + clock = g_object_new (GST_TYPE_PINOS_CLOCK, NULL); + clock->stream = stream; + + return GST_CLOCK_CAST (clock); +} + +static GstClockTime +gst_pinos_clock_get_internal_time (GstClock * clock) +{ + GstPinosClock *pclock = (GstPinosClock *) clock; + GstClockTime result; + PinosTime t; + + pinos_stream_get_time (pclock->stream, &t); + + result = gst_util_uint64_scale_int (t.ticks, GST_SECOND, t.rate); + GST_DEBUG ("%"PRId64", %d %"PRId64, t.ticks, t.rate, result); + + return result; +} + + +static void +gst_pinos_clock_finalize (GObject * object) +{ + GstPinosClock *clock = GST_PINOS_CLOCK (object); + + GST_DEBUG_OBJECT (clock, "finalize"); + + G_OBJECT_CLASS (gst_pinos_clock_parent_class)->finalize (object); +} + +static void +gst_pinos_clock_class_init (GstPinosClockClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass); + + gobject_class->finalize = gst_pinos_clock_finalize; + + gstclock_class->get_internal_time = gst_pinos_clock_get_internal_time; + + GST_DEBUG_CATEGORY_INIT (gst_pinos_clock_debug_category, "pinosclock", 0, + "debug category for pinosclock object"); +} + +static void +gst_pinos_clock_init (GstPinosClock * clock) +{ + GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER); +} diff --git a/pinos/gst/gstpinosclock.h b/pinos/gst/gstpinosclock.h new file mode 100644 index 000000000..89e0ce247 --- /dev/null +++ b/pinos/gst/gstpinosclock.h @@ -0,0 +1,62 @@ +/* GStreamer + * Copyright (C) <2016> Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PINOS_CLOCK_H__ +#define __GST_PINOS_CLOCK_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PINOS_CLOCK \ + (gst_pinos_clock_get_type()) +#define GST_PINOS_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PINOS_CLOCK,GstPinosClock)) +#define GST_PINOS_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PINOS_CLOCK,GstPinosClockClass)) +#define GST_IS_PINOS_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PINOS_CLOCK)) +#define GST_IS_PINOS_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PINOS_CLOCK)) +#define GST_PINOS_CLOCK_GET_CLASS(klass) \ + (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PINOS_CLOCK, GstPinosClockClass)) + +typedef struct _GstPinosClock GstPinosClock; +typedef struct _GstPinosClockClass GstPinosClockClass; + +struct _GstPinosClock { + GstSystemClock parent; + + PinosStream *stream; +}; + +struct _GstPinosClockClass { + GstSystemClockClass parent_class; +}; + +GType gst_pinos_clock_get_type (void); + +GstClock * gst_pinos_clock_new (PinosStream *stream); + + +G_END_DECLS + +#endif /* __GST_PINOS_CLOCK_H__ */ diff --git a/pinos/gst/gstpinossrc.c b/pinos/gst/gstpinossrc.c index e78e2d2ea..4247b86e8 100644 --- a/pinos/gst/gstpinossrc.c +++ b/pinos/gst/gstpinossrc.c @@ -48,6 +48,7 @@ #include #include +#include "gstpinosclock.h" static GQuark process_mem_data_quark; @@ -269,6 +270,7 @@ gst_pinos_src_init (GstPinosSrc * src) gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + gst_base_src_set_live (GST_BASE_SRC (src), TRUE); g_queue_init (&src->queue); @@ -523,31 +525,11 @@ parse_stream_properties (GstPinosSrc *pinossrc, PinosProperties *props) var = pinos_properties_get (props, "pinos.latency.max"); pinossrc->max_latency = var ? (GstClockTime) atoi (var) : GST_CLOCK_TIME_NONE; - var = pinos_properties_get (props, "pinos.clock.type"); - if (var != NULL) { - GST_DEBUG_OBJECT (pinossrc, "got clock type %s", var); - if (strcmp (var, "gst.net.time.provider") == 0) { - const gchar *address; - gint port; - GstClockTime base_time; - - address = pinos_properties_get (props, "pinos.clock.address"); - port = atoi (pinos_properties_get (props, "pinos.clock.port")); - base_time = atoll (pinos_properties_get (props, "pinos.clock.base-time")); - - GST_DEBUG_OBJECT (pinossrc, "making net clock for %s:%d %" G_GUINT64_FORMAT, address, port, base_time); - if (pinossrc->clock) - gst_object_unref (pinossrc->clock); - pinossrc->clock = gst_net_client_clock_new ("pinosclock", address, port, base_time); - - gst_element_post_message (GST_ELEMENT_CAST (pinossrc), - gst_message_new_clock_provide (GST_OBJECT_CAST (pinossrc), - pinossrc->clock, TRUE)); - } - } + pinossrc->is_live = TRUE; + pinossrc->min_latency = 100000000; + pinossrc->max_latency = GST_CLOCK_TIME_NONE; } - static gboolean gst_pinos_src_stream_start (GstPinosSrc *pinossrc) { @@ -817,6 +799,8 @@ gst_pinos_src_query (GstBaseSrc * src, GstQuery * query) switch (GST_QUERY_TYPE (query)) { case GST_QUERY_LATENCY: + pinossrc->min_latency = 10000000; + pinossrc->max_latency = GST_CLOCK_TIME_NONE; gst_query_set_latency (query, pinossrc->is_live, pinossrc->min_latency, pinossrc->max_latency); res = TRUE; break; @@ -1004,6 +988,7 @@ gst_pinos_src_open (GstPinosSrc * pinossrc) g_signal_connect (pinossrc->stream, "add-buffer", (GCallback) on_add_buffer, pinossrc); g_signal_connect (pinossrc->stream, "remove-buffer", (GCallback) on_remove_buffer, pinossrc); g_signal_connect (pinossrc->stream, "new-buffer", (GCallback) on_new_buffer, pinossrc); + pinossrc->clock = gst_pinos_clock_new (pinossrc->stream); pinos_main_loop_unlock (pinossrc->loop); return TRUE; @@ -1031,6 +1016,7 @@ gst_pinos_src_close (GstPinosSrc * pinossrc) g_main_context_unref (pinossrc->context); GST_OBJECT_LOCK (pinossrc); pinossrc->stream_state = PINOS_STREAM_STATE_UNCONNECTED; + g_clear_object (&pinossrc->clock); g_clear_object (&pinossrc->stream); GST_OBJECT_UNLOCK (pinossrc); clear_queue (pinossrc); diff --git a/pinos/server/node.c b/pinos/server/node.c index 5c99902b1..755728561 100644 --- a/pinos/server/node.c +++ b/pinos/server/node.c @@ -292,6 +292,29 @@ suspend_node (PinosNode *this) } +static void +send_clock_update (PinosNode *this) +{ + PinosNodePrivate *priv = this->priv; + + if (priv->clock) { + SpaNodeCommand cmd; + SpaNodeCommandClockUpdate cu; + SpaResult res; + + cmd.type = SPA_NODE_COMMAND_CLOCK_UPDATE; + cmd.data = &cu; + cmd.size = sizeof (cu); + cu.change_mask = SPA_NODE_COMMAND_CLOCK_UPDATE_TIME; + res = spa_clock_get_time (priv->clock, &cu.rate, &cu.ticks, &cu.monotonic_time); + cu.scale = (1 << 16) | 1; + cu.state = SPA_CLOCK_STATE_RUNNING; + + if ((res = spa_node_send_command (this->node, &cmd)) < 0) + g_debug ("got error %d", res); + } +} + static gboolean node_set_state (PinosNode *this, PinosNodeState state) @@ -482,26 +505,9 @@ on_node_event (SpaNode *node, SpaNodeEvent *event, void *user_data) break; } case SPA_NODE_EVENT_TYPE_REQUEST_CLOCK_UPDATE: - { - SpaResult res; - - if (priv->clock) { - SpaNodeCommand cmd; - SpaNodeCommandClockUpdate cu; - - cmd.type = SPA_NODE_COMMAND_CLOCK_UPDATE; - cmd.data = &cu; - cmd.size = sizeof (cu); - cu.change_mask = SPA_NODE_COMMAND_CLOCK_UPDATE_TIME; - res = spa_clock_get_time (priv->clock, &cu.timestamp, &cu.monotonic_time); - cu.scale = (1 << 16) | 1; - cu.state = SPA_CLOCK_STATE_RUNNING; - - if ((res = spa_node_send_command (this->node, &cmd)) < 0) - g_debug ("got error %d", res); - } + send_clock_update (this); break; - } + default: g_debug ("node %p: got event %d", this, event->type); break; diff --git a/spa/include/spa/clock.h b/spa/include/spa/clock.h index 880449c0a..866b40816 100644 --- a/spa/include/spa/clock.h +++ b/spa/include/spa/clock.h @@ -107,7 +107,8 @@ struct _SpaClock { const SpaProps *props); SpaResult (*get_time) (SpaClock *clock, - int64_t *clock_time, + int32_t *rate, + int64_t *ticks, int64_t *monotonic_time); }; diff --git a/spa/include/spa/node-command.h b/spa/include/spa/node-command.h index 16396c836..aba4b7998 100644 --- a/spa/include/spa/node-command.h +++ b/spa/include/spa/node-command.h @@ -48,11 +48,12 @@ struct _SpaNodeCommand { /** * SpaNodeCommandClockUpdate: * @change_mask: marks which fields are updated - * @timestamp: the new timestamp, when @change_mask = 1<<0 - * @monotonic_time: the new monotonic time associated with @timestamp, when - * @change_mask = 1<<0 + * @rate: the number of @ticks per second + * @ticks: the new ticks, when @change_mask = 1<<0 + * @monotonic_time: the new monotonic time in nanoseconds associated with + * @ticks, when @change_mask = 1<<0 * @offset: the difference between the time when this update was generated - * and @monotonic_time + * and @monotonic_time in nanoseconds * @scale: update to the speed stored as Q16.16, @change_mask = 1<<1 * @state: the new clock state, when @change_mask = 1<<2 */ @@ -61,7 +62,8 @@ typedef struct { #define SPA_NODE_COMMAND_CLOCK_UPDATE_SCALE (1 << 1) #define SPA_NODE_COMMAND_CLOCK_UPDATE_STATE (1 << 2) uint32_t change_mask; - int64_t timestamp; + int32_t rate; + int64_t ticks; int64_t monotonic_time; int64_t offset; int32_t scale; diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index 290ecdaa8..95b0a4dee 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -115,7 +115,7 @@ typedef struct { SpaAllocParamBuffers param_buffers; SpaPortStatus status; - int64_t last_timestamp; + int64_t last_ticks; int64_t last_monotonic; } SpaV4l2State; @@ -759,7 +759,8 @@ spa_v4l2_source_clock_set_props (SpaClock *clock, static SpaResult spa_v4l2_source_clock_get_time (SpaClock *clock, - int64_t *clock_time, + int32_t *rate, + int64_t *ticks, int64_t *monotonic_time) { SpaV4l2Source *this; @@ -771,8 +772,10 @@ spa_v4l2_source_clock_get_time (SpaClock *clock, this = (SpaV4l2Source *) clock->handle; state = &this->state[0]; - if (clock_time) - *clock_time = state->last_timestamp; + if (rate) + *rate = 1000000; + if (ticks) + *ticks = state->last_ticks; if (monotonic_time) *monotonic_time = state->last_monotonic; diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index de303c6b9..d373bb569 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -788,7 +788,8 @@ spa_v4l2_set_format (SpaV4l2Source *this, V4l2Format *f, bool try_only) state->info.flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS | SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; state->info.maxbuffering = -1; - state->info.latency = -1; + state->info.latency = (streamparm.parm.capture.timeperframe.numerator * 1000000000LL) / + streamparm.parm.capture.timeperframe.denominator; state->info.n_params = 1; state->info.params = state->params; @@ -834,12 +835,13 @@ mmap_read (SpaV4l2Source *this) if (buf.flags & V4L2_BUF_FLAG_ERROR) b->header.flags |= SPA_BUFFER_FLAG_CORRUPTED; + state->last_ticks = (int64_t)buf.timestamp.tv_sec * 1000000 + (uint64_t)buf.timestamp.tv_usec; + b->header.seq = buf.sequence; - b->header.pts = (uint64_t)buf.timestamp.tv_sec * 1000000000lu + (uint64_t)buf.timestamp.tv_usec * 1000lu; - state->last_timestamp = b->header.pts; + b->header.pts = state->last_ticks * 1000; if (buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) - state->last_monotonic = state->last_timestamp; + state->last_monotonic = b->header.pts; else state->last_monotonic = SPA_TIME_INVALID;