* Last reviewed on 2007-07-25 (0.10.14)
*/
-
+#include <math.h>
#include <stdio.h>
#include <string.h>
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_clock_range (const gchar * str, GstRTSPTimeRange * range)
+parse_utc_time (const gchar * str, GstRTSPTime * time, GstRTSPTime2 * time2,
+ const gchar * limit)
{
- return GST_RTSP_ENOTIMPL;
+
+ 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_utc_range (const gchar * str, GstRTSPTimeRange * range)
+{
+ 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;
}
/**
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);
}
}
-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;
}
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:
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;
+ }
}
/**
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;
+}