static void
convert_to_internal_clock (GstDecklinkVideoSink * self,
    GstClockTime * timestamp, GstClockTime * duration)
{
  GstClock *clock, *audio_clock;

  g_assert (timestamp != NULL);

  clock = gst_element_get_clock (GST_ELEMENT_CAST (self));
  audio_clock = gst_decklink_output_get_audio_clock (self->output);
  if (clock && clock != self->output->clock && clock != audio_clock) {
    GstClockTime internal, external, rate_n, rate_d;
    gst_clock_get_calibration (self->output->clock, &internal, &external,
        &rate_n, &rate_d);

    if (self->internal_base_time != GST_CLOCK_TIME_NONE) {
      GstClockTime external_timestamp = *timestamp;
      GstClockTime base_time;

      // Convert to the running time corresponding to both clock times
      if (internal < self->internal_base_time)
        internal = 0;
      else
        internal -= self->internal_base_time;

      if (external < self->external_base_time)
        external = 0;
      else
        external -= self->external_base_time;

      // Convert timestamp to the "running time" since we started scheduled
      // playback, that is the difference between the pipeline's base time
      // and our own base time.
      base_time = gst_element_get_base_time (GST_ELEMENT_CAST (self));
      if (base_time > self->external_base_time)
        base_time = 0;
      else
        base_time = self->external_base_time - base_time;

      if (external_timestamp < base_time)
        external_timestamp = 0;
      else
        external_timestamp = external_timestamp - base_time;

      // Get the difference in the external time, note
      // that the running time is external time.
      // Then scale this difference and offset it to
      // our internal time. Now we have the running time
      // according to our internal clock.
      //
      // For the duration we just scale
      *timestamp =
          gst_clock_unadjust_with_calibration (NULL, external_timestamp,
          internal, external, rate_n, rate_d);

      GST_LOG_OBJECT (self,
          "Converted %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %"
          GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)",
          GST_TIME_ARGS (external_timestamp), GST_TIME_ARGS (*timestamp),
          GST_TIME_ARGS (internal), GST_TIME_ARGS (external),
          ((gdouble) rate_n) / ((gdouble) rate_d));

      if (duration) {
        GstClockTime external_duration = *duration;

        *duration = gst_util_uint64_scale (external_duration, rate_d, rate_n);

        GST_LOG_OBJECT (self,
            "Converted duration %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
            " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT
            " rate: %lf)", GST_TIME_ARGS (external_duration),
            GST_TIME_ARGS (*duration), GST_TIME_ARGS (internal),
            GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d));
      }
    } else {
      GST_LOG_OBJECT (self, "No clock conversion needed, not started yet");
    }
  } else {
    GST_LOG_OBJECT (self, "No clock conversion needed, same clocks");
  }
}
static void
convert_to_internal_clock (GstDecklinkVideoSink * self,
    GstClockTime * timestamp, GstClockTime * duration)
{
  GstClock *clock, *audio_clock;

  g_assert (timestamp != NULL);

  clock = gst_element_get_clock (GST_ELEMENT_CAST (self));
  audio_clock = gst_decklink_output_get_audio_clock (self->output);
  if (clock && clock != self->output->clock && clock != audio_clock) {
    GstClockTime internal, external, rate_n, rate_d;
    gst_clock_get_calibration (self->output->clock, &internal, &external,
        &rate_n, &rate_d);

    if (rate_n != rate_d && self->internal_base_time != GST_CLOCK_TIME_NONE) {
      GstClockTime external_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 external time, note
      // that the running time is external time.
      // Then scale this difference and offset it to
      // our internal time. Now we have the running time
      // according to our internal clock.
      //
      // For the duration we just scale
      if (external > external_timestamp) {
        guint64 diff = external - external_timestamp;
        diff = gst_util_uint64_scale (diff, rate_d, rate_n);
        *timestamp = internal - diff;
      } else {
        guint64 diff = external_timestamp - external;
        diff = gst_util_uint64_scale (diff, rate_d, rate_n);
        *timestamp = internal + diff;
      }

      GST_LOG_OBJECT (self,
          "Converted %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT " (internal: %"
          GST_TIME_FORMAT " external %" GST_TIME_FORMAT " rate: %lf)",
          GST_TIME_ARGS (external_timestamp), GST_TIME_ARGS (*timestamp),
          GST_TIME_ARGS (internal), GST_TIME_ARGS (external),
          ((gdouble) rate_n) / ((gdouble) rate_d));

      if (duration) {
        GstClockTime external_duration = *duration;

        *duration = gst_util_uint64_scale (external_duration, rate_d, rate_n);

        GST_LOG_OBJECT (self,
            "Converted duration %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT
            " (internal: %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT
            " rate: %lf)", GST_TIME_ARGS (external_duration),
            GST_TIME_ARGS (*duration), GST_TIME_ARGS (internal),
            GST_TIME_ARGS (external), ((gdouble) rate_n) / ((gdouble) rate_d));
      }
    } else {
      GST_LOG_OBJECT (self, "No clock conversion needed, relative rate is 1.0");
    }
  } else {
    GST_LOG_OBJECT (self, "No clock conversion needed, same clocks");
  }
}
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_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;
}