mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-11 01:45:33 +00:00
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:
parent
1640ee2b33
commit
93edd99bf7
4 changed files with 187 additions and 11 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue