mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-26 17:18:15 +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
|
#endif
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <gst/base/gsttypefindhelper.h>
|
#include <gst/base/gsttypefindhelper.h>
|
||||||
|
@ -192,7 +193,7 @@ enum
|
||||||
/* Clock drift compensation for live streams */
|
/* Clock drift compensation for live streams */
|
||||||
#define SLOW_CLOCK_UPDATE_INTERVAL (1000000 * 30 * 60) /* 30 minutes */
|
#define SLOW_CLOCK_UPDATE_INTERVAL (1000000 * 30 * 60) /* 30 minutes */
|
||||||
#define FAST_CLOCK_UPDATE_INTERVAL (1000000 * 30) /* 30 seconds */
|
#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 */
|
#define NTP_TO_UNIX_EPOCH G_GUINT64_CONSTANT(2208988800) /* difference (in seconds) between NTP epoch and Unix epoch */
|
||||||
|
|
||||||
struct _GstDashDemuxClockDrift
|
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);
|
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
|
The timing information is contained in the message body of the HTTP
|
||||||
response and contains a time value formatted according to NTP timestamp
|
response and contains a time value formatted according to NTP timestamp
|
||||||
|
@ -1744,15 +1860,25 @@ gst_dash_demux_poll_clock_drift (GstDashDemux * demux)
|
||||||
start = g_date_time_new_now_utc ();
|
start = g_date_time_new_now_utc ();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
GstFragment *download;
|
GstFragment *download;
|
||||||
|
gint64 range_start = 0, range_end = -1;
|
||||||
GST_DEBUG_OBJECT (demux, "Fetching current time from %s",
|
GST_DEBUG_OBJECT (demux, "Fetching current time from %s",
|
||||||
urls[clock_drift->selected_url]);
|
urls[clock_drift->selected_url]);
|
||||||
|
if (method == GST_MPD_UTCTIMING_TYPE_HTTP_HEAD) {
|
||||||
|
range_start = -1;
|
||||||
|
}
|
||||||
download =
|
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,
|
(demux)->downloader, urls[clock_drift->selected_url], NULL, TRUE, TRUE,
|
||||||
TRUE, NULL);
|
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);
|
buffer = gst_fragment_get_buffer (download);
|
||||||
|
}
|
||||||
g_object_unref (download);
|
g_object_unref (download);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
g_mutex_unlock (&clock_drift->clock_lock);
|
g_mutex_unlock (&clock_drift->clock_lock);
|
||||||
if (!value && !buffer) {
|
if (!value && !buffer) {
|
||||||
GST_ERROR_OBJECT (demux, "Failed to fetch time from %s",
|
GST_ERROR_OBJECT (demux, "Failed to fetch time from %s",
|
||||||
|
|
|
@ -167,6 +167,7 @@ gst_fragment_init (GstFragment * fragment)
|
||||||
fragment->name = g_strdup ("");
|
fragment->name = g_strdup ("");
|
||||||
fragment->completed = FALSE;
|
fragment->completed = FALSE;
|
||||||
fragment->discontinuous = FALSE;
|
fragment->discontinuous = FALSE;
|
||||||
|
fragment->headers = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
GstFragment *
|
GstFragment *
|
||||||
|
@ -183,6 +184,8 @@ gst_fragment_finalize (GObject * gobject)
|
||||||
g_free (fragment->uri);
|
g_free (fragment->uri);
|
||||||
g_free (fragment->redirect_uri);
|
g_free (fragment->redirect_uri);
|
||||||
g_free (fragment->name);
|
g_free (fragment->name);
|
||||||
|
if (fragment->headers)
|
||||||
|
gst_structure_free (fragment->headers);
|
||||||
g_mutex_clear (&fragment->priv->lock);
|
g_mutex_clear (&fragment->priv->lock);
|
||||||
|
|
||||||
G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject);
|
G_OBJECT_CLASS (gst_fragment_parent_class)->finalize (gobject);
|
||||||
|
|
|
@ -44,6 +44,8 @@ struct _GstFragment
|
||||||
gchar * uri; /* URI of the fragment */
|
gchar * uri; /* URI of the fragment */
|
||||||
gchar * redirect_uri; /* Redirect target if any */
|
gchar * redirect_uri; /* Redirect target if any */
|
||||||
gboolean redirect_permanent; /* If the redirect is permanent */
|
gboolean redirect_permanent; /* If the redirect is permanent */
|
||||||
|
gint64 range_start;
|
||||||
|
gint64 range_end;
|
||||||
|
|
||||||
gchar * name; /* Name of the fragment */
|
gchar * name; /* Name of the fragment */
|
||||||
gboolean completed; /* Whether the fragment is complete or not */
|
gboolean completed; /* Whether the fragment is complete or not */
|
||||||
|
@ -53,6 +55,7 @@ struct _GstFragment
|
||||||
guint64 stop_time; /* Stop time of the fragment */
|
guint64 stop_time; /* Stop time of the fragment */
|
||||||
gboolean index; /* Index of the fragment */
|
gboolean index; /* Index of the fragment */
|
||||||
gboolean discontinuous; /* Whether this fragment is discontinuous or not */
|
gboolean discontinuous; /* Whether this fragment is discontinuous or not */
|
||||||
|
GstStructure *headers; /* HTTP request/response headers */
|
||||||
|
|
||||||
GstFragmentPrivate *priv;
|
GstFragmentPrivate *priv;
|
||||||
};
|
};
|
||||||
|
|
|
@ -178,6 +178,20 @@ gst_uri_downloader_sink_event (GstPad * pad, GstObject * parent,
|
||||||
gst_event_unref (event);
|
gst_event_unref (event);
|
||||||
break;
|
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:
|
default:
|
||||||
ret = gst_pad_event_default (pad, parent, event);
|
ret = gst_pad_event_default (pad, parent, event);
|
||||||
break;
|
break;
|
||||||
|
@ -428,6 +442,23 @@ gst_uri_downloader_set_uri (GstUriDownloader * downloader, const gchar * uri,
|
||||||
return TRUE;
|
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 *
|
GstFragment *
|
||||||
gst_uri_downloader_fetch_uri (GstUriDownloader * downloader,
|
gst_uri_downloader_fetch_uri (GstUriDownloader * downloader,
|
||||||
const gchar * uri, const gchar * referer, gboolean compress,
|
const gchar * uri, const gchar * referer, gboolean compress,
|
||||||
|
@ -477,6 +508,8 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader *
|
||||||
if (downloader->priv->download)
|
if (downloader->priv->download)
|
||||||
g_object_unref (downloader->priv->download);
|
g_object_unref (downloader->priv->download);
|
||||||
downloader->priv->download = gst_fragment_new ();
|
downloader->priv->download = gst_fragment_new ();
|
||||||
|
downloader->priv->download->range_start = range_start;
|
||||||
|
downloader->priv->download->range_end = range_end;
|
||||||
GST_OBJECT_UNLOCK (downloader);
|
GST_OBJECT_UNLOCK (downloader);
|
||||||
ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_READY);
|
ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_READY);
|
||||||
GST_OBJECT_LOCK (downloader);
|
GST_OBJECT_LOCK (downloader);
|
||||||
|
@ -490,10 +523,17 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader *
|
||||||
goto quit;
|
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)) {
|
if (!gst_uri_downloader_set_range (downloader, range_start, range_end)) {
|
||||||
GST_WARNING_OBJECT (downloader, "Failed to set range");
|
GST_WARNING_OBJECT (downloader, "Failed to set range");
|
||||||
goto quit;
|
goto quit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GST_OBJECT_UNLOCK (downloader);
|
GST_OBJECT_UNLOCK (downloader);
|
||||||
ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_PLAYING);
|
ret = gst_element_set_state (downloader->priv->urisrc, GST_STATE_PLAYING);
|
||||||
|
@ -531,10 +571,14 @@ gst_uri_downloader_fetch_uri_with_range (GstUriDownloader *
|
||||||
download = downloader->priv->download;
|
download = downloader->priv->download;
|
||||||
downloader->priv->download = NULL;
|
downloader->priv->download = NULL;
|
||||||
if (!downloader->priv->got_buffer) {
|
if (!downloader->priv->got_buffer) {
|
||||||
|
if (download->range_start < 0 && download->range_end < 0) {
|
||||||
|
/* HEAD request, so we don't expect a response */
|
||||||
|
} else {
|
||||||
g_object_unref (download);
|
g_object_unref (download);
|
||||||
download = NULL;
|
download = NULL;
|
||||||
GST_ERROR_OBJECT (downloader, "Didn't retrieve a buffer before EOS");
|
GST_ERROR_OBJECT (downloader, "Didn't retrieve a buffer before EOS");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (download != NULL)
|
if (download != NULL)
|
||||||
GST_INFO_OBJECT (downloader, "URI fetched successfully");
|
GST_INFO_OBJECT (downloader, "URI fetched successfully");
|
||||||
|
|
Loading…
Reference in a new issue