static GstClockTime gst_net_client_clock_get_internal_time (GstClock * clock) { GstNetClientClock *self = GST_NET_CLIENT_CLOCK (clock); if (!gst_clock_is_synced (self->priv->internal_clock)) { GstClockTime now = gst_clock_get_internal_time (self->priv->internal_clock); return gst_clock_adjust_with_calibration (self->priv->internal_clock, now, self->priv->internal_base_time, self->priv->base_time, 1, 1); } return gst_clock_get_time (self->priv->internal_clock); }
/** * gst_clock_adjust_unlocked: * @clock: a #GstClock to use * @internal: a clock time * * Converts the given @internal clock time to the external time, adjusting for the * rate and reference time set with gst_clock_set_calibration() and making sure * that the returned time is increasing. This function should be called with the * clock's OBJECT_LOCK held and is mainly used by clock subclasses. * * This function is the reverse of gst_clock_unadjust_unlocked(). * * Returns: the converted time of the clock. */ GstClockTime gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) { GstClockTime ret, cinternal, cexternal, cnum, cdenom; GstClockPrivate *priv = clock->priv; /* get calibration values for readability */ cinternal = priv->internal_calibration; cexternal = priv->external_calibration; cnum = priv->rate_numerator; cdenom = priv->rate_denominator; ret = gst_clock_adjust_with_calibration (clock, internal, cinternal, cexternal, cnum, cdenom); /* make sure the time is increasing */ priv->last_time = MAX (ret, priv->last_time); return priv->last_time; }
void gst_decklink_video_src_convert_to_external_clock (GstDecklinkVideoSrc * self, GstClockTime * timestamp, GstClockTime * duration) { GstClock *clock; g_assert (timestamp != NULL); if (*timestamp == GST_CLOCK_TIME_NONE) return; clock = gst_element_get_clock (GST_ELEMENT_CAST (self)); if (clock && clock != self->input->clock) { GstClockTime internal, external, rate_n, rate_d; GstClockTimeDiff external_start_time_diff; gst_clock_get_calibration (self->input->clock, &internal, &external, &rate_n, &rate_d); if (rate_n != rate_d && self->internal_base_time != GST_CLOCK_TIME_NONE) { GstClockTime internal_timestamp = *timestamp; // Convert to the running time corresponding to both clock times internal -= self->internal_base_time; external -= self->external_base_time; // Get the difference in the internal time, note // that the capture time is internal time. // Then scale this difference and offset it to // our external time. Now we have the running time // according to our external clock. // // For the duration we just scale *timestamp = gst_clock_adjust_with_calibration (NULL, internal_timestamp, internal, external, rate_n, rate_d); GST_LOG_OBJECT (self, "Converted %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (external: %" GST_TIME_FORMAT " internal %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (internal_timestamp), GST_TIME_ARGS (*timestamp), GST_TIME_ARGS (external), GST_TIME_ARGS (internal), ((gdouble) rate_n) / ((gdouble) rate_d)); if (duration) { GstClockTime internal_duration = *duration; *duration = gst_util_uint64_scale (internal_duration, rate_d, rate_n); GST_LOG_OBJECT (self, "Converted duration %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (external: %" GST_TIME_FORMAT " internal %" GST_TIME_FORMAT " rate: %lf)", GST_TIME_ARGS (internal_duration), GST_TIME_ARGS (*duration), GST_TIME_ARGS (external), GST_TIME_ARGS (internal), ((gdouble) rate_n) / ((gdouble) rate_d)); } } else { GST_LOG_OBJECT (self, "No clock conversion needed, relative rate is 1.0"); } // Add the diff between the external time when we // went to playing and the external time when the // pipeline went to playing. Otherwise we will // always start outputting from 0 instead of the // current running time. external_start_time_diff = gst_element_get_base_time (GST_ELEMENT_CAST (self)); external_start_time_diff = self->external_base_time - external_start_time_diff; *timestamp += external_start_time_diff; } else { GST_LOG_OBJECT (self, "No clock conversion needed, same clocks"); } }
static void gst_decklink_audio_src_got_packet (GstElement * element, IDeckLinkAudioInputPacket * packet, GstClockTime capture_time, GstClockTime packet_time, gboolean no_signal) { GstDecklinkAudioSrc *self = GST_DECKLINK_AUDIO_SRC_CAST (element); GstClockTime timestamp; GST_LOG_OBJECT (self, "Got audio packet at %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT ", no signal %d", GST_TIME_ARGS (capture_time), GST_TIME_ARGS (packet_time), no_signal); g_mutex_lock (&self->input->lock); if (self->input->videosrc) { GstDecklinkVideoSrc *videosrc = GST_DECKLINK_VIDEO_SRC_CAST (gst_object_ref (self->input->videosrc)); if (videosrc->drop_no_signal_frames && no_signal) { g_mutex_unlock (&self->input->lock); return; } if (videosrc->first_time == GST_CLOCK_TIME_NONE) videosrc->first_time = packet_time; if (videosrc->skip_first_time > 0 && packet_time - videosrc->first_time < videosrc->skip_first_time) { GST_DEBUG_OBJECT (self, "Skipping frame as requested: %" GST_TIME_FORMAT " < %" GST_TIME_FORMAT, GST_TIME_ARGS (packet_time), GST_TIME_ARGS (videosrc->skip_first_time + videosrc->first_time)); g_mutex_unlock (&self->input->lock); return; } if (videosrc->output_stream_time) timestamp = packet_time; else timestamp = gst_clock_adjust_with_calibration (NULL, packet_time, videosrc->current_time_mapping.xbase, videosrc->current_time_mapping.b, videosrc->current_time_mapping.num, videosrc->current_time_mapping.den); } else { timestamp = capture_time; } g_mutex_unlock (&self->input->lock); GST_LOG_OBJECT (self, "Converted times to %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); g_mutex_lock (&self->lock); if (!self->flushing) { CapturePacket *p; while (g_queue_get_length (&self->current_packets) >= self->buffer_size) { p = (CapturePacket *) g_queue_pop_head (&self->current_packets); GST_WARNING_OBJECT (self, "Dropping old packet at %" GST_TIME_FORMAT, GST_TIME_ARGS (p->timestamp)); capture_packet_free (p); } p = (CapturePacket *) g_malloc0 (sizeof (CapturePacket)); p->packet = packet; p->timestamp = timestamp; p->no_signal = no_signal; packet->AddRef (); g_queue_push_tail (&self->current_packets, p); g_cond_signal (&self->cond); } g_mutex_unlock (&self->lock); }
static void gst_net_client_internal_clock_observe_times (GstNetClientInternalClock * self, GstClockTime local_1, GstClockTime remote_1, GstClockTime remote_2, GstClockTime local_2) { GstClockTime current_timeout = 0; GstClockTime local_avg, remote_avg; gdouble r_squared; GstClock *clock; GstClockTime rtt, rtt_limit, min_update_interval; /* Use for discont tracking */ GstClockTime time_before = 0; GstClockTime min_guess = 0; GstClockTimeDiff time_discont = 0; gboolean synched, now_synched; GstClockTime internal_time, external_time, rate_num, rate_den; GstClockTime orig_internal_time, orig_external_time, orig_rate_num, orig_rate_den; GstClockTime max_discont; GstClockTime last_rtts[MEDIAN_PRE_FILTERING_WINDOW]; GstClockTime median; gint i; GST_OBJECT_LOCK (self); rtt_limit = self->roundtrip_limit; GST_LOG_OBJECT (self, "local1 %" G_GUINT64_FORMAT " remote1 %" G_GUINT64_FORMAT " remote2 %" G_GUINT64_FORMAT " local2 %" G_GUINT64_FORMAT, local_1, remote_1, remote_2, local_2); /* If the server told us a poll interval and it's bigger than the * one configured via the property, use the server's */ if (self->last_remote_poll_interval != GST_CLOCK_TIME_NONE && self->last_remote_poll_interval > self->minimum_update_interval) min_update_interval = self->last_remote_poll_interval; else min_update_interval = self->minimum_update_interval; GST_OBJECT_UNLOCK (self); if (local_2 < local_1) { GST_LOG_OBJECT (self, "Dropping observation: receive time %" GST_TIME_FORMAT " < send time %" GST_TIME_FORMAT, GST_TIME_ARGS (local_1), GST_TIME_ARGS (local_2)); goto bogus_observation; } if (remote_2 < remote_1) { GST_LOG_OBJECT (self, "Dropping observation: remote receive time %" GST_TIME_FORMAT " < send time %" GST_TIME_FORMAT, GST_TIME_ARGS (remote_1), GST_TIME_ARGS (remote_2)); goto bogus_observation; } /* The round trip time is (assuming symmetric path delays) * delta = (local_2 - local_1) - (remote_2 - remote_1) */ rtt = GST_CLOCK_DIFF (local_1, local_2) - GST_CLOCK_DIFF (remote_1, remote_2); if ((rtt_limit > 0) && (rtt > rtt_limit)) { GST_LOG_OBJECT (self, "Dropping observation: RTT %" GST_TIME_FORMAT " > limit %" GST_TIME_FORMAT, GST_TIME_ARGS (rtt), GST_TIME_ARGS (rtt_limit)); goto bogus_observation; } for (i = 1; i < MEDIAN_PRE_FILTERING_WINDOW; i++) self->last_rtts[i - 1] = self->last_rtts[i]; self->last_rtts[i - 1] = rtt; if (self->last_rtts_missing) { self->last_rtts_missing--; } else { memcpy (&last_rtts, &self->last_rtts, sizeof (last_rtts)); g_qsort_with_data (&last_rtts, MEDIAN_PRE_FILTERING_WINDOW, sizeof (GstClockTime), (GCompareDataFunc) compare_clock_time, NULL); median = last_rtts[MEDIAN_PRE_FILTERING_WINDOW / 2]; /* FIXME: We might want to use something else here, like only allowing * things in the interquartile range, or also filtering away delays that * are too small compared to the median. This here worked well enough * in tests so far. */ if (rtt > 2 * median) { GST_LOG_OBJECT (self, "Dropping observation, long RTT %" GST_TIME_FORMAT " > 2 * median %" GST_TIME_FORMAT, GST_TIME_ARGS (rtt), GST_TIME_ARGS (median)); goto bogus_observation; } } /* Track an average round trip time, for a bit of smoothing */ /* Always update before discarding a sample, so genuine changes in * the network get picked up, eventually */ if (self->rtt_avg == GST_CLOCK_TIME_NONE) self->rtt_avg = rtt; else if (rtt < self->rtt_avg) /* Shorter RTTs carry more weight than longer */ self->rtt_avg = (3 * self->rtt_avg + rtt) / 4; else self->rtt_avg = (15 * self->rtt_avg + rtt) / 16; if (rtt > 2 * self->rtt_avg) { GST_LOG_OBJECT (self, "Dropping observation, long RTT %" GST_TIME_FORMAT " > 2 * avg %" GST_TIME_FORMAT, GST_TIME_ARGS (rtt), GST_TIME_ARGS (self->rtt_avg)); goto bogus_observation; } /* The difference between the local and remote clock (again assuming * symmetric path delays): * * local_1 + delta / 2 - remote_1 = theta * or * local_2 - delta / 2 - remote_2 = theta * * which gives after some simple algebraic transformations: * * (remote_1 - local_1) + (remote_2 - local_2) * theta = ------------------------------------------- * 2 * * * Thus remote time at local_avg is equal to: * * local_avg + theta = * * local_1 + local_2 (remote_1 - local_1) + (remote_2 - local_2) * ----------------- + ------------------------------------------- * 2 2 * * = * * remote_1 + remote_2 * ------------------- = remote_avg * 2 * * We use this for our clock estimation, i.e. local_avg at remote clock * being the same as remote_avg. */ local_avg = (local_2 + local_1) / 2; remote_avg = (remote_2 + remote_1) / 2; GST_LOG_OBJECT (self, "remoteavg %" G_GUINT64_FORMAT " localavg %" G_GUINT64_FORMAT, remote_avg, local_avg); clock = GST_CLOCK_CAST (self); /* Store what the clock produced as 'now' before this update */ gst_clock_get_calibration (GST_CLOCK_CAST (self), &orig_internal_time, &orig_external_time, &orig_rate_num, &orig_rate_den); internal_time = orig_internal_time; external_time = orig_external_time; rate_num = orig_rate_num; rate_den = orig_rate_den; min_guess = gst_clock_adjust_with_calibration (GST_CLOCK_CAST (self), local_1, internal_time, external_time, rate_num, rate_den); time_before = gst_clock_adjust_with_calibration (GST_CLOCK_CAST (self), local_2, internal_time, external_time, rate_num, rate_den); /* Maximum discontinuity, when we're synched with the master. Could make this a property, * but this value seems to work fine */ max_discont = self->rtt_avg / 4; /* If the remote observation was within a max_discont window around our min/max estimates, we're synched */ synched = (GST_CLOCK_DIFF (remote_avg, min_guess) < (GstClockTimeDiff) (max_discont) && GST_CLOCK_DIFF (time_before, remote_avg) < (GstClockTimeDiff) (max_discont)); if (gst_clock_add_observation_unapplied (GST_CLOCK_CAST (self), local_avg, remote_avg, &r_squared, &internal_time, &external_time, &rate_num, &rate_den)) { /* Now compare the difference (discont) in the clock * after this observation */ time_discont = GST_CLOCK_DIFF (time_before, gst_clock_adjust_with_calibration (GST_CLOCK_CAST (self), local_2, internal_time, external_time, rate_num, rate_den)); /* If we were in sync with the remote clock, clamp the allowed * discontinuity to within quarter of one RTT. In sync means our send/receive estimates * of remote time correctly windowed the actual remote time observation */ if (synched && ABS (time_discont) > max_discont) { GstClockTimeDiff offset; GST_DEBUG_OBJECT (clock, "Too large a discont, clamping to 1/4 average RTT = %" GST_TIME_FORMAT, GST_TIME_ARGS (max_discont)); if (time_discont > 0) { /* Too large a forward step - add a -ve offset */ offset = max_discont - time_discont; if (-offset > external_time) external_time = 0; else external_time += offset; } else { /* Too large a backward step - add a +ve offset */ offset = -(max_discont + time_discont); external_time += offset; } time_discont += offset; } /* Check if the new clock params would have made our observation within range */ now_synched = (GST_CLOCK_DIFF (remote_avg, gst_clock_adjust_with_calibration (GST_CLOCK_CAST (self), local_1, internal_time, external_time, rate_num, rate_den)) < (GstClockTimeDiff) (max_discont)) && (GST_CLOCK_DIFF (gst_clock_adjust_with_calibration (GST_CLOCK_CAST (self), local_2, internal_time, external_time, rate_num, rate_den), remote_avg) < (GstClockTimeDiff) (max_discont)); /* Only update the clock if we had synch or just gained it */ if (synched || now_synched || self->skipped_updates > MAX_SKIPPED_UPDATES) { gst_clock_set_calibration (GST_CLOCK_CAST (self), internal_time, external_time, rate_num, rate_den); /* ghetto formula - shorter timeout for bad correlations */ current_timeout = (1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND; current_timeout = MIN (current_timeout, gst_clock_get_timeout (GST_CLOCK_CAST (self))); self->skipped_updates = 0; /* FIXME: When do we consider the clock absolutely not synced anymore? */ gst_clock_set_synced (GST_CLOCK (self), TRUE); } else { /* Restore original calibration vars for the report, we're not changing the clock */ internal_time = orig_internal_time; external_time = orig_external_time; rate_num = orig_rate_num; rate_den = orig_rate_den; time_discont = 0; self->skipped_updates++; } } /* Limit the polling to at most one per minimum_update_interval */ if (rtt < min_update_interval) current_timeout = MAX (min_update_interval - rtt, current_timeout); GST_OBJECT_LOCK (self); if (self->busses) { GstStructure *s; GstMessage *msg; GList *l; /* Output a stats message, whether we updated the clock or not */ s = gst_structure_new ("gst-netclock-statistics", "synchronised", G_TYPE_BOOLEAN, synched, "rtt", G_TYPE_UINT64, rtt, "rtt-average", G_TYPE_UINT64, self->rtt_avg, "local", G_TYPE_UINT64, local_avg, "remote", G_TYPE_UINT64, remote_avg, "discontinuity", G_TYPE_INT64, time_discont, "remote-min-estimate", G_TYPE_UINT64, min_guess, "remote-max-estimate", G_TYPE_UINT64, time_before, "remote-min-error", G_TYPE_INT64, GST_CLOCK_DIFF (remote_avg, min_guess), "remote-max-error", G_TYPE_INT64, GST_CLOCK_DIFF (remote_avg, time_before), "request-send", G_TYPE_UINT64, local_1, "request-receive", G_TYPE_UINT64, local_2, "r-squared", G_TYPE_DOUBLE, r_squared, "timeout", G_TYPE_UINT64, current_timeout, "internal-time", G_TYPE_UINT64, internal_time, "external-time", G_TYPE_UINT64, external_time, "rate-num", G_TYPE_UINT64, rate_num, "rate-den", G_TYPE_UINT64, rate_den, "rate", G_TYPE_DOUBLE, (gdouble) (rate_num) / rate_den, "local-clock-offset", G_TYPE_INT64, GST_CLOCK_DIFF (internal_time, external_time), NULL); msg = gst_message_new_element (GST_OBJECT (self), s); for (l = self->busses; l; l = l->next) gst_bus_post (l->data, gst_message_ref (msg)); gst_message_unref (msg); } GST_OBJECT_UNLOCK (self); GST_INFO ("next timeout: %" GST_TIME_FORMAT, GST_TIME_ARGS (current_timeout)); self->timeout_expiration = gst_util_get_timestamp () + current_timeout; return; bogus_observation: /* Schedule a new packet again soon */ self->timeout_expiration = gst_util_get_timestamp () + (GST_SECOND / 4); return; }