diff --git a/ext/dash/Makefile.am b/ext/dash/Makefile.am index f4e729028b..deae7acc27 100644 --- a/ext/dash/Makefile.am +++ b/ext/dash/Makefile.am @@ -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 diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c index cfa8ffa807..bbda84d451 100644 --- a/ext/dash/gstdashdemux.c +++ b/ext/dash/gstdashdemux.c @@ -146,8 +146,10 @@ #include #include +#include #include #include +#include #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; +} diff --git a/ext/dash/gstdashdemux.h b/ext/dash/gstdashdemux.h index c945a198ca..ca2af31ed7 100644 --- a/ext/dash/gstdashdemux.h +++ b/ext/dash/gstdashdemux.h @@ -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; diff --git a/ext/dash/gstmpdparser.c b/ext/dash/gstmpdparser.c index c588142fc7..f500c1fa0e 100644 --- a/ext/dash/gstmpdparser.c +++ b/ext/dash/gstmpdparser.c @@ -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) diff --git a/ext/dash/gstmpdparser.h b/ext/dash/gstmpdparser.h index 36e45903da..b31b7e6c32 100644 --- a/ext/dash/gstmpdparser.h +++ b/ext/dash/gstmpdparser.h @@ -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); diff --git a/tests/check/elements/dash_mpd.c b/tests/check/elements/dash_mpd.c index b1f7762432..ee5b6cbbd2 100644 --- a/tests/check/elements/dash_mpd.c +++ b/tests/check/elements/dash_mpd.c @@ -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 = + "" + "" + "" + "" + "" + ""; + 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);