summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Ashley <bugzilla@ashley-family.net>2015-08-14 09:44:24 +0100
committerThiago Santos <thiagoss@osg.samsung.com>2015-08-14 06:47:21 -0300
commit93edd99bf7fa481b4bebc9f05ab560111eeb397c (patch)
tree69fbb8167d5df06a4ec28ce7bb500aa35188dba2
parent1640ee2b339fb35cd8eb5779875242e347697265 (diff)
downloadgstreamer-plugins-bad-93edd99bf7fa481b4bebc9f05ab560111eeb397c.tar.gz
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
-rw-r--r--ext/dash/gstdashdemux.c136
-rw-r--r--gst-libs/gst/uridownloader/gstfragment.c3
-rw-r--r--gst-libs/gst/uridownloader/gstfragment.h3
-rw-r--r--gst-libs/gst/uridownloader/gsturidownloader.c56
4 files changed, 187 insertions, 11 deletions
diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c
index 1e7dab6e0..06df497ba 100644
--- a/ext/dash/gstdashdemux.c
+++ b/ext/dash/gstdashdemux.c
@@ -145,6 +145,7 @@
#endif
#include <string.h>
+#include <stdio.h>
#include <inttypes.h>
#include <gio/gio.h>
#include <gst/base/gsttypefindhelper.h>
@@ -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 3f9552bd0..6d415930b 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 bdf04f3cd..a8e0a545c 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 3d5a215d2..72e5e79b2 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)