rtsprange: use gst_util_gdouble_to_guint64 in get_seconds
[platform/upstream/gstreamer.git] / gst-libs / gst / rtsp / gstrtsprange.c
index 3bb3b30..7197455 100644 (file)
@@ -49,7 +49,7 @@
  * Last reviewed on 2007-07-25 (0.10.14)
  */
 
-
+#include <math.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -86,11 +86,16 @@ parse_npt_time (const gchar * str, GstRTSPTime * time)
   } else if (strstr (str, ":")) {
     gint hours, mins;
 
-    sscanf (str, "%2d:%2d:", &hours, &mins);
-    str = strchr (str, ':') + 1;
-    str = strchr (str, ':') + 1;
+    if (sscanf (str, "%2d:%2d:", &hours, &mins) != 2)
+      return GST_RTSP_EINVAL;
+
+    str = strchr (str, ':');
+    str = strchr (str + 1, ':');
+    if (str == NULL)
+      return GST_RTSP_EINVAL;
+
     time->type = GST_RTSP_TIME_SECONDS;
-    time->seconds = ((hours * 60) + mins) * 60 + gst_strtod (str);
+    time->seconds = ((hours * 60) + mins) * 60 + gst_strtod (str + 1);
   } else {
     time->type = GST_RTSP_TIME_SECONDS;
     time->seconds = gst_strtod (str);
@@ -127,16 +132,124 @@ done:
   return res;
 }
 
+/*   utc-time     =   utc-date "T" utc-time "Z"
+ *   utc-date     =   8DIGIT                    ; < YYYYMMDD >
+ *   utc-time     =   6DIGIT [ "." fraction ]   ; < HHMMSS.fraction >
+ *
+ *   Example for November 8, 1996 at 14h37 and 20 and a quarter seconds
+ *   UTC:
+ *
+ *   19961108T143720.25Z
+ */
+static GstRTSPResult
+parse_utc_time (const gchar * str, GstRTSPTime * time, GstRTSPTime2 * time2,
+    const gchar * limit)
+{
+
+  if (str[0] == '\0') {
+    time->type = GST_RTSP_TIME_END;
+    return GST_RTSP_OK;
+  } else {
+    gint year, month, day;
+    gint hours, mins;
+    gdouble secs;
+    gchar *T, *Z;
+
+    T = strchr (str, 'T');
+    if (T == NULL || T != str + 8)
+      return GST_RTSP_EINVAL;
+
+    Z = strchr (T + 1, 'Z');
+    if (Z == NULL)
+      return GST_RTSP_EINVAL;
+
+    time->type = GST_RTSP_TIME_UTC;
+
+    if (sscanf (str, "%4d%2d%2dT%2d%2d%lfZ", &year, &month, &day, &hours,
+            &mins, &secs) != 6)
+      return GST_RTSP_EINVAL;
+
+    time2->year = year;
+    time2->month = month;
+    time2->day = day;
+    time->seconds = ((hours * 60) + mins) * 60 + secs;
+  }
+  return GST_RTSP_OK;
+}
+
+/*   utc-range    =   "clock" "=" utc-time "-" [ utc-time ]
+ */
 static GstRTSPResult
-parse_clock_range (const gchar * str, GstRTSPTimeRange * range)
+parse_utc_range (const gchar * str, GstRTSPTimeRange * range)
 {
-  return GST_RTSP_ENOTIMPL;
+  GstRTSPResult res;
+  gchar *p;
+
+  range->unit = GST_RTSP_RANGE_CLOCK;
+
+  /* find '-' separator, can't have a single - */
+  p = strstr (str, "-");
+  if (p == NULL || p == str)
+    return GST_RTSP_EINVAL;
+
+  if ((res = parse_utc_time (str, &range->min, &range->min2, p)) != GST_RTSP_OK)
+    goto done;
+
+  res = parse_utc_time (p + 1, &range->max, &range->max2, NULL);
+
+done:
+  return res;
+}
+
+/* smpte-time   =   1*2DIGIT ":" 1*2DIGIT ":" 1*2DIGIT [ ":" 1*2DIGIT ]
+ *                     [ "." 1*2DIGIT ]
+ *  hours:minutes:seconds:frames.subframes
+*/
+static GstRTSPResult
+parse_smpte_time (const gchar * str, GstRTSPTime * time, GstRTSPTime2 * time2,
+    const gchar * limit)
+{
+  gint hours, mins, secs;
+
+  if (str[0] == '\0') {
+    time->type = GST_RTSP_TIME_END;
+    return GST_RTSP_OK;
+  } else {
+    if (sscanf (str, "%2d:%2d:%2d", &hours, &mins, &secs) != 3)
+      return GST_RTSP_EINVAL;
+
+    time->type = GST_RTSP_TIME_FRAMES;
+    time->seconds = ((hours * 60) + mins) * 60 + secs;
+    str = strchr (str, ':');
+    str = strchr (str + 1, ':');
+    str = strchr (str + 1, ':');
+    if (str && (limit == NULL || str < limit))
+      time2->frames = gst_strtod (str + 1);
+  }
+  return GST_RTSP_OK;
 }
 
+/* smpte-range  =   smpte-type "=" smpte-time "-" [ smpte-time ]
+ */
 static GstRTSPResult
 parse_smpte_range (const gchar * str, GstRTSPTimeRange * range)
 {
-  return GST_RTSP_ENOTIMPL;
+  GstRTSPResult res;
+  gchar *p;
+
+  /* find '-' separator, can't have a single - */
+  p = strstr (str, "-");
+  if (p == NULL || p == str)
+    return GST_RTSP_EINVAL;
+
+  if ((res =
+          parse_smpte_time (str, &range->min, &range->min2, p)) != GST_RTSP_OK)
+    goto done;
+
+  res = parse_smpte_time (p + 1, &range->max, &range->max2, NULL);
+
+done:
+  return res;
 }
 
 /**
@@ -165,7 +278,7 @@ gst_rtsp_range_parse (const gchar * rangestr, GstRTSPTimeRange ** range)
   if (g_str_has_prefix (p, "npt=")) {
     ret = parse_npt_range (p + 4, res);
   } else if (g_str_has_prefix (p, "clock=")) {
-    ret = parse_clock_range (p + 6, res);
+    ret = parse_utc_range (p + 6, res);
   } else if (g_str_has_prefix (p, "smpte=")) {
     res->unit = GST_RTSP_RANGE_SMPTE;
     ret = parse_smpte_range (p + 6, res);
@@ -192,23 +305,93 @@ invalid:
   }
 }
 
-static gboolean
-npt_time_string (const GstRTSPTime * time, GString * string)
+static void
+string_append_dtostr (GString * string, gdouble value, guint precision)
 {
   gchar dstrbuf[G_ASCII_DTOSTR_BUF_SIZE] = { 0, };
+  gchar *dot;
+  guint len;
+
+  precision++;
+
+  if (value != 0.0)
+    value += 4.9 * pow (10.0, precision * -1.0);
+
+  g_ascii_dtostr (dstrbuf, G_ASCII_DTOSTR_BUF_SIZE, value);
+
+  dot = strchr (dstrbuf, '.');
+
+  if (dot == NULL)
+    goto done;
+
+  for (; *dot != '.' && *dot != '0'; dot++);
+
+  if ((dot - dstrbuf) + precision < G_ASCII_DTOSTR_BUF_SIZE)
+    dot[precision] = 0;
+
+  len = strlen (dstrbuf);
+  while (dstrbuf[len - 1] == '0')
+    dstrbuf[--len] = 0;
+  if (dstrbuf[len - 1] == '.')
+    dstrbuf[--len] = 0;
+
+done:
+
+  g_string_append (string, dstrbuf);
+}
+
+static gboolean
+time_to_string (const GstRTSPTime * t1, const GstRTSPTime2 * t2,
+    GString * string)
+{
   gboolean res = TRUE;;
 
-  switch (time->type) {
+  switch (t1->type) {
     case GST_RTSP_TIME_SECONDS:
       /* need to format floating point value strings as in C locale */
-      g_ascii_dtostr (dstrbuf, G_ASCII_DTOSTR_BUF_SIZE, time->seconds);
-      g_string_append (string, dstrbuf);
+      string_append_dtostr (string, t1->seconds +
+          (t1->seconds ? 0.00000000005 : 0), 9);
       break;
     case GST_RTSP_TIME_NOW:
       g_string_append (string, "now");
       break;
     case GST_RTSP_TIME_END:
       break;
+    case GST_RTSP_TIME_FRAMES:
+    {
+      gint64 sec = t1->seconds;
+
+      /* need to format floating point value strings as in C locale */
+      g_string_append_printf (string, "%d:%02d:%02d", (gint) sec / (60 * 60),
+          (gint) (sec % (60 * 60)) / 60, (gint) sec % 60);
+
+      if (t2->frames > 0.0) {
+        g_string_append_printf (string, ":%s", t2->frames < 10 ? "0" : "");
+        string_append_dtostr (string, t2->frames + 0.005, 2);
+      }
+      break;
+    }
+    case GST_RTSP_TIME_UTC:
+    {
+      gint64 sec = t1->seconds;
+      gint hours, minutes;
+      gdouble seconds;
+
+      hours = sec / (60 * 60);
+      sec -= hours * 60 * 60;
+      minutes = sec / 60;
+      sec = ((hours * 60) + minutes) * 60;
+      seconds = t1->seconds - sec;
+      if (seconds)
+        seconds += 0.00000000005;
+
+      g_string_append_printf (string, "%04d%02d%02dT%02d%02d%s",
+          t2->year, t2->month, t2->day, hours, minutes,
+          seconds < 10 ? "0" : "");
+      string_append_dtostr (string, seconds, 9);
+      g_string_append (string, "Z");
+      break;
+    }
     default:
       res = FALSE;
       break;
@@ -217,16 +400,16 @@ npt_time_string (const GstRTSPTime * time, GString * string)
 }
 
 static gboolean
-npt_range_string (const GstRTSPTimeRange * range, GString * string)
+range_to_string (const GstRTSPTimeRange * range, GString * string)
 {
   gboolean res;
 
-  if (!(res = npt_time_string (&range->min, string)))
+  if (!(res = time_to_string (&range->min, &range->min2, string)))
     goto done;
 
   g_string_append (string, "-");
 
-  if (!(res = npt_time_string (&range->max, string)))
+  if (!(res = time_to_string (&range->max, &range->max2, string)))
     goto done;
 
 done:
@@ -244,35 +427,44 @@ done:
 gchar *
 gst_rtsp_range_to_string (const GstRTSPTimeRange * range)
 {
-  gchar *result = NULL;
   GString *string;
 
   g_return_val_if_fail (range != NULL, NULL);
 
-  string = g_string_new ("");
-
   switch (range->unit) {
     case GST_RTSP_RANGE_NPT:
-      g_string_append (string, "npt=");
-      if (!npt_range_string (range, string)) {
-        g_string_free (string, TRUE);
-        string = NULL;
-      }
+      string = g_string_new ("npt=");
       break;
     case GST_RTSP_RANGE_SMPTE:
     case GST_RTSP_RANGE_SMPTE_30_DROP:
+      string = g_string_new ("smpte=");
+      break;
     case GST_RTSP_RANGE_SMPTE_25:
+      string = g_string_new ("smpte-25=");
+      break;
     case GST_RTSP_RANGE_CLOCK:
-    default:
-      g_warning ("time range unit not yet implemented");
-      g_string_free (string, TRUE);
-      string = NULL;
+      string = g_string_new ("clock=");
       break;
+    default:
+      goto not_implemented;
   }
-  if (string)
-    result = g_string_free (string, FALSE);
 
-  return result;
+  if (!range_to_string (range, string))
+    goto format_failed;
+
+  return g_string_free (string, FALSE);
+
+  /* ERRORS */
+not_implemented:
+  {
+    g_warning ("time range unit not yet implemented");
+    return NULL;
+  }
+format_failed:
+  {
+    g_string_free (string, TRUE);
+    return NULL;
+  }
 }
 
 /**
@@ -284,5 +476,217 @@ gst_rtsp_range_to_string (const GstRTSPTimeRange * range)
 void
 gst_rtsp_range_free (GstRTSPTimeRange * range)
 {
+  g_return_if_fail (range != NULL);
+
   g_free (range);
 }
+
+static GstClockTime
+get_seconds (const GstRTSPTime * t)
+{
+  if (t->seconds < G_MAXINT) {
+    gint num, denom;
+    /* Don't do direct multiply with GST_SECOND to avoid rounding
+     * errors.
+     * This only works for "small" numbers, because num is limited to 32-bit
+     */
+    gst_util_double_to_fraction (t->seconds, &num, &denom);
+    return gst_util_uint64_scale_int (GST_SECOND, num, denom);
+  } else {
+    return gst_util_gdouble_to_guint64 (t->seconds * GST_SECOND);
+  }
+}
+
+static GstClockTime
+get_frames (const GstRTSPTime2 * t, GstRTSPRangeUnit unit)
+{
+  gint num, denom;
+
+  gst_util_double_to_fraction (t->frames, &num, &denom);
+
+  switch (unit) {
+    case GST_RTSP_RANGE_SMPTE_25:
+      denom *= 25;
+      break;
+    case GST_RTSP_RANGE_SMPTE:
+    case GST_RTSP_RANGE_SMPTE_30_DROP:
+    default:
+      num *= 1001;
+      denom *= 30003;
+      break;
+  }
+  return gst_util_uint64_scale_int (GST_SECOND, num, denom);
+}
+
+static GstClockTime
+get_time (GstRTSPRangeUnit unit, const GstRTSPTime * t1,
+    const GstRTSPTime2 * t2)
+{
+  GstClockTime res;
+
+  switch (t1->type) {
+    case GST_RTSP_TIME_SECONDS:
+    {
+      res = get_seconds (t1);
+      break;
+    }
+    case GST_RTSP_TIME_UTC:
+    {
+      GDateTime *dt, *bt;
+      GTimeSpan span;
+
+      /* make time base, we use 1900 */
+      bt = g_date_time_new_utc (1900, 1, 1, 0, 0, 0.0);
+      /* convert to GDateTime without the seconds */
+      dt = g_date_time_new_utc (t2->year, t2->month, t2->day, 0, 0, 0.0);
+      /* get amount of microseconds */
+      span = g_date_time_difference (dt, bt);
+      g_date_time_unref (bt);
+      g_date_time_unref (dt);
+      /* add seconds */
+      res = get_seconds (t1) + (span * 1000);
+      break;
+    }
+    case GST_RTSP_TIME_FRAMES:
+      res = get_seconds (t1);
+      res += get_frames (t2, unit);
+      break;
+    default:
+    case GST_RTSP_TIME_NOW:
+    case GST_RTSP_TIME_END:
+      res = GST_CLOCK_TIME_NONE;
+      break;
+  }
+  return res;
+}
+
+/**
+ * gst_rtsp_range_get_times:
+ * @range: a #GstRTSPTimeRange
+ * @min: result minimum #GstClockTime
+ * @max: result maximum #GstClockTime
+ *
+ * Retrieve the minimum and maximum values from @range converted to
+ * #GstClockTime in @min and @max.
+ *
+ * A value of %GST_CLOCK_TIME_NONE will be used to signal #GST_RTSP_TIME_NOW
+ * and #GST_RTSP_TIME_END for @min and @max respectively.
+ *
+ * UTC times will be converted to nanoseconds since 1900.
+ *
+ * Returns: %TRUE on success.
+ *
+ * Since: 1.1.1
+ */
+gboolean
+gst_rtsp_range_get_times (const GstRTSPTimeRange * range,
+    GstClockTime * min, GstClockTime * max)
+{
+  g_return_val_if_fail (range != NULL, FALSE);
+
+  if (min)
+    *min = get_time (range->unit, &range->min, &range->min2);
+  if (max)
+    *max = get_time (range->unit, &range->max, &range->max2);
+
+  return TRUE;
+}
+
+static void
+set_time (GstRTSPTime * time, GstRTSPTime2 * time2, GstRTSPRangeUnit unit,
+    GstClockTime clock_time)
+{
+  memset (time, 0, sizeof (GstRTSPTime));
+  memset (time2, 0, sizeof (GstRTSPTime2));
+
+  if (clock_time == GST_CLOCK_TIME_NONE) {
+    time->type = GST_RTSP_TIME_END;
+    return;
+  }
+
+  switch (unit) {
+    case GST_RTSP_RANGE_SMPTE:
+    case GST_RTSP_RANGE_SMPTE_30_DROP:
+    {
+      time->seconds = (guint64) (clock_time / GST_SECOND);
+      time2->frames = 30003 * (clock_time % GST_SECOND) /
+          (gdouble) (1001 * GST_SECOND);
+      time->type = GST_RTSP_TIME_FRAMES;
+      g_assert (time2->frames < 30);
+      break;
+    }
+    case GST_RTSP_RANGE_SMPTE_25:
+    {
+      time->seconds = (guint64) (clock_time / GST_SECOND);
+      time2->frames = (25 * (clock_time % GST_SECOND)) / (gdouble) GST_SECOND;
+      time->type = GST_RTSP_TIME_FRAMES;
+      g_assert (time2->frames < 25);
+      break;
+    }
+    case GST_RTSP_RANGE_NPT:
+    {
+      time->seconds = (gdouble) clock_time / (gdouble) GST_SECOND;
+      time->type = GST_RTSP_TIME_SECONDS;
+      break;
+    }
+    case GST_RTSP_RANGE_CLOCK:
+    {
+      GDateTime *bt, *datetime;
+      GstClockTime subsecond = clock_time % GST_SECOND;
+
+      bt = g_date_time_new_utc (1900, 1, 1, 0, 0, 0.0);
+      datetime = g_date_time_add_seconds (bt, clock_time / GST_SECOND);
+
+      time2->year = g_date_time_get_year (datetime);
+      time2->month = g_date_time_get_month (datetime);
+      time2->day = g_date_time_get_day_of_month (datetime);
+
+      time->seconds = g_date_time_get_hour (datetime) * 60 * 60;
+      time->seconds += g_date_time_get_minute (datetime) * 60;
+      time->seconds += g_date_time_get_seconds (datetime);
+      time->seconds += (gdouble) subsecond / (gdouble) GST_SECOND;
+      time->type = GST_RTSP_TIME_UTC;
+
+      g_date_time_unref (bt);
+      g_date_time_unref (datetime);
+      break;
+    }
+  }
+
+  if (time->seconds < 0.000000001)
+    time->seconds = 0;
+  if (time2->frames < 0.000000001)
+    time2->frames = 0;
+}
+
+/**
+ * gst_rtsp_range_convert_units:
+ * @range: a #GstRTSPTimeRange
+ * @unit: the unit to convert the range into
+ *
+ * Converts the range in-place between different types of units.
+ * Ranges containing the special value #GST_RTSP_TIME_NOW can not be
+ * converted as these are only valid for #GST_RTSP_RANGE_NPT.
+ *
+ * Returns: %TRUE if the range could be converted
+ */
+
+gboolean
+gst_rtsp_range_convert_units (GstRTSPTimeRange * range, GstRTSPRangeUnit unit)
+{
+  if (range->unit == unit)
+    return TRUE;
+
+  if (range->min.type == GST_RTSP_TIME_NOW ||
+      range->max.type == GST_RTSP_TIME_NOW)
+    return FALSE;
+
+  set_time (&range->min, &range->min2, unit,
+      get_time (range->unit, &range->min, &range->min2));
+  set_time (&range->max, &range->max2, unit,
+      get_time (range->unit, &range->max, &range->max2));
+
+  range->unit = unit;
+
+  return TRUE;
+}