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
This commit is contained in:
Alex Ashley 2015-08-14 09:44:24 +01:00 committed by Thiago Santos
parent 1640ee2b33
commit 93edd99bf7
4 changed files with 187 additions and 11 deletions

View file

@ -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) {

View file

@ -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);

View file

@ -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;
};

View file

@ -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)