From: Alex Ashley Date: Fri, 14 Aug 2015 08:44:24 +0000 (+0100) Subject: dashdemux: add support for HTTP HEAD method of time sync X-Git-Tag: 1.19.3~507^2~8135 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=93edd99bf7fa481b4bebc9f05ab560111eeb397c;p=platform%2Fupstream%2Fgstreamer.git dashdemux: add support for HTTP HEAD method of time sync The urn:mpeg:dash:utc:http-head:2014 method of time synchronisation uses an HTTP HEAD request to a specified URL and then parses the Date: HTTP response header. This commit adds support to dashdemux for this method of time synchronisation by making a HEAD request and then parsing the Date: response. This commit adds support to gstfragment to return the HTTP headers and to uridownloader to support HEAD requests. To avoid creating a new API, the RANGE get function is re-used (abused?) with start=-1 and end=-1 to indicate a HEAD request. https://bugzilla.gnome.org/show_bug.cgi?id=752413 --- diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c index 1e7dab6..06df497 100644 --- a/ext/dash/gstdashdemux.c +++ b/ext/dash/gstdashdemux.c @@ -145,6 +145,7 @@ #endif #include +#include #include #include #include @@ -192,7 +193,7 @@ enum /* Clock drift compensation for live streams */ #define SLOW_CLOCK_UPDATE_INTERVAL (1000000 * 30 * 60) /* 30 minutes */ #define FAST_CLOCK_UPDATE_INTERVAL (1000000 * 30) /* 30 seconds */ -#define SUPPORTED_CLOCK_FORMATS (GST_MPD_UTCTIMING_TYPE_NTP | GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE | GST_MPD_UTCTIMING_TYPE_HTTP_ISO | GST_MPD_UTCTIMING_TYPE_HTTP_NTP) +#define SUPPORTED_CLOCK_FORMATS (GST_MPD_UTCTIMING_TYPE_NTP | GST_MPD_UTCTIMING_TYPE_HTTP_HEAD | GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE | GST_MPD_UTCTIMING_TYPE_HTTP_ISO | GST_MPD_UTCTIMING_TYPE_HTTP_NTP) #define NTP_TO_UNIX_EPOCH G_GUINT64_CONSTANT(2208988800) /* difference (in seconds) between NTP epoch and Unix epoch */ struct _GstDashDemuxClockDrift @@ -1631,6 +1632,121 @@ gst_dash_demux_poll_ntp_server (GstDashDemuxClockDrift * clock_drift, return gst_date_time_new_from_g_date_time (dt2); } +struct Rfc822TimeZone +{ + const gchar *name; + gfloat tzoffset; +}; + +/* + Parse an RFC822 (section 5) date-time from the Date: field in the + HTTP response. + See https://tools.ietf.org/html/rfc822#section-5 +*/ +static GstDateTime * +gst_dash_demux_parse_http_head (GstDashDemuxClockDrift * clock_drift, + GstFragment * download) +{ + static const gchar *months[] = { NULL, "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec", NULL + }; + static const struct Rfc822TimeZone timezones[] = { + {"Z", 0}, + {"UT", 0}, + {"GMT", 0}, + {"BST", 1}, + {"EST", -5}, + {"EDT", -4}, + {"CST", -6}, + {"CDT", -5}, + {"MST", -7}, + {"MDT", -6}, + {"PST", -8}, + {"PDT", -7}, + {NULL, 0} + }; + GstDateTime *value = NULL; + const GstStructure *response_headers; + const gchar *http_date; + const GValue *val; + gint ret; + const gchar *pos; + gint year = -1, month = -1, day = -1, hour = -1, minute = -1, second = -1; + gchar zone[6]; + gchar monthstr[4]; + gfloat tzoffset = 0; + gboolean parsed_tz = FALSE; + + g_return_val_if_fail (download != NULL, NULL); + g_return_val_if_fail (download->headers != NULL, NULL); + + val = gst_structure_get_value (download->headers, "response-headers"); + if (!val) { + return NULL; + } + response_headers = gst_value_get_structure (val); + http_date = gst_structure_get_string (response_headers, "Date"); + if (!http_date) { + return NULL; + } + + /* skip optional text version of day of the week */ + pos = strchr (http_date, ','); + if (pos) + pos++; + else + pos = http_date; + ret = + sscanf (pos, "%02d %3s %04d %02d:%02d:%02d %5s", &day, monthstr, &year, + &hour, &minute, &second, zone); + if (ret == 7) { + gchar *z = zone; + for (int i = 1; months[i]; ++i) { + if (g_ascii_strncasecmp (months[i], monthstr, strlen (months[i])) == 0) { + month = i; + break; + } + } + while (*z == ' ') { + ++z; + } + for (int i = 0; timezones[i].name && !parsed_tz; ++i) { + if (g_ascii_strncasecmp (timezones[i].name, z, + strlen (timezones[i].name)) == 0) { + tzoffset = timezones[i].tzoffset; + parsed_tz = TRUE; + } + } + if (!parsed_tz) { + gint hh, mm; + gboolean neg = FALSE; + /* check if it is in the form +-HHMM */ + if (*z == '+' || *z == '-') { + if (*z == '+') + ++z; + else if (*z == '-') { + ++z; + neg = TRUE; + } + ret = sscanf (z, "%02d%02d", &hh, &mm); + if (ret == 2) { + tzoffset = hh; + tzoffset += mm / 60.0; + if (neg) + tzoffset = -tzoffset; + parsed_tz = TRUE; + } + } + } + } + if (month > 0 && parsed_tz) { + value = gst_date_time_new (tzoffset, + year, month, day, hour, minute, second); + } + return value; +} + /* The timing information is contained in the message body of the HTTP response and contains a time value formatted according to NTP timestamp @@ -1744,14 +1860,24 @@ gst_dash_demux_poll_clock_drift (GstDashDemux * demux) start = g_date_time_new_now_utc (); if (!value) { GstFragment *download; + gint64 range_start = 0, range_end = -1; GST_DEBUG_OBJECT (demux, "Fetching current time from %s", urls[clock_drift->selected_url]); + if (method == GST_MPD_UTCTIMING_TYPE_HTTP_HEAD) { + range_start = -1; + } download = - gst_uri_downloader_fetch_uri (GST_ADAPTIVE_DEMUX_CAST + gst_uri_downloader_fetch_uri_with_range (GST_ADAPTIVE_DEMUX_CAST (demux)->downloader, urls[clock_drift->selected_url], NULL, TRUE, TRUE, - TRUE, NULL); - buffer = gst_fragment_get_buffer (download); - g_object_unref (download); + TRUE, range_start, range_end, NULL); + if (download) { + if (method == GST_MPD_UTCTIMING_TYPE_HTTP_HEAD && download->headers) { + value = gst_dash_demux_parse_http_head (clock_drift, download); + } else { + buffer = gst_fragment_get_buffer (download); + } + g_object_unref (download); + } } g_mutex_unlock (&clock_drift->clock_lock); if (!value && !buffer) { diff --git a/gst-libs/gst/uridownloader/gstfragment.c b/gst-libs/gst/uridownloader/gstfragment.c index 3f9552b..6d41593 100644 --- a/gst-libs/gst/uridownloader/gstfragment.c +++ b/gst-libs/gst/uridownloader/gstfragment.c @@ -167,6 +167,7 @@ gst_fragment_init (GstFragment * fragment) fragment->name = g_strdup (""); fragment->completed = FALSE; fragment->discontinuous = FALSE; + fragment->headers = NULL; } GstFragment * @@ -183,6 +184,8 @@ gst_fragment_finalize (GObject * gobject) g_free (fragment->uri); g_free (fragment->redirect_uri); g_free (fragment->name); + if (fragment->headers) + gst_structure_free (fragment->headers); g_mutex_clear (&fragment->priv->lock); G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject); diff --git a/gst-libs/gst/uridownloader/gstfragment.h b/gst-libs/gst/uridownloader/gstfragment.h index bdf04f3..a8e0a54 100644 --- a/gst-libs/gst/uridownloader/gstfragment.h +++ b/gst-libs/gst/uridownloader/gstfragment.h @@ -44,6 +44,8 @@ struct _GstFragment gchar * uri; /* URI of the fragment */ gchar * redirect_uri; /* Redirect target if any */ gboolean redirect_permanent; /* If the redirect is permanent */ + gint64 range_start; + gint64 range_end; gchar * name; /* Name of the fragment */ gboolean completed; /* Whether the fragment is complete or not */ @@ -53,6 +55,7 @@ struct _GstFragment guint64 stop_time; /* Stop time of the fragment */ gboolean index; /* Index of the fragment */ gboolean discontinuous; /* Whether this fragment is discontinuous or not */ + GstStructure *headers; /* HTTP request/response headers */ GstFragmentPrivate *priv; }; diff --git a/gst-libs/gst/uridownloader/gsturidownloader.c b/gst-libs/gst/uridownloader/gsturidownloader.c index 3d5a215..72e5e79 100644 --- a/gst-libs/gst/uridownloader/gsturidownloader.c +++ b/gst-libs/gst/uridownloader/gsturidownloader.c @@ -178,6 +178,20 @@ gst_uri_downloader_sink_event (GstPad * pad, GstObject * parent, gst_event_unref (event); break; } + case GST_EVENT_CUSTOM_DOWNSTREAM_STICKY:{ + const GstStructure *str; + str = gst_event_get_structure (event); + if (gst_structure_has_name (str, "http-headers")) { + GST_OBJECT_LOCK (downloader); + if (downloader->priv->download != NULL) { + if (downloader->priv->download->headers) + gst_structure_free (downloader->priv->download->headers); + downloader->priv->download->headers = gst_structure_copy (str); + } + GST_OBJECT_UNLOCK (downloader); + } + } + /* falls through */ default: ret = gst_pad_event_default (pad, parent, event); break; @@ -428,6 +442,23 @@ gst_uri_downloader_set_uri (GstUriDownloader * downloader, const gchar * uri, return TRUE; } +static gboolean +gst_uri_downloader_set_method (GstUriDownloader * downloader, + const gchar * method) +{ + GObjectClass *gobject_class; + + if (!downloader->priv->urisrc) + return FALSE; + + gobject_class = G_OBJECT_GET_CLASS (downloader->priv->urisrc); + if (g_object_class_find_property (gobject_class, "method")) { + g_object_set (downloader->priv->urisrc, "method", method, NULL); + return TRUE; + } + return FALSE; +} + GstFragment * gst_uri_downloader_fetch_uri (GstUriDownloader * downloader, const gchar * uri, const gchar * referer, gboolean compress, @@ -477,6 +508,8 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * if (downloader->priv->download) g_object_unref (downloader->priv->download); downloader->priv->download = gst_fragment_new (); + downloader->priv->download->range_start = range_start; + downloader->priv->download->range_end = range_end; GST_OBJECT_UNLOCK (downloader); ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_READY); GST_OBJECT_LOCK (downloader); @@ -490,9 +523,16 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * goto quit; } - if (!gst_uri_downloader_set_range (downloader, range_start, range_end)) { - GST_WARNING_OBJECT (downloader, "Failed to set range"); - goto quit; + if (range_start < 0 && range_end < 0) { + if (!gst_uri_downloader_set_method (downloader, "HEAD")) { + GST_WARNING_OBJECT (downloader, "Failed to set HTTP method"); + goto quit; + } + } else { + if (!gst_uri_downloader_set_range (downloader, range_start, range_end)) { + GST_WARNING_OBJECT (downloader, "Failed to set range"); + goto quit; + } } GST_OBJECT_UNLOCK (downloader); @@ -531,9 +571,13 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader * download = downloader->priv->download; downloader->priv->download = NULL; if (!downloader->priv->got_buffer) { - g_object_unref (download); - download = NULL; - GST_ERROR_OBJECT (downloader, "Didn't retrieve a buffer before EOS"); + if (download->range_start < 0 && download->range_end < 0) { + /* HEAD request, so we don't expect a response */ + } else { + g_object_unref (download); + download = NULL; + GST_ERROR_OBJECT (downloader, "Didn't retrieve a buffer before EOS"); + } } if (download != NULL)