dashdemux: add support for UTCTiming elements for clock drift compensation

Unless the DASH client can compensate for the difference between its
clock and the clock used by the server, the client might request
fragments that either not yet on the server or fragments that have
already been expired from the server. This is an issue because these
requests can propagate all the way back to the origin

ISO/IEC 23009-1:2014/Amd 1 [PDAM1] defines a new UTCTiming element to allow
a DASH client to track the clock used by the server generating the
DASH stream. Multiple UTCTiming elements might be present, to indicate
support for multiple methods of UTC time gathering. Each element can
contain a white space separated list of URLs that can be contacted
to discover the UTC time from the server's perspective.

This commit provides parsing of UTCTiming elements, unit tests of this
parsing and a function to poll a time server. This function
supports the following methods:
    urn:mpeg:dash:utc:ntp:2014
    urn:mpeg:dash:utc:http-xsdate:2014
    urn:mpeg:dash:utc:http-iso:2014
    urn:mpeg:dash:utc:http-ntp:2014

The manifest update task is used to poll the clock time server,
to save having to create a new thread.

When choosing the starting fragment number and when waiting for a
fragment to become available, the difference between the server's idea
of UTC and the client's idea of UTC is taken into account. For example,
if the server's time is behind the client's idea of UTC, we wait for
longer before requesting a fragment

[PDAM1]: http://www.iso.org/iso/home/store/catalogue_tc/catalogue_detail.htm?csnumber=66068

dashdemux: support NTP time servers in UTCTiming elements

Use the gst_ntp_clock to support the use of an NTP server.

https://bugzilla.gnome.org/show_bug.cgi?id=752413
This commit is contained in:
Alex Ashley 2015-07-15 11:56:13 +01:00 committed by Thiago Santos
parent a7444ad850
commit 95c705ae8f
6 changed files with 541 additions and 5 deletions

View file

@ -27,6 +27,7 @@ libgstdashdemux_la_LIBADD = \
-lgsttag-$(GST_API_VERSION) \
$(GST_BASE_LIBS) \
$(GST_LIBS) \
-lgstnet-$(GST_API_VERSION) \
$(LIBXML2_LIBS)
libgstdashdemux_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
libgstdashdemux_la_LIBTOOLFLAGS = --tag=disable-static

View file

@ -146,8 +146,10 @@
#include <string.h>
#include <inttypes.h>
#include <gio/gio.h>
#include <gst/base/gsttypefindhelper.h>
#include <gst/tag/tag.h>
#include <gst/net/gstnet.h>
#include "gst/gst-i18n-plugin.h"
#include "gstdashdemux.h"
#include "gstdash_debug.h"
@ -187,6 +189,25 @@ enum
#define DEFAULT_BANDWIDTH_USAGE 0.8 /* 0 to 1 */
#define DEFAULT_MAX_BITRATE 24000000 /* in bit/s */
/* 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 NTP_TO_UNIX_EPOCH 2208988800LL /* difference (in seconds) between NTP epoch and Unix epoch */
struct _GstDashDemuxClockDrift
{
GMutex clock_lock; /* used to protect access to struct */
guint selected_url;
gint64 next_update;
GCond clock_cond; /* used for waiting until got_clock==TRUE */
/* @clock_compensation: amount (in usecs) to add to client's idea of
now to map it to the server's idea of now */
GTimeSpan clock_compensation;
gboolean got_clock; /* indicates time source has returned a valid clock at least once */
GstClock *ntp_clock;
};
/* GObject */
static void gst_dash_demux_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
@ -236,6 +257,11 @@ static GstCaps *gst_dash_demux_get_input_caps (GstDashDemux * demux,
GstActiveStream * stream);
static GstPad *gst_dash_demux_create_pad (GstDashDemux * demux,
GstActiveStream * stream);
static GstDashDemuxClockDrift *gst_dash_demux_clock_drift_new (void);
static void gst_dash_demux_clock_drift_free (GstDashDemuxClockDrift *);
static gboolean gst_dash_demux_poll_clock_drift (GstDashDemux * demux);
static GTimeSpan gst_dash_demux_get_clock_compensation (GstDashDemux * demux);
static GDateTime *gst_dash_demux_get_server_now_utc (GstDashDemux * demux);
#define SIDX(s) (&(s)->sidx_parser.sidx)
#define SIDX_ENTRY(s,i) (&(SIDX(s)->entries[(i)]))
@ -264,6 +290,8 @@ gst_dash_demux_dispose (GObject * obj)
g_mutex_clear (&demux->client_lock);
gst_dash_demux_clock_drift_free (demux->clock_drift);
demux->clock_drift = NULL;
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
@ -272,7 +300,7 @@ gst_dash_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start,
gint64 * stop)
{
GstDashDemux *self = GST_DASH_DEMUX (demux);
GDateTime *now = g_date_time_new_now_utc ();
GDateTime *now = gst_dash_demux_get_server_now_utc (self);
GDateTime *mstart =
gst_date_time_to_g_date_time (self->client->
mpd_node->availabilityStartTime);
@ -606,9 +634,21 @@ gst_dash_demux_setup_streams (GstAdaptiveDemux * demux)
* non-live */
period_idx = 0;
if (gst_mpd_client_is_live (dashdemux->client)) {
GDateTime *g_now;
if (dashdemux->clock_drift == NULL) {
gchar **urls;
urls =
gst_mpd_client_get_utc_timing_sources (dashdemux->client,
SUPPORTED_CLOCK_FORMATS, NULL);
if (urls) {
GST_DEBUG_OBJECT (dashdemux, "Found a supported UTCTiming element");
dashdemux->clock_drift = gst_dash_demux_clock_drift_new ();
gst_dash_demux_poll_clock_drift (dashdemux);
}
}
/* get period index for period encompassing the current time */
now = gst_date_time_new_now_utc ();
g_now = gst_dash_demux_get_server_now_utc (dashdemux);
now = gst_date_time_new_from_g_date_time (g_now);
if (dashdemux->client->mpd_node->suggestedPresentationDelay != -1) {
GstDateTime *target = gst_mpd_client_add_time_difference (now,
dashdemux->client->mpd_node->suggestedPresentationDelay * -1000);
@ -751,6 +791,8 @@ gst_dash_demux_reset (GstAdaptiveDemux * ademux)
gst_mpd_client_free (demux->client);
demux->client = NULL;
}
gst_dash_demux_clock_drift_free (demux->clock_drift);
demux->clock_drift = NULL;
demux->client = gst_mpd_client_new ();
demux->n_audio_streams = 0;
@ -1186,7 +1228,8 @@ static gint64
gst_dash_demux_get_manifest_update_interval (GstAdaptiveDemux * demux)
{
GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux);
return dashdemux->client->mpd_node->minimumUpdatePeriod * 1000;
return MIN (dashdemux->client->mpd_node->minimumUpdatePeriod * 1000,
SLOW_CLOCK_UPDATE_INTERVAL);
}
static GstFlowReturn
@ -1282,6 +1325,9 @@ gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux,
dashdemux->client = new_client;
GST_DEBUG_OBJECT (demux, "Manifest file successfully updated");
if (dashdemux->clock_drift) {
gst_dash_demux_poll_clock_drift (dashdemux);
}
} else {
/* In most cases, this will happen if we set a wrong url in the
* source element and we have received the 404 HTML response instead of
@ -1317,7 +1363,10 @@ gst_dash_demux_stream_get_fragment_waiting_time (GstAdaptiveDemuxStream *
diff = gst_mpd_client_calculate_time_difference (cur_time, seg_end_time);
gst_date_time_unref (seg_end_time);
gst_date_time_unref (cur_time);
return diff;
/* subtract the server's clock drift, so that if the server's
time is behind our idea of UTC, we need to sleep for longer
before requesting a fragment */
return diff - gst_dash_demux_get_clock_compensation (dashdemux);
}
return 0;
}
@ -1480,3 +1529,286 @@ gst_dash_demux_stream_free (GstAdaptiveDemuxStream * stream)
gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
}
static GstDashDemuxClockDrift *
gst_dash_demux_clock_drift_new (void)
{
GstDashDemuxClockDrift *clock_drift;
clock_drift = g_slice_new0 (GstDashDemuxClockDrift);
g_mutex_init (&clock_drift->clock_lock);
g_cond_init (&clock_drift->clock_cond);
clock_drift->next_update = g_get_monotonic_time ();
return clock_drift;
}
static void
gst_dash_demux_clock_drift_free (GstDashDemuxClockDrift * clock_drift)
{
if (clock_drift) {
g_mutex_lock (&clock_drift->clock_lock);
if (clock_drift->ntp_clock)
g_object_unref (clock_drift->ntp_clock);
g_cond_clear (&clock_drift->clock_cond);
g_mutex_unlock (&clock_drift->clock_lock);
g_mutex_clear (&clock_drift->clock_lock);
g_slice_free (GstDashDemuxClockDrift, clock_drift);
}
}
static GstDateTime *
gst_dash_demux_poll_ntp_server (GstDashDemuxClockDrift * clock_drift,
gchar ** urls)
{
GstClockTime ntp_clock_time;
GDateTime *dt, *dt2;
if (!clock_drift->ntp_clock) {
GResolver *resolver;
GList *inet_addrs;
GError *err;
gchar *ip_addr;
resolver = g_resolver_get_default ();
/* We don't round-robin NTP servers. If the manifest specifies multiple
NTP time servers, select one at random */
clock_drift->selected_url = g_random_int_range (0, g_strv_length (urls));
GST_DEBUG ("Connecting to NTP time server %s",
urls[clock_drift->selected_url]);
inet_addrs = g_resolver_lookup_by_name (resolver,
urls[clock_drift->selected_url], NULL, &err);
g_object_unref (resolver);
if (!inet_addrs || g_list_length (inet_addrs) == 0) {
GST_ERROR ("Failed to resolve hostname of NTP server: %s", err->message);
if (inet_addrs)
g_resolver_free_addresses (inet_addrs);
return NULL;
}
ip_addr =
g_inet_address_to_string ((GInetAddress
*) (g_list_first (inet_addrs)->data));
clock_drift->ntp_clock = gst_ntp_clock_new ("dashntp", ip_addr, 123, 0);
g_free (ip_addr);
g_resolver_free_addresses (inet_addrs);
if (!clock_drift->ntp_clock) {
GST_ERROR ("Failed to create NTP clock");
return NULL;
}
if (!gst_clock_wait_for_sync (clock_drift->ntp_clock, 5 * GST_SECOND)) {
g_object_unref (clock_drift->ntp_clock);
clock_drift->ntp_clock = NULL;
GST_ERROR ("Failed to lock to NTP clock");
return NULL;
}
}
ntp_clock_time = gst_clock_get_time (clock_drift->ntp_clock);
if (ntp_clock_time == GST_CLOCK_TIME_NONE) {
GST_ERROR ("Failed to get time from NTP clock");
return NULL;
}
ntp_clock_time -= NTP_TO_UNIX_EPOCH * GST_SECOND;
dt = g_date_time_new_from_unix_utc (ntp_clock_time / GST_SECOND);
if (!dt) {
GST_ERROR ("Failed to create GstDateTime");
return NULL;
}
ntp_clock_time =
gst_util_uint64_scale (ntp_clock_time % GST_SECOND, 1000000, GST_SECOND);
dt2 = g_date_time_add (dt, ntp_clock_time);
g_date_time_unref (dt);
return gst_date_time_new_from_g_date_time (dt2);
}
static GstDateTime *
gst_dash_demux_parse_http_ntp (GstDashDemuxClockDrift * clock_drift,
GstBuffer * buffer)
{
gint64 seconds;
guint64 fraction;
GDateTime *dt, *dt2;
GstMapInfo mapinfo;
/* See https://tools.ietf.org/html/rfc5905#page-12 for details of
the NTP Timestamp Format */
gst_buffer_map (buffer, &mapinfo, GST_MAP_READ);
if (mapinfo.size != 8) {
gst_buffer_unmap (buffer, &mapinfo);
return NULL;
}
seconds = GST_READ_UINT32_BE (mapinfo.data);
fraction = GST_READ_UINT32_BE (mapinfo.data + 4);
gst_buffer_unmap (buffer, &mapinfo);
fraction = gst_util_uint64_scale (fraction, 1000000,
G_GUINT64_CONSTANT (1) << 32);
/* subtract constant to convert from 1900 based time to 1970 based time */
seconds -= NTP_TO_UNIX_EPOCH;
dt = g_date_time_new_from_unix_utc (seconds);
dt2 = g_date_time_add (dt, fraction);
g_date_time_unref (dt);
return gst_date_time_new_from_g_date_time (dt2);
}
static GstDateTime *
gst_dash_demux_parse_http_xsdate (GstDashDemuxClockDrift * clock_drift,
GstBuffer * buffer)
{
GstDateTime *value;
GstMapInfo mapinfo;
/* the string from the server might not be zero terminated */
gst_buffer_resize (buffer, 0, gst_buffer_get_size (buffer) + 1);
gst_buffer_map (buffer, &mapinfo, GST_MAP_READ | GST_MAP_WRITE);
mapinfo.data[mapinfo.size - 1] = '\0';
value = gst_date_time_new_from_iso8601_string ((const gchar *) mapinfo.data);
gst_buffer_unmap (buffer, &mapinfo);
return value;
}
static gboolean
gst_dash_demux_poll_clock_drift (GstDashDemux * demux)
{
GstDashDemuxClockDrift *clock_drift;
GDateTime *start = NULL, *end;
GstBuffer *buffer = NULL;
GstDateTime *value = NULL;
gboolean ret = FALSE;
gint64 now;
GstMPDUTCTimingType method;
gchar **urls;
g_return_val_if_fail (demux != NULL, FALSE);
g_return_val_if_fail (demux->clock_drift != NULL, FALSE);
clock_drift = demux->clock_drift;
now = g_get_monotonic_time ();
if (now < clock_drift->next_update) {
/*TODO: If a fragment fails to download in adaptivedemux, it waits
for a manifest reload before another attempt to fetch a fragment.
Section 10.8.6 of the DVB-DASH standard states that the DASH client
shall refresh the manifest and resynchronise to one of the time sources.
Currently the fact that the manifest refresh follows a download failure
does not make it into dashdemux. */
return TRUE;
}
urls = gst_mpd_client_get_utc_timing_sources (demux->client,
SUPPORTED_CLOCK_FORMATS, &method);
if (!urls) {
return FALSE;
}
/* Update selected_url just in case the number of URLs in the UTCTiming
element has shrunk since the last poll */
clock_drift->selected_url = clock_drift->selected_url % g_strv_length (urls);
g_mutex_lock (&clock_drift->clock_lock);
if (method == GST_MPD_UTCTIMING_TYPE_NTP) {
value = gst_dash_demux_poll_ntp_server (clock_drift, urls);
if (!value) {
GST_ERROR_OBJECT (demux, "Failed to fetch time from NTP server %s",
urls[clock_drift->selected_url]);
g_mutex_unlock (&clock_drift->clock_lock);
goto quit;
}
}
start = g_date_time_new_now_utc ();
if (!value) {
GstFragment *download;
GST_DEBUG_OBJECT (demux, "Fetching current time from %s",
urls[clock_drift->selected_url]);
download =
gst_uri_downloader_fetch_uri (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);
}
g_mutex_unlock (&clock_drift->clock_lock);
if (!value && !buffer) {
GST_ERROR_OBJECT (demux, "Failed to fetch time from %s",
urls[clock_drift->selected_url]);
goto quit;
}
end = g_date_time_new_now_utc ();
if (!value && method == GST_MPD_UTCTIMING_TYPE_HTTP_NTP) {
value = gst_dash_demux_parse_http_ntp (clock_drift, buffer);
} else if (!value) {
/* GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE or GST_MPD_UTCTIMING_TYPE_HTTP_ISO */
value = gst_dash_demux_parse_http_xsdate (clock_drift, buffer);
}
if (buffer)
gst_buffer_unref (buffer);
if (value) {
GTimeSpan download_duration = g_date_time_difference (end, start);
GDateTime *client_now, *server_now;
/* We don't know when the server sampled its clock, but we know
it must have been before "end" and probably after "start".
A reasonable estimate is to use (start+end)/2
*/
client_now = g_date_time_add (start, download_duration / 2);
server_now = gst_date_time_to_g_date_time (value);
/* If gst_date_time_new_from_iso8601_string is given an unsupported
ISO 8601 format, it can return a GstDateTime that is not valid,
which causes gst_date_time_to_g_date_time to return NULL */
if (server_now) {
g_mutex_lock (&clock_drift->clock_lock);
clock_drift->clock_compensation =
g_date_time_difference (server_now, client_now);
clock_drift->got_clock = TRUE;
g_cond_broadcast (&clock_drift->clock_cond);
g_mutex_unlock (&clock_drift->clock_lock);
GST_DEBUG_OBJECT (demux,
"Difference between client and server clocks is %lfs",
((double) clock_drift->clock_compensation) / 1000000.0);
g_date_time_unref (server_now);
ret = TRUE;
} else {
GST_ERROR_OBJECT (demux, "Failed to parse DateTime from server");
}
g_date_time_unref (client_now);
gst_date_time_unref (value);
} else {
GST_ERROR_OBJECT (demux, "Failed to parse DateTime from server");
}
g_date_time_unref (end);
quit:
if (start)
g_date_time_unref (start);
/* if multiple URLs were specified, use a simple round-robin to
poll each server */
g_mutex_lock (&clock_drift->clock_lock);
if (method == GST_MPD_UTCTIMING_TYPE_NTP) {
clock_drift->next_update = now + FAST_CLOCK_UPDATE_INTERVAL;
} else {
clock_drift->selected_url =
(1 + clock_drift->selected_url) % g_strv_length (urls);
if (ret) {
clock_drift->next_update = now + SLOW_CLOCK_UPDATE_INTERVAL;
} else {
clock_drift->next_update = now + FAST_CLOCK_UPDATE_INTERVAL;
}
}
g_mutex_unlock (&clock_drift->clock_lock);
return ret;
}
static GTimeSpan
gst_dash_demux_get_clock_compensation (GstDashDemux * demux)
{
GTimeSpan rv = 0;
if (demux->clock_drift) {
g_mutex_lock (&demux->clock_drift->clock_lock);
rv = demux->clock_drift->clock_compensation;
g_mutex_unlock (&demux->clock_drift->clock_lock);
}
GST_LOG_OBJECT (demux, "Clock drift %" GST_STIME_FORMAT, GST_STIME_ARGS (rv));
return rv;
}
static GDateTime *
gst_dash_demux_get_server_now_utc (GstDashDemux * demux)
{
GDateTime *client_now = g_date_time_new_now_utc ();
GDateTime *server_now = g_date_time_add (client_now,
gst_dash_demux_get_clock_compensation (demux));
g_date_time_unref (client_now);
return server_now;
}

View file

@ -52,6 +52,7 @@ G_BEGIN_DECLS
#define GST_DASH_DEMUX_CAST(obj) \
((GstDashDemux *)obj)
typedef struct _GstDashDemuxClockDrift GstDashDemuxClockDrift;
typedef struct _GstDashDemuxStream GstDashDemuxStream;
typedef struct _GstDashDemux GstDashDemux;
typedef struct _GstDashDemuxClass GstDashDemuxClass;
@ -87,6 +88,8 @@ struct _GstDashDemux
GstMpdClient *client; /* MPD client */
GMutex client_lock;
GstDashDemuxClockDrift *clock_drift;
gboolean end_of_period;
gboolean end_of_manifest;

View file

@ -112,6 +112,8 @@ static void gst_mpdparser_parse_metrics_range_node (GList ** list,
static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node);
static void gst_mpdparser_parse_root_node (GstMPDNode ** pointer,
xmlNode * a_node);
static void gst_mpdparser_parse_utctiming_node (GList ** list,
xmlNode * a_node);
/* Helper functions */
static gint convert_to_millisecs (gint decimals, gint pos);
@ -200,10 +202,41 @@ static void gst_mpdparser_free_descriptor_type_node (GstDescriptorType *
descriptor_type);
static void gst_mpdparser_free_content_component_node (GstContentComponentNode *
content_component_node);
static void gst_mpdparser_free_utctiming_node (GstUTCTimingNode * timing_type);
static void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period);
static void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment);
static void gst_mpdparser_free_active_stream (GstActiveStream * active_stream);
struct GstMpdParserUtcTimingMethod
{
const gchar *name;
GstMPDUTCTimingType method;
};
static const struct GstMpdParserUtcTimingMethod
gst_mpdparser_utc_timing_methods[] = {
{"urn:mpeg:dash:utc:ntp:2014", GST_MPD_UTCTIMING_TYPE_NTP},
{"urn:mpeg:dash:utc:sntp:2014", GST_MPD_UTCTIMING_TYPE_SNTP},
{"urn:mpeg:dash:utc:http-head:2014", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
{"urn:mpeg:dash:utc:http-xsdate:2014", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
{"urn:mpeg:dash:utc:http-iso:2014", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
{"urn:mpeg:dash:utc:http-ntp:2014", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
{"urn:mpeg:dash:utc:direct:2014", GST_MPD_UTCTIMING_TYPE_DIRECT},
/*
* Early working drafts used the :2012 namespace and this namespace is
* used by some DASH packagers. To work-around these packagers, we also
* accept the early draft scheme names.
*/
{"urn:mpeg:dash:utc:ntp:2012", GST_MPD_UTCTIMING_TYPE_NTP},
{"urn:mpeg:dash:utc:sntp:2012", GST_MPD_UTCTIMING_TYPE_SNTP},
{"urn:mpeg:dash:utc:http-head:2012", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
{"urn:mpeg:dash:utc:http-xsdate:2012", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
{"urn:mpeg:dash:utc:http-iso:2012", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
{"urn:mpeg:dash:utc:http-ntp:2012", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
{"urn:mpeg:dash:utc:direct:2012", GST_MPD_UTCTIMING_TYPE_DIRECT},
{NULL, 0}
};
/* functions to parse node namespaces, content and properties */
static gboolean
gst_mpdparser_get_xml_prop_string (xmlNode * a_node,
@ -1766,6 +1799,43 @@ gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node)
}
}
/* The UTCTiming element is defined in
* ISO/IEC 23009-1:2014/PDAM 1 "Information technology — Dynamic adaptive streaming over HTTP (DASH) — Part 1: Media presentation description and segment formats / Amendment 1: High Profile and Availability Time Synchronization"
*/
static void
gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node)
{
GstUTCTimingNode *new_timing;
gchar *method = NULL;
gchar *value = NULL;
new_timing = g_slice_new0 (GstUTCTimingNode);
GST_LOG ("attributes of UTCTiming node:");
if (gst_mpdparser_get_xml_prop_string (a_node, "schemeIdUri", &method)) {
for (int i = 0; gst_mpdparser_utc_timing_methods[i].name; ++i) {
if (g_ascii_strncasecmp (gst_mpdparser_utc_timing_methods[i].name,
method, strlen (gst_mpdparser_utc_timing_methods[i].name)) == 0) {
new_timing->method = gst_mpdparser_utc_timing_methods[i].method;
break;
}
}
xmlFree (method);
}
if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) {
int max_tokens = 0;
if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) {
/* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case
* that is not a space separated list.
*/
max_tokens = 1;
}
new_timing->urls = g_strsplit (value, " ", max_tokens);
xmlFree (value);
*list = g_list_append (*list, new_timing);
}
}
static void
gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node)
{
@ -1820,6 +1890,8 @@ gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node)
gst_mpdparser_parse_location_node (&new_mpd->Locations, cur_node);
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) {
gst_mpdparser_parse_metrics_node (&new_mpd->Metrics, cur_node);
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "UTCTiming") == 0) {
gst_mpdparser_parse_utctiming_node (&new_mpd->UTCTiming, cur_node);
}
}
}
@ -2057,6 +2129,8 @@ gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node)
(GDestroyNotify) gst_mpdparser_free_period_node);
g_list_free_full (mpd_node->Metrics,
(GDestroyNotify) gst_mpdparser_free_metrics_node);
g_list_free_full (mpd_node->UTCTiming,
(GDestroyNotify) gst_mpdparser_free_utctiming_node);
g_slice_free (GstMPDNode, mpd_node);
}
}
@ -2394,6 +2468,16 @@ gst_mpdparser_free_content_component_node (GstContentComponentNode *
}
}
static void
gst_mpdparser_free_utctiming_node (GstUTCTimingNode * timing_type)
{
if (timing_type) {
if (timing_type->urls)
g_strfreev (timing_type->urls);
g_slice_free (GstUTCTimingNode, timing_type);
}
}
static void
gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period)
{
@ -3663,6 +3747,43 @@ gst_mpd_parser_get_stream_presentation_offset (GstMpdClient * client,
return 0;
}
/**
* gst_mpd_client_get_utc_timing_sources:
* @client: #GstMpdClient to check for UTCTiming elements
* @methods: A bit mask of #GstMPDUTCTimingType that specifies the methods
* to search for.
* @selected_method: (nullable): The selected method
* Returns: (transfer none): A NULL terminated array of URLs of servers
* that use @selected_method to provide a realtime clock.
*
* Searches the UTCTiming elements found in the manifest for an element
* that uses one of the UTC timing methods specified in @selected_method.
* If multiple UTCTiming elements are present that support one of the
* methods specified in @selected_method, the first one is returned.
*
* Since: 1.6
*/
gchar **
gst_mpd_client_get_utc_timing_sources (GstMpdClient * client,
guint methods, GstMPDUTCTimingType * selected_method)
{
GList *list;
g_return_val_if_fail (client != NULL, NULL);
g_return_val_if_fail (client->mpd_node != NULL, NULL);
for (list = g_list_first (client->mpd_node->UTCTiming); list;
list = g_list_next (list)) {
const GstUTCTimingNode *node = (const GstUTCTimingNode *) list->data;
if (node->method & methods) {
if (selected_method) {
*selected_method = node->method;
}
return node->urls;
}
}
return NULL;
}
gboolean
gst_mpd_client_get_next_fragment (GstMpdClient * client,
guint indexStream, GstMediaFragmentInfo * fragment)

View file

@ -56,6 +56,7 @@ typedef struct _GstSubsetNode GstSubsetNode;
typedef struct _GstProgramInformationNode GstProgramInformationNode;
typedef struct _GstMetricsRangeNode GstMetricsRangeNode;
typedef struct _GstMetricsNode GstMetricsNode;
typedef struct _GstUTCTimingNode GstUTCTimingNode;
typedef struct _GstSNode GstSNode;
typedef struct _GstSegmentTimelineNode GstSegmentTimelineNode;
typedef struct _GstSegmentBaseType GstSegmentBaseType;
@ -90,6 +91,18 @@ typedef enum
GST_SAP_TYPE_6
} GstSAPType;
typedef enum
{
GST_MPD_UTCTIMING_TYPE_UNKNOWN = 0x00,
GST_MPD_UTCTIMING_TYPE_NTP = 0x01,
GST_MPD_UTCTIMING_TYPE_SNTP = 0x02,
GST_MPD_UTCTIMING_TYPE_HTTP_HEAD = 0x04,
GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE = 0x08,
GST_MPD_UTCTIMING_TYPE_HTTP_ISO = 0x10,
GST_MPD_UTCTIMING_TYPE_HTTP_NTP = 0x20,
GST_MPD_UTCTIMING_TYPE_DIRECT = 0x40
} GstMPDUTCTimingType;
struct _GstBaseURL
{
gchar *baseURL;
@ -362,6 +375,12 @@ struct _GstMetricsNode
GList *Reportings;
};
struct _GstUTCTimingNode {
GstMPDUTCTimingType method;
/* NULL terminated array of strings */
gchar **urls;
};
struct _GstMPDNode
{
gchar *default_namespace;
@ -390,6 +409,8 @@ struct _GstMPDNode
GList *Periods;
/* list of Metrics nodes */
GList *Metrics;
/* list of GstUTCTimingNode nodes */
GList *UTCTiming;
};
/**
@ -505,6 +526,7 @@ gboolean gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * st
gboolean gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time);
gint gst_mpd_client_check_time_position (GstMpdClient * client, GstActiveStream * stream, GstClockTime ts, gint64 * diff);
GstClockTime gst_mpd_parser_get_stream_presentation_offset (GstMpdClient *client, guint stream_idx);
gchar** gst_mpd_client_get_utc_timing_sources (GstMpdClient *client, guint methods, GstMPDUTCTimingType *selected_method);
/* Period selection */
guint gst_mpd_client_get_period_index_at_time (GstMpdClient * client, GstDateTime * time);

View file

@ -2267,6 +2267,62 @@ GST_START_TEST (dash_mpdparser_period_subset)
GST_END_TEST;
/*
* Test parsing UTCTiming elements
*
*/
GST_START_TEST (dash_mpdparser_utctiming)
{
const gchar *xml =
"<?xml version=\"1.0\"?>"
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
" profiles=\"urn:mpeg:dash:profile:isoff-main:2011\">"
"<UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:http-xsdate:2014\" value=\"http://time.akamai.com/?iso http://example.time/xsdate\"/>"
"<UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:direct:2014\" value=\"2002-05-30T09:30:10Z \"/>"
"<UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:ntp:2014\" value=\"0.europe.pool.ntp.org 1.europe.pool.ntp.org 2.europe.pool.ntp.org 3.europe.pool.ntp.org\"/>"
"</MPD>";
gboolean ret;
GstMpdClient *mpdclient = gst_mpd_client_new ();
GstMPDUTCTimingType selected_method;
gchar **urls;
ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml));
assert_equals_int (ret, TRUE);
fail_if (mpdclient->mpd_node == NULL);
fail_if (mpdclient->mpd_node->UTCTiming == NULL);
assert_equals_int (g_list_length (mpdclient->mpd_node->UTCTiming), 3);
urls =
gst_mpd_client_get_utc_timing_sources (mpdclient,
GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE, &selected_method);
fail_if (urls == NULL);
assert_equals_int (selected_method, GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE);
assert_equals_int (g_strv_length (urls), 2);
assert_equals_string (urls[0], "http://time.akamai.com/?iso");
assert_equals_string (urls[1], "http://example.time/xsdate");
urls =
gst_mpd_client_get_utc_timing_sources (mpdclient,
GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE | GST_MPD_UTCTIMING_TYPE_HTTP_ISO,
&selected_method);
fail_if (urls == NULL);
assert_equals_int (selected_method, GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE);
urls =
gst_mpd_client_get_utc_timing_sources (mpdclient,
GST_MPD_UTCTIMING_TYPE_DIRECT, NULL);
fail_if (urls == NULL);
assert_equals_int (g_strv_length (urls), 1);
assert_equals_string (urls[0], "2002-05-30T09:30:10Z ");
urls =
gst_mpd_client_get_utc_timing_sources (mpdclient,
GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE | GST_MPD_UTCTIMING_TYPE_DIRECT,
&selected_method);
fail_if (urls == NULL);
assert_equals_int (selected_method, GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE);
gst_mpd_client_free (mpdclient);
}
GST_END_TEST;
/*
* Test parsing the type property: value "dynamic"
*
@ -4333,6 +4389,7 @@ dash_suite (void)
tcase_add_test (tc_simpleMPD,
dash_mpdparser_period_adaptationSet_representation_segmentTemplate);
tcase_add_test (tc_simpleMPD, dash_mpdparser_period_subset);
tcase_add_test (tc_simpleMPD, dash_mpdparser_utctiming);
/* tests checking other possible values for attributes */
tcase_add_test (tc_simpleMPD, dash_mpdparser_type_dynamic);