From 86b251b7d10e4611e201becab8df6592d2120304 Mon Sep 17 00:00:00 2001 From: =?utf8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 20 May 2019 18:48:23 +0200 Subject: [PATCH] dash: split mpdparser, mpdclient and xmlhelper provide a separate namespace for mpd helper for xml parsing and the real mpd parsing. --- ext/dash/gstdashdemux.c | 35 +- ext/dash/gstdashdemux.h | 2 +- ext/dash/gstmpdclient.c | 2915 ++++++++++++++++++ ext/dash/gstmpdclient.h | 142 + ext/dash/gstmpdhelper.c | 78 + ext/dash/gstmpdhelper.h | 49 + ext/dash/gstmpdparser.c | 6190 +++++++-------------------------------- ext/dash/gstmpdparser.h | 204 +- ext/dash/gstxmlhelper.c | 1024 +++++++ ext/dash/gstxmlhelper.h | 113 + ext/dash/meson.build | 3 + tests/check/elements/dash_mpd.c | 418 +-- 12 files changed, 5690 insertions(+), 5483 deletions(-) create mode 100644 ext/dash/gstmpdclient.c create mode 100644 ext/dash/gstmpdclient.h create mode 100644 ext/dash/gstmpdhelper.c create mode 100644 ext/dash/gstmpdhelper.h create mode 100644 ext/dash/gstxmlhelper.c create mode 100644 ext/dash/gstxmlhelper.h diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c index f550b30..9b0fa26 100644 --- a/ext/dash/gstdashdemux.c +++ b/ext/dash/gstdashdemux.c @@ -520,7 +520,7 @@ gst_dash_demux_get_presentation_offset (GstAdaptiveDemux * demux, GstDashDemuxStream *dashstream = (GstDashDemuxStream *) stream; GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux); - return gst_mpd_parser_get_stream_presentation_offset (dashdemux->client, + return gst_mpd_client_get_stream_presentation_offset (dashdemux->client, dashstream->index); } @@ -529,7 +529,7 @@ gst_dash_demux_get_period_start_time (GstAdaptiveDemux * demux) { GstDashDemux *dashdemux = GST_DASH_DEMUX_CAST (demux); - return gst_mpd_parser_get_period_start_time (dashdemux->client); + return gst_mpd_client_get_period_start_time (dashdemux->client); } static void @@ -778,14 +778,14 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux) gst_mpd_client_get_period_index (demux->client)); /* clean old active stream list, if any */ - gst_active_streams_free (demux->client); + gst_mpd_client_active_streams_free (demux->client); if (!gst_dash_demux_setup_mpdparser_streams (demux, demux->client)) { return FALSE; } GST_DEBUG_OBJECT (demux, "Creating stream objects"); - for (i = 0; i < gst_mpdparser_get_nb_active_stream (demux->client); i++) { + for (i = 0; i < gst_mpd_client_get_nb_active_stream (demux->client); i++) { GstDashDemuxStream *stream; GstActiveStream *active_stream; GstCaps *caps; @@ -794,7 +794,8 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux) gchar *lang = NULL; GstTagList *tags = NULL; - active_stream = gst_mpdparser_get_active_stream_by_index (demux->client, i); + active_stream = + gst_mpd_client_get_active_stream_by_index (demux->client, i); if (active_stream == NULL) continue; @@ -1036,7 +1037,7 @@ gst_dash_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf) if (gst_buffer_map (buf, &mapinfo, GST_MAP_READ)) { manifest = (gchar *) mapinfo.data; - if (gst_mpd_parse (dashdemux->client, manifest, mapinfo.size)) { + if (gst_mpd_client_parse (dashdemux->client, manifest, mapinfo.size)) { if (gst_mpd_client_setup_media_presentation (dashdemux->client, 0, 0, NULL)) { ret = TRUE; @@ -1232,7 +1233,7 @@ gst_dash_demux_stream_update_headers_info (GstAdaptiveDemuxStream * stream) if (path != NULL) { stream->fragment.header_uri = - gst_uri_join_strings (gst_mpdparser_get_baseURL (dashdemux->client, + gst_uri_join_strings (gst_mpd_client_get_baseURL (dashdemux->client, dashstream->index), path); g_free (path); path = NULL; @@ -1244,7 +1245,7 @@ gst_dash_demux_stream_update_headers_info (GstAdaptiveDemuxStream * stream) if (path != NULL) { stream->fragment.index_uri = - gst_uri_join_strings (gst_mpdparser_get_baseURL (dashdemux->client, + gst_uri_join_strings (gst_mpd_client_get_baseURL (dashdemux->client, dashstream->index), path); g_free (path); } @@ -1501,9 +1502,9 @@ gst_dash_demux_stream_seek (GstAdaptiveDemuxStream * stream, gboolean forward, if (is_isobmff) { GstClockTime period_start, offset; - period_start = gst_mpd_parser_get_period_start_time (dashdemux->client); + period_start = gst_mpd_client_get_period_start_time (dashdemux->client); offset = - gst_mpd_parser_get_stream_presentation_offset (dashdemux->client, + gst_mpd_client_get_stream_presentation_offset (dashdemux->client, dashstream->index); if (G_UNLIKELY (ts < period_start)) @@ -2198,12 +2199,12 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream, if (GST_ADAPTIVE_DEMUX_IN_TRICKMODE_KEY_UNITS (base_demux) || ABS (base_demux->segment.rate) <= 1.0) { new_index = - gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, bitrate, + gst_mpd_client_get_rep_idx_with_max_bandwidth (rep_list, bitrate, demux->max_video_width, demux->max_video_height, demux->max_video_framerate_n, demux->max_video_framerate_d); } else { new_index = - gst_mpdparser_get_rep_idx_with_max_bandwidth (rep_list, + gst_mpd_client_get_rep_idx_with_max_bandwidth (rep_list, bitrate / ABS (base_demux->segment.rate), demux->max_video_width, demux->max_video_height, demux->max_video_framerate_n, demux->max_video_framerate_d); @@ -2211,7 +2212,7 @@ gst_dash_demux_stream_select_bitrate (GstAdaptiveDemuxStream * stream, /* if no representation has the required bandwidth, take the lowest one */ if (new_index == -1) - new_index = gst_mpdparser_get_rep_idx_with_min_bandwidth (rep_list); + new_index = gst_mpd_client_get_rep_idx_with_min_bandwidth (rep_list); if (new_index != active_stream->representation_idx) { GstRepresentationNode *rep = g_list_nth_data (rep_list, new_index); @@ -2346,7 +2347,7 @@ gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) GST_DEBUG_OBJECT (demux, "Seeking to Period %d", current_period); /* clean old active stream list, if any */ - gst_active_streams_free (dashdemux->client); + gst_mpd_client_active_streams_free (dashdemux->client); dashdemux->trickmode_no_audio = trickmode_no_audio; /* setup video, audio and subtitle streams, starting from the new Period */ @@ -2356,7 +2357,7 @@ gst_dash_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek) streams = demux->next_streams; } else if (dashdemux->trickmode_no_audio != trickmode_no_audio) { /* clean old active stream list, if any */ - gst_active_streams_free (dashdemux->client); + gst_mpd_client_active_streams_free (dashdemux->client); dashdemux->trickmode_no_audio = trickmode_no_audio; /* setup video, audio and subtitle streams, starting from the new Period */ @@ -2404,7 +2405,7 @@ gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux, new_client->mpd_base_uri = g_strdup (demux->manifest_base_uri); gst_buffer_map (buffer, &mapinfo, GST_MAP_READ); - if (gst_mpd_parse (new_client, (gchar *) mapinfo.data, mapinfo.size)) { + if (gst_mpd_client_parse (new_client, (gchar *) mapinfo.data, mapinfo.size)) { const gchar *period_id; guint period_idx; GList *iter; @@ -2490,7 +2491,7 @@ gst_dash_demux_update_manifest_data (GstAdaptiveDemux * demux, /* _get_next_fragment_timestamp() returned relative timestamp to * corresponding period start, but _client_stream_seek expects absolute * MPD time. */ - ts += gst_mpd_parser_get_period_start_time (dashdemux->client); + ts += gst_mpd_client_get_period_start_time (dashdemux->client); GST_DEBUG_OBJECT (GST_ADAPTIVE_DEMUX_STREAM_PAD (demux_stream), "Current position: %" GST_TIME_FORMAT ", updating to %" diff --git a/ext/dash/gstdashdemux.h b/ext/dash/gstdashdemux.h index fdbbf31..ae22a19 100644 --- a/ext/dash/gstdashdemux.h +++ b/ext/dash/gstdashdemux.h @@ -34,7 +34,7 @@ #include #include #include -#include "gstmpdparser.h" +#include "gstmpdclient.h" #include #include diff --git a/ext/dash/gstmpdclient.c b/ext/dash/gstmpdclient.c new file mode 100644 index 0000000..a4015d8 --- /dev/null +++ b/ext/dash/gstmpdclient.c @@ -0,0 +1,2915 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gstmpdclient.h" +#include "gstmpdparser.h" + +GST_DEBUG_CATEGORY_STATIC (gst_dash_mpd_client_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT gst_dash_mpd_client_debug + + +static GstSegmentBaseType *gst_mpd_client_get_segment_base (GstPeriodNode * + Period, GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation); +static GstSegmentListNode *gst_mpd_client_get_segment_list (GstMpdClient * + client, GstPeriodNode * Period, GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation); +/* Segments */ +static guint gst_mpd_client_get_segments_counts (GstMpdClient * client, + GstActiveStream * stream); + +static GList *gst_mpd_client_fetch_external_periods (GstMpdClient * client, + GstPeriodNode * period_node); +static GList *gst_mpd_client_fetch_external_adaptation_set (GstMpdClient * + client, GstPeriodNode * period, GstAdaptationSetNode * adapt_set); + +static GstRepresentationNode *gst_mpd_client_get_lowest_representation (GList * + Representations); +static GstStreamPeriod *gst_mpd_client_get_stream_period (GstMpdClient * + client); + + +static GstRepresentationNode * +gst_mpd_client_get_lowest_representation (GList * Representations) +{ + GList *list = NULL; + GstRepresentationNode *rep = NULL; + GstRepresentationNode *lowest = NULL; + + if (Representations == NULL) + return NULL; + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + rep = (GstRepresentationNode *) list->data; + if (rep && (!lowest || rep->bandwidth < lowest->bandwidth)) { + lowest = rep; + } + } + + return lowest; +} + +#if 0 +static GstRepresentationNode * +gst_mpdparser_get_highest_representation (GList * Representations) +{ + GList *list = NULL; + + if (Representations == NULL) + return NULL; + + list = g_list_last (Representations); + + return list ? (GstRepresentationNode *) list->data : NULL; +} + +static GstRepresentationNode * +gst_mpdparser_get_representation_with_max_bandwidth (GList * Representations, + gint max_bandwidth) +{ + GList *list = NULL; + GstRepresentationNode *representation, *best_rep = NULL; + + if (Representations == NULL) + return NULL; + + if (max_bandwidth <= 0) /* 0 => get highest representation available */ + return gst_mpdparser_get_highest_representation (Representations); + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + representation = (GstRepresentationNode *) list->data; + if (representation && representation->bandwidth <= max_bandwidth) { + best_rep = representation; + } + } + + return best_rep; +} +#endif + +static GstSegmentListNode * +gst_mpd_client_fetch_external_segment_list (GstMpdClient * client, + GstPeriodNode * Period, + GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation, + GstSegmentListNode * parent, GstSegmentListNode * segment_list) +{ + GstFragment *download; + GstBuffer *segment_list_buffer = NULL; + GstMapInfo map; + GError *err = NULL; + + GstUri *base_uri, *uri; + gchar *query = NULL; + gchar *uri_string; + GstSegmentListNode *new_segment_list = NULL; + + /* ISO/IEC 23009-1:2014 5.5.3 4) + * Remove nodes that resolve to nothing when resolving + */ + if (strcmp (segment_list->xlink_href, + "urn:mpeg:dash:resolve-to-zero:2013") == 0) { + return NULL; + } + + if (!client->downloader) { + return NULL; + } + + /* Build absolute URI */ + + /* Get base URI at the MPD level */ + base_uri = + gst_uri_from_string (client-> + mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); + + /* combine a BaseURL at the MPD level with the current base url */ + base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); + + /* combine a BaseURL at the Period level with the current base url */ + base_uri = combine_urls (base_uri, Period->BaseURLs, &query, 0); + + if (AdaptationSet) { + /* combine a BaseURL at the AdaptationSet level with the current base url */ + base_uri = combine_urls (base_uri, AdaptationSet->BaseURLs, &query, 0); + + if (Representation) { + /* combine a BaseURL at the Representation level with the current base url */ + base_uri = combine_urls (base_uri, Representation->BaseURLs, &query, 0); + } + } + + uri = gst_uri_from_string_with_base (base_uri, segment_list->xlink_href); + if (query) + gst_uri_set_query_string (uri, query); + g_free (query); + uri_string = gst_uri_to_string (uri); + gst_uri_unref (base_uri); + gst_uri_unref (uri); + + download = + gst_uri_downloader_fetch_uri (client->downloader, + uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); + g_free (uri_string); + + if (!download) { + GST_ERROR ("Failed to download external SegmentList node at '%s': %s", + segment_list->xlink_href, err->message); + g_clear_error (&err); + return NULL; + } + + segment_list_buffer = gst_fragment_get_buffer (download); + g_object_unref (download); + + gst_buffer_map (segment_list_buffer, &map, GST_MAP_READ); + + new_segment_list = + gst_mpdparser_get_external_segment_list ((const gchar *) map.data, + map.size, parent); + + if (segment_list_buffer) { + gst_buffer_unmap (segment_list_buffer, &map); + gst_buffer_unref (segment_list_buffer); + } + + return new_segment_list; +} + +static GstSegmentBaseType * +gst_mpd_client_get_segment_base (GstPeriodNode * Period, + GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation) +{ + GstSegmentBaseType *SegmentBase = NULL; + + if (Representation && Representation->SegmentBase) { + SegmentBase = Representation->SegmentBase; + } else if (AdaptationSet && AdaptationSet->SegmentBase) { + SegmentBase = AdaptationSet->SegmentBase; + } else if (Period && Period->SegmentBase) { + SegmentBase = Period->SegmentBase; + } + /* the SegmentBase element could be encoded also inside a SegmentList element */ + if (SegmentBase == NULL) { + if (Representation && Representation->SegmentList + && Representation->SegmentList->MultSegBaseType + && Representation->SegmentList->MultSegBaseType->SegBaseType) { + SegmentBase = Representation->SegmentList->MultSegBaseType->SegBaseType; + } else if (AdaptationSet && AdaptationSet->SegmentList + && AdaptationSet->SegmentList->MultSegBaseType + && AdaptationSet->SegmentList->MultSegBaseType->SegBaseType) { + SegmentBase = AdaptationSet->SegmentList->MultSegBaseType->SegBaseType; + } else if (Period && Period->SegmentList + && Period->SegmentList->MultSegBaseType + && Period->SegmentList->MultSegBaseType->SegBaseType) { + SegmentBase = Period->SegmentList->MultSegBaseType->SegBaseType; + } + } + + return SegmentBase; +} + +static GstSegmentListNode * +gst_mpd_client_get_segment_list (GstMpdClient * client, GstPeriodNode * Period, + GstAdaptationSetNode * AdaptationSet, + GstRepresentationNode * Representation) +{ + GstSegmentListNode **SegmentList; + GstSegmentListNode *ParentSegmentList = NULL; + + if (Representation && Representation->SegmentList) { + SegmentList = &Representation->SegmentList; + ParentSegmentList = AdaptationSet->SegmentList; + } else if (AdaptationSet && AdaptationSet->SegmentList) { + SegmentList = &AdaptationSet->SegmentList; + ParentSegmentList = Period->SegmentList; + Representation = NULL; + } else { + Representation = NULL; + AdaptationSet = NULL; + SegmentList = &Period->SegmentList; + } + + /* Resolve external segment list here. */ + if (*SegmentList && (*SegmentList)->xlink_href) { + GstSegmentListNode *new_segment_list; + + /* TODO: Use SegmentList of parent if + * - Parent has its own SegmentList + * - Fail to get SegmentList from external xml + */ + new_segment_list = + gst_mpd_client_fetch_external_segment_list (client, Period, + AdaptationSet, Representation, ParentSegmentList, *SegmentList); + + gst_mpdparser_free_segment_list_node (*SegmentList); + *SegmentList = new_segment_list; + } + + return *SegmentList; +} + +static GstClockTime +gst_mpd_client_get_segment_duration (GstMpdClient * client, + GstActiveStream * stream, guint64 * scale_dur) +{ + GstStreamPeriod *stream_period; + GstMultSegmentBaseType *base = NULL; + GstClockTime duration = 0; + + g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE); + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, GST_CLOCK_TIME_NONE); + + if (stream->cur_segment_list) { + base = stream->cur_segment_list->MultSegBaseType; + } else if (stream->cur_seg_template) { + base = stream->cur_seg_template->MultSegBaseType; + } + + if (base == NULL || base->SegBaseType == NULL) { + /* this may happen when we have a single segment */ + duration = stream_period->duration; + if (scale_dur) + *scale_dur = duration; + } else { + /* duration is guint so this cannot overflow */ + duration = base->duration * GST_SECOND; + if (scale_dur) + *scale_dur = duration; + duration /= base->SegBaseType->timescale; + } + + return duration; +} + + +GstMpdClient * +gst_mpd_client_new (void) +{ + GstMpdClient *client; + GST_DEBUG_CATEGORY_INIT (gst_dash_mpd_client_debug, "dashmpdclient", 0, + "DashmMpdClient"); + client = g_new0 (GstMpdClient, 1); + + return client; +} + +void +gst_mpd_client_active_streams_free (GstMpdClient * client) +{ + if (client->active_streams) { + g_list_foreach (client->active_streams, + (GFunc) gst_mpdparser_free_active_stream, NULL); + g_list_free (client->active_streams); + client->active_streams = NULL; + } +} + +void +gst_mpd_client_free (GstMpdClient * client) +{ + g_return_if_fail (client != NULL); + + if (client->mpd_node) + gst_mpdparser_free_mpd_node (client->mpd_node); + + if (client->periods) { + g_list_free_full (client->periods, + (GDestroyNotify) gst_mpdparser_free_stream_period); + } + + gst_mpd_client_active_streams_free (client); + + g_free (client->mpd_uri); + client->mpd_uri = NULL; + g_free (client->mpd_base_uri); + client->mpd_base_uri = NULL; + + if (client->downloader) + gst_object_unref (client->downloader); + client->downloader = NULL; + + g_free (client); +} + +gboolean +gst_mpd_client_parse (GstMpdClient * client, const gchar * data, gint size) +{ + gboolean ret = FALSE; + + + ret = gst_mpdparser_get_mpd_node (&client->mpd_node, data, size); + + if (ret) { + gst_mpd_client_check_profiles (client); + gst_mpd_client_fetch_on_load_external_resources (client); + } + + return ret; +} + +GstDateTime * +gst_mpd_client_get_availability_start_time (GstMpdClient * client) +{ + GstDateTime *start_time; + + if (client == NULL) + return (GstDateTime *) NULL; + + start_time = client->mpd_node->availabilityStartTime; + if (start_time) + gst_date_time_ref (start_time); + return start_time; +} + +void +gst_mpd_client_set_uri_downloader (GstMpdClient * client, + GstUriDownloader * downloader) +{ + if (client->downloader) + gst_object_unref (client->downloader); + client->downloader = gst_object_ref (downloader); +} + +void +gst_mpd_client_check_profiles (GstMpdClient * client) +{ + GST_DEBUG ("Profiles: %s", + client->mpd_node->profiles ? client->mpd_node->profiles : ""); + + if (!client->mpd_node->profiles) + return; + + if (g_strstr_len (client->mpd_node->profiles, -1, + "urn:mpeg:dash:profile:isoff-on-demand:2011")) { + client->profile_isoff_ondemand = TRUE; + GST_DEBUG ("Found ISOFF on demand profile (2011)"); + } +} + +void +gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client) +{ + GList *l; + + for (l = client->mpd_node->Periods; l; /* explicitly advanced below */ ) { + GstPeriodNode *period = l->data; + GList *m; + + if (period->xlink_href && period->actuate == GST_XLINK_ACTUATE_ON_LOAD) { + GList *new_periods, *prev, *next; + + new_periods = gst_mpd_client_fetch_external_periods (client, period); + + prev = l->prev; + client->mpd_node->Periods = + g_list_delete_link (client->mpd_node->Periods, l); + gst_mpdparser_free_period_node (period); + period = NULL; + + /* Get new next node, we will insert before this */ + if (prev) + next = prev->next; + else + next = client->mpd_node->Periods; + + while (new_periods) { + client->mpd_node->Periods = + g_list_insert_before (client->mpd_node->Periods, next, + new_periods->data); + new_periods = g_list_delete_link (new_periods, new_periods); + } + next = NULL; + + /* Update our iterator to the first new period if any, or the next */ + if (prev) + l = prev->next; + else + l = client->mpd_node->Periods; + + continue; + } + + if (period->SegmentList && period->SegmentList->xlink_href + && period->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) { + GstSegmentListNode *new_segment_list; + + new_segment_list = + gst_mpd_client_fetch_external_segment_list (client, period, NULL, + NULL, NULL, period->SegmentList); + + gst_mpdparser_free_segment_list_node (period->SegmentList); + period->SegmentList = new_segment_list; + } + + for (m = period->AdaptationSets; m; /* explicitly advanced below */ ) { + GstAdaptationSetNode *adapt_set = m->data; + GList *n; + + if (adapt_set->xlink_href + && adapt_set->actuate == GST_XLINK_ACTUATE_ON_LOAD) { + GList *new_adapt_sets, *prev, *next; + + new_adapt_sets = + gst_mpd_client_fetch_external_adaptation_set (client, period, + adapt_set); + + prev = m->prev; + period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m); + gst_mpdparser_free_adaptation_set_node (adapt_set); + adapt_set = NULL; + + /* Get new next node, we will insert before this */ + if (prev) + next = prev->next; + else + next = period->AdaptationSets; + + while (new_adapt_sets) { + period->AdaptationSets = + g_list_insert_before (period->AdaptationSets, next, + new_adapt_sets->data); + new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets); + } + next = NULL; + + /* Update our iterator to the first new adapt_set if any, or the next */ + if (prev) + m = prev->next; + else + m = period->AdaptationSets; + + continue; + } + + if (adapt_set->SegmentList && adapt_set->SegmentList->xlink_href + && adapt_set->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) { + GstSegmentListNode *new_segment_list; + + new_segment_list = + gst_mpd_client_fetch_external_segment_list (client, period, + adapt_set, NULL, period->SegmentList, adapt_set->SegmentList); + + gst_mpdparser_free_segment_list_node (adapt_set->SegmentList); + adapt_set->SegmentList = new_segment_list; + } + + for (n = adapt_set->Representations; n; n = n->next) { + GstRepresentationNode *representation = n->data; + + if (representation->SegmentList + && representation->SegmentList->xlink_href + && representation->SegmentList->actuate == + GST_XLINK_ACTUATE_ON_LOAD) { + + GstSegmentListNode *new_segment_list; + + new_segment_list = + gst_mpd_client_fetch_external_segment_list (client, period, + adapt_set, representation, adapt_set->SegmentList, + representation->SegmentList); + + gst_mpdparser_free_segment_list_node (representation->SegmentList); + representation->SegmentList = new_segment_list; + + } + } + + m = m->next; + } + + l = l->next; + } +} + + +static GstStreamPeriod * +gst_mpd_client_get_stream_period (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->periods != NULL, NULL); + + return g_list_nth_data (client->periods, client->period_idx); +} + +const gchar * +gst_mpd_client_get_baseURL (GstMpdClient * client, guint indexStream) +{ + GstActiveStream *stream; + + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->active_streams != NULL, NULL); + stream = g_list_nth_data (client->active_streams, indexStream); + g_return_val_if_fail (stream != NULL, NULL); + + return stream->baseURL; +} + +/* select a stream and extract the baseURL (if present) */ +gchar * +gst_mpd_client_parse_baseURL (GstMpdClient * client, GstActiveStream * stream, + gchar ** query) +{ + GstStreamPeriod *stream_period; + static const gchar empty[] = ""; + gchar *ret = NULL; + GstUri *abs_url; + + g_return_val_if_fail (stream != NULL, g_strdup (empty)); + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, g_strdup (empty)); + g_return_val_if_fail (stream_period->period != NULL, g_strdup (empty)); + + /* NULLify query return before we start */ + if (query) + *query = NULL; + + /* initialise base url */ + abs_url = + gst_uri_from_string (client-> + mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); + + /* combine a BaseURL at the MPD level with the current base url */ + abs_url = + combine_urls (abs_url, client->mpd_node->BaseURLs, query, + stream->baseURL_idx); + + /* combine a BaseURL at the Period level with the current base url */ + abs_url = + combine_urls (abs_url, stream_period->period->BaseURLs, query, + stream->baseURL_idx); + + GST_DEBUG ("Current adaptation set id %i (%s)", stream->cur_adapt_set->id, + stream->cur_adapt_set->contentType); + /* combine a BaseURL at the AdaptationSet level with the current base url */ + abs_url = + combine_urls (abs_url, stream->cur_adapt_set->BaseURLs, query, + stream->baseURL_idx); + + /* combine a BaseURL at the Representation level with the current base url */ + abs_url = + combine_urls (abs_url, stream->cur_representation->BaseURLs, query, + stream->baseURL_idx); + + ret = gst_uri_to_string (abs_url); + gst_uri_unref (abs_url); + + return ret; +} + +static GstClockTime +gst_mpd_client_get_segment_end_time (GstMpdClient * client, + GPtrArray * segments, const GstMediaSegment * segment, gint index) +{ + const GstStreamPeriod *stream_period; + GstClockTime end; + + if (segment->repeat >= 0) + return segment->start + (segment->repeat + 1) * segment->duration; + + if (index < segments->len - 1) { + const GstMediaSegment *next_segment = + g_ptr_array_index (segments, index + 1); + end = next_segment->start; + } else { + stream_period = gst_mpd_client_get_stream_period (client); + end = stream_period->start + stream_period->duration; + } + return end; +} + +static gboolean +gst_mpd_client_add_media_segment (GstActiveStream * stream, + GstSegmentURLNode * url_node, guint number, gint repeat, + guint64 scale_start, guint64 scale_duration, + GstClockTime start, GstClockTime duration) +{ + GstMediaSegment *media_segment; + + g_return_val_if_fail (stream->segments != NULL, FALSE); + + media_segment = g_slice_new0 (GstMediaSegment); + + media_segment->SegmentURL = url_node; + media_segment->number = number; + media_segment->scale_start = scale_start; + media_segment->scale_duration = scale_duration; + media_segment->start = start; + media_segment->duration = duration; + media_segment->repeat = repeat; + + g_ptr_array_add (stream->segments, media_segment); + GST_LOG ("Added new segment: number %d, repeat %d, " + "ts: %" GST_TIME_FORMAT ", dur: %" + GST_TIME_FORMAT, number, repeat, + GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); + + return TRUE; +} + +static void +gst_mpd_client_stream_update_presentation_time_offset (GstMpdClient * client, + GstActiveStream * stream) +{ + GstSegmentBaseType *segbase = NULL; + + /* Find the used segbase */ + if (stream->cur_segment_list) { + segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType; + } else if (stream->cur_seg_template) { + segbase = stream->cur_seg_template->MultSegBaseType->SegBaseType; + } else if (stream->cur_segment_base) { + segbase = stream->cur_segment_base; + } + + if (segbase) { + /* Avoid overflows */ + stream->presentationTimeOffset = + gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND, + segbase->timescale); + } else { + stream->presentationTimeOffset = 0; + } + + GST_LOG ("Setting stream's presentation time offset to %" GST_TIME_FORMAT, + GST_TIME_ARGS (stream->presentationTimeOffset)); +} + +gboolean +gst_mpd_client_setup_representation (GstMpdClient * client, + GstActiveStream * stream, GstRepresentationNode * representation) +{ + GstStreamPeriod *stream_period; + GList *rep_list; + GstClockTime PeriodStart, PeriodEnd, start_time, duration; + guint i; + guint64 start; + + if (stream->cur_adapt_set == NULL) { + GST_WARNING ("No valid AdaptationSet node in the MPD file, aborting..."); + return FALSE; + } + + rep_list = stream->cur_adapt_set->Representations; + stream->cur_representation = representation; + stream->representation_idx = g_list_index (rep_list, representation); + + /* clean the old segment list, if any */ + if (stream->segments) { + g_ptr_array_unref (stream->segments); + stream->segments = NULL; + } + + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + PeriodStart = stream_period->start; + if (GST_CLOCK_TIME_IS_VALID (stream_period->duration)) + PeriodEnd = stream_period->start + stream_period->duration; + else + PeriodEnd = GST_CLOCK_TIME_NONE; + + GST_LOG ("Building segment list for Period from %" GST_TIME_FORMAT " to %" + GST_TIME_FORMAT, GST_TIME_ARGS (PeriodStart), GST_TIME_ARGS (PeriodEnd)); + + if (representation->SegmentBase != NULL + || representation->SegmentList != NULL) { + GList *SegmentURL; + + /* We have a fixed list of segments for any of the cases here, + * init the segments list */ + gst_mpdparser_init_active_stream_segments (stream); + + /* get the first segment_base of the selected representation */ + if ((stream->cur_segment_base = + gst_mpd_client_get_segment_base (stream_period->period, + stream->cur_adapt_set, representation)) == NULL) { + GST_DEBUG ("No useful SegmentBase node for the current Representation"); + } + + /* get the first segment_list of the selected representation */ + if ((stream->cur_segment_list = + gst_mpd_client_get_segment_list (client, stream_period->period, + stream->cur_adapt_set, representation)) == NULL) { + GST_DEBUG ("No useful SegmentList node for the current Representation"); + /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ + if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0, + PeriodEnd - PeriodStart, PeriodStart, PeriodEnd - PeriodStart)) { + return FALSE; + } + } else { + /* build the list of GstMediaSegment nodes from the SegmentList node */ + SegmentURL = stream->cur_segment_list->SegmentURL; + if (SegmentURL == NULL) { + GST_WARNING + ("No valid list of SegmentURL nodes in the MPD file, aborting..."); + return FALSE; + } + + /* build segment list */ + i = stream->cur_segment_list->MultSegBaseType->startNumber; + start = 0; + start_time = PeriodStart; + + GST_LOG ("Building media segment list using a SegmentList node"); + if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) { + GstSegmentTimelineNode *timeline; + GstSNode *S; + GList *list; + GstClockTime presentationTimeOffset; + GstSegmentBaseType *segbase; + + segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType; + presentationTimeOffset = + gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND, + segbase->timescale); + GST_LOG ("presentationTimeOffset = %" G_GUINT64_FORMAT, + presentationTimeOffset); + + timeline = stream->cur_segment_list->MultSegBaseType->SegmentTimeline; + for (list = g_queue_peek_head_link (&timeline->S); list; + list = g_list_next (list)) { + guint timescale; + + S = (GstSNode *) list->data; + GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%d t=%" + G_GUINT64_FORMAT, S->d, S->r, S->t); + timescale = + stream->cur_segment_list->MultSegBaseType->SegBaseType->timescale; + duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale); + + if (S->t > 0) { + start = S->t; + start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale) + + PeriodStart - presentationTimeOffset; + } + + if (!SegmentURL) { + GST_WARNING + ("SegmentTimeline does not have a matching SegmentURL, aborting..."); + return FALSE; + } + + if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, + S->r, start, S->d, start_time, duration)) { + return FALSE; + } + i += S->r + 1; + start_time += duration * (S->r + 1); + start += S->d * (S->r + 1); + SegmentURL = g_list_next (SegmentURL); + } + } else { + guint64 scale_dur; + + duration = + gst_mpd_client_get_segment_duration (client, stream, &scale_dur); + if (!GST_CLOCK_TIME_IS_VALID (duration)) + return FALSE; + + while (SegmentURL) { + if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, + 0, start, scale_dur, start_time, duration)) { + return FALSE; + } + i++; + start += scale_dur; + start_time += duration; + SegmentURL = g_list_next (SegmentURL); + } + } + } + } else { + if (representation->SegmentTemplate != NULL) { + stream->cur_seg_template = representation->SegmentTemplate; + } else if (stream->cur_adapt_set->SegmentTemplate != NULL) { + stream->cur_seg_template = stream->cur_adapt_set->SegmentTemplate; + } else if (stream_period->period->SegmentTemplate != NULL) { + stream->cur_seg_template = stream_period->period->SegmentTemplate; + } + + if (stream->cur_seg_template == NULL + || stream->cur_seg_template->MultSegBaseType == NULL) { + + gst_mpdparser_init_active_stream_segments (stream); + /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ + if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0, + PeriodEnd - PeriodStart, 0, PeriodEnd - PeriodStart)) { + return FALSE; + } + } else { + GstClockTime presentationTimeOffset; + GstMultSegmentBaseType *mult_seg = + stream->cur_seg_template->MultSegBaseType; + presentationTimeOffset = + gst_util_uint64_scale (mult_seg->SegBaseType->presentationTimeOffset, + GST_SECOND, mult_seg->SegBaseType->timescale); + GST_LOG ("presentationTimeOffset = %" GST_TIME_FORMAT, + GST_TIME_ARGS (presentationTimeOffset)); + /* build segment list */ + i = mult_seg->startNumber; + start = 0; + start_time = 0; + + GST_LOG ("Building media segment list using this template: %s", + stream->cur_seg_template->media); + + if (mult_seg->SegmentTimeline) { + GstSegmentTimelineNode *timeline; + GstSNode *S; + GList *list; + + timeline = mult_seg->SegmentTimeline; + gst_mpdparser_init_active_stream_segments (stream); + for (list = g_queue_peek_head_link (&timeline->S); list; + list = g_list_next (list)) { + guint timescale; + + S = (GstSNode *) list->data; + GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%u t=%" + G_GUINT64_FORMAT, S->d, S->r, S->t); + timescale = mult_seg->SegBaseType->timescale; + duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale); + if (S->t > 0) { + start = S->t; + start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale) + + PeriodStart - presentationTimeOffset; + } + + if (!gst_mpd_client_add_media_segment (stream, NULL, i, S->r, start, + S->d, start_time, duration)) { + return FALSE; + } + i += S->r + 1; + start += S->d * (S->r + 1); + start_time += duration * (S->r + 1); + } + } else { + /* NOP - The segment is created on demand with the template, no need + * to build a list */ + } + } + } + + /* clip duration of segments to stop at period end */ + if (stream->segments && stream->segments->len) { + if (GST_CLOCK_TIME_IS_VALID (PeriodEnd)) { + guint n; + + for (n = 0; n < stream->segments->len; ++n) { + GstMediaSegment *media_segment = + g_ptr_array_index (stream->segments, n); + if (media_segment) { + if (media_segment->start + media_segment->duration > PeriodEnd) { + GstClockTime stop = PeriodEnd; + if (n < stream->segments->len - 1) { + GstMediaSegment *next_segment = + g_ptr_array_index (stream->segments, n + 1); + if (next_segment && next_segment->start < PeriodEnd) + stop = next_segment->start; + } + media_segment->duration = + media_segment->start > stop ? 0 : stop - media_segment->start; + GST_LOG ("Fixed duration of segment %u: %" GST_TIME_FORMAT, n, + GST_TIME_ARGS (media_segment->duration)); + + /* If the segment was clipped entirely, we discard it and all + * subsequent ones */ + if (media_segment->duration == 0) { + GST_WARNING ("Discarding %u segments outside period", + stream->segments->len - n); + /* _set_size should properly unref elements */ + g_ptr_array_set_size (stream->segments, n); + break; + } + } + } + } + } +#ifndef GST_DISABLE_GST_DEBUG + if (stream->segments->len > 0) { + GstMediaSegment *last_media_segment = + g_ptr_array_index (stream->segments, stream->segments->len - 1); + GST_LOG ("Built a list of %d segments", last_media_segment->number); + } else { + GST_LOG ("All media segments were clipped"); + } +#endif + } + + g_free (stream->baseURL); + g_free (stream->queryURL); + stream->baseURL = + gst_mpd_client_parse_baseURL (client, stream, &stream->queryURL); + + gst_mpd_client_stream_update_presentation_time_offset (client, stream); + + return TRUE; +} + +#define CUSTOM_WRAPPER_START "" +#define CUSTOM_WRAPPER_END "" + +static GList * +gst_mpd_client_fetch_external_periods (GstMpdClient * client, + GstPeriodNode * period_node) +{ + GstFragment *download; + GstAdapter *adapter; + GstBuffer *period_buffer; + GError *err = NULL; + + GstUri *base_uri, *uri; + gchar *query = NULL; + gchar *uri_string, *wrapper; + GList *new_periods = NULL; + const gchar *data; + + /* ISO/IEC 23009-1:2014 5.5.3 4) + * Remove nodes that resolve to nothing when resolving + */ + if (strcmp (period_node->xlink_href, + "urn:mpeg:dash:resolve-to-zero:2013") == 0) { + return NULL; + } + + if (!client->downloader) { + return NULL; + } + + /* Build absolute URI */ + + /* Get base URI at the MPD level */ + base_uri = + gst_uri_from_string (client-> + mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); + + /* combine a BaseURL at the MPD level with the current base url */ + base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); + uri = gst_uri_from_string_with_base (base_uri, period_node->xlink_href); + if (query) + gst_uri_set_query_string (uri, query); + g_free (query); + uri_string = gst_uri_to_string (uri); + gst_uri_unref (base_uri); + gst_uri_unref (uri); + + download = + gst_uri_downloader_fetch_uri (client->downloader, + uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); + g_free (uri_string); + + if (!download) { + GST_ERROR ("Failed to download external Period node at '%s': %s", + period_node->xlink_href, err->message); + g_clear_error (&err); + return NULL; + } + + period_buffer = gst_fragment_get_buffer (download); + g_object_unref (download); + + /* external xml could have multiple period without root xmlNode. + * To avoid xml parsing error caused by no root node, wrapping it with + * custom root node */ + adapter = gst_adapter_new (); + + wrapper = g_new (gchar, strlen (CUSTOM_WRAPPER_START)); + memcpy (wrapper, CUSTOM_WRAPPER_START, strlen (CUSTOM_WRAPPER_START)); + gst_adapter_push (adapter, + gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_START))); + + gst_adapter_push (adapter, period_buffer); + + wrapper = g_strdup (CUSTOM_WRAPPER_END); + gst_adapter_push (adapter, + gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_END) + 1)); + + data = gst_adapter_map (adapter, gst_adapter_available (adapter)); + + new_periods = + gst_mpdparser_get_external_periods (data, + gst_adapter_available (adapter)); + + gst_adapter_unmap (adapter); + gst_adapter_clear (adapter); + gst_object_unref (adapter); + + return new_periods; +} + +gboolean +gst_mpd_client_setup_media_presentation (GstMpdClient * client, + GstClockTime time, gint period_idx, const gchar * period_id) +{ + GstStreamPeriod *stream_period; + GstClockTime start, duration; + GList *list, *next; + guint idx; + gboolean ret = FALSE; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->mpd_node != NULL, FALSE); + + /* Check if we set up the media presentation far enough already */ + for (list = client->periods; list; list = list->next) { + GstStreamPeriod *stream_period = list->data; + + if ((time != GST_CLOCK_TIME_NONE + && stream_period->duration != GST_CLOCK_TIME_NONE + && stream_period->start + stream_period->duration >= time) + || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time)) + return TRUE; + + if (period_idx != -1 && stream_period->number >= period_idx) + return TRUE; + + if (period_id != NULL && stream_period->period->id != NULL + && strcmp (stream_period->period->id, period_id) == 0) + return TRUE; + + } + + GST_DEBUG ("Building the list of Periods in the Media Presentation"); + /* clean the old period list, if any */ + /* TODO: In theory we could reuse the ones we have so far but that + * seems more complicated than the overhead caused here + */ + if (client->periods) { + g_list_foreach (client->periods, + (GFunc) gst_mpdparser_free_stream_period, NULL); + g_list_free (client->periods); + client->periods = NULL; + } + + idx = 0; + start = 0; + duration = GST_CLOCK_TIME_NONE; + + if (client->mpd_node->mediaPresentationDuration <= 0 && + client->mpd_node->mediaPresentationDuration != -1) { + /* Invalid MPD file: MPD duration is negative or zero */ + goto syntax_error; + } + + for (list = client->mpd_node->Periods; list; /* explicitly advanced below */ ) { + GstPeriodNode *period_node = list->data; + GstPeriodNode *next_period_node = NULL; + + /* Download external period */ + if (period_node->xlink_href) { + GList *new_periods; + GList *prev; + + new_periods = gst_mpd_client_fetch_external_periods (client, period_node); + + prev = list->prev; + client->mpd_node->Periods = + g_list_delete_link (client->mpd_node->Periods, list); + gst_mpdparser_free_period_node (period_node); + period_node = NULL; + + /* Get new next node, we will insert before this */ + if (prev) + next = prev->next; + else + next = client->mpd_node->Periods; + + while (new_periods) { + client->mpd_node->Periods = + g_list_insert_before (client->mpd_node->Periods, next, + new_periods->data); + new_periods = g_list_delete_link (new_periods, new_periods); + } + next = NULL; + + /* Update our iterator to the first new period if any, or the next */ + if (prev) + list = prev->next; + else + list = client->mpd_node->Periods; + + /* And try again */ + continue; + } + + if (period_node->start != -1) { + /* we have a regular period */ + /* start cannot be smaller than previous start */ + if (list != g_list_first (client->mpd_node->Periods) + && start >= period_node->start * GST_MSECOND) { + /* Invalid MPD file: duration would be negative or zero */ + goto syntax_error; + } + start = period_node->start * GST_MSECOND; + } else if (duration != GST_CLOCK_TIME_NONE) { + /* start time inferred from previous period, this is still a regular period */ + start += duration; + } else if (idx == 0 && client->mpd_node->type == GST_MPD_FILE_TYPE_STATIC) { + /* first period of a static MPD file, start time is 0 */ + start = 0; + } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { + /* this should be a live stream, let this pass */ + } else { + /* this is an 'Early Available Period' */ + goto early; + } + + /* compute duration. + If there is a start time for the next period, or this is the last period + and mediaPresentationDuration was set, those values will take precedence + over a configured period duration in computing this period's duration + + ISO/IEC 23009-1:2014(E), chapter 5.3.2.1 + "The Period extends until the PeriodStart of the next Period, or until + the end of the Media Presentation in the case of the last Period." + */ + + while ((next = g_list_next (list)) != NULL) { + /* try to infer this period duration from the start time of the next period */ + next_period_node = next->data; + + if (next_period_node->xlink_href) { + GList *new_periods; + + new_periods = + gst_mpd_client_fetch_external_periods (client, next_period_node); + + client->mpd_node->Periods = + g_list_delete_link (client->mpd_node->Periods, next); + gst_mpdparser_free_period_node (next_period_node); + next_period_node = NULL; + /* Get new next node, we will insert before this */ + next = g_list_next (list); + while (new_periods) { + client->mpd_node->Periods = + g_list_insert_before (client->mpd_node->Periods, next, + new_periods->data); + new_periods = g_list_delete_link (new_periods, new_periods); + } + + /* And try again, getting the next list element which is now our newly + * inserted nodes. If any */ + } else { + /* Got the next period and it doesn't have to be downloaded first */ + break; + } + } + + if (next_period_node) { + if (next_period_node->start != -1) { + if (start >= next_period_node->start * GST_MSECOND) { + /* Invalid MPD file: duration would be negative or zero */ + goto syntax_error; + } + duration = next_period_node->start * GST_MSECOND - start; + } else if (period_node->duration != -1) { + if (period_node->duration <= 0) { + /* Invalid MPD file: duration would be negative or zero */ + goto syntax_error; + } + duration = period_node->duration * GST_MSECOND; + } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { + /* might be a live file, ignore unspecified duration */ + } else { + /* Invalid MPD file! */ + goto syntax_error; + } + } else if (client->mpd_node->mediaPresentationDuration != -1) { + /* last Period of the Media Presentation */ + if (client->mpd_node->mediaPresentationDuration * GST_MSECOND <= start) { + /* Invalid MPD file: duration would be negative or zero */ + goto syntax_error; + } + duration = + client->mpd_node->mediaPresentationDuration * GST_MSECOND - start; + } else if (period_node->duration != -1) { + duration = period_node->duration * GST_MSECOND; + } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { + /* might be a live file, ignore unspecified duration */ + } else { + /* Invalid MPD file! */ + goto syntax_error; + } + + stream_period = g_slice_new0 (GstStreamPeriod); + client->periods = g_list_append (client->periods, stream_period); + stream_period->period = period_node; + stream_period->number = idx++; + stream_period->start = start; + stream_period->duration = duration; + ret = TRUE; + GST_LOG (" - added Period %d start=%" GST_TIME_FORMAT " duration=%" + GST_TIME_FORMAT, idx, GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); + + if ((time != GST_CLOCK_TIME_NONE + && stream_period->duration != GST_CLOCK_TIME_NONE + && stream_period->start + stream_period->duration >= time) + || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time)) + break; + + if (period_idx != -1 && stream_period->number >= period_idx) + break; + + if (period_id != NULL && stream_period->period->id != NULL + && strcmp (stream_period->period->id, period_id) == 0) + break; + + list = list->next; + } + + GST_DEBUG + ("Found a total of %d valid Periods in the Media Presentation up to this point", + idx); + return ret; + +early: + GST_WARNING + ("Found an Early Available Period, skipping the rest of the Media Presentation"); + return ret; + +syntax_error: + GST_WARNING + ("Cannot get the duration of the Period %d, skipping the rest of the Media Presentation", + idx); + return ret; +} + +static GList * +gst_mpd_client_fetch_external_adaptation_set (GstMpdClient * client, + GstPeriodNode * period, GstAdaptationSetNode * adapt_set) +{ + GstFragment *download; + GstBuffer *adapt_set_buffer; + GstMapInfo map; + GError *err = NULL; + GstUri *base_uri, *uri; + gchar *query = NULL; + gchar *uri_string; + GList *new_adapt_sets = NULL; + + /* ISO/IEC 23009-1:2014 5.5.3 4) + * Remove nodes that resolve to nothing when resolving + */ + if (strcmp (adapt_set->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013") == 0) { + return NULL; + } + + if (!client->downloader) { + return NULL; + } + + /* Build absolute URI */ + + /* Get base URI at the MPD level */ + base_uri = + gst_uri_from_string (client-> + mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); + + /* combine a BaseURL at the MPD level with the current base url */ + base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); + + /* combine a BaseURL at the Period level with the current base url */ + base_uri = combine_urls (base_uri, period->BaseURLs, &query, 0); + + uri = gst_uri_from_string_with_base (base_uri, adapt_set->xlink_href); + if (query) + gst_uri_set_query_string (uri, query); + g_free (query); + uri_string = gst_uri_to_string (uri); + gst_uri_unref (base_uri); + gst_uri_unref (uri); + + download = + gst_uri_downloader_fetch_uri (client->downloader, + uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); + g_free (uri_string); + + if (!download) { + GST_ERROR ("Failed to download external AdaptationSet node at '%s': %s", + adapt_set->xlink_href, err->message); + g_clear_error (&err); + return NULL; + } + + adapt_set_buffer = gst_fragment_get_buffer (download); + g_object_unref (download); + + gst_buffer_map (adapt_set_buffer, &map, GST_MAP_READ); + + new_adapt_sets = + gst_mpdparser_get_external_adaptation_sets ((const gchar *) map.data, + map.size, period); + + gst_buffer_unmap (adapt_set_buffer, &map); + gst_buffer_unref (adapt_set_buffer); + + return new_adapt_sets; +} + +static GList * +gst_mpd_client_get_adaptation_sets_for_period (GstMpdClient * client, + GstStreamPeriod * period) +{ + GList *list; + + g_return_val_if_fail (period != NULL, NULL); + + /* Resolve all external adaptation sets of this period. Every user of + * the adaptation sets would need to know the content of all adaptation sets + * to decide which one to use, so we have to resolve them all here + */ + for (list = period->period->AdaptationSets; list; + /* advanced explicitly below */ ) { + GstAdaptationSetNode *adapt_set = (GstAdaptationSetNode *) list->data; + GList *new_adapt_sets = NULL, *prev, *next; + + if (!adapt_set->xlink_href) { + list = list->next; + continue; + } + + new_adapt_sets = + gst_mpd_client_fetch_external_adaptation_set (client, period->period, + adapt_set); + + prev = list->prev; + period->period->AdaptationSets = + g_list_delete_link (period->period->AdaptationSets, list); + gst_mpdparser_free_adaptation_set_node (adapt_set); + adapt_set = NULL; + + /* Get new next node, we will insert before this */ + if (prev) + next = prev->next; + else + next = period->period->AdaptationSets; + + while (new_adapt_sets) { + period->period->AdaptationSets = + g_list_insert_before (period->period->AdaptationSets, next, + new_adapt_sets->data); + new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets); + } + + /* Update our iterator to the first new adaptation set if any, or the next */ + if (prev) + list = prev->next; + else + list = period->period->AdaptationSets; + } + + return period->period->AdaptationSets; +} + +GList * +gst_mpd_client_get_adaptation_sets (GstMpdClient * client) +{ + GstStreamPeriod *stream_period; + + stream_period = gst_mpd_client_get_stream_period (client); + if (stream_period == NULL || stream_period->period == NULL) { + GST_DEBUG ("No more Period nodes in the MPD file, terminating..."); + return NULL; + } + + return gst_mpd_client_get_adaptation_sets_for_period (client, stream_period); +} + +gboolean +gst_mpd_client_setup_streaming (GstMpdClient * client, + GstAdaptationSetNode * adapt_set) +{ + GstRepresentationNode *representation; + GList *rep_list = NULL; + GstActiveStream *stream; + + rep_list = adapt_set->Representations; + if (!rep_list) { + GST_WARNING ("Can not retrieve any representation, aborting..."); + return FALSE; + } + + stream = g_slice_new0 (GstActiveStream); + gst_mpdparser_init_active_stream_segments (stream); + + stream->baseURL_idx = 0; + stream->cur_adapt_set = adapt_set; + + GST_DEBUG ("0. Current stream %p", stream); + +#if 0 + /* fast start */ + representation = + gst_mpdparser_get_representation_with_max_bandwidth (rep_list, + stream->max_bandwidth); + + if (!representation) { + GST_WARNING + ("Can not retrieve a representation with the requested bandwidth"); + representation = gst_mpd_client_get_lowest_representation (rep_list); + } +#else + /* slow start */ + representation = gst_mpd_client_get_lowest_representation (rep_list); +#endif + + if (!representation) { + GST_WARNING ("No valid representation in the MPD file, aborting..."); + gst_mpdparser_free_active_stream (stream); + return FALSE; + } + stream->mimeType = + gst_mpdparser_representation_get_mimetype (adapt_set, representation); + if (stream->mimeType == GST_STREAM_UNKNOWN) { + GST_WARNING ("Unknown mime type in the representation, aborting..."); + gst_mpdparser_free_active_stream (stream); + return FALSE; + } + + client->active_streams = g_list_append (client->active_streams, stream); + if (!gst_mpd_client_setup_representation (client, stream, representation)) { + GST_WARNING ("Failed to setup the representation, aborting..."); + return FALSE; + } + + GST_INFO ("Successfully setup the download pipeline for mimeType %d", + stream->mimeType); + + return TRUE; +} + +gboolean +gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * stream, + gboolean forward, GstSeekFlags flags, GstClockTime ts, + GstClockTime * final_ts) +{ + gint index = 0; + gint repeat_index = 0; + GstMediaSegment *selectedChunk = NULL; + + g_return_val_if_fail (stream != NULL, 0); + + if (stream->segments) { + for (index = 0; index < stream->segments->len; index++) { + gboolean in_segment = FALSE; + GstMediaSegment *segment = g_ptr_array_index (stream->segments, index); + GstClockTime end_time; + + GST_DEBUG ("Looking at fragment sequence chunk %d / %d", index, + stream->segments->len); + + end_time = + gst_mpd_client_get_segment_end_time (client, stream->segments, + segment, index); + + /* avoid downloading another fragment just for 1ns in reverse mode */ + if (forward) + in_segment = ts < end_time; + else + in_segment = ts <= end_time; + + if (in_segment) { + GstClockTime chunk_time; + + selectedChunk = segment; + repeat_index = (ts - segment->start) / segment->duration; + + chunk_time = segment->start + segment->duration * repeat_index; + + /* At the end of a segment in reverse mode, start from the previous fragment */ + if (!forward && repeat_index > 0 + && ((ts - segment->start) % segment->duration == 0)) + repeat_index--; + + if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) { + if (repeat_index + 1 < segment->repeat) { + if (ts - chunk_time > chunk_time + segment->duration - ts) + repeat_index++; + } else if (index + 1 < stream->segments->len) { + GstMediaSegment *next_segment = + g_ptr_array_index (stream->segments, index + 1); + + if (ts - chunk_time > next_segment->start - ts) { + repeat_index = 0; + selectedChunk = next_segment; + index++; + } + } + } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) || + (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) && + ts != chunk_time) { + + if (repeat_index + 1 < segment->repeat) { + repeat_index++; + } else { + repeat_index = 0; + if (index + 1 >= stream->segments->len) { + selectedChunk = NULL; + } else { + selectedChunk = g_ptr_array_index (stream->segments, ++index); + } + } + } + break; + } + } + + if (selectedChunk == NULL) { + stream->segment_index = stream->segments->len; + stream->segment_repeat_index = 0; + GST_DEBUG ("Seek to after last segment"); + return FALSE; + } + + if (final_ts) + *final_ts = selectedChunk->start + selectedChunk->duration * repeat_index; + } else { + GstClockTime duration = + gst_mpd_client_get_segment_duration (client, stream, NULL); + GstStreamPeriod *stream_period = gst_mpd_client_get_stream_period (client); + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + GstClockTime index_time; + + g_return_val_if_fail (stream->cur_seg_template-> + MultSegBaseType->SegmentTimeline == NULL, FALSE); + if (!GST_CLOCK_TIME_IS_VALID (duration)) { + return FALSE; + } + + if (ts > stream_period->start) + ts -= stream_period->start; + else + ts = 0; + + index = ts / duration; + + /* At the end of a segment in reverse mode, start from the previous fragment */ + if (!forward && index > 0 && ts % duration == 0) + index--; + + index_time = index * duration; + + if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) { + if (ts - index_time > index_time + duration - ts) + index++; + } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) || + (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) + && ts != index_time) { + index++; + } + + if (segments_count > 0 && index >= segments_count) { + stream->segment_index = segments_count; + stream->segment_repeat_index = 0; + GST_DEBUG ("Seek to after last segment"); + return FALSE; + } + if (final_ts) + *final_ts = index * duration; + } + + stream->segment_repeat_index = repeat_index; + stream->segment_index = index; + + return TRUE; +} + +gint64 +gst_mpd_client_calculate_time_difference (const GstDateTime * t1, + const GstDateTime * t2) +{ + GDateTime *gdt1, *gdt2; + GTimeSpan diff; + + g_assert (t1 != NULL && t2 != NULL); + gdt1 = gst_date_time_to_g_date_time ((GstDateTime *) t1); + gdt2 = gst_date_time_to_g_date_time ((GstDateTime *) t2); + diff = g_date_time_difference (gdt2, gdt1); + g_date_time_unref (gdt1); + g_date_time_unref (gdt2); + return diff * GST_USECOND; +} + +GstDateTime * +gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs) +{ + GDateTime *gdt; + GDateTime *gdt2; + GstDateTime *rv; + + g_assert (t1 != NULL); + gdt = gst_date_time_to_g_date_time (t1); + g_assert (gdt != NULL); + gdt2 = g_date_time_add (gdt, usecs); + g_assert (gdt2 != NULL); + g_date_time_unref (gdt); + rv = gst_date_time_new_from_g_date_time (gdt2); + + /* Don't g_date_time_unref(gdt2) because gst_date_time_new_from_g_date_time takes + * ownership of the GDateTime pointer. + */ + + return rv; +} + +gboolean +gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client, + guint stream_idx, GstClockTime * ts) +{ + GstActiveStream *stream; + gint segment_idx; + GstMediaSegment *currentChunk; + GstStreamPeriod *stream_period; + + GST_DEBUG ("Stream index: %i", stream_idx); + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, 0); + + if (!stream->segments) { + stream_period = gst_mpd_client_get_stream_period (client); + *ts = stream_period->start + stream_period->duration; + } else { + segment_idx = gst_mpd_client_get_segments_counts (client, stream) - 1; + if (segment_idx >= stream->segments->len) { + GST_WARNING ("Segment index %d is outside of segment list of length %d", + segment_idx, stream->segments->len); + return FALSE; + } + currentChunk = g_ptr_array_index (stream->segments, segment_idx); + + if (currentChunk->repeat >= 0) { + *ts = + currentChunk->start + (currentChunk->duration * (1 + + currentChunk->repeat)); + } else { + /* 5.3.9.6.1: negative repeat means repeat till the end of the + * period, or the next update of the MPD (which I think is + * implicit, as this will all get deleted/recreated), or the + * start of the next segment, if any. */ + stream_period = gst_mpd_client_get_stream_period (client); + *ts = stream_period->start + stream_period->duration; + } + } + + return TRUE; +} + +gboolean +gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, + guint stream_idx, GstClockTime * ts) +{ + GstActiveStream *stream; + GstMediaSegment *currentChunk; + + GST_DEBUG ("Stream index: %i", stream_idx); + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, 0); + + if (stream->segments) { + GST_DEBUG ("Looking for fragment sequence chunk %d / %d", + stream->segment_index, stream->segments->len); + if (stream->segment_index >= stream->segments->len) + return FALSE; + currentChunk = g_ptr_array_index (stream->segments, stream->segment_index); + + *ts = + currentChunk->start + + (currentChunk->duration * stream->segment_repeat_index); + } else { + GstClockTime duration = + gst_mpd_client_get_segment_duration (client, stream, NULL); + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + + g_return_val_if_fail (stream->cur_seg_template-> + MultSegBaseType->SegmentTimeline == NULL, FALSE); + if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 + && stream->segment_index >= segments_count)) { + return FALSE; + } + *ts = stream->segment_index * duration; + } + + return TRUE; +} + +GstClockTime +gst_mpd_client_get_stream_presentation_offset (GstMpdClient * client, + guint stream_idx) +{ + GstActiveStream *stream = NULL; + + g_return_val_if_fail (client != NULL, 0); + g_return_val_if_fail (client->active_streams != NULL, 0); + stream = g_list_nth_data (client->active_streams, stream_idx); + g_return_val_if_fail (stream != NULL, 0); + + return stream->presentationTimeOffset; +} + +GstClockTime +gst_mpd_client_get_period_start_time (GstMpdClient * client) +{ + GstStreamPeriod *stream_period = NULL; + + g_return_val_if_fail (client != NULL, 0); + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, 0); + + return stream_period->start; +} + +/** + * 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) +{ + GstActiveStream *stream = NULL; + GstMediaSegment *currentChunk; + gchar *mediaURL = NULL; + gchar *indexURL = NULL; + GstUri *base_url, *frag_url; + + /* select stream */ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->active_streams != NULL, FALSE); + stream = g_list_nth_data (client->active_streams, indexStream); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != NULL, FALSE); + + if (stream->segments) { + GST_DEBUG ("Looking for fragment sequence chunk %d / %d", + stream->segment_index, stream->segments->len); + if (stream->segment_index >= stream->segments->len) + return FALSE; + } else { + GstClockTime duration = gst_mpd_client_get_segment_duration (client, + stream, NULL); + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + + g_return_val_if_fail (stream->cur_seg_template-> + MultSegBaseType->SegmentTimeline == NULL, FALSE); + if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 + && stream->segment_index >= segments_count)) { + return FALSE; + } + fragment->duration = duration; + } + + /* FIXME rework discont checking */ + /* fragment->discontinuity = segment_idx != currentChunk.number; */ + fragment->range_start = 0; + fragment->range_end = -1; + fragment->index_uri = NULL; + fragment->index_range_start = 0; + fragment->index_range_end = -1; + + if (stream->segments) { + currentChunk = g_ptr_array_index (stream->segments, stream->segment_index); + + GST_DEBUG ("currentChunk->SegmentURL = %p", currentChunk->SegmentURL); + if (currentChunk->SegmentURL != NULL) { + mediaURL = + g_strdup (gst_mpdparser_get_mediaURL (stream, + currentChunk->SegmentURL)); + indexURL = g_strdup (currentChunk->SegmentURL->index); + } else if (stream->cur_seg_template != NULL) { + mediaURL = + gst_mpdparser_build_URL_from_template (stream-> + cur_seg_template->media, stream->cur_representation->id, + currentChunk->number + stream->segment_repeat_index, + stream->cur_representation->bandwidth, + currentChunk->scale_start + + stream->segment_repeat_index * currentChunk->scale_duration); + if (stream->cur_seg_template->index) { + indexURL = + gst_mpdparser_build_URL_from_template (stream-> + cur_seg_template->index, stream->cur_representation->id, + currentChunk->number + stream->segment_repeat_index, + stream->cur_representation->bandwidth, + currentChunk->scale_start + + stream->segment_repeat_index * currentChunk->scale_duration); + } + } + GST_DEBUG ("mediaURL = %s", mediaURL); + GST_DEBUG ("indexURL = %s", indexURL); + + fragment->timestamp = + currentChunk->start + + stream->segment_repeat_index * currentChunk->duration; + fragment->duration = currentChunk->duration; + if (currentChunk->SegmentURL) { + if (currentChunk->SegmentURL->mediaRange) { + fragment->range_start = + currentChunk->SegmentURL->mediaRange->first_byte_pos; + fragment->range_end = + currentChunk->SegmentURL->mediaRange->last_byte_pos; + } + if (currentChunk->SegmentURL->indexRange) { + fragment->index_range_start = + currentChunk->SegmentURL->indexRange->first_byte_pos; + fragment->index_range_end = + currentChunk->SegmentURL->indexRange->last_byte_pos; + } + } + } else { + if (stream->cur_seg_template != NULL) { + mediaURL = + gst_mpdparser_build_URL_from_template (stream-> + cur_seg_template->media, stream->cur_representation->id, + stream->segment_index + + stream->cur_seg_template->MultSegBaseType->startNumber, + stream->cur_representation->bandwidth, + stream->segment_index * fragment->duration); + if (stream->cur_seg_template->index) { + indexURL = + gst_mpdparser_build_URL_from_template (stream-> + cur_seg_template->index, stream->cur_representation->id, + stream->segment_index + + stream->cur_seg_template->MultSegBaseType->startNumber, + stream->cur_representation->bandwidth, + stream->segment_index * fragment->duration); + } + } else { + return FALSE; + } + + GST_DEBUG ("mediaURL = %s", mediaURL); + GST_DEBUG ("indexURL = %s", indexURL); + + fragment->timestamp = stream->segment_index * fragment->duration; + } + + base_url = gst_uri_from_string (stream->baseURL); + frag_url = gst_uri_from_string_with_base (base_url, mediaURL); + g_free (mediaURL); + if (stream->queryURL) { + frag_url = gst_uri_make_writable (frag_url); + gst_uri_set_query_string (frag_url, stream->queryURL); + } + fragment->uri = gst_uri_to_string (frag_url); + gst_uri_unref (frag_url); + + if (indexURL != NULL) { + frag_url = gst_uri_make_writable (gst_uri_from_string_with_base (base_url, + indexURL)); + gst_uri_set_query_string (frag_url, stream->queryURL); + fragment->index_uri = gst_uri_to_string (frag_url); + gst_uri_unref (frag_url); + g_free (indexURL); + } else if (indexURL == NULL && (fragment->index_range_start + || fragment->index_range_end != -1)) { + /* index has no specific URL but has a range, we should only use this if + * the media also has a range, otherwise we are serving some data twice + * (in the media fragment and again in the index) */ + if (!(fragment->range_start || fragment->range_end != -1)) { + GST_WARNING ("Ignoring index ranges because there isn't a media range " + "and URIs would be the same"); + /* removing index information */ + fragment->index_range_start = 0; + fragment->index_range_end = -1; + } + } + + gst_uri_unref (base_url); + + GST_DEBUG ("Loading chunk with URL %s", fragment->uri); + + return TRUE; +} + +gboolean +gst_mpd_client_has_next_segment (GstMpdClient * client, + GstActiveStream * stream, gboolean forward) +{ + if (forward) { + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + + if (segments_count > 0 && stream->segments + && stream->segment_index + 1 == segments_count) { + GstMediaSegment *segment; + + segment = g_ptr_array_index (stream->segments, stream->segment_index); + if (segment->repeat >= 0 + && stream->segment_repeat_index >= segment->repeat) + return FALSE; + } else if (segments_count > 0 + && stream->segment_index + 1 >= segments_count) { + return FALSE; + } + } else { + if (stream->segment_index < 0) + return FALSE; + } + + return TRUE; +} + +GstFlowReturn +gst_mpd_client_advance_segment (GstMpdClient * client, GstActiveStream * stream, + gboolean forward) +{ + GstMediaSegment *segment; + GstFlowReturn ret = GST_FLOW_OK; + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + + GST_DEBUG ("Advancing segment. Current: %d / %d r:%d", stream->segment_index, + segments_count, stream->segment_repeat_index); + + /* handle special cases first */ + if (forward) { + if (segments_count > 0 && stream->segment_index >= segments_count) { + ret = GST_FLOW_EOS; + goto done; + } + + if (stream->segments == NULL) { + if (stream->segment_index < 0) { + stream->segment_index = 0; + } else { + stream->segment_index++; + if (segments_count > 0 && stream->segment_index >= segments_count) { + ret = GST_FLOW_EOS; + } + } + goto done; + } + + /* special case for when playback direction is reverted right at * + * the end of the segment list */ + if (stream->segment_index < 0) { + stream->segment_index = 0; + goto done; + } + } else { + if (stream->segments == NULL) + stream->segment_index--; + if (stream->segment_index < 0) { + stream->segment_index = -1; + ret = GST_FLOW_EOS; + goto done; + } + if (stream->segments == NULL) + goto done; + + /* special case for when playback direction is reverted right at * + * the end of the segment list */ + if (stream->segment_index >= segments_count) { + stream->segment_index = segments_count - 1; + segment = g_ptr_array_index (stream->segments, stream->segment_index); + if (segment->repeat >= 0) { + stream->segment_repeat_index = segment->repeat; + } else { + GstClockTime start = segment->start; + GstClockTime end = + gst_mpd_client_get_segment_end_time (client, stream->segments, + segment, + stream->segment_index); + stream->segment_repeat_index = + (guint) (end - start) / segment->duration; + } + goto done; + } + } + + /* for the normal cases we can get the segment safely here */ + segment = g_ptr_array_index (stream->segments, stream->segment_index); + if (forward) { + if (segment->repeat >= 0 && stream->segment_repeat_index >= segment->repeat) { + stream->segment_repeat_index = 0; + stream->segment_index++; + if (segments_count > 0 && stream->segment_index >= segments_count) { + ret = GST_FLOW_EOS; + goto done; + } + } else { + stream->segment_repeat_index++; + } + } else { + if (stream->segment_repeat_index == 0) { + stream->segment_index--; + if (stream->segment_index < 0) { + ret = GST_FLOW_EOS; + goto done; + } + + segment = g_ptr_array_index (stream->segments, stream->segment_index); + /* negative repeats only seem to make sense at the end of a list, + * so this one will probably not be. Needs some sanity checking + * when loading the XML data. */ + if (segment->repeat >= 0) { + stream->segment_repeat_index = segment->repeat; + } else { + GstClockTime start = segment->start; + GstClockTime end = + gst_mpd_client_get_segment_end_time (client, stream->segments, + segment, + stream->segment_index); + stream->segment_repeat_index = + (guint) (end - start) / segment->duration; + } + } else { + stream->segment_repeat_index--; + } + } + +done: + GST_DEBUG ("Advanced to segment: %d / %d r:%d (ret: %s)", + stream->segment_index, segments_count, + stream->segment_repeat_index, gst_flow_get_name (ret)); + return ret; +} + +gboolean +gst_mpd_client_get_next_header (GstMpdClient * client, gchar ** uri, + guint stream_idx, gint64 * range_start, gint64 * range_end) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + + stream = gst_mpd_client_get_active_stream_by_index (client, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != NULL, FALSE); + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + *range_start = 0; + *range_end = -1; + + GST_DEBUG ("Looking for current representation header"); + *uri = NULL; + if (stream->cur_segment_base) { + if (stream->cur_segment_base->Initialization) { + *uri = + g_strdup (gst_mpdparser_get_initializationURL (stream, + stream->cur_segment_base->Initialization)); + if (stream->cur_segment_base->Initialization->range) { + *range_start = + stream->cur_segment_base->Initialization->range->first_byte_pos; + *range_end = + stream->cur_segment_base->Initialization->range->last_byte_pos; + } + } else if (stream->cur_segment_base->indexRange) { + *uri = + g_strdup (gst_mpdparser_get_initializationURL (stream, + stream->cur_segment_base->Initialization)); + *range_start = 0; + *range_end = stream->cur_segment_base->indexRange->first_byte_pos - 1; + } + } else if (stream->cur_seg_template + && stream->cur_seg_template->initialization) { + *uri = + gst_mpdparser_build_URL_from_template (stream-> + cur_seg_template->initialization, stream->cur_representation->id, 0, + stream->cur_representation->bandwidth, 0); + } + + return *uri == NULL ? FALSE : TRUE; +} + +gboolean +gst_mpd_client_get_next_header_index (GstMpdClient * client, gchar ** uri, + guint stream_idx, gint64 * range_start, gint64 * range_end) +{ + GstActiveStream *stream; + GstStreamPeriod *stream_period; + + stream = gst_mpd_client_get_active_stream_by_index (client, stream_idx); + g_return_val_if_fail (stream != NULL, FALSE); + g_return_val_if_fail (stream->cur_representation != NULL, FALSE); + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, FALSE); + g_return_val_if_fail (stream_period->period != NULL, FALSE); + + *range_start = 0; + *range_end = -1; + + GST_DEBUG ("Looking for current representation index"); + *uri = NULL; + if (stream->cur_segment_base && stream->cur_segment_base->indexRange) { + *uri = + g_strdup (gst_mpdparser_get_initializationURL (stream, + stream->cur_segment_base->RepresentationIndex)); + *range_start = stream->cur_segment_base->indexRange->first_byte_pos; + *range_end = stream->cur_segment_base->indexRange->last_byte_pos; + } else if (stream->cur_seg_template && stream->cur_seg_template->index) { + *uri = + gst_mpdparser_build_URL_from_template (stream->cur_seg_template->index, + stream->cur_representation->id, 0, + stream->cur_representation->bandwidth, 0); + } + + return *uri == NULL ? FALSE : TRUE; +} + +GstClockTime +gst_mpd_client_get_next_fragment_duration (GstMpdClient * client, + GstActiveStream * stream) +{ + GstMediaSegment *media_segment = NULL; + gint seg_idx; + + g_return_val_if_fail (stream != NULL, 0); + + seg_idx = stream->segment_index; + + if (stream->segments) { + if (seg_idx < stream->segments->len && seg_idx >= 0) + media_segment = g_ptr_array_index (stream->segments, seg_idx); + + return media_segment == NULL ? 0 : media_segment->duration; + } else { + GstClockTime duration = + gst_mpd_client_get_segment_duration (client, stream, NULL); + guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + + g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType-> + SegmentTimeline == NULL, 0); + + if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 + && seg_idx >= segments_count)) { + return 0; + } + return duration; + } +} + +GstClockTime +gst_mpd_client_get_media_presentation_duration (GstMpdClient * client) +{ + GstClockTime duration; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + + if (client->mpd_node->mediaPresentationDuration != -1) { + duration = client->mpd_node->mediaPresentationDuration * GST_MSECOND; + } else { + /* We can only get the duration for on-demand streams */ + duration = GST_CLOCK_TIME_NONE; + } + + return duration; +} + +gboolean +gst_mpd_client_set_period_id (GstMpdClient * client, const gchar * period_id) +{ + GstStreamPeriod *next_stream_period; + gboolean ret = FALSE; + GList *iter; + guint period_idx; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + g_return_val_if_fail (period_id != NULL, FALSE); + + if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, -1, + period_id)) + return FALSE; + + for (period_idx = 0, iter = client->periods; iter; + period_idx++, iter = g_list_next (iter)) { + next_stream_period = iter->data; + + if (next_stream_period->period->id + && strcmp (next_stream_period->period->id, period_id) == 0) { + ret = TRUE; + client->period_idx = period_idx; + break; + } + } + + return ret; +} + +gboolean +gst_mpd_client_set_period_index (GstMpdClient * client, guint period_idx) +{ + GstStreamPeriod *next_stream_period; + gboolean ret = FALSE; + + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + + if (!gst_mpd_client_setup_media_presentation (client, -1, period_idx, NULL)) + return FALSE; + + next_stream_period = g_list_nth_data (client->periods, period_idx); + if (next_stream_period != NULL) { + client->period_idx = period_idx; + ret = TRUE; + } + + return ret; +} + +guint +gst_mpd_client_get_period_index (GstMpdClient * client) +{ + guint period_idx; + + g_return_val_if_fail (client != NULL, 0); + period_idx = client->period_idx; + + return period_idx; +} + +const gchar * +gst_mpd_client_get_period_id (GstMpdClient * client) +{ + GstStreamPeriod *period; + gchar *period_id = NULL; + + g_return_val_if_fail (client != NULL, 0); + period = g_list_nth_data (client->periods, client->period_idx); + if (period && period->period) + period_id = period->period->id; + + return period_id; +} + +gboolean +gst_mpd_client_has_next_period (GstMpdClient * client) +{ + GList *next_stream_period; + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + + if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, + client->period_idx + 1, NULL)) + return FALSE; + + next_stream_period = + g_list_nth_data (client->periods, client->period_idx + 1); + return next_stream_period != NULL; +} + +gboolean +gst_mpd_client_has_previous_period (GstMpdClient * client) +{ + GList *next_stream_period; + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->periods != NULL, FALSE); + + if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, + client->period_idx - 1, NULL)) + return FALSE; + + next_stream_period = + g_list_nth_data (client->periods, client->period_idx - 1); + + return next_stream_period != NULL; +} + +gint +gst_mpd_client_get_rep_idx_with_min_bandwidth (GList * Representations) +{ + GList *list = NULL, *lowest = NULL; + GstRepresentationNode *rep = NULL; + gint lowest_bandwidth = -1; + + if (Representations == NULL) + return -1; + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + rep = (GstRepresentationNode *) list->data; + if (rep && (!lowest || rep->bandwidth < lowest_bandwidth)) { + lowest = list; + lowest_bandwidth = rep->bandwidth; + } + } + + return lowest ? g_list_position (Representations, lowest) : -1; +} + +gint +gst_mpd_client_get_rep_idx_with_max_bandwidth (GList * Representations, + gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint + max_video_framerate_n, gint max_video_framerate_d) +{ + GList *list = NULL, *best = NULL; + GstRepresentationNode *representation; + gint best_bandwidth = 0; + + GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth); + + if (Representations == NULL) + return -1; + + if (max_bandwidth <= 0) /* 0 => get lowest representation available */ + return gst_mpd_client_get_rep_idx_with_min_bandwidth (Representations); + + for (list = g_list_first (Representations); list; list = g_list_next (list)) { + GstXMLFrameRate *framerate = NULL; + + representation = (GstRepresentationNode *) list->data; + + /* FIXME: Really? */ + if (!representation) + continue; + + framerate = representation->RepresentationBase->frameRate; + if (!framerate) + framerate = representation->RepresentationBase->maxFrameRate; + + if (framerate && max_video_framerate_n > 0) { + if (gst_util_fraction_compare (framerate->num, framerate->den, + max_video_framerate_n, max_video_framerate_d) > 0) + continue; + } + + if (max_video_width > 0 + && representation->RepresentationBase->width > max_video_width) + continue; + if (max_video_height > 0 + && representation->RepresentationBase->height > max_video_height) + continue; + + if (representation->bandwidth <= max_bandwidth && + representation->bandwidth > best_bandwidth) { + best = list; + best_bandwidth = representation->bandwidth; + } + } + + return best ? g_list_position (Representations, best) : -1; +} + +void +gst_mpd_client_seek_to_first_segment (GstMpdClient * client) +{ + GList *list; + + g_return_if_fail (client != NULL); + g_return_if_fail (client->active_streams != NULL); + + for (list = g_list_first (client->active_streams); list; + list = g_list_next (list)) { + GstActiveStream *stream = (GstActiveStream *) list->data; + if (stream) { + stream->segment_index = 0; + stream->segment_repeat_index = 0; + } + } +} + +static guint +gst_mpd_client_get_segments_counts (GstMpdClient * client, + GstActiveStream * stream) +{ + GstStreamPeriod *stream_period; + + g_return_val_if_fail (stream != NULL, 0); + + if (stream->segments) + return stream->segments->len; + g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType-> + SegmentTimeline == NULL, 0); + + stream_period = gst_mpd_client_get_stream_period (client); + if (stream_period->duration != -1) + return gst_util_uint64_scale_ceil (stream_period->duration, 1, + gst_mpd_client_get_segment_duration (client, stream, NULL)); + + return 0; +} + +gboolean +gst_mpd_client_is_live (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, FALSE); + g_return_val_if_fail (client->mpd_node != NULL, FALSE); + + return client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC; +} + +guint +gst_mpd_client_get_nb_active_stream (GstMpdClient * client) +{ + g_return_val_if_fail (client != NULL, 0); + + return g_list_length (client->active_streams); +} + +guint +gst_mpd_client_get_nb_adaptationSet (GstMpdClient * client) +{ + GstStreamPeriod *stream_period; + + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, 0); + g_return_val_if_fail (stream_period->period != NULL, 0); + + return g_list_length (stream_period->period->AdaptationSets); +} + +GstActiveStream * +gst_mpd_client_get_active_stream_by_index (GstMpdClient * client, + guint stream_idx) +{ + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (client->active_streams != NULL, NULL); + + return g_list_nth_data (client->active_streams, stream_idx); +} + +gboolean +gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream) +{ + const gchar *mimeType; + const gchar *adapt_set_codecs; + const gchar *rep_codecs; + + mimeType = stream->cur_representation->RepresentationBase->mimeType; + if (!mimeType) + mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; + + if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 || + g_strcmp0 (mimeType, "text/vtt") == 0) + return TRUE; + + adapt_set_codecs = stream->cur_adapt_set->RepresentationBase->codecs; + rep_codecs = stream->cur_representation->RepresentationBase->codecs; + + return (adapt_set_codecs && g_str_has_prefix (adapt_set_codecs, "stpp")) + || (rep_codecs && g_str_has_prefix (rep_codecs, "stpp")); +} + +GstCaps * +gst_mpd_client_get_stream_caps (GstActiveStream * stream) +{ + const gchar *mimeType, *caps_string; + GstCaps *ret = NULL; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return NULL; + + mimeType = stream->cur_representation->RepresentationBase->mimeType; + if (mimeType == NULL) { + mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; + } + + caps_string = gst_mpdparser_mimetype_to_caps (mimeType); + + if ((g_strcmp0 (caps_string, "application/mp4") == 0) + && gst_mpd_client_active_stream_contains_subtitles (stream)) + caps_string = "video/quicktime"; + + if (caps_string) + ret = gst_caps_from_string (caps_string); + + return ret; +} + +gboolean +gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream) +{ + if (stream == NULL || stream->cur_adapt_set == NULL) + return FALSE; + + return stream->cur_adapt_set->bitstreamSwitching; +} + +guint +gst_mpd_client_get_video_stream_width (GstActiveStream * stream) +{ + guint width; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + width = stream->cur_representation->RepresentationBase->width; + if (width == 0) { + width = stream->cur_adapt_set->RepresentationBase->width; + } + + return width; +} + +guint +gst_mpd_client_get_video_stream_height (GstActiveStream * stream) +{ + guint height; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + height = stream->cur_representation->RepresentationBase->height; + if (height == 0) { + height = stream->cur_adapt_set->RepresentationBase->height; + } + + return height; +} + +gboolean +gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream, + gint * fps_num, gint * fps_den) +{ + if (stream == NULL) + return FALSE; + + if (stream->cur_adapt_set && + stream->cur_adapt_set->RepresentationBase->frameRate != NULL) { + *fps_num = stream->cur_adapt_set->RepresentationBase->frameRate->num; + *fps_den = stream->cur_adapt_set->RepresentationBase->frameRate->den; + return TRUE; + } + + if (stream->cur_adapt_set && + stream->cur_adapt_set->RepresentationBase->maxFrameRate != NULL) { + *fps_num = stream->cur_adapt_set->RepresentationBase->maxFrameRate->num; + *fps_den = stream->cur_adapt_set->RepresentationBase->maxFrameRate->den; + return TRUE; + } + + if (stream->cur_representation && + stream->cur_representation->RepresentationBase->frameRate != NULL) { + *fps_num = stream->cur_representation->RepresentationBase->frameRate->num; + *fps_den = stream->cur_representation->RepresentationBase->frameRate->den; + return TRUE; + } + + if (stream->cur_representation && + stream->cur_representation->RepresentationBase->maxFrameRate != NULL) { + *fps_num = + stream->cur_representation->RepresentationBase->maxFrameRate->num; + *fps_den = + stream->cur_representation->RepresentationBase->maxFrameRate->den; + return TRUE; + } + + return FALSE; +} + +guint +gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream) +{ + const gchar *rate; + + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + + rate = stream->cur_representation->RepresentationBase->audioSamplingRate; + if (rate == NULL) { + rate = stream->cur_adapt_set->RepresentationBase->audioSamplingRate; + } + + return rate ? atoi (rate) : 0; +} + +guint +gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream) +{ + if (stream == NULL || stream->cur_adapt_set == NULL + || stream->cur_representation == NULL) + return 0; + /* TODO: here we have to parse the AudioChannelConfiguration descriptors */ + return 0; +} + +guint +gst_mpd_client_get_list_and_nb_of_audio_language (GstMpdClient * client, + GList ** lang) +{ + GstStreamPeriod *stream_period; + GstAdaptationSetNode *adapt_set; + GList *adaptation_sets, *list; + const gchar *this_mimeType = "audio"; + gchar *mimeType = NULL; + guint nb_adaptation_set = 0; + + stream_period = gst_mpd_client_get_stream_period (client); + g_return_val_if_fail (stream_period != NULL, 0); + g_return_val_if_fail (stream_period->period != NULL, 0); + + adaptation_sets = + gst_mpd_client_get_adaptation_sets_for_period (client, stream_period); + for (list = adaptation_sets; list; list = g_list_next (list)) { + adapt_set = (GstAdaptationSetNode *) list->data; + if (adapt_set && adapt_set->lang) { + gchar *this_lang = adapt_set->lang; + GstRepresentationNode *rep; + rep = + gst_mpd_client_get_lowest_representation (adapt_set->Representations); + mimeType = NULL; + if (rep->RepresentationBase) + mimeType = rep->RepresentationBase->mimeType; + if (!mimeType && adapt_set->RepresentationBase) { + mimeType = adapt_set->RepresentationBase->mimeType; + } + + if (strncmp_ext (mimeType, this_mimeType) == 0) { + nb_adaptation_set++; + *lang = g_list_append (*lang, this_lang); + } + } + } + + return nb_adaptation_set; +} + + +GstDateTime * +gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client, + GstActiveStream * stream) +{ + GstDateTime *availability_start_time, *rv; + gint seg_idx; + GstMediaSegment *segment; + GstClockTime segmentEndTime; + const GstStreamPeriod *stream_period; + GstClockTime period_start = 0; + + g_return_val_if_fail (client != NULL, NULL); + g_return_val_if_fail (stream != NULL, NULL); + + stream_period = gst_mpd_client_get_stream_period (client); + if (stream_period && stream_period->period) { + period_start = stream_period->start; + } + + seg_idx = stream->segment_index; + + if (stream->segments) { + segment = g_ptr_array_index (stream->segments, seg_idx); + + if (segment->repeat >= 0) { + segmentEndTime = segment->start + (stream->segment_repeat_index + 1) * + segment->duration; + } else if (seg_idx < stream->segments->len - 1) { + const GstMediaSegment *next_segment = + g_ptr_array_index (stream->segments, seg_idx + 1); + segmentEndTime = next_segment->start; + } else { + g_return_val_if_fail (stream_period != NULL, NULL); + segmentEndTime = period_start + stream_period->duration; + } + } else { + GstClockTime seg_duration; + seg_duration = gst_mpd_client_get_segment_duration (client, stream, NULL); + if (seg_duration == 0) + return NULL; + segmentEndTime = period_start + (1 + seg_idx) * seg_duration; + } + + availability_start_time = gst_mpd_client_get_availability_start_time (client); + if (availability_start_time == NULL) { + GST_WARNING_OBJECT (client, "Failed to get availability_start_time"); + return NULL; + } + + rv = gst_mpd_client_add_time_difference (availability_start_time, + segmentEndTime / GST_USECOND); + gst_date_time_unref (availability_start_time); + if (rv == NULL) { + GST_WARNING_OBJECT (client, "Failed to offset availability_start_time"); + return NULL; + } + + return rv; +} + +gboolean +gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time) +{ + GDateTime *start; + GTimeSpan ts_microseconds; + GstClockTime ts; + gboolean ret = TRUE; + GList *stream; + + g_return_val_if_fail (gst_mpd_client_is_live (client), FALSE); + g_return_val_if_fail (client->mpd_node->availabilityStartTime != NULL, FALSE); + + start = + gst_date_time_to_g_date_time (client->mpd_node->availabilityStartTime); + + ts_microseconds = g_date_time_difference (time, start); + g_date_time_unref (start); + + /* Clamp to availability start time, otherwise calculations wrap around */ + if (ts_microseconds < 0) + ts_microseconds = 0; + + ts = ts_microseconds * GST_USECOND; + for (stream = client->active_streams; stream; stream = g_list_next (stream)) { + ret = + ret & gst_mpd_client_stream_seek (client, stream->data, TRUE, 0, ts, + NULL); + } + return ret; +} + +gboolean +gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient * client) +{ + return client->profile_isoff_ondemand; +} + +/** + * gst_mpd_client_parse_default_presentation_delay: + * @client: #GstMpdClient that has a parsed manifest + * @default_presentation_delay: A string that specifies a time period + * in fragments (e.g. "5 f"), seconds ("12 s") or milliseconds + * ("12000 ms") + * Returns: the parsed string in milliseconds + * + * Since: 1.6 + */ +gint64 +gst_mpd_client_parse_default_presentation_delay (GstMpdClient * client, + const gchar * default_presentation_delay) +{ + gint64 value; + char *endptr = NULL; + + g_return_val_if_fail (client != NULL, 0); + g_return_val_if_fail (default_presentation_delay != NULL, 0); + value = strtol (default_presentation_delay, &endptr, 10); + if (endptr == default_presentation_delay || value == 0) { + return 0; + } + while (*endptr == ' ') + endptr++; + if (*endptr == 's' || *endptr == 'S') { + value *= 1000; /* convert to ms */ + } else if (*endptr == 'f' || *endptr == 'F') { + gint64 segment_duration; + g_assert (client->mpd_node != NULL); + segment_duration = client->mpd_node->maxSegmentDuration; + value *= segment_duration; + } else if (*endptr != 'm' && *endptr != 'M') { + GST_ERROR ("Unable to parse default presentation delay: %s", + default_presentation_delay); + value = 0; + } + return value; +} + +GstClockTime +gst_mpd_client_get_maximum_segment_duration (GstMpdClient * client) +{ + GstClockTime ret = GST_CLOCK_TIME_NONE, dur; + GList *stream; + + g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + g_return_val_if_fail (client->mpd_node != NULL, GST_CLOCK_TIME_NONE); + + if (client->mpd_node->maxSegmentDuration != GST_MPD_DURATION_NONE) { + return client->mpd_node->maxSegmentDuration * GST_MSECOND; + } + + /* According to the DASH specification, if maxSegmentDuration is not present: + "If not present, then the maximum Segment duration shall be the maximum + duration of any Segment documented in this MPD" + */ + for (stream = client->active_streams; stream; stream = g_list_next (stream)) { + dur = gst_mpd_client_get_segment_duration (client, stream->data, NULL); + if (dur != GST_CLOCK_TIME_NONE && (dur > ret || ret == GST_CLOCK_TIME_NONE)) { + ret = dur; + } + } + return ret; +} + +guint +gst_mpd_client_get_period_index_at_time (GstMpdClient * client, + GstDateTime * time) +{ + GList *iter; + guint period_idx = G_MAXUINT; + guint idx; + gint64 time_offset; + GstDateTime *avail_start = + gst_mpd_client_get_availability_start_time (client); + GstStreamPeriod *stream_period; + + if (avail_start == NULL) + return 0; + + time_offset = gst_mpd_client_calculate_time_difference (avail_start, time); + gst_date_time_unref (avail_start); + + if (time_offset < 0) + return 0; + + if (!gst_mpd_client_setup_media_presentation (client, time_offset, -1, NULL)) + return 0; + + for (idx = 0, iter = client->periods; iter; idx++, iter = g_list_next (iter)) { + stream_period = iter->data; + if (stream_period->start <= time_offset + && (!GST_CLOCK_TIME_IS_VALID (stream_period->duration) + || stream_period->start + stream_period->duration > time_offset)) { + period_idx = idx; + break; + } + } + + return period_idx; +} diff --git a/ext/dash/gstmpdclient.h b/ext/dash/gstmpdclient.h new file mode 100644 index 0000000..fbe287c --- /dev/null +++ b/ext/dash/gstmpdclient.h @@ -0,0 +1,142 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __GST_MPDCLIENT_H__ +#define __GST_MPDCLIENT_H__ + +#include "gstmpdparser.h" + +G_BEGIN_DECLS + +#define GST_MPD_CLIENT_LOCK(c) g_mutex_lock (&c->lock); +#define GST_MPD_CLIENT_UNLOCK(c) g_mutex_unlock (&c->lock); + +typedef struct _GstMpdClient GstMpdClient; + +struct _GstMpdClient +{ + GstMPDNode *mpd_node; /* active MPD manifest file */ + + GList *periods; /* list of GstStreamPeriod */ + guint period_idx; /* index of current Period */ + + GList *active_streams; /* list of GstActiveStream */ + + guint update_failed_count; + gchar *mpd_uri; /* manifest file URI */ + gchar *mpd_base_uri; /* base URI for resolving relative URIs. + * this will be different for redirects */ + + /* profiles */ + gboolean profile_isoff_ondemand; + + GstUriDownloader * downloader; +}; + +/* Basic initialization/deinitialization functions */ +GstMpdClient *gst_mpd_client_new (void); +void gst_mpd_client_active_streams_free (GstMpdClient * client); +void gst_mpd_client_free (GstMpdClient * client); + +/* main mpd parsing methods from xml data */ +gboolean gst_mpd_client_parse (GstMpdClient * client, const gchar * data, gint size); + +void gst_mpd_client_set_uri_downloader (GstMpdClient * client, GstUriDownloader * download); +void gst_mpd_client_check_profiles (GstMpdClient * client); +void gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client); + +/* Streaming management */ +gboolean gst_mpd_client_setup_media_presentation (GstMpdClient *client, GstClockTime time, gint period_index, const gchar *period_id); +gboolean gst_mpd_client_setup_streaming (GstMpdClient * client, GstAdaptationSetNode * adapt_set); +gboolean gst_mpd_client_setup_representation (GstMpdClient *client, GstActiveStream *stream, GstRepresentationNode *representation); + +GstClockTime gst_mpd_client_get_next_fragment_duration (GstMpdClient * client, GstActiveStream * stream); +GstClockTime gst_mpd_client_get_media_presentation_duration (GstMpdClient *client); +GstClockTime gst_mpd_client_get_maximum_segment_duration (GstMpdClient * client); +gboolean gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client, guint stream_idx, GstClockTime * ts); +gboolean gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, guint stream_idx, GstClockTime * ts); +gboolean gst_mpd_client_get_next_fragment (GstMpdClient *client, guint indexStream, GstMediaFragmentInfo * fragment); +gboolean gst_mpd_client_get_next_header (GstMpdClient *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end); +gboolean gst_mpd_client_get_next_header_index (GstMpdClient *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end); +gboolean gst_mpd_client_is_live (GstMpdClient * client); +gboolean gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * stream, gboolean forward, GstSeekFlags flags, GstClockTime ts, GstClockTime * final_ts); +gboolean gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time); +GstClockTime gst_mpd_client_get_stream_presentation_offset (GstMpdClient *client, guint stream_idx); +gchar** gst_mpd_client_get_utc_timing_sources (GstMpdClient *client, guint methods, GstMPDUTCTimingType *selected_method); +GstClockTime gst_mpd_client_get_period_start_time (GstMpdClient *client); + +/* Period selection */ +guint gst_mpd_client_get_period_index_at_time (GstMpdClient * client, GstDateTime * time); +gboolean gst_mpd_client_set_period_index (GstMpdClient *client, guint period_idx); +gboolean gst_mpd_client_set_period_id (GstMpdClient *client, const gchar * period_id); +guint gst_mpd_client_get_period_index (GstMpdClient *client); +const gchar *gst_mpd_client_get_period_id (GstMpdClient *client); +gboolean gst_mpd_client_has_next_period (GstMpdClient *client); +gboolean gst_mpd_client_has_previous_period (GstMpdClient * client); + +/* Representation selection */ +gint gst_mpd_client_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d); +gint gst_mpd_client_get_rep_idx_with_min_bandwidth (GList * Representations); + +GstDateTime * +gst_mpd_client_get_availability_start_time (GstMpdClient * client); + +/* URL management */ +const gchar *gst_mpd_client_get_baseURL (GstMpdClient *client, guint indexStream); +gchar *gst_mpd_client_parse_baseURL (GstMpdClient * client, GstActiveStream * stream, gchar ** query); + +/* Active stream */ +guint gst_mpd_client_get_nb_active_stream (GstMpdClient *client); +GstActiveStream *gst_mpd_client_get_active_stream_by_index (GstMpdClient *client, guint stream_idx); +gboolean gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream); + +/* AdaptationSet */ +guint gst_mpd_client_get_nb_adaptationSet (GstMpdClient *client); +GList * gst_mpd_client_get_adaptation_sets (GstMpdClient * client); + +/* Segment */ +gboolean gst_mpd_client_has_next_segment (GstMpdClient * client, GstActiveStream * stream, gboolean forward); +GstFlowReturn gst_mpd_client_advance_segment (GstMpdClient * client, GstActiveStream * stream, gboolean forward); +void gst_mpd_client_seek_to_first_segment (GstMpdClient * client); +GstDateTime *gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client, GstActiveStream * stream); + +/* Get audio/video stream parameters (caps, width, height, rate, number of channels) */ +GstCaps * gst_mpd_client_get_stream_caps (GstActiveStream * stream); +gboolean gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream); +guint gst_mpd_client_get_video_stream_width (GstActiveStream * stream); +guint gst_mpd_client_get_video_stream_height (GstActiveStream * stream); +gboolean gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream, gint * fps_num, gint * fps_den); +guint gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream); +guint gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream); + +/* Support multi language */ +guint gst_mpd_client_get_list_and_nb_of_audio_language (GstMpdClient *client, GList **lang); + +gint64 gst_mpd_client_calculate_time_difference (const GstDateTime * t1, const GstDateTime * t2); +GstDateTime *gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs); +gint64 gst_mpd_client_parse_default_presentation_delay(GstMpdClient * client, const gchar * default_presentation_delay); + +/* profiles */ +gboolean gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient *client); + + +G_END_DECLS + +#endif /* __GST_MPDCLIENT_H__ */ diff --git a/ext/dash/gstmpdhelper.c b/ext/dash/gstmpdhelper.c new file mode 100644 index 0000000..ed2cfca --- /dev/null +++ b/ext/dash/gstmpdhelper.c @@ -0,0 +1,78 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gstmpdhelper.h" + +gboolean +gst_mpd_helper_get_mpd_type (xmlNode * a_node, + const gchar * property_name, GstMPDFileType * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + *property_value = GST_MPD_FILE_TYPE_STATIC; /* default */ + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (xmlStrcmp (prop_string, (xmlChar *) "OnDemand") == 0 + || xmlStrcmp (prop_string, (xmlChar *) "static") == 0) { + exists = TRUE; + *property_value = GST_MPD_FILE_TYPE_STATIC; + GST_LOG (" - %s: static", property_name); + } else if (xmlStrcmp (prop_string, (xmlChar *) "Live") == 0 + || xmlStrcmp (prop_string, (xmlChar *) "dynamic") == 0) { + exists = TRUE; + *property_value = GST_MPD_FILE_TYPE_DYNAMIC; + GST_LOG (" - %s: dynamic", property_name); + } else { + GST_WARNING ("failed to parse MPD type property %s from xml string %s", + property_name, prop_string); + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_mpd_helper_get_SAP_type (xmlNode * a_node, + const gchar * property_name, GstMPDSAPType * property_value) +{ + xmlChar *prop_string; + guint prop_SAP_type = 0; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%u", &prop_SAP_type) == 1 + && prop_SAP_type <= 6) { + exists = TRUE; + *property_value = (GstMPDSAPType) prop_SAP_type; + GST_LOG (" - %s: %u", property_name, prop_SAP_type); + } else { + GST_WARNING + ("failed to parse unsigned integer property %s from xml string %s", + property_name, prop_string); + } + xmlFree (prop_string); + } + + return exists; +} diff --git a/ext/dash/gstmpdhelper.h b/ext/dash/gstmpdhelper.h new file mode 100644 index 0000000..3da5df4 --- /dev/null +++ b/ext/dash/gstmpdhelper.h @@ -0,0 +1,49 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __GST_MPDHELPER_H__ +#define __GST_MPDHELPER_H__ + +#include "gstxmlhelper.h" + +G_BEGIN_DECLS + +typedef enum +{ + GST_MPD_FILE_TYPE_STATIC, + GST_MPD_FILE_TYPE_DYNAMIC +} GstMPDFileType; + +typedef enum +{ + GST_SAP_TYPE_0 = 0, + GST_SAP_TYPE_1, + GST_SAP_TYPE_2, + GST_SAP_TYPE_3, + GST_SAP_TYPE_4, + GST_SAP_TYPE_5, + GST_SAP_TYPE_6 +} GstMPDSAPType; + + +gboolean gst_mpd_helper_get_mpd_type (xmlNode * a_node, const gchar * property_name, GstMPDFileType * property_value); +gboolean gst_mpd_helper_get_SAP_type (xmlNode * a_node, const gchar * property_name, GstMPDSAPType * property_value); +G_END_DECLS +#endif /* __GST_MPDHELPER_H__ */ diff --git a/ext/dash/gstmpdparser.c b/ext/dash/gstmpdparser.c index 3713296..a924678 100644 --- a/ext/dash/gstmpdparser.c +++ b/ext/dash/gstmpdparser.c @@ -25,63 +25,12 @@ */ #include -#include -#include + #include "gstmpdparser.h" #include "gstdash_debug.h" #define GST_CAT_DEFAULT gst_dash_demux_debug -/* Property parsing */ -static gboolean gst_mpdparser_get_xml_prop_validated_string (xmlNode * a_node, - const gchar * property_name, gchar ** property_value, - gboolean (*validator) (const char *)); -static gboolean gst_mpdparser_get_xml_prop_string (xmlNode * a_node, - const gchar * property_name, gchar ** property_value); -static gboolean gst_mpdparser_get_xml_prop_string_stripped (xmlNode * a_node, - const gchar * property_name, gchar ** property_value); -static gboolean gst_mpdparser_get_xml_ns_prop_string (xmlNode * a_node, - const gchar * ns_name, const gchar * property_name, - gchar ** property_value); -static gboolean gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node, - const gchar * property_name, gchar *** property_value); -static gboolean gst_mpdparser_get_xml_prop_signed_integer (xmlNode * a_node, - const gchar * property_name, gint default_val, gint * property_value); -static gboolean gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node, - const gchar * property_name, guint default_val, guint * property_value); -static gboolean gst_mpdparser_get_xml_prop_unsigned_integer_64 (xmlNode * - a_node, const gchar * property_name, guint64 default_val, - guint64 * property_value); -static gboolean gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node, - const gchar * property_name, guint ** property_value, guint * value_size); -static gboolean gst_mpdparser_get_xml_prop_double (xmlNode * a_node, - const gchar * property_name, gdouble * property_value); -static gboolean gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node, - const gchar * property_name, gboolean default_val, - gboolean * property_value); -static gboolean gst_mpdparser_get_xml_prop_type (xmlNode * a_node, - const gchar * property_name, GstMPDFileType * property_value); -static gboolean gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node, - const gchar * property_name, GstSAPType * property_value); -static gboolean gst_mpdparser_get_xml_prop_range (xmlNode * a_node, - const gchar * property_name, GstRange ** property_value); -static gboolean gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node, - const gchar * property_name, GstRatio ** property_value); -static gboolean gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node, - const gchar * property_name, GstFrameRate ** property_value); -static gboolean gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node, - const gchar * property_name, GstConditionalUintType ** property_value); -static gboolean gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, - const gchar * property_name, GstDateTime ** property_value); -static gboolean gst_mpdparser_get_xml_prop_duration (xmlNode * a_node, - const gchar * property_name, guint64 default_value, - guint64 * property_value); -static gboolean gst_mpdparser_get_xml_node_content (xmlNode * a_node, - gchar ** content); -static gchar *gst_mpdparser_get_xml_node_namespace (xmlNode * a_node, - const gchar * prefix); -static gboolean gst_mpdparser_get_xml_node_as_string (xmlNode * a_node, - gchar ** content); /* XML node parsing */ static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node); @@ -130,73 +79,27 @@ static gboolean gst_mpdparser_parse_root_node (GstMPDNode ** pointer, static void gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node); -/* Helper functions */ -static guint convert_to_millisecs (guint decimals, gint pos); -static int strncmp_ext (const char *s1, const char *s2); -static GstStreamPeriod *gst_mpdparser_get_stream_period (GstMpdClient * client); static GstSNode *gst_mpdparser_clone_s_node (GstSNode * pointer); static GstSegmentTimelineNode * gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer); -static GstRange *gst_mpdparser_clone_range (GstRange * range); + static GstURLType *gst_mpdparser_clone_URL (GstURLType * url); -static gchar *gst_mpdparser_parse_baseURL (GstMpdClient * client, - GstActiveStream * stream, gchar ** query); static GstSegmentURLNode *gst_mpdparser_clone_segment_url (GstSegmentURLNode * seg_url); -static gchar *gst_mpdparser_get_mediaURL (GstActiveStream * stream, - GstSegmentURLNode * segmentURL); -static const gchar *gst_mpdparser_get_initializationURL (GstActiveStream * - stream, GstURLType * InitializationURL); -static gchar *gst_mpdparser_build_URL_from_template (const gchar * url_template, - const gchar * id, guint number, guint bandwidth, guint64 time); -static gboolean gst_mpd_client_add_media_segment (GstActiveStream * stream, - GstSegmentURLNode * url_node, guint number, gint repeat, - guint64 scale_start, guint64 scale_duration, GstClockTime start, - GstClockTime duration); -static const gchar *gst_mpdparser_mimetype_to_caps (const gchar * mimeType); -static GstClockTime gst_mpd_client_get_segment_duration (GstMpdClient * client, - GstActiveStream * stream, guint64 * scale_duration); -static GstDateTime *gst_mpd_client_get_availability_start_time (GstMpdClient * - client); - -/* Representation */ -static GstRepresentationNode *gst_mpdparser_get_lowest_representation (GList * - Representations); -#if 0 -static GstRepresentationNode *gst_mpdparser_get_highest_representation (GList * - Representations); -static GstRepresentationNode - * gst_mpdparser_get_representation_with_max_bandwidth (GList * - Representations, gint max_bandwidth); -#endif -static GstSegmentBaseType *gst_mpdparser_get_segment_base (GstPeriodNode * - Period, GstAdaptationSetNode * AdaptationSet, - GstRepresentationNode * Representation); -static GstSegmentListNode *gst_mpdparser_get_segment_list (GstMpdClient * - client, GstPeriodNode * Period, GstAdaptationSetNode * AdaptationSet, - GstRepresentationNode * Representation); - -/* Segments */ -static guint gst_mpd_client_get_segments_counts (GstMpdClient * client, - GstActiveStream * stream); /* Memory management */ static GstSegmentTimelineNode *gst_mpdparser_segment_timeline_node_new (void); -static void gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node); static void gst_mpdparser_free_prog_info_node (GstProgramInformationNode * prog_info_node); static void gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node); static void gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * metrics_range_node); -static void gst_mpdparser_free_period_node (GstPeriodNode * period_node); static void gst_mpdparser_free_subset_node (GstSubsetNode * subset_node); static void gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode * segment_template_node); static void gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * representation_base); -static void gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * - adaptation_set_node); static void gst_mpdparser_free_representation_node (GstRepresentationNode * representation_node); static void gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode @@ -209,27 +112,13 @@ static void gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * seg_base_type); static void gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType * mult_seg_base_type); -static void gst_mpdparser_free_segment_list_node (GstSegmentListNode * - segment_list_node); static void gst_mpdparser_free_segment_url_node (GstSegmentURLNode * segment_url); -static void gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node); 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); - -static GstUri *combine_urls (GstUri * base, GList * list, gchar ** query, - guint idx); - -static GList *gst_mpd_client_fetch_external_period (GstMpdClient * client, - GstPeriodNode * period_node); -static GList *gst_mpd_client_fetch_external_adaptation_set (GstMpdClient * - client, GstPeriodNode * period, GstAdaptationSetNode * adapt_set); struct GstMpdParserUtcTimingMethod { @@ -261,1624 +150,581 @@ static const struct GstMpdParserUtcTimingMethod {NULL, 0} }; -/* functions to parse node namespaces, content and properties */ -static gboolean -gst_mpdparser_get_xml_prop_validated_string (xmlNode * a_node, - const gchar * property_name, gchar ** property_value, - gboolean (*validate) (const char *)) -{ - xmlChar *prop_string; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (validate && !(*validate) ((const char *) prop_string)) { - GST_WARNING ("Validation failure: %s", prop_string); - xmlFree (prop_string); - return FALSE; - } - *property_value = (gchar *) prop_string; - exists = TRUE; - GST_LOG (" - %s: %s", property_name, prop_string); - } - return exists; -} -static gboolean -gst_mpdparser_get_xml_ns_prop_string (xmlNode * a_node, - const gchar * ns_name, const gchar * property_name, gchar ** property_value) -{ - xmlChar *prop_string; - gboolean exists = FALSE; - - prop_string = - xmlGetNsProp (a_node, (const xmlChar *) property_name, - (const xmlChar *) ns_name); - if (prop_string) { - *property_value = (gchar *) prop_string; - exists = TRUE; - GST_LOG (" - %s:%s: %s", ns_name, property_name, prop_string); - } - return exists; -} +/* + Duration Data Type -static gboolean -gst_mpdparser_get_xml_prop_string (xmlNode * a_node, - const gchar * property_name, gchar ** property_value) -{ - return gst_mpdparser_get_xml_prop_validated_string (a_node, property_name, - property_value, NULL); -} + The duration data type is used to specify a time interval. -static gboolean -gst_mpdparser_get_xml_prop_string_stripped (xmlNode * a_node, - const gchar * property_name, gchar ** property_value) -{ - gboolean ret; - ret = - gst_mpdparser_get_xml_prop_string (a_node, property_name, property_value); - if (ret) - *property_value = g_strstrip (*property_value); - return ret; -} + The time interval is specified in the following form "-PnYnMnDTnHnMnS" where: -static gboolean -gst_mpdparser_validate_no_whitespace (const char *s) -{ - return !strpbrk (s, "\r\n\t "); -} + * - indicates the negative sign (optional) + * P indicates the period (required) + * nY indicates the number of years + * nM indicates the number of months + * nD indicates the number of days + * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds) + * nH indicates the number of hours + * nM indicates the number of minutes + * nS indicates the number of seconds +*/ -static gboolean -gst_mpdparser_get_xml_prop_string_no_whitespace (xmlNode * a_node, - const gchar * property_name, gchar ** property_value) -{ - return gst_mpdparser_get_xml_prop_validated_string (a_node, property_name, - property_value, gst_mpdparser_validate_no_whitespace); -} -static gboolean -gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node, - const gchar * property_name, gchar *** property_value) -{ - xmlChar *prop_string; - gchar **prop_string_vector = NULL; - guint i = 0; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - prop_string_vector = g_strsplit ((gchar *) prop_string, " ", -1); - if (prop_string_vector) { - exists = TRUE; - *property_value = prop_string_vector; - GST_LOG (" - %s:", property_name); - while (prop_string_vector[i]) { - GST_LOG (" %s", prop_string_vector[i]); - i++; - } - } else { - GST_WARNING ("Scan of string vector property failed!"); - } - xmlFree (prop_string); - } - return exists; -} -static gboolean -gst_mpdparser_get_xml_prop_signed_integer (xmlNode * a_node, - const gchar * property_name, gint default_val, gint * property_value) +static void +gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - gboolean exists = FALSE; - - *property_value = default_val; - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (sscanf ((const gchar *) prop_string, "%d", property_value) == 1) { - exists = TRUE; - GST_LOG (" - %s: %d", property_name, *property_value); - } else { - GST_WARNING - ("failed to parse signed integer property %s from xml string %s", - property_name, prop_string); - } - xmlFree (prop_string); - } + GstBaseURL *new_base_url; - return exists; -} + new_base_url = g_slice_new0 (GstBaseURL); + *list = g_list_append (*list, new_base_url); -static gboolean -gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node, - const gchar * property_name, guint default_val, guint * property_value) -{ - xmlChar *prop_string; - gboolean exists = FALSE; - - *property_value = default_val; - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (sscanf ((gchar *) prop_string, "%u", property_value) == 1 && - strstr ((gchar *) prop_string, "-") == NULL) { - exists = TRUE; - GST_LOG (" - %s: %u", property_name, *property_value); - } else { - GST_WARNING - ("failed to parse unsigned integer property %s from xml string %s", - property_name, prop_string); - /* sscanf might have written to *property_value. Restore to default */ - *property_value = default_val; - } - xmlFree (prop_string); - } + GST_LOG ("content of BaseURL node:"); + gst_xml_helper_get_node_content (a_node, &new_base_url->baseURL); - return exists; + GST_LOG ("attributes of BaseURL node:"); + gst_xml_helper_get_prop_string (a_node, "serviceLocation", + &new_base_url->serviceLocation); + gst_xml_helper_get_prop_string (a_node, "byteRange", + &new_base_url->byteRange); } -static gboolean -gst_mpdparser_get_xml_prop_unsigned_integer_64 (xmlNode * a_node, - const gchar * property_name, guint64 default_val, guint64 * property_value) +static void +gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - gboolean exists = FALSE; - - *property_value = default_val; - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (sscanf ((gchar *) prop_string, "%" G_GUINT64_FORMAT, - property_value) == 1 && - strstr ((gchar *) prop_string, "-") == NULL) { - exists = TRUE; - GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value); - } else { - GST_WARNING - ("failed to parse unsigned integer property %s from xml string %s", - property_name, prop_string); - /* sscanf might have written to *property_value. Restore to default */ - *property_value = default_val; - } - xmlFree (prop_string); - } + GstDescriptorType *new_descriptor; - return exists; -} + new_descriptor = g_slice_new0 (GstDescriptorType); + *list = g_list_append (*list, new_descriptor); -static gboolean -gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node, - const gchar * property_name, guint ** property_value, guint * value_size) -{ - xmlChar *prop_string; - gchar **str_vector; - guint *prop_uint_vector = NULL, i; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - str_vector = g_strsplit ((gchar *) prop_string, " ", -1); - if (str_vector) { - *value_size = g_strv_length (str_vector); - prop_uint_vector = g_malloc (*value_size * sizeof (guint)); - if (prop_uint_vector) { - exists = TRUE; - GST_LOG (" - %s:", property_name); - for (i = 0; i < *value_size; i++) { - if (sscanf ((gchar *) str_vector[i], "%u", &prop_uint_vector[i]) == 1 - && strstr (str_vector[i], "-") == NULL) { - GST_LOG (" %u", prop_uint_vector[i]); - } else { - GST_WARNING - ("failed to parse uint vector type property %s from xml string %s", - property_name, str_vector[i]); - /* there is no special value to put in prop_uint_vector[i] to - * signal it is invalid, so we just clean everything and return - * FALSE - */ - g_free (prop_uint_vector); - prop_uint_vector = NULL; - exists = FALSE; - break; - } - } - *property_value = prop_uint_vector; - } else { - GST_WARNING ("Array allocation failed!"); - } - } else { - GST_WARNING ("Scan of uint vector property failed!"); - } - xmlFree (prop_string); - g_strfreev (str_vector); + GST_LOG ("attributes of %s node:", a_node->name); + gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri", + &new_descriptor->schemeIdUri); + if (!gst_xml_helper_get_prop_string (a_node, "value", &new_descriptor->value)) { + /* if no value attribute, use XML string representation of the node */ + gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value); } - - return exists; } -static gboolean -gst_mpdparser_get_xml_prop_double (xmlNode * a_node, - const gchar * property_name, gdouble * property_value) +static void +gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (sscanf ((gchar *) prop_string, "%lf", property_value) == 1) { - exists = TRUE; - GST_LOG (" - %s: %lf", property_name, *property_value); - } else { - GST_WARNING ("failed to parse double property %s from xml string %s", - property_name, prop_string); - } - xmlFree (prop_string); - } - - return exists; -} + xmlNode *cur_node; + GstContentComponentNode *new_content_component; -static gboolean -gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node, - const gchar * property_name, gboolean default_val, - gboolean * property_value) -{ - xmlChar *prop_string; - gboolean exists = FALSE; - - *property_value = default_val; - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (xmlStrcmp (prop_string, (xmlChar *) "false") == 0) { - exists = TRUE; - *property_value = FALSE; - GST_LOG (" - %s: false", property_name); - } else if (xmlStrcmp (prop_string, (xmlChar *) "true") == 0) { - exists = TRUE; - *property_value = TRUE; - GST_LOG (" - %s: true", property_name); - } else { - GST_WARNING ("failed to parse boolean property %s from xml string %s", - property_name, prop_string); - } - xmlFree (prop_string); - } + new_content_component = g_slice_new0 (GstContentComponentNode); + *list = g_list_append (*list, new_content_component); - return exists; -} + GST_LOG ("attributes of ContentComponent node:"); + gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, + &new_content_component->id); + gst_xml_helper_get_prop_string (a_node, "lang", &new_content_component->lang); + gst_xml_helper_get_prop_string (a_node, "contentType", + &new_content_component->contentType); + gst_xml_helper_get_prop_ratio (a_node, "par", &new_content_component->par); -static gboolean -gst_mpdparser_get_xml_prop_type (xmlNode * a_node, - const gchar * property_name, GstMPDFileType * property_value) -{ - xmlChar *prop_string; - gboolean exists = FALSE; - - *property_value = GST_MPD_FILE_TYPE_STATIC; /* default */ - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (xmlStrcmp (prop_string, (xmlChar *) "OnDemand") == 0 - || xmlStrcmp (prop_string, (xmlChar *) "static") == 0) { - exists = TRUE; - *property_value = GST_MPD_FILE_TYPE_STATIC; - GST_LOG (" - %s: static", property_name); - } else if (xmlStrcmp (prop_string, (xmlChar *) "Live") == 0 - || xmlStrcmp (prop_string, (xmlChar *) "dynamic") == 0) { - exists = TRUE; - *property_value = GST_MPD_FILE_TYPE_DYNAMIC; - GST_LOG (" - %s: dynamic", property_name); - } else { - GST_WARNING ("failed to parse MPD type property %s from xml string %s", - property_name, prop_string); + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) { + gst_mpdparser_parse_descriptor_type_node + (&new_content_component->Accessibility, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { + gst_mpdparser_parse_descriptor_type_node (&new_content_component->Role, + cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { + gst_mpdparser_parse_descriptor_type_node + (&new_content_component->Rating, cur_node); + } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { + gst_mpdparser_parse_descriptor_type_node + (&new_content_component->Viewpoint, cur_node); + } } - xmlFree (prop_string); } - - return exists; } -static gboolean -gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node, - const gchar * property_name, GstSAPType * property_value) +static void +gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - guint prop_SAP_type = 0; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - if (sscanf ((gchar *) prop_string, "%u", &prop_SAP_type) == 1 - && prop_SAP_type <= 6) { - exists = TRUE; - *property_value = (GstSAPType) prop_SAP_type; - GST_LOG (" - %s: %u", property_name, prop_SAP_type); - } else { - GST_WARNING - ("failed to parse unsigned integer property %s from xml string %s", - property_name, prop_string); - } - xmlFree (prop_string); - } + gchar *location = NULL; - return exists; + GST_LOG ("content of Location node:"); + if (gst_xml_helper_get_node_content (a_node, &location)) + *list = g_list_append (*list, location); } -static gboolean -gst_mpdparser_get_xml_prop_range (xmlNode * a_node, const gchar * property_name, - GstRange ** property_value) +static void +gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - guint64 first_byte_pos = 0, last_byte_pos = -1; - guint len, pos; - gchar *str; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - len = xmlStrlen (prop_string); - str = (gchar *) prop_string; - GST_TRACE ("range: %s, len %d", str, len); - - /* read "-" */ - pos = strcspn (str, "-"); - if (pos >= len) { - GST_TRACE ("pos %d >= len %d", pos, len); - goto error; - } - /* read first_byte_pos */ - if (pos != 0) { - /* replace str[pos] with '\0' to allow sscanf to not be confused by - * the minus sign (eg " -1" (observe the space before -) would otherwise - * be interpreted as range -1 to 1) - */ - str[pos] = 0; - if (sscanf (str, "%" G_GUINT64_FORMAT, &first_byte_pos) != 1 || - strstr (str, "-") != NULL) { - /* sscanf failed or it found a negative number */ - /* restore the '-' sign */ - str[pos] = '-'; - goto error; - } - /* restore the '-' sign */ - str[pos] = '-'; - } - /* read last_byte_pos */ - if (pos < (len - 1)) { - if (sscanf (str + pos + 1, "%" G_GUINT64_FORMAT, &last_byte_pos) != 1 || - strstr (str + pos + 1, "-") != NULL) { - goto error; - } - } - /* malloc return data structure */ - *property_value = g_slice_new0 (GstRange); - exists = TRUE; - (*property_value)->first_byte_pos = first_byte_pos; - (*property_value)->last_byte_pos = last_byte_pos; - xmlFree (prop_string); - GST_LOG (" - %s: %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, - property_name, first_byte_pos, last_byte_pos); - } - - return exists; - -error: - GST_WARNING ("failed to parse property %s from xml string %s", property_name, - prop_string); - xmlFree (prop_string); - return FALSE; -} + GstSubRepresentationNode *new_subrep; -static gboolean -gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node, - const gchar * property_name, GstRatio ** property_value) -{ - xmlChar *prop_string; - guint num = 0, den = 1; - guint len, pos; - gchar *str; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - len = xmlStrlen (prop_string); - str = (gchar *) prop_string; - GST_TRACE ("ratio: %s, len %d", str, len); - - /* read ":" */ - pos = strcspn (str, ":"); - if (pos >= len) { - GST_TRACE ("pos %d >= len %d", pos, len); - goto error; - } - /* search for negative sign */ - if (strstr (str, "-") != NULL) { - goto error; - } - /* read num */ - if (pos != 0) { - if (sscanf (str, "%u", &num) != 1) { - goto error; - } - } - /* read den */ - if (pos < (len - 1)) { - if (sscanf (str + pos + 1, "%u", &den) != 1) { - goto error; - } - } - /* malloc return data structure */ - *property_value = g_slice_new0 (GstRatio); - exists = TRUE; - (*property_value)->num = num; - (*property_value)->den = den; - xmlFree (prop_string); - GST_LOG (" - %s: %u:%u", property_name, num, den); - } + new_subrep = g_slice_new0 (GstSubRepresentationNode); + *list = g_list_append (*list, new_subrep); - return exists; + GST_LOG ("attributes of SubRepresentation node:"); + gst_xml_helper_get_prop_unsigned_integer (a_node, "level", 0, + &new_subrep->level); + gst_xml_helper_get_prop_uint_vector_type (a_node, "dependencyLevel", + &new_subrep->dependencyLevel, &new_subrep->size); + gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0, + &new_subrep->bandwidth); + gst_xml_helper_get_prop_string_vector_type (a_node, + "contentComponent", &new_subrep->contentComponent); -error: - GST_WARNING ("failed to parse property %s from xml string %s", property_name, - prop_string); - xmlFree (prop_string); - return FALSE; + /* RepresentationBase extension */ + gst_mpdparser_parse_representation_base_type (&new_subrep->RepresentationBase, + a_node); } -static gboolean -gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node, - const gchar * property_name, GstFrameRate ** property_value) +static GstSegmentURLNode * +gst_mpdparser_clone_segment_url (GstSegmentURLNode * seg_url) { - xmlChar *prop_string; - guint num = 0, den = 1; - guint len, pos; - gchar *str; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - len = xmlStrlen (prop_string); - str = (gchar *) prop_string; - GST_TRACE ("framerate: %s, len %d", str, len); - - /* search for negative sign */ - if (strstr (str, "-") != NULL) { - goto error; - } + GstSegmentURLNode *clone = NULL; - /* read "/" if available */ - pos = strcspn (str, "/"); - /* read num */ - if (pos != 0) { - if (sscanf (str, "%u", &num) != 1) { - goto error; - } - } - /* read den (if available) */ - if (pos < (len - 1)) { - if (sscanf (str + pos + 1, "%u", &den) != 1) { - goto error; - } - } - /* alloc return data structure */ - *property_value = g_slice_new0 (GstFrameRate); - exists = TRUE; - (*property_value)->num = num; - (*property_value)->den = den; - xmlFree (prop_string); - if (den == 1) - GST_LOG (" - %s: %u", property_name, num); - else - GST_LOG (" - %s: %u/%u", property_name, num, den); + if (seg_url) { + clone = g_slice_new0 (GstSegmentURLNode); + clone->media = xmlMemStrdup (seg_url->media); + clone->mediaRange = gst_xml_helper_clone_range (seg_url->mediaRange); + clone->index = xmlMemStrdup (seg_url->index); + clone->indexRange = gst_xml_helper_clone_range (seg_url->indexRange); } - return exists; - -error: - GST_WARNING ("failed to parse property %s from xml string %s", property_name, - prop_string); - xmlFree (prop_string); - return FALSE; + return clone; } -static gboolean -gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node, - const gchar * property_name, GstConditionalUintType ** property_value) +static void +gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node) { - xmlChar *prop_string; - gchar *str; - gboolean flag; - guint val; - gboolean exists = FALSE; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - str = (gchar *) prop_string; - GST_TRACE ("conditional uint: %s", str); - - if (strcmp (str, "false") == 0) { - flag = FALSE; - val = 0; - } else if (strcmp (str, "true") == 0) { - flag = TRUE; - val = 0; - } else { - flag = TRUE; - if (sscanf (str, "%u", &val) != 1 || strstr (str, "-") != NULL) - goto error; - } - - /* alloc return data structure */ - *property_value = g_slice_new0 (GstConditionalUintType); - exists = TRUE; - (*property_value)->flag = flag; - (*property_value)->value = val; - xmlFree (prop_string); - GST_LOG (" - %s: flag=%s val=%u", property_name, flag ? "true" : "false", - val); - } + GstSegmentURLNode *new_segment_url; - return exists; + new_segment_url = g_slice_new0 (GstSegmentURLNode); + *list = g_list_append (*list, new_segment_url); -error: - GST_WARNING ("failed to parse property %s from xml string %s", property_name, - prop_string); - xmlFree (prop_string); - return FALSE; + GST_LOG ("attributes of SegmentURL node:"); + gst_xml_helper_get_prop_string (a_node, "media", &new_segment_url->media); + gst_xml_helper_get_prop_range (a_node, "mediaRange", + &new_segment_url->mediaRange); + gst_xml_helper_get_prop_string (a_node, "index", &new_segment_url->index); + gst_xml_helper_get_prop_range (a_node, "indexRange", + &new_segment_url->indexRange); } -/* - DateTime Data Type - - The dateTime data type is used to specify a date and a time. +static void +gst_mpdparser_parse_url_type_node (GstURLType ** pointer, xmlNode * a_node) +{ + GstURLType *new_url_type; - The lexical form of xs:dateTime is YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] + gst_mpdparser_free_url_type_node (*pointer); + *pointer = new_url_type = g_slice_new0 (GstURLType); - * YYYY indicates the year - * MM indicates the month - * DD indicates the day - * T indicates the start of the required time section - * hh indicates the hour - * mm indicates the minute - * ss indicates the second + GST_LOG ("attributes of URLType node:"); + gst_xml_helper_get_prop_string (a_node, "sourceURL", + &new_url_type->sourceURL); + gst_xml_helper_get_prop_range (a_node, "range", &new_url_type->range); +} - The time zone may be specified as Z (UTC) or (+|-)hh:mm -*/ -static gboolean -gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node, - const gchar * property_name, GstDateTime ** property_value) +static void +gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer, + xmlNode * a_node, GstSegmentBaseType * parent) { - xmlChar *prop_string; - gchar *str; - gint ret, pos; - gint year, month, day, hour, minute; - gdouble second; - gboolean exists = FALSE; - gfloat tzoffset = 0.0; - gint gmt_offset_hour = -99, gmt_offset_min = -99; - - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - str = (gchar *) prop_string; - GST_TRACE ("dateTime: %s, len %d", str, xmlStrlen (prop_string)); - /* parse year */ - ret = sscanf (str, "%d", &year); - if (ret != 1 || year <= 0) - goto error; - pos = strcspn (str, "-"); - str += (pos + 1); - GST_TRACE (" - year %d", year); - /* parse month */ - ret = sscanf (str, "%d", &month); - if (ret != 1 || month <= 0) - goto error; - pos = strcspn (str, "-"); - str += (pos + 1); - GST_TRACE (" - month %d", month); - /* parse day */ - ret = sscanf (str, "%d", &day); - if (ret != 1 || day <= 0) - goto error; - pos = strcspn (str, "T"); - str += (pos + 1); - GST_TRACE (" - day %d", day); - /* parse hour */ - ret = sscanf (str, "%d", &hour); - if (ret != 1 || hour < 0) - goto error; - pos = strcspn (str, ":"); - str += (pos + 1); - GST_TRACE (" - hour %d", hour); - /* parse minute */ - ret = sscanf (str, "%d", &minute); - if (ret != 1 || minute < 0) - goto error; - pos = strcspn (str, ":"); - str += (pos + 1); - GST_TRACE (" - minute %d", minute); - /* parse second */ - ret = sscanf (str, "%lf", &second); - if (ret != 1 || second < 0) - goto error; - GST_TRACE (" - second %lf", second); - - GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%09.6lf", property_name, - year, month, day, hour, minute, second); - - if (strrchr (str, '+') || strrchr (str, '-')) { - /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */ - gint gmt_offset = -1; - gchar *plus_pos = NULL; - gchar *neg_pos = NULL; - gchar *pos = NULL; - - GST_LOG ("Checking for timezone information"); - - /* check if there is timezone info */ - plus_pos = strrchr (str, '+'); - neg_pos = strrchr (str, '-'); - if (plus_pos) - pos = plus_pos + 1; - else if (neg_pos) - pos = neg_pos + 1; - - if (pos && strlen (pos) >= 3) { - gint ret_tz; - if (pos[2] == ':') - ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min); - else - ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min); - - GST_DEBUG ("Parsing timezone: %s", pos); - - if (ret_tz == 2) { - if (neg_pos != NULL && neg_pos + 1 == pos) { - gmt_offset_hour *= -1; - gmt_offset_min *= -1; - } - gmt_offset = gmt_offset_hour * 60 + gmt_offset_min; + xmlNode *cur_node; + GstSegmentBaseType *seg_base_type; + guint intval; + guint64 int64val; + gboolean boolval; + GstXMLRange *rangeval; - tzoffset = gmt_offset / 60.0; + gst_mpdparser_free_seg_base_type_ext (*pointer); + *pointer = seg_base_type = g_slice_new0 (GstSegmentBaseType); - GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset); - } else - GST_WARNING ("Failed to parse timezone information"); - } - } + /* Initialize values that have defaults */ + seg_base_type->indexRangeExact = FALSE; + seg_base_type->timescale = 1; - exists = TRUE; - *property_value = - gst_date_time_new (tzoffset, year, month, day, hour, minute, second); - xmlFree (prop_string); + /* Inherit attribute values from parent */ + if (parent) { + seg_base_type->timescale = parent->timescale; + seg_base_type->presentationTimeOffset = parent->presentationTimeOffset; + seg_base_type->indexRange = gst_xml_helper_clone_range (parent->indexRange); + seg_base_type->indexRangeExact = parent->indexRangeExact; + seg_base_type->Initialization = + gst_mpdparser_clone_URL (parent->Initialization); + seg_base_type->RepresentationIndex = + gst_mpdparser_clone_URL (parent->RepresentationIndex); } - return exists; - -error: - GST_WARNING ("failed to parse property %s from xml string %s", property_name, - prop_string); - xmlFree (prop_string); - return FALSE; -} - - -/* - Duration Data Type - - The duration data type is used to specify a time interval. - - The time interval is specified in the following form "-PnYnMnDTnHnMnS" where: - - * - indicates the negative sign (optional) - * P indicates the period (required) - * nY indicates the number of years - * nM indicates the number of months - * nD indicates the number of days - * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds) - * nH indicates the number of hours - * nM indicates the number of minutes - * nS indicates the number of seconds -*/ - -/* this function computes decimals * 10 ^ (3 - pos) */ -static guint -convert_to_millisecs (guint decimals, gint pos) -{ - guint num = 1, den = 1; - gint i = 3 - pos; - - while (i < 0) { - den *= 10; - i++; - } - while (i > 0) { - num *= 10; - i--; - } - /* if i == 0 we have exactly 3 decimals and nothing to do */ - return decimals * num / den; -} - -static gboolean -accumulate (guint64 * v, guint64 mul, guint64 add) -{ - guint64 tmp; - - if (*v > G_MAXUINT64 / mul) - return FALSE; - tmp = *v * mul; - if (tmp > G_MAXUINT64 - add) - return FALSE; - *v = tmp + add; - return TRUE; -} - -static gboolean -gst_mpdparser_parse_duration (const char *str, guint64 * value) -{ - gint ret, len, pos, posT; - gint years = -1, months = -1, days = -1, hours = -1, minutes = -1, seconds = - -1, decimals = -1, read; - gboolean have_ms = FALSE; - guint64 tmp_value; - - len = strlen (str); - GST_TRACE ("duration: %s, len %d", str, len); - if (strspn (str, "PT0123456789., \tHMDSY") < len) { - GST_WARNING ("Invalid character found: '%s'", str); - goto error; + /* We must retrieve each value first to see if it exists. If it does not + * exist, we do not want to overwrite an inherited value */ + GST_LOG ("attributes of SegmentBaseType extension:"); + if (gst_xml_helper_get_prop_unsigned_integer (a_node, "timescale", 1, + &intval)) { + seg_base_type->timescale = intval; } - /* skip leading/trailing whitespace */ - while (g_ascii_isspace (str[0])) { - str++; - len--; + if (gst_xml_helper_get_prop_unsigned_integer_64 (a_node, + "presentationTimeOffset", 0, &int64val)) { + seg_base_type->presentationTimeOffset = int64val; } - while (len > 0 && g_ascii_isspace (str[len - 1])) - --len; - - /* read "P" for period */ - if (str[0] != 'P') { - GST_WARNING ("P not found at the beginning of the string!"); - goto error; + if (gst_xml_helper_get_prop_range (a_node, "indexRange", &rangeval)) { + if (seg_base_type->indexRange) { + g_slice_free (GstXMLRange, seg_base_type->indexRange); + } + seg_base_type->indexRange = rangeval; } - str++; - len--; - - /* read "T" for time (if present) */ - posT = strcspn (str, "T"); - len -= posT; - if (posT > 0) { - /* there is some room between P and T, so there must be a period section */ - /* read years, months, days */ - do { - GST_TRACE ("parsing substring %s", str); - pos = strcspn (str, "YMD"); - ret = sscanf (str, "%u", &read); - if (ret != 1) { - GST_WARNING ("can not read integer value from string %s!", str); - goto error; - } - switch (str[pos]) { - case 'Y': - if (years != -1 || months != -1 || days != -1) { - GST_WARNING ("year, month or day was already set"); - goto error; - } - years = read; - break; - case 'M': - if (months != -1 || days != -1) { - GST_WARNING ("month or day was already set"); - goto error; - } - months = read; - if (months >= 12) { - GST_WARNING ("Month out of range"); - goto error; - } - break; - case 'D': - if (days != -1) { - GST_WARNING ("day was already set"); - goto error; - } - days = read; - if (days >= 31) { - GST_WARNING ("Day out of range"); - goto error; - } - break; - default: - GST_WARNING ("unexpected char %c!", str[pos]); - goto error; - break; - } - GST_TRACE ("read number %u type %c", read, str[pos]); - str += (pos + 1); - posT -= (pos + 1); - } while (posT > 0); + if (gst_xml_helper_get_prop_boolean (a_node, "indexRangeExact", + FALSE, &boolval)) { + seg_base_type->indexRangeExact = boolval; } - if (years == -1) - years = 0; - if (months == -1) - months = 0; - if (days == -1) - days = 0; - - GST_TRACE ("Y:M:D=%d:%d:%d", years, months, days); - - /* read "T" for time (if present) */ - /* here T is at pos == 0 */ - str++; - len--; - pos = 0; - if (pos < len) { - /* T found, there is a time section */ - /* read hours, minutes, seconds, hundredths of second */ - do { - GST_TRACE ("parsing substring %s", str); - pos = strcspn (str, "HMS,."); - ret = sscanf (str, "%u", &read); - if (ret != 1) { - GST_WARNING ("can not read integer value from string %s!", str); - goto error; - } - switch (str[pos]) { - case 'H': - if (hours != -1 || minutes != -1 || seconds != -1) { - GST_WARNING ("hour, minute or second was already set"); - goto error; - } - hours = read; - if (hours >= 24) { - GST_WARNING ("Hour out of range"); - goto error; - } - break; - case 'M': - if (minutes != -1 || seconds != -1) { - GST_WARNING ("minute or second was already set"); - goto error; - } - minutes = read; - if (minutes >= 60) { - GST_WARNING ("Minute out of range"); - goto error; - } - break; - case 'S': - if (have_ms) { - /* we have read the decimal part of the seconds */ - decimals = convert_to_millisecs (read, pos); - GST_TRACE ("decimal number %u (%d digits) -> %d ms", read, pos, - decimals); - } else { - if (seconds != -1) { - GST_WARNING ("second was already set"); - goto error; - } - /* no decimals */ - seconds = read; - } - break; - case '.': - case ',': - /* we have read the integer part of a decimal number in seconds */ - if (seconds != -1) { - GST_WARNING ("second was already set"); - goto error; - } - seconds = read; - have_ms = TRUE; - break; - default: - GST_WARNING ("unexpected char %c!", str[pos]); - goto error; - break; + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "Initialization") == 0 || + xmlStrcmp (cur_node->name, (xmlChar *) "Initialisation") == 0) { + /* parse will free the previous pointer to create a new one */ + gst_mpdparser_parse_url_type_node (&seg_base_type->Initialization, + cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "RepresentationIndex") == 0) { + /* parse will free the previous pointer to create a new one */ + gst_mpdparser_parse_url_type_node (&seg_base_type->RepresentationIndex, + cur_node); } - GST_TRACE ("read number %u type %c", read, str[pos]); - str += pos + 1; - len -= (pos + 1); - } while (len > 0); + } } - - if (hours == -1) - hours = 0; - if (minutes == -1) - minutes = 0; - if (seconds == -1) - seconds = 0; - if (decimals == -1) - decimals = 0; - GST_TRACE ("H:M:S.MS=%d:%d:%d.%03d", hours, minutes, seconds, decimals); - - tmp_value = 0; - if (!accumulate (&tmp_value, 1, years) - || !accumulate (&tmp_value, 365, months * 30) - || !accumulate (&tmp_value, 1, days) - || !accumulate (&tmp_value, 24, hours) - || !accumulate (&tmp_value, 60, minutes) - || !accumulate (&tmp_value, 60, seconds) - || !accumulate (&tmp_value, 1000, decimals)) - goto error; - - /* ensure it can be converted from milliseconds to nanoseconds */ - if (tmp_value > G_MAXUINT64 / 1000000) - goto error; - - *value = tmp_value; - return TRUE; - -error: - return FALSE; } -static gboolean -gst_mpdparser_get_xml_prop_duration (xmlNode * a_node, - const gchar * property_name, guint64 default_value, - guint64 * property_value) +static GstSNode * +gst_mpdparser_clone_s_node (GstSNode * pointer) { - xmlChar *prop_string; - gchar *str; - gboolean exists = FALSE; - - *property_value = default_value; - prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); - if (prop_string) { - str = (gchar *) prop_string; - if (!gst_mpdparser_parse_duration (str, property_value)) - goto error; - GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value); - xmlFree (prop_string); - exists = TRUE; - } - return exists; - -error: - xmlFree (prop_string); - return FALSE; -} + GstSNode *clone = NULL; -static gboolean -gst_mpdparser_get_xml_node_content (xmlNode * a_node, gchar ** content) -{ - xmlChar *node_content = NULL; - gboolean exists = FALSE; - - node_content = xmlNodeGetContent (a_node); - if (node_content) { - exists = TRUE; - *content = (gchar *) node_content; - GST_LOG (" - %s: %s", a_node->name, *content); + if (pointer) { + clone = g_slice_new0 (GstSNode); + clone->t = pointer->t; + clone->d = pointer->d; + clone->r = pointer->r; } - return exists; + return clone; } -static gboolean -gst_mpdparser_get_xml_node_as_string (xmlNode * a_node, gchar ** content) +static void +gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node) { - gboolean exists = FALSE; - const char *txt_encoding; - xmlOutputBufferPtr out_buf; - - txt_encoding = (const char *) a_node->doc->encoding; - out_buf = xmlAllocOutputBuffer (NULL); - g_assert (out_buf != NULL); - xmlNodeDumpOutput (out_buf, a_node->doc, a_node, 0, 0, txt_encoding); - xmlOutputBufferFlush (out_buf); -#ifdef LIBXML2_NEW_BUFFER - if (xmlOutputBufferGetSize (out_buf) > 0) { - *content = - (gchar *) xmlStrndup (xmlOutputBufferGetContent (out_buf), - xmlOutputBufferGetSize (out_buf)); - exists = TRUE; - } -#else - if (out_buf->conv && out_buf->conv->use > 0) { - *content = - (gchar *) xmlStrndup (out_buf->conv->content, out_buf->conv->use); - exists = TRUE; - } else if (out_buf->buffer && out_buf->buffer->use > 0) { - *content = - (gchar *) xmlStrndup (out_buf->buffer->content, out_buf->buffer->use); - exists = TRUE; - } -#endif // LIBXML2_NEW_BUFFER - (void) xmlOutputBufferClose (out_buf); + GstSNode *new_s_node; - if (exists) { - GST_LOG (" - %s: %s", a_node->name, *content); - } - return exists; + new_s_node = g_slice_new0 (GstSNode); + g_queue_push_tail (queue, new_s_node); + + GST_LOG ("attributes of S node:"); + gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "t", 0, &new_s_node->t); + gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "d", 0, &new_s_node->d); + gst_xml_helper_get_prop_signed_integer (a_node, "r", 0, &new_s_node->r); } -static gchar * -gst_mpdparser_get_xml_node_namespace (xmlNode * a_node, const gchar * prefix) +static GstSegmentTimelineNode * +gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer) { - xmlNs *curr_ns; - gchar *namespace = NULL; - - if (prefix == NULL) { - /* return the default namespace */ - if (a_node->ns) { - namespace = xmlMemStrdup ((const gchar *) a_node->ns->href); - if (namespace) { - GST_LOG (" - default namespace: %s", namespace); - } - } - } else { - /* look for the specified prefix in the namespace list */ - for (curr_ns = a_node->ns; curr_ns; curr_ns = curr_ns->next) { - if (xmlStrcmp (curr_ns->prefix, (xmlChar *) prefix) == 0) { - namespace = xmlMemStrdup ((const gchar *) curr_ns->href); - if (namespace) { - GST_LOG (" - %s namespace: %s", curr_ns->prefix, curr_ns->href); + GstSegmentTimelineNode *clone = NULL; + + if (pointer) { + clone = gst_mpdparser_segment_timeline_node_new (); + if (clone) { + GList *list; + for (list = g_queue_peek_head_link (&pointer->S); list; + list = g_list_next (list)) { + GstSNode *s_node; + s_node = (GstSNode *) list->data; + if (s_node) { + g_queue_push_tail (&clone->S, gst_mpdparser_clone_s_node (s_node)); } } + } else { + GST_WARNING ("Allocation of SegmentTimeline node failed!"); } } - return namespace; + return clone; } static void -gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node) +gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer, + xmlNode * a_node) { - GstBaseURL *new_base_url; - - new_base_url = g_slice_new0 (GstBaseURL); - *list = g_list_append (*list, new_base_url); + xmlNode *cur_node; + GstSegmentTimelineNode *new_seg_timeline; - GST_LOG ("content of BaseURL node:"); - gst_mpdparser_get_xml_node_content (a_node, &new_base_url->baseURL); + gst_mpdparser_free_segment_timeline_node (*pointer); + *pointer = new_seg_timeline = gst_mpdparser_segment_timeline_node_new (); + if (new_seg_timeline == NULL) { + GST_WARNING ("Allocation of SegmentTimeline node failed!"); + return; + } - GST_LOG ("attributes of BaseURL node:"); - gst_mpdparser_get_xml_prop_string (a_node, "serviceLocation", - &new_base_url->serviceLocation); - gst_mpdparser_get_xml_prop_string (a_node, "byteRange", - &new_base_url->byteRange); + /* explore children nodes */ + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "S") == 0) { + gst_mpdparser_parse_s_node (&new_seg_timeline->S, cur_node); + } + } + } } -static void -gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node) +static gboolean +gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer, + xmlNode * a_node, GstMultSegmentBaseType * parent) { - GstDescriptorType *new_descriptor; + xmlNode *cur_node; + GstMultSegmentBaseType *mult_seg_base_type; + guint intval; + gboolean has_timeline = FALSE, has_duration = FALSE; - new_descriptor = g_slice_new0 (GstDescriptorType); - *list = g_list_append (*list, new_descriptor); + gst_mpdparser_free_mult_seg_base_type_ext (*pointer); + mult_seg_base_type = g_slice_new0 (GstMultSegmentBaseType); - GST_LOG ("attributes of %s node:", a_node->name); - gst_mpdparser_get_xml_prop_string_stripped (a_node, "schemeIdUri", - &new_descriptor->schemeIdUri); - if (!gst_mpdparser_get_xml_prop_string (a_node, "value", - &new_descriptor->value)) { - /* if no value attribute, use XML string representation of the node */ - gst_mpdparser_get_xml_node_as_string (a_node, &new_descriptor->value); + mult_seg_base_type->duration = 0; + mult_seg_base_type->startNumber = 1; + + /* Inherit attribute values from parent */ + if (parent) { + mult_seg_base_type->duration = parent->duration; + mult_seg_base_type->startNumber = parent->startNumber; + mult_seg_base_type->SegmentTimeline = + gst_mpdparser_clone_segment_timeline (parent->SegmentTimeline); + mult_seg_base_type->BitstreamSwitching = + gst_mpdparser_clone_URL (parent->BitstreamSwitching); } -} -static void -gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node) -{ - xmlNode *cur_node; - GstContentComponentNode *new_content_component; + GST_LOG ("attributes of MultipleSegmentBaseType extension:"); + if (gst_xml_helper_get_prop_unsigned_integer (a_node, "duration", 0, &intval)) { + mult_seg_base_type->duration = intval; + } - new_content_component = g_slice_new0 (GstContentComponentNode); - *list = g_list_append (*list, new_content_component); + /* duration might be specified from parent */ + if (mult_seg_base_type->duration) + has_duration = TRUE; - GST_LOG ("attributes of ContentComponent node:"); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0, - &new_content_component->id); - gst_mpdparser_get_xml_prop_string (a_node, "lang", - &new_content_component->lang); - gst_mpdparser_get_xml_prop_string (a_node, "contentType", - &new_content_component->contentType); - gst_mpdparser_get_xml_prop_ratio (a_node, "par", &new_content_component->par); + if (gst_xml_helper_get_prop_unsigned_integer (a_node, "startNumber", 1, + &intval)) { + mult_seg_base_type->startNumber = intval; + } + + GST_LOG ("extension of MultipleSegmentBaseType extension:"); + gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_type->SegBaseType, + a_node, (parent ? parent->SegBaseType : NULL)); /* explore children nodes */ for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) { - gst_mpdparser_parse_descriptor_type_node - (&new_content_component->Accessibility, cur_node); - } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { - gst_mpdparser_parse_descriptor_type_node (&new_content_component->Role, - cur_node); - } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { - gst_mpdparser_parse_descriptor_type_node - (&new_content_component->Rating, cur_node); - } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { - gst_mpdparser_parse_descriptor_type_node - (&new_content_component->Viewpoint, cur_node); + if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTimeline") == 0) { + /* parse frees the segmenttimeline if any */ + gst_mpdparser_parse_segment_timeline_node + (&mult_seg_base_type->SegmentTimeline, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "BitstreamSwitching") == 0) { + /* parse frees the old url before setting the new one */ + gst_mpdparser_parse_url_type_node + (&mult_seg_base_type->BitstreamSwitching, cur_node); } } } -} -static void -gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node) -{ - gchar *location = NULL; + has_timeline = mult_seg_base_type->SegmentTimeline != NULL; - GST_LOG ("content of Location node:"); - if (gst_mpdparser_get_xml_node_content (a_node, &location)) - *list = g_list_append (*list, location); -} + /* Checking duration and timeline only at Representation's child level */ + if (xmlStrcmp (a_node->parent->name, (xmlChar *) "Representation") == 0 + && !has_duration && !has_timeline) { + GST_ERROR ("segment has neither duration nor timeline"); + goto error; + } -static void -gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node) -{ - GstSubRepresentationNode *new_subrep; + *pointer = mult_seg_base_type; + return TRUE; - new_subrep = g_slice_new0 (GstSubRepresentationNode); - *list = g_list_append (*list, new_subrep); - - GST_LOG ("attributes of SubRepresentation node:"); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "level", 0, - &new_subrep->level); - gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "dependencyLevel", - &new_subrep->dependencyLevel, &new_subrep->size); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0, - &new_subrep->bandwidth); - gst_mpdparser_get_xml_prop_string_vector_type (a_node, - "contentComponent", &new_subrep->contentComponent); - - /* RepresentationBase extension */ - gst_mpdparser_parse_representation_base_type (&new_subrep->RepresentationBase, - a_node); -} - -static GstSegmentURLNode * -gst_mpdparser_clone_segment_url (GstSegmentURLNode * seg_url) -{ - GstSegmentURLNode *clone = NULL; - - if (seg_url) { - clone = g_slice_new0 (GstSegmentURLNode); - clone->media = xmlMemStrdup (seg_url->media); - clone->mediaRange = gst_mpdparser_clone_range (seg_url->mediaRange); - clone->index = xmlMemStrdup (seg_url->index); - clone->indexRange = gst_mpdparser_clone_range (seg_url->indexRange); - } - - return clone; -} - -static void -gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node) -{ - GstSegmentURLNode *new_segment_url; - - new_segment_url = g_slice_new0 (GstSegmentURLNode); - *list = g_list_append (*list, new_segment_url); - - GST_LOG ("attributes of SegmentURL node:"); - gst_mpdparser_get_xml_prop_string (a_node, "media", &new_segment_url->media); - gst_mpdparser_get_xml_prop_range (a_node, "mediaRange", - &new_segment_url->mediaRange); - gst_mpdparser_get_xml_prop_string (a_node, "index", &new_segment_url->index); - gst_mpdparser_get_xml_prop_range (a_node, "indexRange", - &new_segment_url->indexRange); -} - -static void -gst_mpdparser_parse_url_type_node (GstURLType ** pointer, xmlNode * a_node) -{ - GstURLType *new_url_type; - - gst_mpdparser_free_url_type_node (*pointer); - *pointer = new_url_type = g_slice_new0 (GstURLType); - - GST_LOG ("attributes of URLType node:"); - gst_mpdparser_get_xml_prop_string (a_node, "sourceURL", - &new_url_type->sourceURL); - gst_mpdparser_get_xml_prop_range (a_node, "range", &new_url_type->range); +error: + gst_mpdparser_free_mult_seg_base_type_ext (mult_seg_base_type); + return FALSE; } -static void -gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer, - xmlNode * a_node, GstSegmentBaseType * parent) +static gboolean +gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** pointer, + xmlNode * a_node, GstSegmentListNode * parent) { xmlNode *cur_node; - GstSegmentBaseType *seg_base_type; - guint intval; - guint64 int64val; - gboolean boolval; - GstRange *rangeval; - - gst_mpdparser_free_seg_base_type_ext (*pointer); - *pointer = seg_base_type = g_slice_new0 (GstSegmentBaseType); + GstSegmentListNode *new_segment_list; + gchar *actuate; + gboolean segment_urls_inherited_from_parent = FALSE; - /* Initialize values that have defaults */ - seg_base_type->indexRangeExact = FALSE; - seg_base_type->timescale = 1; + gst_mpdparser_free_segment_list_node (*pointer); + new_segment_list = g_slice_new0 (GstSegmentListNode); /* Inherit attribute values from parent */ if (parent) { - seg_base_type->timescale = parent->timescale; - seg_base_type->presentationTimeOffset = parent->presentationTimeOffset; - seg_base_type->indexRange = gst_mpdparser_clone_range (parent->indexRange); - seg_base_type->indexRangeExact = parent->indexRangeExact; - seg_base_type->Initialization = - gst_mpdparser_clone_URL (parent->Initialization); - seg_base_type->RepresentationIndex = - gst_mpdparser_clone_URL (parent->RepresentationIndex); - } - - /* We must retrieve each value first to see if it exists. If it does not - * exist, we do not want to overwrite an inherited value */ - GST_LOG ("attributes of SegmentBaseType extension:"); - if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "timescale", 1, - &intval)) { - seg_base_type->timescale = intval; - } - if (gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node, - "presentationTimeOffset", 0, &int64val)) { - seg_base_type->presentationTimeOffset = int64val; - } - if (gst_mpdparser_get_xml_prop_range (a_node, "indexRange", &rangeval)) { - if (seg_base_type->indexRange) { - g_slice_free (GstRange, seg_base_type->indexRange); + GList *list; + GstSegmentURLNode *seg_url; + for (list = g_list_first (parent->SegmentURL); list; + list = g_list_next (list)) { + seg_url = (GstSegmentURLNode *) list->data; + new_segment_list->SegmentURL = + g_list_append (new_segment_list->SegmentURL, + gst_mpdparser_clone_segment_url (seg_url)); + segment_urls_inherited_from_parent = TRUE; } - seg_base_type->indexRange = rangeval; } - if (gst_mpdparser_get_xml_prop_boolean (a_node, "indexRangeExact", - FALSE, &boolval)) { - seg_base_type->indexRangeExact = boolval; + + new_segment_list->actuate = GST_XLINK_ACTUATE_ON_REQUEST; + if (gst_xml_helper_get_ns_prop_string (a_node, + "http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href) + && gst_xml_helper_get_ns_prop_string (a_node, + "http://www.w3.org/1999/xlink", "actuate", &actuate)) { + if (strcmp (actuate, "onLoad") == 0) + new_segment_list->actuate = GST_XLINK_ACTUATE_ON_LOAD; + xmlFree (actuate); } + GST_LOG ("extension of SegmentList node:"); + if (!gst_mpdparser_parse_mult_seg_base_type_ext + (&new_segment_list->MultSegBaseType, a_node, + (parent ? parent->MultSegBaseType : NULL))) + goto error; + /* explore children nodes */ for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "Initialization") == 0 || - xmlStrcmp (cur_node->name, (xmlChar *) "Initialisation") == 0) { - /* parse will free the previous pointer to create a new one */ - gst_mpdparser_parse_url_type_node (&seg_base_type->Initialization, - cur_node); - } else if (xmlStrcmp (cur_node->name, - (xmlChar *) "RepresentationIndex") == 0) { - /* parse will free the previous pointer to create a new one */ - gst_mpdparser_parse_url_type_node (&seg_base_type->RepresentationIndex, + if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentURL") == 0) { + if (segment_urls_inherited_from_parent) { + /* + * SegmentBase, SegmentTemplate and SegmentList shall inherit + * attributes and elements from the same element on a higher level. + * If the same attribute or element is present on both levels, + * the one on the lower level shall take precedence over the one + * on the higher level. + */ + + /* Clear the list of inherited segment URLs */ + g_list_free_full (new_segment_list->SegmentURL, + (GDestroyNotify) gst_mpdparser_free_segment_url_node); + new_segment_list->SegmentURL = NULL; + + /* mark the fact that we cleared the list, so that it is not tried again */ + segment_urls_inherited_from_parent = FALSE; + } + gst_mpdparser_parse_segment_url_node (&new_segment_list->SegmentURL, cur_node); } } } -} - -static GstSNode * -gst_mpdparser_clone_s_node (GstSNode * pointer) -{ - GstSNode *clone = NULL; - if (pointer) { - clone = g_slice_new0 (GstSNode); - clone->t = pointer->t; - clone->d = pointer->d; - clone->r = pointer->r; - } + *pointer = new_segment_list; + return TRUE; - return clone; +error: + gst_mpdparser_free_segment_list_node (new_segment_list); + return FALSE; } static void -gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node) +gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node) { - GstSNode *new_s_node; - - new_s_node = g_slice_new0 (GstSNode); - g_queue_push_tail (queue, new_s_node); - - GST_LOG ("attributes of S node:"); - gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node, "t", 0, - &new_s_node->t); - gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node, "d", 0, - &new_s_node->d); - gst_mpdparser_get_xml_prop_signed_integer (a_node, "r", 0, &new_s_node->r); -} + gchar *value = NULL; + if (gst_xml_helper_get_prop_string (a_node, "value", &value)) { + if (!g_strcmp0 (value, "MSPR 2.0")) { + xmlNode *cur_node; + for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { + if (cur_node->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) { + GstDescriptorType *new_descriptor; + new_descriptor = g_slice_new0 (GstDescriptorType); + *list = g_list_append (*list, new_descriptor); -static GstSegmentTimelineNode * -gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer) -{ - GstSegmentTimelineNode *clone = NULL; + gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri", + &new_descriptor->schemeIdUri); - if (pointer) { - clone = gst_mpdparser_segment_timeline_node_new (); - if (clone) { - GList *list; - for (list = g_queue_peek_head_link (&pointer->S); list; - list = g_list_next (list)) { - GstSNode *s_node; - s_node = (GstSNode *) list->data; - if (s_node) { - g_queue_push_tail (&clone->S, gst_mpdparser_clone_s_node (s_node)); + gst_xml_helper_get_node_content (cur_node, &new_descriptor->value); + goto beach; + } } } } else { - GST_WARNING ("Allocation of SegmentTimeline node failed!"); + gst_mpdparser_parse_descriptor_type_node (list, a_node); } + } else { + gst_mpdparser_parse_descriptor_type_node (list, a_node); } - - return clone; +beach: + if (value) + g_free (value); } static void -gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer, - xmlNode * a_node) +gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType ** + pointer, xmlNode * a_node) { xmlNode *cur_node; - GstSegmentTimelineNode *new_seg_timeline; + GstRepresentationBaseType *representation_base; - gst_mpdparser_free_segment_timeline_node (*pointer); - *pointer = new_seg_timeline = gst_mpdparser_segment_timeline_node_new (); - if (new_seg_timeline == NULL) { - GST_WARNING ("Allocation of SegmentTimeline node failed!"); - return; - } + gst_mpdparser_free_representation_base_type (*pointer); + *pointer = representation_base = g_slice_new0 (GstRepresentationBaseType); + + GST_LOG ("attributes of RepresentationBaseType extension:"); + gst_xml_helper_get_prop_string (a_node, "profiles", + &representation_base->profiles); + gst_xml_helper_get_prop_unsigned_integer (a_node, "width", 0, + &representation_base->width); + gst_xml_helper_get_prop_unsigned_integer (a_node, "height", 0, + &representation_base->height); + gst_xml_helper_get_prop_ratio (a_node, "sar", &representation_base->sar); + gst_xml_helper_get_prop_framerate (a_node, "frameRate", + &representation_base->frameRate); + gst_xml_helper_get_prop_framerate (a_node, "minFrameRate", + &representation_base->minFrameRate); + gst_xml_helper_get_prop_framerate (a_node, "maxFrameRate", + &representation_base->maxFrameRate); + gst_xml_helper_get_prop_string (a_node, "audioSamplingRate", + &representation_base->audioSamplingRate); + gst_xml_helper_get_prop_string (a_node, "mimeType", + &representation_base->mimeType); + gst_xml_helper_get_prop_string (a_node, "segmentProfiles", + &representation_base->segmentProfiles); + gst_xml_helper_get_prop_string (a_node, "codecs", + &representation_base->codecs); + gst_xml_helper_get_prop_double (a_node, "maximumSAPPeriod", + &representation_base->maximumSAPPeriod); + gst_mpd_helper_get_SAP_type (a_node, "startWithSAP", + &representation_base->startWithSAP); + gst_xml_helper_get_prop_double (a_node, "maxPlayoutRate", + &representation_base->maxPlayoutRate); + gst_xml_helper_get_prop_boolean (a_node, "codingDependency", + FALSE, &representation_base->codingDependency); + gst_xml_helper_get_prop_string (a_node, "scanType", + &representation_base->scanType); /* explore children nodes */ for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "S") == 0) { - gst_mpdparser_parse_s_node (&new_seg_timeline->S, cur_node); + if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) { + gst_mpdparser_parse_descriptor_type_node + (&representation_base->FramePacking, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "AudioChannelConfiguration") == 0) { + gst_mpdparser_parse_descriptor_type_node + (&representation_base->AudioChannelConfiguration, cur_node); + } else if (xmlStrcmp (cur_node->name, + (xmlChar *) "ContentProtection") == 0) { + gst_mpdparser_parse_content_protection_node + (&representation_base->ContentProtection, cur_node); } } } } static gboolean -gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer, - xmlNode * a_node, GstMultSegmentBaseType * parent) -{ - xmlNode *cur_node; - GstMultSegmentBaseType *mult_seg_base_type; - guint intval; - gboolean has_timeline = FALSE, has_duration = FALSE; - - gst_mpdparser_free_mult_seg_base_type_ext (*pointer); - mult_seg_base_type = g_slice_new0 (GstMultSegmentBaseType); - - mult_seg_base_type->duration = 0; - mult_seg_base_type->startNumber = 1; - - /* Inherit attribute values from parent */ - if (parent) { - mult_seg_base_type->duration = parent->duration; - mult_seg_base_type->startNumber = parent->startNumber; - mult_seg_base_type->SegmentTimeline = - gst_mpdparser_clone_segment_timeline (parent->SegmentTimeline); - mult_seg_base_type->BitstreamSwitching = - gst_mpdparser_clone_URL (parent->BitstreamSwitching); - } - - GST_LOG ("attributes of MultipleSegmentBaseType extension:"); - if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "duration", 0, - &intval)) { - mult_seg_base_type->duration = intval; - } - - /* duration might be specified from parent */ - if (mult_seg_base_type->duration) - has_duration = TRUE; - - if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "startNumber", 1, - &intval)) { - mult_seg_base_type->startNumber = intval; - } - - GST_LOG ("extension of MultipleSegmentBaseType extension:"); - gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_type->SegBaseType, - a_node, (parent ? parent->SegBaseType : NULL)); - - /* explore children nodes */ - for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { - if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTimeline") == 0) { - /* parse frees the segmenttimeline if any */ - gst_mpdparser_parse_segment_timeline_node - (&mult_seg_base_type->SegmentTimeline, cur_node); - } else if (xmlStrcmp (cur_node->name, - (xmlChar *) "BitstreamSwitching") == 0) { - /* parse frees the old url before setting the new one */ - gst_mpdparser_parse_url_type_node - (&mult_seg_base_type->BitstreamSwitching, cur_node); - } - } - } - - has_timeline = mult_seg_base_type->SegmentTimeline != NULL; - - /* Checking duration and timeline only at Representation's child level */ - if (xmlStrcmp (a_node->parent->name, (xmlChar *) "Representation") == 0 - && !has_duration && !has_timeline) { - GST_ERROR ("segment has neither duration nor timeline"); - goto error; - } - - *pointer = mult_seg_base_type; - return TRUE; - -error: - gst_mpdparser_free_mult_seg_base_type_ext (mult_seg_base_type); - return FALSE; -} - -static gboolean -gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** pointer, - xmlNode * a_node, GstSegmentListNode * parent) -{ - xmlNode *cur_node; - GstSegmentListNode *new_segment_list; - gchar *actuate; - gboolean segment_urls_inherited_from_parent = FALSE; - - gst_mpdparser_free_segment_list_node (*pointer); - new_segment_list = g_slice_new0 (GstSegmentListNode); - - /* Inherit attribute values from parent */ - if (parent) { - GList *list; - GstSegmentURLNode *seg_url; - for (list = g_list_first (parent->SegmentURL); list; - list = g_list_next (list)) { - seg_url = (GstSegmentURLNode *) list->data; - new_segment_list->SegmentURL = - g_list_append (new_segment_list->SegmentURL, - gst_mpdparser_clone_segment_url (seg_url)); - segment_urls_inherited_from_parent = TRUE; - } - } - - new_segment_list->actuate = GST_XLINK_ACTUATE_ON_REQUEST; - if (gst_mpdparser_get_xml_ns_prop_string (a_node, - "http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href) - && gst_mpdparser_get_xml_ns_prop_string (a_node, - "http://www.w3.org/1999/xlink", "actuate", &actuate)) { - if (strcmp (actuate, "onLoad") == 0) - new_segment_list->actuate = GST_XLINK_ACTUATE_ON_LOAD; - xmlFree (actuate); - } - - GST_LOG ("extension of SegmentList node:"); - if (!gst_mpdparser_parse_mult_seg_base_type_ext - (&new_segment_list->MultSegBaseType, a_node, - (parent ? parent->MultSegBaseType : NULL))) - goto error; - - /* explore children nodes */ - for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { - if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentURL") == 0) { - if (segment_urls_inherited_from_parent) { - /* - * SegmentBase, SegmentTemplate and SegmentList shall inherit - * attributes and elements from the same element on a higher level. - * If the same attribute or element is present on both levels, - * the one on the lower level shall take precedence over the one - * on the higher level. - */ - - /* Clear the list of inherited segment URLs */ - g_list_free_full (new_segment_list->SegmentURL, - (GDestroyNotify) gst_mpdparser_free_segment_url_node); - new_segment_list->SegmentURL = NULL; - - /* mark the fact that we cleared the list, so that it is not tried again */ - segment_urls_inherited_from_parent = FALSE; - } - gst_mpdparser_parse_segment_url_node (&new_segment_list->SegmentURL, - cur_node); - } - } - } - - *pointer = new_segment_list; - return TRUE; - -error: - gst_mpdparser_free_segment_list_node (new_segment_list); - return FALSE; -} - -static void -gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node) -{ - gchar *value = NULL; - if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) { - if (!g_strcmp0 (value, "MSPR 2.0")) { - xmlNode *cur_node; - for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { - if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) { - GstDescriptorType *new_descriptor; - new_descriptor = g_slice_new0 (GstDescriptorType); - *list = g_list_append (*list, new_descriptor); - - gst_mpdparser_get_xml_prop_string_stripped (a_node, "schemeIdUri", - &new_descriptor->schemeIdUri); - - gst_mpdparser_get_xml_node_content (cur_node, - &new_descriptor->value); - goto beach; - } - } - } - } else { - gst_mpdparser_parse_descriptor_type_node (list, a_node); - } - } else { - gst_mpdparser_parse_descriptor_type_node (list, a_node); - } -beach: - if (value) - g_free (value); -} - -static void -gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType ** - pointer, xmlNode * a_node) -{ - xmlNode *cur_node; - GstRepresentationBaseType *representation_base; - - gst_mpdparser_free_representation_base_type (*pointer); - *pointer = representation_base = g_slice_new0 (GstRepresentationBaseType); - - GST_LOG ("attributes of RepresentationBaseType extension:"); - gst_mpdparser_get_xml_prop_string (a_node, "profiles", - &representation_base->profiles); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "width", 0, - &representation_base->width); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "height", 0, - &representation_base->height); - gst_mpdparser_get_xml_prop_ratio (a_node, "sar", &representation_base->sar); - gst_mpdparser_get_xml_prop_framerate (a_node, "frameRate", - &representation_base->frameRate); - gst_mpdparser_get_xml_prop_framerate (a_node, "minFrameRate", - &representation_base->minFrameRate); - gst_mpdparser_get_xml_prop_framerate (a_node, "maxFrameRate", - &representation_base->maxFrameRate); - gst_mpdparser_get_xml_prop_string (a_node, "audioSamplingRate", - &representation_base->audioSamplingRate); - gst_mpdparser_get_xml_prop_string (a_node, "mimeType", - &representation_base->mimeType); - gst_mpdparser_get_xml_prop_string (a_node, "segmentProfiles", - &representation_base->segmentProfiles); - gst_mpdparser_get_xml_prop_string (a_node, "codecs", - &representation_base->codecs); - gst_mpdparser_get_xml_prop_double (a_node, "maximumSAPPeriod", - &representation_base->maximumSAPPeriod); - gst_mpdparser_get_xml_prop_SAP_type (a_node, "startWithSAP", - &representation_base->startWithSAP); - gst_mpdparser_get_xml_prop_double (a_node, "maxPlayoutRate", - &representation_base->maxPlayoutRate); - gst_mpdparser_get_xml_prop_boolean (a_node, "codingDependency", - FALSE, &representation_base->codingDependency); - gst_mpdparser_get_xml_prop_string (a_node, "scanType", - &representation_base->scanType); - - /* explore children nodes */ - for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { - if (cur_node->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) { - gst_mpdparser_parse_descriptor_type_node - (&representation_base->FramePacking, cur_node); - } else if (xmlStrcmp (cur_node->name, - (xmlChar *) "AudioChannelConfiguration") == 0) { - gst_mpdparser_parse_descriptor_type_node - (&representation_base->AudioChannelConfiguration, cur_node); - } else if (xmlStrcmp (cur_node->name, - (xmlChar *) "ContentProtection") == 0) { - gst_mpdparser_parse_content_protection_node - (&representation_base->ContentProtection, cur_node); - } - } - } -} - -static gboolean -gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node, - GstAdaptationSetNode * parent, GstPeriodNode * period_node) +gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node, + GstAdaptationSetNode * parent, GstPeriodNode * period_node) { xmlNode *cur_node; GstRepresentationNode *new_representation; @@ -1886,21 +732,21 @@ gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node, new_representation = g_slice_new0 (GstRepresentationNode); GST_LOG ("attributes of Representation node:"); - if (!gst_mpdparser_get_xml_prop_string_no_whitespace (a_node, "id", + if (!gst_xml_helper_get_prop_string_no_whitespace (a_node, "id", &new_representation->id)) { GST_ERROR ("Cannot parse Representation id, invalid manifest"); goto error; } - if (!gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0, + if (!gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0, &new_representation->bandwidth)) { GST_ERROR ("Cannot parse Representation bandwidth, invalid manifest"); goto error; } - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "qualityRanking", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "qualityRanking", 0, &new_representation->qualityRanking); - gst_mpdparser_get_xml_prop_string_vector_type (a_node, "dependencyId", + gst_xml_helper_get_prop_string_vector_type (a_node, "dependencyId", &new_representation->dependencyId); - gst_mpdparser_get_xml_prop_string_vector_type (a_node, + gst_xml_helper_get_prop_string_vector_type (a_node, "mediaStreamStructureId", &new_representation->mediaStreamStructureId); /* RepresentationBase extension */ @@ -1960,38 +806,37 @@ gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, GST_LOG ("attributes of AdaptationSet node:"); new_adap_set->actuate = GST_XLINK_ACTUATE_ON_REQUEST; - if (gst_mpdparser_get_xml_ns_prop_string (a_node, + if (gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "href", &new_adap_set->xlink_href) - && gst_mpdparser_get_xml_ns_prop_string (a_node, + && gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "actuate", &actuate)) { if (strcmp (actuate, "onLoad") == 0) new_adap_set->actuate = GST_XLINK_ACTUATE_ON_LOAD; xmlFree (actuate); } - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0, - &new_adap_set->id); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "group", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, &new_adap_set->id); + gst_xml_helper_get_prop_unsigned_integer (a_node, "group", 0, &new_adap_set->group); - gst_mpdparser_get_xml_prop_string (a_node, "lang", &new_adap_set->lang); - gst_mpdparser_get_xml_prop_string (a_node, "contentType", + gst_xml_helper_get_prop_string (a_node, "lang", &new_adap_set->lang); + gst_xml_helper_get_prop_string (a_node, "contentType", &new_adap_set->contentType); - gst_mpdparser_get_xml_prop_ratio (a_node, "par", &new_adap_set->par); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minBandwidth", 0, + gst_xml_helper_get_prop_ratio (a_node, "par", &new_adap_set->par); + gst_xml_helper_get_prop_unsigned_integer (a_node, "minBandwidth", 0, &new_adap_set->minBandwidth); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxBandwidth", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "maxBandwidth", 0, &new_adap_set->maxBandwidth); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minWidth", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "minWidth", 0, &new_adap_set->minWidth); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxWidth", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "maxWidth", 0, &new_adap_set->maxWidth); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minHeight", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "minHeight", 0, &new_adap_set->minHeight); - gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxHeight", 0, + gst_xml_helper_get_prop_unsigned_integer (a_node, "maxHeight", 0, &new_adap_set->maxHeight); - gst_mpdparser_get_xml_prop_cond_uint (a_node, "segmentAlignment", + gst_xml_helper_get_prop_cond_uint (a_node, "segmentAlignment", &new_adap_set->segmentAlignment); - gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching", + gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching", parent->bitstreamSwitching, &new_adap_set->bitstreamSwitching); if (parent->bitstreamSwitching && !new_adap_set->bitstreamSwitching) { /* according to the standard, if the Period's bitstreamSwitching attribute @@ -2002,9 +847,9 @@ gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, */ new_adap_set->bitstreamSwitching = parent->bitstreamSwitching; } - gst_mpdparser_get_xml_prop_cond_uint (a_node, "subsegmentAlignment", + gst_xml_helper_get_prop_cond_uint (a_node, "subsegmentAlignment", &new_adap_set->subsegmentAlignment); - gst_mpdparser_get_xml_prop_SAP_type (a_node, "subsegmentStartsWithSAP", + gst_mpd_helper_get_SAP_type (a_node, "subsegmentStartsWithSAP", &new_adap_set->subsegmentStartsWithSAP); /* RepresentationBase extension */ @@ -2078,7 +923,7 @@ gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node) *list = g_list_append (*list, new_subset); GST_LOG ("attributes of Subset node:"); - gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "contains", + gst_xml_helper_get_prop_uint_vector_type (a_node, "contains", &new_subset->contains, &new_subset->size); } @@ -2100,26 +945,26 @@ gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer, /* Inherit attribute values from parent when the value isn't found */ GST_LOG ("attributes of SegmentTemplate node:"); - if (gst_mpdparser_get_xml_prop_string (a_node, "media", &strval)) { + if (gst_xml_helper_get_prop_string (a_node, "media", &strval)) { new_segment_template->media = strval; } else if (parent) { new_segment_template->media = xmlMemStrdup (parent->media); } - if (gst_mpdparser_get_xml_prop_string (a_node, "index", &strval)) { + if (gst_xml_helper_get_prop_string (a_node, "index", &strval)) { new_segment_template->index = strval; } else if (parent) { new_segment_template->index = xmlMemStrdup (parent->index); } - if (gst_mpdparser_get_xml_prop_string (a_node, "initialization", &strval)) { + if (gst_xml_helper_get_prop_string (a_node, "initialization", &strval)) { new_segment_template->initialization = strval; } else if (parent) { new_segment_template->initialization = xmlMemStrdup (parent->initialization); } - if (gst_mpdparser_get_xml_prop_string (a_node, "bitstreamSwitching", &strval)) { + if (gst_xml_helper_get_prop_string (a_node, "bitstreamSwitching", &strval)) { new_segment_template->bitstreamSwitching = strval; } else if (parent) { new_segment_template->bitstreamSwitching = @@ -2146,21 +991,21 @@ gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node) GST_LOG ("attributes of Period node:"); new_period->actuate = GST_XLINK_ACTUATE_ON_REQUEST; - if (gst_mpdparser_get_xml_ns_prop_string (a_node, + if (gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "href", &new_period->xlink_href) - && gst_mpdparser_get_xml_ns_prop_string (a_node, + && gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "actuate", &actuate)) { if (strcmp (actuate, "onLoad") == 0) new_period->actuate = GST_XLINK_ACTUATE_ON_LOAD; xmlFree (actuate); } - gst_mpdparser_get_xml_prop_string (a_node, "id", &new_period->id); - gst_mpdparser_get_xml_prop_duration (a_node, "start", GST_MPD_DURATION_NONE, + gst_xml_helper_get_prop_string (a_node, "id", &new_period->id); + gst_xml_helper_get_prop_duration (a_node, "start", GST_MPD_DURATION_NONE, &new_period->start); - gst_mpdparser_get_xml_prop_duration (a_node, "duration", + gst_xml_helper_get_prop_duration (a_node, "duration", GST_MPD_DURATION_NONE, &new_period->duration); - gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching", FALSE, + gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching", FALSE, &new_period->bitstreamSwitching); /* explore children nodes */ @@ -2217,8 +1062,8 @@ gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node) *list = g_list_append (*list, new_prog_info); GST_LOG ("attributes of ProgramInformation node:"); - gst_mpdparser_get_xml_prop_string (a_node, "lang", &new_prog_info->lang); - gst_mpdparser_get_xml_prop_string (a_node, "moreInformationURL", + gst_xml_helper_get_prop_string (a_node, "lang", &new_prog_info->lang); + gst_xml_helper_get_prop_string (a_node, "moreInformationURL", &new_prog_info->moreInformationURL); /* explore children nodes */ @@ -2226,12 +1071,11 @@ gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node) for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (xmlStrcmp (cur_node->name, (xmlChar *) "Title") == 0) { - gst_mpdparser_get_xml_node_content (cur_node, &new_prog_info->Title); + gst_xml_helper_get_node_content (cur_node, &new_prog_info->Title); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) { - gst_mpdparser_get_xml_node_content (cur_node, &new_prog_info->Source); + gst_xml_helper_get_node_content (cur_node, &new_prog_info->Source); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) { - gst_mpdparser_get_xml_node_content (cur_node, - &new_prog_info->Copyright); + gst_xml_helper_get_node_content (cur_node, &new_prog_info->Copyright); } } } @@ -2246,9 +1090,9 @@ gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node) *list = g_list_append (*list, new_metrics_range); GST_LOG ("attributes of Metrics Range node:"); - gst_mpdparser_get_xml_prop_duration (a_node, "starttime", + gst_xml_helper_get_prop_duration (a_node, "starttime", GST_MPD_DURATION_NONE, &new_metrics_range->starttime); - gst_mpdparser_get_xml_prop_duration (a_node, "duration", + gst_xml_helper_get_prop_duration (a_node, "duration", GST_MPD_DURATION_NONE, &new_metrics_range->duration); } @@ -2262,7 +1106,7 @@ gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node) *list = g_list_append (*list, new_metrics); GST_LOG ("attributes of Metrics node:"); - gst_mpdparser_get_xml_prop_string (a_node, "metrics", &new_metrics->metrics); + gst_xml_helper_get_prop_string (a_node, "metrics", &new_metrics->metrics); /* explore children nodes */ GST_LOG ("children of Metrics node:"); @@ -2294,7 +1138,7 @@ gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node) new_timing = g_slice_new0 (GstUTCTimingNode); GST_LOG ("attributes of UTCTiming node:"); - if (gst_mpdparser_get_xml_prop_string (a_node, "schemeIdUri", &method)) { + if (gst_xml_helper_get_prop_string (a_node, "schemeIdUri", &method)) { int i; for (i = 0; gst_mpdparser_utc_timing_methods[i].name; ++i) { @@ -2307,7 +1151,7 @@ gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node) xmlFree (method); } - if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) { + if (gst_xml_helper_get_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 @@ -2339,34 +1183,33 @@ gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node) new_mpd = g_slice_new0 (GstMPDNode); GST_LOG ("namespaces of root MPD node:"); - new_mpd->default_namespace = - gst_mpdparser_get_xml_node_namespace (a_node, NULL); - new_mpd->namespace_xsi = gst_mpdparser_get_xml_node_namespace (a_node, "xsi"); - new_mpd->namespace_ext = gst_mpdparser_get_xml_node_namespace (a_node, "ext"); + new_mpd->default_namespace = gst_xml_helper_get_node_namespace (a_node, NULL); + new_mpd->namespace_xsi = gst_xml_helper_get_node_namespace (a_node, "xsi"); + new_mpd->namespace_ext = gst_xml_helper_get_node_namespace (a_node, "ext"); GST_LOG ("attributes of root MPD node:"); - gst_mpdparser_get_xml_prop_string (a_node, "schemaLocation", + gst_xml_helper_get_prop_string (a_node, "schemaLocation", &new_mpd->schemaLocation); - gst_mpdparser_get_xml_prop_string (a_node, "id", &new_mpd->id); - gst_mpdparser_get_xml_prop_string (a_node, "profiles", &new_mpd->profiles); - gst_mpdparser_get_xml_prop_type (a_node, "type", &new_mpd->type); - gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityStartTime", + gst_xml_helper_get_prop_string (a_node, "id", &new_mpd->id); + gst_xml_helper_get_prop_string (a_node, "profiles", &new_mpd->profiles); + gst_mpd_helper_get_mpd_type (a_node, "type", &new_mpd->type); + gst_xml_helper_get_prop_dateTime (a_node, "availabilityStartTime", &new_mpd->availabilityStartTime); - gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityEndTime", + gst_xml_helper_get_prop_dateTime (a_node, "availabilityEndTime", &new_mpd->availabilityEndTime); - gst_mpdparser_get_xml_prop_duration (a_node, "mediaPresentationDuration", + gst_xml_helper_get_prop_duration (a_node, "mediaPresentationDuration", GST_MPD_DURATION_NONE, &new_mpd->mediaPresentationDuration); - gst_mpdparser_get_xml_prop_duration (a_node, "minimumUpdatePeriod", + gst_xml_helper_get_prop_duration (a_node, "minimumUpdatePeriod", GST_MPD_DURATION_NONE, &new_mpd->minimumUpdatePeriod); - gst_mpdparser_get_xml_prop_duration (a_node, "minBufferTime", + gst_xml_helper_get_prop_duration (a_node, "minBufferTime", GST_MPD_DURATION_NONE, &new_mpd->minBufferTime); - gst_mpdparser_get_xml_prop_duration (a_node, "timeShiftBufferDepth", + gst_xml_helper_get_prop_duration (a_node, "timeShiftBufferDepth", GST_MPD_DURATION_NONE, &new_mpd->timeShiftBufferDepth); - gst_mpdparser_get_xml_prop_duration (a_node, "suggestedPresentationDelay", + gst_xml_helper_get_prop_duration (a_node, "suggestedPresentationDelay", GST_MPD_DURATION_NONE, &new_mpd->suggestedPresentationDelay); - gst_mpdparser_get_xml_prop_duration (a_node, "maxSegmentDuration", + gst_xml_helper_get_prop_duration (a_node, "maxSegmentDuration", GST_MPD_DURATION_NONE, &new_mpd->maxSegmentDuration); - gst_mpdparser_get_xml_prop_duration (a_node, "maxSubsegmentDuration", + gst_xml_helper_get_prop_duration (a_node, "maxSubsegmentDuration", GST_MPD_DURATION_NONE, &new_mpd->maxSubsegmentDuration); /* explore children Period nodes */ @@ -2398,415 +1241,27 @@ error: return FALSE; } -/* comparison functions */ -static int -strncmp_ext (const char *s1, const char *s2) +/* internal memory management functions */ +static void +gst_mpdparser_free_prog_info_node (GstProgramInformationNode * prog_info_node) { - if (s1 == NULL && s2 == NULL) - return 0; - if (s1 == NULL && s2 != NULL) - return 1; - if (s2 == NULL && s1 != NULL) - return 1; - return strncmp (s1, s2, strlen (s2)); + if (prog_info_node) { + if (prog_info_node->lang) + xmlFree (prog_info_node->lang); + if (prog_info_node->moreInformationURL) + xmlFree (prog_info_node->moreInformationURL); + if (prog_info_node->Title) + xmlFree (prog_info_node->Title); + if (prog_info_node->Source) + xmlFree (prog_info_node->Source); + if (prog_info_node->Copyright) + xmlFree (prog_info_node->Copyright); + g_slice_free (GstProgramInformationNode, prog_info_node); + } } -/* navigation functions */ -static GstStreamMimeType -gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set, - GstRepresentationNode * rep) -{ - gchar *mime = NULL; - if (rep->RepresentationBase) - mime = rep->RepresentationBase->mimeType; - if (mime == NULL && adapt_set->RepresentationBase) { - mime = adapt_set->RepresentationBase->mimeType; - } - - if (strncmp_ext (mime, "audio") == 0) - return GST_STREAM_AUDIO; - if (strncmp_ext (mime, "video") == 0) - return GST_STREAM_VIDEO; - if (strncmp_ext (mime, "application") == 0 || strncmp_ext (mime, "text") == 0) - return GST_STREAM_APPLICATION; - - return GST_STREAM_UNKNOWN; -} - -static GstRepresentationNode * -gst_mpdparser_get_lowest_representation (GList * Representations) -{ - GList *list = NULL; - GstRepresentationNode *rep = NULL; - GstRepresentationNode *lowest = NULL; - - if (Representations == NULL) - return NULL; - - for (list = g_list_first (Representations); list; list = g_list_next (list)) { - rep = (GstRepresentationNode *) list->data; - if (rep && (!lowest || rep->bandwidth < lowest->bandwidth)) { - lowest = rep; - } - } - - return lowest; -} - -#if 0 -static GstRepresentationNode * -gst_mpdparser_get_highest_representation (GList * Representations) -{ - GList *list = NULL; - - if (Representations == NULL) - return NULL; - - list = g_list_last (Representations); - - return list ? (GstRepresentationNode *) list->data : NULL; -} - -static GstRepresentationNode * -gst_mpdparser_get_representation_with_max_bandwidth (GList * Representations, - gint max_bandwidth) -{ - GList *list = NULL; - GstRepresentationNode *representation, *best_rep = NULL; - - if (Representations == NULL) - return NULL; - - if (max_bandwidth <= 0) /* 0 => get highest representation available */ - return gst_mpdparser_get_highest_representation (Representations); - - for (list = g_list_first (Representations); list; list = g_list_next (list)) { - representation = (GstRepresentationNode *) list->data; - if (representation && representation->bandwidth <= max_bandwidth) { - best_rep = representation; - } - } - - return best_rep; -} -#endif - -static GstSegmentBaseType * -gst_mpdparser_get_segment_base (GstPeriodNode * Period, - GstAdaptationSetNode * AdaptationSet, - GstRepresentationNode * Representation) -{ - GstSegmentBaseType *SegmentBase = NULL; - - if (Representation && Representation->SegmentBase) { - SegmentBase = Representation->SegmentBase; - } else if (AdaptationSet && AdaptationSet->SegmentBase) { - SegmentBase = AdaptationSet->SegmentBase; - } else if (Period && Period->SegmentBase) { - SegmentBase = Period->SegmentBase; - } - /* the SegmentBase element could be encoded also inside a SegmentList element */ - if (SegmentBase == NULL) { - if (Representation && Representation->SegmentList - && Representation->SegmentList->MultSegBaseType - && Representation->SegmentList->MultSegBaseType->SegBaseType) { - SegmentBase = Representation->SegmentList->MultSegBaseType->SegBaseType; - } else if (AdaptationSet && AdaptationSet->SegmentList - && AdaptationSet->SegmentList->MultSegBaseType - && AdaptationSet->SegmentList->MultSegBaseType->SegBaseType) { - SegmentBase = AdaptationSet->SegmentList->MultSegBaseType->SegBaseType; - } else if (Period && Period->SegmentList - && Period->SegmentList->MultSegBaseType - && Period->SegmentList->MultSegBaseType->SegBaseType) { - SegmentBase = Period->SegmentList->MultSegBaseType->SegBaseType; - } - } - - return SegmentBase; -} - -gint -gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations) -{ - GList *list = NULL, *lowest = NULL; - GstRepresentationNode *rep = NULL; - gint lowest_bandwidth = -1; - - if (Representations == NULL) - return -1; - - for (list = g_list_first (Representations); list; list = g_list_next (list)) { - rep = (GstRepresentationNode *) list->data; - if (rep && (!lowest || rep->bandwidth < lowest_bandwidth)) { - lowest = list; - lowest_bandwidth = rep->bandwidth; - } - } - - return lowest ? g_list_position (Representations, lowest) : -1; -} - -gint -gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations, - gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint - max_video_framerate_n, gint max_video_framerate_d) -{ - GList *list = NULL, *best = NULL; - GstRepresentationNode *representation; - gint best_bandwidth = 0; - - GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth); - - if (Representations == NULL) - return -1; - - if (max_bandwidth <= 0) /* 0 => get lowest representation available */ - return gst_mpdparser_get_rep_idx_with_min_bandwidth (Representations); - - for (list = g_list_first (Representations); list; list = g_list_next (list)) { - GstFrameRate *framerate = NULL; - - representation = (GstRepresentationNode *) list->data; - - /* FIXME: Really? */ - if (!representation) - continue; - - framerate = representation->RepresentationBase->frameRate; - if (!framerate) - framerate = representation->RepresentationBase->maxFrameRate; - - if (framerate && max_video_framerate_n > 0) { - if (gst_util_fraction_compare (framerate->num, framerate->den, - max_video_framerate_n, max_video_framerate_d) > 0) - continue; - } - - if (max_video_width > 0 - && representation->RepresentationBase->width > max_video_width) - continue; - if (max_video_height > 0 - && representation->RepresentationBase->height > max_video_height) - continue; - - if (representation->bandwidth <= max_bandwidth && - representation->bandwidth > best_bandwidth) { - best = list; - best_bandwidth = representation->bandwidth; - } - } - - return best ? g_list_position (Representations, best) : -1; -} - -static GstSegmentListNode * -gst_mpd_client_fetch_external_segment_list (GstMpdClient * client, - GstPeriodNode * Period, - GstAdaptationSetNode * AdaptationSet, - GstRepresentationNode * Representation, - GstSegmentListNode * parent, GstSegmentListNode * segment_list) -{ - GstFragment *download; - GstBuffer *segment_list_buffer; - GstMapInfo map; - GError *err = NULL; - xmlDocPtr doc = NULL; - GstUri *base_uri, *uri; - gchar *query = NULL; - gchar *uri_string; - GstSegmentListNode *new_segment_list = NULL; - - /* ISO/IEC 23009-1:2014 5.5.3 4) - * Remove nodes that resolve to nothing when resolving - */ - if (strcmp (segment_list->xlink_href, - "urn:mpeg:dash:resolve-to-zero:2013") == 0) { - return NULL; - } - - if (!client->downloader) { - return NULL; - } - - /* Build absolute URI */ - - /* Get base URI at the MPD level */ - base_uri = - gst_uri_from_string (client-> - mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); - - /* combine a BaseURL at the MPD level with the current base url */ - base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); - - /* combine a BaseURL at the Period level with the current base url */ - base_uri = combine_urls (base_uri, Period->BaseURLs, &query, 0); - - if (AdaptationSet) { - /* combine a BaseURL at the AdaptationSet level with the current base url */ - base_uri = combine_urls (base_uri, AdaptationSet->BaseURLs, &query, 0); - - if (Representation) { - /* combine a BaseURL at the Representation level with the current base url */ - base_uri = combine_urls (base_uri, Representation->BaseURLs, &query, 0); - } - } - - uri = gst_uri_from_string_with_base (base_uri, segment_list->xlink_href); - if (query) - gst_uri_set_query_string (uri, query); - g_free (query); - uri_string = gst_uri_to_string (uri); - gst_uri_unref (base_uri); - gst_uri_unref (uri); - - download = - gst_uri_downloader_fetch_uri (client->downloader, - uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); - g_free (uri_string); - - if (!download) { - GST_ERROR ("Failed to download external SegmentList node at '%s': %s", - segment_list->xlink_href, err->message); - g_clear_error (&err); - return NULL; - } - - segment_list_buffer = gst_fragment_get_buffer (download); - g_object_unref (download); - - gst_buffer_map (segment_list_buffer, &map, GST_MAP_READ); - - doc = - xmlReadMemory ((const gchar *) map.data, map.size, "noname.xml", NULL, - XML_PARSE_NONET); - - gst_buffer_unmap (segment_list_buffer, &map); - gst_buffer_unref (segment_list_buffer); - - /* NOTE: ISO/IEC 23009-1:2014 5.3.9.3.2 is saying that one or multiple SegmentList - * in external xml is allowed, however, multiple SegmentList does not make sense - * because Period/AdaptationSet/Representation allow only one SegmentList */ - if (doc) { - xmlNode *root_element = xmlDocGetRootElement (doc); - - if (root_element->type != XML_ELEMENT_NODE || - xmlStrcmp (root_element->name, (xmlChar *) "SegmentList") != 0) { - goto error; - } - - gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element, - parent); - } else { - goto error; - } - -done: - if (doc) - xmlFreeDoc (doc); - - return new_segment_list; - -error: - GST_ERROR ("Failed to parse segment list node XML"); - goto done; -} - -static GstSegmentListNode * -gst_mpdparser_get_segment_list (GstMpdClient * client, GstPeriodNode * Period, - GstAdaptationSetNode * AdaptationSet, - GstRepresentationNode * Representation) -{ - GstSegmentListNode **SegmentList; - GstSegmentListNode *ParentSegmentList = NULL; - - if (Representation && Representation->SegmentList) { - SegmentList = &Representation->SegmentList; - ParentSegmentList = AdaptationSet->SegmentList; - } else if (AdaptationSet && AdaptationSet->SegmentList) { - SegmentList = &AdaptationSet->SegmentList; - ParentSegmentList = Period->SegmentList; - Representation = NULL; - } else { - Representation = NULL; - AdaptationSet = NULL; - SegmentList = &Period->SegmentList; - } - - /* Resolve external segment list here. */ - if (*SegmentList && (*SegmentList)->xlink_href) { - GstSegmentListNode *new_segment_list; - - /* TODO: Use SegmentList of parent if - * - Parent has its own SegmentList - * - Fail to get SegmentList from external xml - */ - new_segment_list = - gst_mpd_client_fetch_external_segment_list (client, Period, - AdaptationSet, Representation, ParentSegmentList, *SegmentList); - - gst_mpdparser_free_segment_list_node (*SegmentList); - *SegmentList = new_segment_list; - } - - return *SegmentList; -} - -/* memory management functions */ -static void -gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node) -{ - if (mpd_node) { - if (mpd_node->default_namespace) - xmlFree (mpd_node->default_namespace); - if (mpd_node->namespace_xsi) - xmlFree (mpd_node->namespace_xsi); - if (mpd_node->namespace_ext) - xmlFree (mpd_node->namespace_ext); - if (mpd_node->schemaLocation) - xmlFree (mpd_node->schemaLocation); - if (mpd_node->id) - xmlFree (mpd_node->id); - if (mpd_node->profiles) - xmlFree (mpd_node->profiles); - if (mpd_node->availabilityStartTime) - gst_date_time_unref (mpd_node->availabilityStartTime); - if (mpd_node->availabilityEndTime) - gst_date_time_unref (mpd_node->availabilityEndTime); - g_list_free_full (mpd_node->ProgramInfo, - (GDestroyNotify) gst_mpdparser_free_prog_info_node); - g_list_free_full (mpd_node->BaseURLs, - (GDestroyNotify) gst_mpdparser_free_base_url_node); - g_list_free_full (mpd_node->Locations, (GDestroyNotify) xmlFree); - g_list_free_full (mpd_node->Periods, - (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); - } -} - -static void -gst_mpdparser_free_prog_info_node (GstProgramInformationNode * prog_info_node) -{ - if (prog_info_node) { - if (prog_info_node->lang) - xmlFree (prog_info_node->lang); - if (prog_info_node->moreInformationURL) - xmlFree (prog_info_node->moreInformationURL); - if (prog_info_node->Title) - xmlFree (prog_info_node->Title); - if (prog_info_node->Source) - xmlFree (prog_info_node->Source); - if (prog_info_node->Copyright) - xmlFree (prog_info_node->Copyright); - g_slice_free (GstProgramInformationNode, prog_info_node); - } -} - -static void -gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node) +static void +gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node) { if (metrics_node) { if (metrics_node->metrics) @@ -2826,27 +1281,6 @@ gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * metrics_range_node) } static void -gst_mpdparser_free_period_node (GstPeriodNode * period_node) -{ - if (period_node) { - if (period_node->id) - xmlFree (period_node->id); - gst_mpdparser_free_seg_base_type_ext (period_node->SegmentBase); - gst_mpdparser_free_segment_list_node (period_node->SegmentList); - gst_mpdparser_free_segment_template_node (period_node->SegmentTemplate); - g_list_free_full (period_node->AdaptationSets, - (GDestroyNotify) gst_mpdparser_free_adaptation_set_node); - g_list_free_full (period_node->Subsets, - (GDestroyNotify) gst_mpdparser_free_subset_node); - g_list_free_full (period_node->BaseURLs, - (GDestroyNotify) gst_mpdparser_free_base_url_node); - if (period_node->xlink_href) - xmlFree (period_node->xlink_href); - g_slice_free (GstPeriodNode, period_node); - } -} - -static void gst_mpdparser_free_subset_node (GstSubsetNode * subset_node) { if (subset_node) { @@ -2883,10 +1317,10 @@ gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * if (representation_base) { if (representation_base->profiles) xmlFree (representation_base->profiles); - g_slice_free (GstRatio, representation_base->sar); - g_slice_free (GstFrameRate, representation_base->frameRate); - g_slice_free (GstFrameRate, representation_base->minFrameRate); - g_slice_free (GstFrameRate, representation_base->maxFrameRate); + g_slice_free (GstXMLRatio, representation_base->sar); + g_slice_free (GstXMLFrameRate, representation_base->frameRate); + g_slice_free (GstXMLFrameRate, representation_base->minFrameRate); + g_slice_free (GstXMLFrameRate, representation_base->maxFrameRate); if (representation_base->audioSamplingRate) xmlFree (representation_base->audioSamplingRate); if (representation_base->mimeType) @@ -2908,46 +1342,6 @@ gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * } static void -gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * - adaptation_set_node) -{ - if (adaptation_set_node) { - if (adaptation_set_node->lang) - xmlFree (adaptation_set_node->lang); - if (adaptation_set_node->contentType) - xmlFree (adaptation_set_node->contentType); - g_slice_free (GstRatio, adaptation_set_node->par); - g_slice_free (GstConditionalUintType, - adaptation_set_node->segmentAlignment); - g_slice_free (GstConditionalUintType, - adaptation_set_node->subsegmentAlignment); - g_list_free_full (adaptation_set_node->Accessibility, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (adaptation_set_node->Role, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (adaptation_set_node->Rating, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (adaptation_set_node->Viewpoint, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - gst_mpdparser_free_representation_base_type - (adaptation_set_node->RepresentationBase); - gst_mpdparser_free_seg_base_type_ext (adaptation_set_node->SegmentBase); - gst_mpdparser_free_segment_list_node (adaptation_set_node->SegmentList); - gst_mpdparser_free_segment_template_node - (adaptation_set_node->SegmentTemplate); - g_list_free_full (adaptation_set_node->BaseURLs, - (GDestroyNotify) gst_mpdparser_free_base_url_node); - g_list_free_full (adaptation_set_node->Representations, - (GDestroyNotify) gst_mpdparser_free_representation_node); - g_list_free_full (adaptation_set_node->ContentComponents, - (GDestroyNotify) gst_mpdparser_free_content_component_node); - if (adaptation_set_node->xlink_href) - xmlFree (adaptation_set_node->xlink_href); - g_slice_free (GstAdaptationSetNode, adaptation_set_node); - } -} - -static void gst_mpdparser_free_representation_node (GstRepresentationNode * representation_node) { @@ -2978,3188 +1372,788 @@ gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode * gst_mpdparser_free_representation_base_type (subrep_node->RepresentationBase); if (subrep_node->dependencyLevel) - xmlFree (subrep_node->dependencyLevel); - g_strfreev (subrep_node->contentComponent); - g_slice_free (GstSubRepresentationNode, subrep_node); - } -} - -static void -gst_mpdparser_free_s_node (GstSNode * s_node) -{ - if (s_node) { - g_slice_free (GstSNode, s_node); - } -} - -static GstSegmentTimelineNode * -gst_mpdparser_segment_timeline_node_new (void) -{ - GstSegmentTimelineNode *node = g_slice_new0 (GstSegmentTimelineNode); - - g_queue_init (&node->S); - - return node; -} - -static void -gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * seg_timeline) -{ - if (seg_timeline) { - g_queue_foreach (&seg_timeline->S, (GFunc) gst_mpdparser_free_s_node, NULL); - g_queue_clear (&seg_timeline->S); - g_slice_free (GstSegmentTimelineNode, seg_timeline); - } -} - -static void -gst_mpdparser_free_url_type_node (GstURLType * url_type_node) -{ - if (url_type_node) { - if (url_type_node->sourceURL) - xmlFree (url_type_node->sourceURL); - g_slice_free (GstRange, url_type_node->range); - g_slice_free (GstURLType, url_type_node); - } -} - -static void -gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * seg_base_type) -{ - if (seg_base_type) { - if (seg_base_type->indexRange) - g_slice_free (GstRange, seg_base_type->indexRange); - gst_mpdparser_free_url_type_node (seg_base_type->Initialization); - gst_mpdparser_free_url_type_node (seg_base_type->RepresentationIndex); - g_slice_free (GstSegmentBaseType, seg_base_type); - } -} - -static void -gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType * - mult_seg_base_type) -{ - if (mult_seg_base_type) { - /* SegmentBaseType extension */ - gst_mpdparser_free_seg_base_type_ext (mult_seg_base_type->SegBaseType); - gst_mpdparser_free_segment_timeline_node - (mult_seg_base_type->SegmentTimeline); - gst_mpdparser_free_url_type_node (mult_seg_base_type->BitstreamSwitching); - g_slice_free (GstMultSegmentBaseType, mult_seg_base_type); - } -} - -static void -gst_mpdparser_free_segment_list_node (GstSegmentListNode * segment_list_node) -{ - if (segment_list_node) { - g_list_free_full (segment_list_node->SegmentURL, - (GDestroyNotify) gst_mpdparser_free_segment_url_node); - /* MultipleSegmentBaseType extension */ - gst_mpdparser_free_mult_seg_base_type_ext - (segment_list_node->MultSegBaseType); - if (segment_list_node->xlink_href) - xmlFree (segment_list_node->xlink_href); - g_slice_free (GstSegmentListNode, segment_list_node); - } -} - -static void -gst_mpdparser_free_segment_url_node (GstSegmentURLNode * segment_url) -{ - if (segment_url) { - if (segment_url->media) - xmlFree (segment_url->media); - g_slice_free (GstRange, segment_url->mediaRange); - if (segment_url->index) - xmlFree (segment_url->index); - g_slice_free (GstRange, segment_url->indexRange); - g_slice_free (GstSegmentURLNode, segment_url); - } -} - -static void -gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node) -{ - if (base_url_node) { - if (base_url_node->baseURL) - xmlFree (base_url_node->baseURL); - if (base_url_node->serviceLocation) - xmlFree (base_url_node->serviceLocation); - if (base_url_node->byteRange) - xmlFree (base_url_node->byteRange); - g_slice_free (GstBaseURL, base_url_node); - } -} - -static void -gst_mpdparser_free_descriptor_type_node (GstDescriptorType * descriptor_type) -{ - if (descriptor_type) { - if (descriptor_type->schemeIdUri) - xmlFree (descriptor_type->schemeIdUri); - if (descriptor_type->value) - xmlFree (descriptor_type->value); - g_slice_free (GstDescriptorType, descriptor_type); - } -} - -static void -gst_mpdparser_free_content_component_node (GstContentComponentNode * - content_component_node) -{ - if (content_component_node) { - if (content_component_node->lang) - xmlFree (content_component_node->lang); - if (content_component_node->contentType) - xmlFree (content_component_node->contentType); - g_slice_free (GstRatio, content_component_node->par); - g_list_free_full (content_component_node->Accessibility, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (content_component_node->Role, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (content_component_node->Rating, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_list_free_full (content_component_node->Viewpoint, - (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); - g_slice_free (GstContentComponentNode, content_component_node); - } -} - -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) -{ - if (stream_period) { - g_slice_free (GstStreamPeriod, stream_period); - } -} - -static void -gst_mpdparser_free_media_segment (GstMediaSegment * media_segment) -{ - if (media_segment) { - g_slice_free (GstMediaSegment, media_segment); - } -} - -static void -gst_mpdparser_init_active_stream_segments (GstActiveStream * stream) -{ - g_assert (stream->segments == NULL); - stream->segments = g_ptr_array_new (); - g_ptr_array_set_free_func (stream->segments, - (GDestroyNotify) gst_mpdparser_free_media_segment); -} - -static void -gst_mpdparser_free_active_stream (GstActiveStream * active_stream) -{ - if (active_stream) { - g_free (active_stream->baseURL); - active_stream->baseURL = NULL; - g_free (active_stream->queryURL); - active_stream->queryURL = NULL; - if (active_stream->segments) - g_ptr_array_unref (active_stream->segments); - g_slice_free (GstActiveStream, active_stream); - } -} - -static gchar * -gst_mpdparser_get_mediaURL (GstActiveStream * stream, - GstSegmentURLNode * segmentURL) -{ - const gchar *url_prefix; - - g_return_val_if_fail (stream != NULL, NULL); - g_return_val_if_fail (segmentURL != NULL, NULL); - - url_prefix = segmentURL->media ? segmentURL->media : stream->baseURL; - g_return_val_if_fail (url_prefix != NULL, NULL); - - return segmentURL->media; -} - -static const gchar * -gst_mpdparser_get_initializationURL (GstActiveStream * stream, - GstURLType * InitializationURL) -{ - const gchar *url_prefix; - - g_return_val_if_fail (stream != NULL, NULL); - - url_prefix = (InitializationURL - && InitializationURL->sourceURL) ? InitializationURL-> - sourceURL : stream->baseURL; - - return url_prefix; -} - -/* ISO/IEC 23009-1:2004 5.3.9.4.4 */ -static gboolean -validate_format (const gchar * format) -{ - const gchar *p = format; - - /* Check if it starts with % */ - if (!p || p[0] != '%') - return FALSE; - p++; - - /* the spec mandates a format like %0[width]d */ - /* Following the %, we must have a 0 */ - if (p[0] != '0') - return FALSE; - - /* Following the % must be a number starting with 0 - */ - while (g_ascii_isdigit (*p)) - p++; - - /* After any 0 and alphanumeric values, there must be a d. - */ - if (p[0] != 'd') - return FALSE; - p++; - - /* And then potentially more characters without any - * further %, even if the spec does not mention this - */ - p = strchr (p, '%'); - if (p) - return FALSE; - - return TRUE; -} - -static gchar * -promote_format_to_uint64 (const gchar * format) -{ - const gchar *p = format; - gchar *promoted_format; - - /* Must be called with a validated format! */ - g_return_val_if_fail (validate_format (format), NULL); - - /* it starts with % */ - p++; - - /* Following the % must be a 0, or any of d, x or u. - * x and u are not part of the spec, but don't hurt us - */ - if (p[0] == '0') { - p++; - - while (g_ascii_isdigit (*p)) - p++; - } - - /* After any 0 and alphanumeric values, there must be a d. - * Otherwise validation would have failed - */ - g_assert (p[0] == 'd'); - - promoted_format = - g_strdup_printf ("%.*s" G_GINT64_MODIFIER "%s", (gint) (p - format), - format, p); - - return promoted_format; -} - -static gboolean -gst_mpdparser_validate_rfc1738_url (const char *s) -{ - while (*s) { - if (!strchr - (";:@&=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789$-_.+!*'(),%/", - *s)) - return FALSE; - if (*s == '%') { - /* g_ascii_isdigit returns FALSE for NUL, and || is a short circuiting - operator, so this is safe for strings ending before two hex digits */ - if (!g_ascii_isxdigit (s[1]) || !g_ascii_isxdigit (s[2])) - return FALSE; - s += 2; - } - s++; - } - return TRUE; -} - -static gchar * -gst_mpdparser_build_URL_from_template (const gchar * url_template, - const gchar * id, guint number, guint bandwidth, guint64 time) -{ - static const gchar default_format[] = "%01d"; - gchar **tokens, *token, *ret; - const gchar *format; - gint i, num_tokens; - - g_return_val_if_fail (url_template != NULL, NULL); - tokens = g_strsplit_set (url_template, "$", -1); - if (!tokens) { - GST_WARNING ("Scan of URL template failed!"); - return NULL; - } - num_tokens = g_strv_length (tokens); - - /* - * each identifier is guarded by 2 $, which means that we must have an odd number of tokens - * An even number of tokens means the string is not valid. - */ - if ((num_tokens & 1) == 0) { - GST_ERROR ("Invalid number of tokens (%d). url_template is '%s'", - num_tokens, url_template); - g_strfreev (tokens); - return NULL; - } - - for (i = 0; i < num_tokens; i++) { - token = tokens[i]; - format = default_format; - - /* the tokens to replace must be provided between $ characters, eg $token$ - * For a string like token0$token1$token2$token3$token4, only the odd number - * tokens (1,3,...) must be parsed. - * - * Skip even tokens - */ - if ((i & 1) == 0) - continue; - - if (!g_strcmp0 (token, "RepresentationID")) { - if (!gst_mpdparser_validate_rfc1738_url (id)) - goto invalid_representation_id; - - tokens[i] = g_strdup_printf ("%s", id); - g_free (token); - } else if (!strncmp (token, "Number", 6)) { - if (strlen (token) > 6) { - format = token + 6; /* format tag */ - } - if (!validate_format (format)) - goto invalid_format; - - tokens[i] = g_strdup_printf (format, number); - g_free (token); - } else if (!strncmp (token, "Bandwidth", 9)) { - if (strlen (token) > 9) { - format = token + 9; /* format tag */ - } - if (!validate_format (format)) - goto invalid_format; - - tokens[i] = g_strdup_printf (format, bandwidth); - g_free (token); - } else if (!strncmp (token, "Time", 4)) { - gchar *promoted_format; - - if (strlen (token) > 4) { - format = token + 4; /* format tag */ - } - if (!validate_format (format)) - goto invalid_format; - - promoted_format = promote_format_to_uint64 (format); - tokens[i] = g_strdup_printf (promoted_format, time); - g_free (promoted_format); - g_free (token); - } else if (!g_strcmp0 (token, "")) { - tokens[i] = g_strdup_printf ("%s", "$"); - g_free (token); - } else { - /* unexpected identifier found between $ signs - * - * "If the URL contains unescaped $ symbols which do not enclose a valid - * identifier then the result of URL formation is undefined" - */ - goto invalid_format; - } - } - - ret = g_strjoinv (NULL, tokens); - - g_strfreev (tokens); - - return ret; - -invalid_format: - { - GST_ERROR ("Invalid format '%s' in '%s'", format, token); - - g_strfreev (tokens); - - return NULL; - } -invalid_representation_id: - { - GST_ERROR - ("Representation ID string '%s' has characters invalid in an RFC 1738 URL", - id); - - g_strfreev (tokens); - - return NULL; - } -} - -guint -gst_mpd_client_get_period_index_at_time (GstMpdClient * client, - GstDateTime * time) -{ - GList *iter; - guint period_idx = G_MAXUINT; - guint idx; - gint64 time_offset; - GstDateTime *avail_start = - gst_mpd_client_get_availability_start_time (client); - GstStreamPeriod *stream_period; - - if (avail_start == NULL) - return 0; - - time_offset = gst_mpd_client_calculate_time_difference (avail_start, time); - gst_date_time_unref (avail_start); - - if (time_offset < 0) - return 0; - - if (!gst_mpd_client_setup_media_presentation (client, time_offset, -1, NULL)) - return 0; - - for (idx = 0, iter = client->periods; iter; idx++, iter = g_list_next (iter)) { - stream_period = iter->data; - if (stream_period->start <= time_offset - && (!GST_CLOCK_TIME_IS_VALID (stream_period->duration) - || stream_period->start + stream_period->duration > time_offset)) { - period_idx = idx; - break; - } - } - - return period_idx; -} - -static GstStreamPeriod * -gst_mpdparser_get_stream_period (GstMpdClient * client) -{ - g_return_val_if_fail (client != NULL, NULL); - g_return_val_if_fail (client->periods != NULL, NULL); - - return g_list_nth_data (client->periods, client->period_idx); -} - -static GstRange * -gst_mpdparser_clone_range (GstRange * range) -{ - GstRange *clone = NULL; - - if (range) { - clone = g_slice_new0 (GstRange); - clone->first_byte_pos = range->first_byte_pos; - clone->last_byte_pos = range->last_byte_pos; - } - - return clone; -} - -static GstURLType * -gst_mpdparser_clone_URL (GstURLType * url) -{ - - GstURLType *clone = NULL; - - if (url) { - clone = g_slice_new0 (GstURLType); - if (url->sourceURL) { - clone->sourceURL = xmlMemStrdup (url->sourceURL); - } - clone->range = gst_mpdparser_clone_range (url->range); - } - - return clone; -} - -/* - * Combine a base url with the current stream base url from the list of - * baseURLs. Takes ownership of base and returns a new base. - */ -static GstUri * -combine_urls (GstUri * base, GList * list, gchar ** query, guint idx) -{ - GstBaseURL *baseURL; - GstUri *ret = base; - - if (list != NULL) { - baseURL = g_list_nth_data (list, idx); - if (!baseURL) { - baseURL = list->data; - } - - ret = gst_uri_from_string_with_base (base, baseURL->baseURL); - gst_uri_unref (base); - - if (ret && query) { - g_free (*query); - *query = gst_uri_get_query_string (ret); - if (*query) { - ret = gst_uri_make_writable (ret); - gst_uri_set_query_table (ret, NULL); - } - } - } - - return ret; -} - -/* select a stream and extract the baseURL (if present) */ -static gchar * -gst_mpdparser_parse_baseURL (GstMpdClient * client, GstActiveStream * stream, - gchar ** query) -{ - GstStreamPeriod *stream_period; - static const gchar empty[] = ""; - gchar *ret = NULL; - GstUri *abs_url; - - g_return_val_if_fail (stream != NULL, g_strdup (empty)); - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, g_strdup (empty)); - g_return_val_if_fail (stream_period->period != NULL, g_strdup (empty)); - - /* NULLify query return before we start */ - if (query) - *query = NULL; - - /* initialise base url */ - abs_url = - gst_uri_from_string (client-> - mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); - - /* combine a BaseURL at the MPD level with the current base url */ - abs_url = - combine_urls (abs_url, client->mpd_node->BaseURLs, query, - stream->baseURL_idx); - - /* combine a BaseURL at the Period level with the current base url */ - abs_url = - combine_urls (abs_url, stream_period->period->BaseURLs, query, - stream->baseURL_idx); - - GST_DEBUG ("Current adaptation set id %i (%s)", stream->cur_adapt_set->id, - stream->cur_adapt_set->contentType); - /* combine a BaseURL at the AdaptationSet level with the current base url */ - abs_url = - combine_urls (abs_url, stream->cur_adapt_set->BaseURLs, query, - stream->baseURL_idx); - - /* combine a BaseURL at the Representation level with the current base url */ - abs_url = - combine_urls (abs_url, stream->cur_representation->BaseURLs, query, - stream->baseURL_idx); - - ret = gst_uri_to_string (abs_url); - gst_uri_unref (abs_url); - - return ret; -} - -static GstClockTime -gst_mpd_client_get_segment_duration (GstMpdClient * client, - GstActiveStream * stream, guint64 * scale_dur) -{ - GstStreamPeriod *stream_period; - GstMultSegmentBaseType *base = NULL; - GstClockTime duration = 0; - - g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE); - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, GST_CLOCK_TIME_NONE); - - if (stream->cur_segment_list) { - base = stream->cur_segment_list->MultSegBaseType; - } else if (stream->cur_seg_template) { - base = stream->cur_seg_template->MultSegBaseType; - } - - if (base == NULL || base->SegBaseType == NULL) { - /* this may happen when we have a single segment */ - duration = stream_period->duration; - if (scale_dur) - *scale_dur = duration; - } else { - /* duration is guint so this cannot overflow */ - duration = base->duration * GST_SECOND; - if (scale_dur) - *scale_dur = duration; - duration /= base->SegBaseType->timescale; - } - - return duration; -} - -/*****************************/ -/******* API functions *******/ -/*****************************/ - -GstMpdClient * -gst_mpd_client_new (void) -{ - GstMpdClient *client; - - client = g_new0 (GstMpdClient, 1); - - return client; -} - -void -gst_active_streams_free (GstMpdClient * client) -{ - if (client->active_streams) { - g_list_foreach (client->active_streams, - (GFunc) gst_mpdparser_free_active_stream, NULL); - g_list_free (client->active_streams); - client->active_streams = NULL; - } -} - -void -gst_mpd_client_free (GstMpdClient * client) -{ - g_return_if_fail (client != NULL); - - if (client->mpd_node) - gst_mpdparser_free_mpd_node (client->mpd_node); - - if (client->periods) { - g_list_free_full (client->periods, - (GDestroyNotify) gst_mpdparser_free_stream_period); - } - - gst_active_streams_free (client); - - g_free (client->mpd_uri); - client->mpd_uri = NULL; - g_free (client->mpd_base_uri); - client->mpd_base_uri = NULL; - - if (client->downloader) - gst_object_unref (client->downloader); - client->downloader = NULL; - - g_free (client); -} - -void -gst_mpd_client_set_uri_downloader (GstMpdClient * client, - GstUriDownloader * downloader) -{ - if (client->downloader) - gst_object_unref (client->downloader); - client->downloader = gst_object_ref (downloader); -} - -static void -gst_mpd_client_check_profiles (GstMpdClient * client) -{ - GST_DEBUG ("Profiles: %s", - client->mpd_node->profiles ? client->mpd_node->profiles : ""); - - if (!client->mpd_node->profiles) - return; - - if (g_strstr_len (client->mpd_node->profiles, -1, - "urn:mpeg:dash:profile:isoff-on-demand:2011")) { - client->profile_isoff_ondemand = TRUE; - GST_DEBUG ("Found ISOFF on demand profile (2011)"); - } -} - -static void -gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client) -{ - GList *l; - - for (l = client->mpd_node->Periods; l; /* explicitly advanced below */ ) { - GstPeriodNode *period = l->data; - GList *m; - - if (period->xlink_href && period->actuate == GST_XLINK_ACTUATE_ON_LOAD) { - GList *new_periods, *prev, *next; - - new_periods = gst_mpd_client_fetch_external_period (client, period); - - prev = l->prev; - client->mpd_node->Periods = - g_list_delete_link (client->mpd_node->Periods, l); - gst_mpdparser_free_period_node (period); - period = NULL; - - /* Get new next node, we will insert before this */ - if (prev) - next = prev->next; - else - next = client->mpd_node->Periods; - - while (new_periods) { - client->mpd_node->Periods = - g_list_insert_before (client->mpd_node->Periods, next, - new_periods->data); - new_periods = g_list_delete_link (new_periods, new_periods); - } - next = NULL; - - /* Update our iterator to the first new period if any, or the next */ - if (prev) - l = prev->next; - else - l = client->mpd_node->Periods; - - continue; - } - - if (period->SegmentList && period->SegmentList->xlink_href - && period->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) { - GstSegmentListNode *new_segment_list; - - new_segment_list = - gst_mpd_client_fetch_external_segment_list (client, period, NULL, - NULL, NULL, period->SegmentList); - - gst_mpdparser_free_segment_list_node (period->SegmentList); - period->SegmentList = new_segment_list; - } - - for (m = period->AdaptationSets; m; /* explicitly advanced below */ ) { - GstAdaptationSetNode *adapt_set = m->data; - GList *n; - - if (adapt_set->xlink_href - && adapt_set->actuate == GST_XLINK_ACTUATE_ON_LOAD) { - GList *new_adapt_sets, *prev, *next; - - new_adapt_sets = - gst_mpd_client_fetch_external_adaptation_set (client, period, - adapt_set); - - prev = m->prev; - period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m); - gst_mpdparser_free_adaptation_set_node (adapt_set); - adapt_set = NULL; - - /* Get new next node, we will insert before this */ - if (prev) - next = prev->next; - else - next = period->AdaptationSets; - - while (new_adapt_sets) { - period->AdaptationSets = - g_list_insert_before (period->AdaptationSets, next, - new_adapt_sets->data); - new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets); - } - next = NULL; - - /* Update our iterator to the first new adapt_set if any, or the next */ - if (prev) - m = prev->next; - else - m = period->AdaptationSets; - - continue; - } - - if (adapt_set->SegmentList && adapt_set->SegmentList->xlink_href - && adapt_set->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) { - GstSegmentListNode *new_segment_list; - - new_segment_list = - gst_mpd_client_fetch_external_segment_list (client, period, - adapt_set, NULL, period->SegmentList, adapt_set->SegmentList); - - gst_mpdparser_free_segment_list_node (adapt_set->SegmentList); - adapt_set->SegmentList = new_segment_list; - } - - for (n = adapt_set->Representations; n; n = n->next) { - GstRepresentationNode *representation = n->data; - - if (representation->SegmentList - && representation->SegmentList->xlink_href - && representation->SegmentList->actuate == - GST_XLINK_ACTUATE_ON_LOAD) { - - GstSegmentListNode *new_segment_list; - - new_segment_list = - gst_mpd_client_fetch_external_segment_list (client, period, - adapt_set, representation, adapt_set->SegmentList, - representation->SegmentList); - - gst_mpdparser_free_segment_list_node (representation->SegmentList); - representation->SegmentList = new_segment_list; - - } - } - - m = m->next; - } - - l = l->next; - } -} - -gboolean -gst_mpd_parse (GstMpdClient * client, const gchar * data, gint size) -{ - gboolean ret = FALSE; - - if (data) { - xmlDocPtr doc; - xmlNode *root_element = NULL; - - GST_DEBUG ("MPD file fully buffered, start parsing..."); - - /* parse the complete MPD file into a tree (using the libxml2 default parser API) */ - - /* this initialize the library and check potential ABI mismatches - * between the version it was compiled for and the actual shared - * library used - */ - LIBXML_TEST_VERSION; - - /* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */ - doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); - if (doc == NULL) { - GST_ERROR ("failed to parse the MPD file"); - ret = FALSE; - } else { - /* get the root element node */ - root_element = xmlDocGetRootElement (doc); - - if (root_element->type != XML_ELEMENT_NODE - || xmlStrcmp (root_element->name, (xmlChar *) "MPD") != 0) { - GST_ERROR - ("can not find the root element MPD, failed to parse the MPD file"); - ret = FALSE; /* used to return TRUE before, but this seems wrong */ - } else { - /* now we can parse the MPD root node and all children nodes, recursively */ - ret = gst_mpdparser_parse_root_node (&client->mpd_node, root_element); - } - /* free the document */ - xmlFreeDoc (doc); - } - - if (ret) { - gst_mpd_client_check_profiles (client); - gst_mpd_client_fetch_on_load_external_resources (client); - } - } - - return ret; -} - -const gchar * -gst_mpdparser_get_baseURL (GstMpdClient * client, guint indexStream) -{ - GstActiveStream *stream; - - g_return_val_if_fail (client != NULL, NULL); - g_return_val_if_fail (client->active_streams != NULL, NULL); - stream = g_list_nth_data (client->active_streams, indexStream); - g_return_val_if_fail (stream != NULL, NULL); - - return stream->baseURL; -} - -static GstClockTime -gst_mpdparser_get_segment_end_time (GstMpdClient * client, GPtrArray * segments, - const GstMediaSegment * segment, gint index) -{ - const GstStreamPeriod *stream_period; - GstClockTime end; - - if (segment->repeat >= 0) - return segment->start + (segment->repeat + 1) * segment->duration; - - if (index < segments->len - 1) { - const GstMediaSegment *next_segment = - g_ptr_array_index (segments, index + 1); - end = next_segment->start; - } else { - stream_period = gst_mpdparser_get_stream_period (client); - end = stream_period->start + stream_period->duration; - } - return end; -} - -static gboolean -gst_mpd_client_add_media_segment (GstActiveStream * stream, - GstSegmentURLNode * url_node, guint number, gint repeat, - guint64 scale_start, guint64 scale_duration, - GstClockTime start, GstClockTime duration) -{ - GstMediaSegment *media_segment; - - g_return_val_if_fail (stream->segments != NULL, FALSE); - - media_segment = g_slice_new0 (GstMediaSegment); - - media_segment->SegmentURL = url_node; - media_segment->number = number; - media_segment->scale_start = scale_start; - media_segment->scale_duration = scale_duration; - media_segment->start = start; - media_segment->duration = duration; - media_segment->repeat = repeat; - - g_ptr_array_add (stream->segments, media_segment); - GST_LOG ("Added new segment: number %d, repeat %d, " - "ts: %" GST_TIME_FORMAT ", dur: %" - GST_TIME_FORMAT, number, repeat, - GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); - - return TRUE; -} - -static void -gst_mpd_client_stream_update_presentation_time_offset (GstMpdClient * client, - GstActiveStream * stream) -{ - GstSegmentBaseType *segbase = NULL; - - /* Find the used segbase */ - if (stream->cur_segment_list) { - segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType; - } else if (stream->cur_seg_template) { - segbase = stream->cur_seg_template->MultSegBaseType->SegBaseType; - } else if (stream->cur_segment_base) { - segbase = stream->cur_segment_base; - } - - if (segbase) { - /* Avoid overflows */ - stream->presentationTimeOffset = - gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND, - segbase->timescale); - } else { - stream->presentationTimeOffset = 0; - } - - GST_LOG ("Setting stream's presentation time offset to %" GST_TIME_FORMAT, - GST_TIME_ARGS (stream->presentationTimeOffset)); -} - -gboolean -gst_mpd_client_setup_representation (GstMpdClient * client, - GstActiveStream * stream, GstRepresentationNode * representation) -{ - GstStreamPeriod *stream_period; - GList *rep_list; - GstClockTime PeriodStart, PeriodEnd, start_time, duration; - guint i; - guint64 start; - - if (stream->cur_adapt_set == NULL) { - GST_WARNING ("No valid AdaptationSet node in the MPD file, aborting..."); - return FALSE; - } - - rep_list = stream->cur_adapt_set->Representations; - stream->cur_representation = representation; - stream->representation_idx = g_list_index (rep_list, representation); - - /* clean the old segment list, if any */ - if (stream->segments) { - g_ptr_array_unref (stream->segments); - stream->segments = NULL; - } - - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, FALSE); - g_return_val_if_fail (stream_period->period != NULL, FALSE); - - PeriodStart = stream_period->start; - if (GST_CLOCK_TIME_IS_VALID (stream_period->duration)) - PeriodEnd = stream_period->start + stream_period->duration; - else - PeriodEnd = GST_CLOCK_TIME_NONE; - - GST_LOG ("Building segment list for Period from %" GST_TIME_FORMAT " to %" - GST_TIME_FORMAT, GST_TIME_ARGS (PeriodStart), GST_TIME_ARGS (PeriodEnd)); - - if (representation->SegmentBase != NULL - || representation->SegmentList != NULL) { - GList *SegmentURL; - - /* We have a fixed list of segments for any of the cases here, - * init the segments list */ - gst_mpdparser_init_active_stream_segments (stream); - - /* get the first segment_base of the selected representation */ - if ((stream->cur_segment_base = - gst_mpdparser_get_segment_base (stream_period->period, - stream->cur_adapt_set, representation)) == NULL) { - GST_DEBUG ("No useful SegmentBase node for the current Representation"); - } - - /* get the first segment_list of the selected representation */ - if ((stream->cur_segment_list = - gst_mpdparser_get_segment_list (client, stream_period->period, - stream->cur_adapt_set, representation)) == NULL) { - GST_DEBUG ("No useful SegmentList node for the current Representation"); - /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ - if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0, - PeriodEnd - PeriodStart, PeriodStart, PeriodEnd - PeriodStart)) { - return FALSE; - } - } else { - /* build the list of GstMediaSegment nodes from the SegmentList node */ - SegmentURL = stream->cur_segment_list->SegmentURL; - if (SegmentURL == NULL) { - GST_WARNING - ("No valid list of SegmentURL nodes in the MPD file, aborting..."); - return FALSE; - } - - /* build segment list */ - i = stream->cur_segment_list->MultSegBaseType->startNumber; - start = 0; - start_time = PeriodStart; - - GST_LOG ("Building media segment list using a SegmentList node"); - if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) { - GstSegmentTimelineNode *timeline; - GstSNode *S; - GList *list; - GstClockTime presentationTimeOffset; - GstSegmentBaseType *segbase; - - segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType; - presentationTimeOffset = - gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND, - segbase->timescale); - GST_LOG ("presentationTimeOffset = %" G_GUINT64_FORMAT, - presentationTimeOffset); - timeline = stream->cur_segment_list->MultSegBaseType->SegmentTimeline; - for (list = g_queue_peek_head_link (&timeline->S); list; - list = g_list_next (list)) { - guint timescale; - - S = (GstSNode *) list->data; - GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%d t=%" - G_GUINT64_FORMAT, S->d, S->r, S->t); - timescale = - stream->cur_segment_list->MultSegBaseType->SegBaseType->timescale; - duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale); - - if (S->t > 0) { - start = S->t; - start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale) - + PeriodStart - presentationTimeOffset; - } - - if (!SegmentURL) { - GST_WARNING - ("SegmentTimeline does not have a matching SegmentURL, aborting..."); - return FALSE; - } - - if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, - S->r, start, S->d, start_time, duration)) { - return FALSE; - } - i += S->r + 1; - start_time += duration * (S->r + 1); - start += S->d * (S->r + 1); - SegmentURL = g_list_next (SegmentURL); - } - } else { - guint64 scale_dur; - - duration = - gst_mpd_client_get_segment_duration (client, stream, &scale_dur); - if (!GST_CLOCK_TIME_IS_VALID (duration)) - return FALSE; - - while (SegmentURL) { - if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i, - 0, start, scale_dur, start_time, duration)) { - return FALSE; - } - i++; - start += scale_dur; - start_time += duration; - SegmentURL = g_list_next (SegmentURL); - } - } - } - } else { - if (representation->SegmentTemplate != NULL) { - stream->cur_seg_template = representation->SegmentTemplate; - } else if (stream->cur_adapt_set->SegmentTemplate != NULL) { - stream->cur_seg_template = stream->cur_adapt_set->SegmentTemplate; - } else if (stream_period->period->SegmentTemplate != NULL) { - stream->cur_seg_template = stream_period->period->SegmentTemplate; - } - - if (stream->cur_seg_template == NULL - || stream->cur_seg_template->MultSegBaseType == NULL) { - - gst_mpdparser_init_active_stream_segments (stream); - /* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */ - if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0, - PeriodEnd - PeriodStart, 0, PeriodEnd - PeriodStart)) { - return FALSE; - } - } else { - GstClockTime presentationTimeOffset; - GstMultSegmentBaseType *mult_seg = - stream->cur_seg_template->MultSegBaseType; - - presentationTimeOffset = - gst_util_uint64_scale (mult_seg->SegBaseType->presentationTimeOffset, - GST_SECOND, mult_seg->SegBaseType->timescale); - GST_LOG ("presentationTimeOffset = %" GST_TIME_FORMAT, - GST_TIME_ARGS (presentationTimeOffset)); - /* build segment list */ - i = mult_seg->startNumber; - start = 0; - start_time = 0; - - GST_LOG ("Building media segment list using this template: %s", - stream->cur_seg_template->media); - - if (mult_seg->SegmentTimeline) { - GstSegmentTimelineNode *timeline; - GstSNode *S; - GList *list; - - timeline = mult_seg->SegmentTimeline; - gst_mpdparser_init_active_stream_segments (stream); - for (list = g_queue_peek_head_link (&timeline->S); list; - list = g_list_next (list)) { - guint timescale; - - S = (GstSNode *) list->data; - GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%u t=%" - G_GUINT64_FORMAT, S->d, S->r, S->t); - timescale = mult_seg->SegBaseType->timescale; - duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale); - if (S->t > 0) { - start = S->t; - start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale) - + PeriodStart - presentationTimeOffset; - } - - if (!gst_mpd_client_add_media_segment (stream, NULL, i, S->r, start, - S->d, start_time, duration)) { - return FALSE; - } - i += S->r + 1; - start += S->d * (S->r + 1); - start_time += duration * (S->r + 1); - } - } else { - /* NOP - The segment is created on demand with the template, no need - * to build a list */ - } - } - } - - /* clip duration of segments to stop at period end */ - if (stream->segments && stream->segments->len) { - if (GST_CLOCK_TIME_IS_VALID (PeriodEnd)) { - guint n; - - for (n = 0; n < stream->segments->len; ++n) { - GstMediaSegment *media_segment = - g_ptr_array_index (stream->segments, n); - if (media_segment) { - if (media_segment->start + media_segment->duration > PeriodEnd) { - GstClockTime stop = PeriodEnd; - if (n < stream->segments->len - 1) { - GstMediaSegment *next_segment = - g_ptr_array_index (stream->segments, n + 1); - if (next_segment && next_segment->start < PeriodEnd) - stop = next_segment->start; - } - media_segment->duration = - media_segment->start > stop ? 0 : stop - media_segment->start; - GST_LOG ("Fixed duration of segment %u: %" GST_TIME_FORMAT, n, - GST_TIME_ARGS (media_segment->duration)); - - /* If the segment was clipped entirely, we discard it and all - * subsequent ones */ - if (media_segment->duration == 0) { - GST_WARNING ("Discarding %u segments outside period", - stream->segments->len - n); - /* _set_size should properly unref elements */ - g_ptr_array_set_size (stream->segments, n); - break; - } - } - } - } - } -#ifndef GST_DISABLE_GST_DEBUG - if (stream->segments->len > 0) { - GstMediaSegment *last_media_segment = - g_ptr_array_index (stream->segments, stream->segments->len - 1); - GST_LOG ("Built a list of %d segments", last_media_segment->number); - } else { - GST_LOG ("All media segments were clipped"); - } -#endif - } - - g_free (stream->baseURL); - g_free (stream->queryURL); - stream->baseURL = - gst_mpdparser_parse_baseURL (client, stream, &stream->queryURL); - - gst_mpd_client_stream_update_presentation_time_offset (client, stream); - - return TRUE; -} - -#define CUSTOM_WRAPPER_START "" -#define CUSTOM_WRAPPER_END "" - -static GList * -gst_mpd_client_fetch_external_period (GstMpdClient * client, - GstPeriodNode * period_node) -{ - GstFragment *download; - GstAdapter *adapter; - GstBuffer *period_buffer; - GError *err = NULL; - xmlDocPtr doc = NULL; - GstUri *base_uri, *uri; - gchar *query = NULL; - gchar *uri_string, *wrapper; - GList *new_periods = NULL; - const gchar *data; - - /* ISO/IEC 23009-1:2014 5.5.3 4) - * Remove nodes that resolve to nothing when resolving - */ - if (strcmp (period_node->xlink_href, - "urn:mpeg:dash:resolve-to-zero:2013") == 0) { - return NULL; - } - - if (!client->downloader) { - return NULL; - } - - /* Build absolute URI */ - - /* Get base URI at the MPD level */ - base_uri = - gst_uri_from_string (client-> - mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); - - /* combine a BaseURL at the MPD level with the current base url */ - base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); - uri = gst_uri_from_string_with_base (base_uri, period_node->xlink_href); - if (query) - gst_uri_set_query_string (uri, query); - g_free (query); - uri_string = gst_uri_to_string (uri); - gst_uri_unref (base_uri); - gst_uri_unref (uri); - - download = - gst_uri_downloader_fetch_uri (client->downloader, - uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); - g_free (uri_string); - - if (!download) { - GST_ERROR ("Failed to download external Period node at '%s': %s", - period_node->xlink_href, err->message); - g_clear_error (&err); - return NULL; - } - - period_buffer = gst_fragment_get_buffer (download); - g_object_unref (download); - - /* external xml could have multiple period without root xmlNode. - * To avoid xml parsing error caused by no root node, wrapping it with - * custom root node */ - adapter = gst_adapter_new (); - - wrapper = g_new (gchar, strlen (CUSTOM_WRAPPER_START)); - memcpy (wrapper, CUSTOM_WRAPPER_START, strlen (CUSTOM_WRAPPER_START)); - gst_adapter_push (adapter, - gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_START))); - - gst_adapter_push (adapter, period_buffer); - - wrapper = g_strdup (CUSTOM_WRAPPER_END); - gst_adapter_push (adapter, - gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_END) + 1)); - - data = gst_adapter_map (adapter, gst_adapter_available (adapter)); - - doc = - xmlReadMemory (data, gst_adapter_available (adapter), "noname.xml", NULL, - XML_PARSE_NONET); - - gst_adapter_unmap (adapter); - gst_adapter_clear (adapter); - gst_object_unref (adapter); - - if (doc) { - xmlNode *root_element = xmlDocGetRootElement (doc); - xmlNode *iter; - - if (root_element->type != XML_ELEMENT_NODE) - goto error; - - for (iter = root_element->children; iter; iter = iter->next) { - if (iter->type == XML_ELEMENT_NODE) { - if (xmlStrcmp (iter->name, (xmlChar *) "Period") == 0) { - gst_mpdparser_parse_period_node (&new_periods, iter); - } else { - goto error; - } - } - } - } else { - goto error; - } - -done: - if (doc) - xmlFreeDoc (doc); - - return new_periods; - -error: - GST_ERROR ("Failed to parse period node XML"); - - if (new_periods) { - g_list_free_full (new_periods, - (GDestroyNotify) gst_mpdparser_free_period_node); - new_periods = NULL; - } - goto done; -} - -gboolean -gst_mpd_client_setup_media_presentation (GstMpdClient * client, - GstClockTime time, gint period_idx, const gchar * period_id) -{ - GstStreamPeriod *stream_period; - GstClockTime start, duration; - GList *list, *next; - guint idx; - gboolean ret = FALSE; - - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->mpd_node != NULL, FALSE); - - /* Check if we set up the media presentation far enough already */ - for (list = client->periods; list; list = list->next) { - GstStreamPeriod *stream_period = list->data; - - if ((time != GST_CLOCK_TIME_NONE - && stream_period->duration != GST_CLOCK_TIME_NONE - && stream_period->start + stream_period->duration >= time) - || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time)) - return TRUE; - - if (period_idx != -1 && stream_period->number >= period_idx) - return TRUE; - - if (period_id != NULL && stream_period->period->id != NULL - && strcmp (stream_period->period->id, period_id) == 0) - return TRUE; - - } - - GST_DEBUG ("Building the list of Periods in the Media Presentation"); - /* clean the old period list, if any */ - /* TODO: In theory we could reuse the ones we have so far but that - * seems more complicated than the overhead caused here - */ - if (client->periods) { - g_list_foreach (client->periods, - (GFunc) gst_mpdparser_free_stream_period, NULL); - g_list_free (client->periods); - client->periods = NULL; - } - - idx = 0; - start = 0; - duration = GST_CLOCK_TIME_NONE; - - if (client->mpd_node->mediaPresentationDuration <= 0 && - client->mpd_node->mediaPresentationDuration != -1) { - /* Invalid MPD file: MPD duration is negative or zero */ - goto syntax_error; - } - - for (list = client->mpd_node->Periods; list; /* explicitly advanced below */ ) { - GstPeriodNode *period_node = list->data; - GstPeriodNode *next_period_node = NULL; - - /* Download external period */ - if (period_node->xlink_href) { - GList *new_periods; - GList *prev; - - new_periods = gst_mpd_client_fetch_external_period (client, period_node); - - prev = list->prev; - client->mpd_node->Periods = - g_list_delete_link (client->mpd_node->Periods, list); - gst_mpdparser_free_period_node (period_node); - period_node = NULL; - - /* Get new next node, we will insert before this */ - if (prev) - next = prev->next; - else - next = client->mpd_node->Periods; - - while (new_periods) { - client->mpd_node->Periods = - g_list_insert_before (client->mpd_node->Periods, next, - new_periods->data); - new_periods = g_list_delete_link (new_periods, new_periods); - } - next = NULL; - - /* Update our iterator to the first new period if any, or the next */ - if (prev) - list = prev->next; - else - list = client->mpd_node->Periods; - - /* And try again */ - continue; - } - - if (period_node->start != -1) { - /* we have a regular period */ - /* start cannot be smaller than previous start */ - if (list != g_list_first (client->mpd_node->Periods) - && start >= period_node->start * GST_MSECOND) { - /* Invalid MPD file: duration would be negative or zero */ - goto syntax_error; - } - start = period_node->start * GST_MSECOND; - } else if (duration != GST_CLOCK_TIME_NONE) { - /* start time inferred from previous period, this is still a regular period */ - start += duration; - } else if (idx == 0 && client->mpd_node->type == GST_MPD_FILE_TYPE_STATIC) { - /* first period of a static MPD file, start time is 0 */ - start = 0; - } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { - /* this should be a live stream, let this pass */ - } else { - /* this is an 'Early Available Period' */ - goto early; - } - - /* compute duration. - If there is a start time for the next period, or this is the last period - and mediaPresentationDuration was set, those values will take precedence - over a configured period duration in computing this period's duration - - ISO/IEC 23009-1:2014(E), chapter 5.3.2.1 - "The Period extends until the PeriodStart of the next Period, or until - the end of the Media Presentation in the case of the last Period." - */ - - while ((next = g_list_next (list)) != NULL) { - /* try to infer this period duration from the start time of the next period */ - next_period_node = next->data; - - if (next_period_node->xlink_href) { - GList *new_periods; - - new_periods = - gst_mpd_client_fetch_external_period (client, next_period_node); - - client->mpd_node->Periods = - g_list_delete_link (client->mpd_node->Periods, next); - gst_mpdparser_free_period_node (next_period_node); - next_period_node = NULL; - /* Get new next node, we will insert before this */ - next = g_list_next (list); - while (new_periods) { - client->mpd_node->Periods = - g_list_insert_before (client->mpd_node->Periods, next, - new_periods->data); - new_periods = g_list_delete_link (new_periods, new_periods); - } - - /* And try again, getting the next list element which is now our newly - * inserted nodes. If any */ - } else { - /* Got the next period and it doesn't have to be downloaded first */ - break; - } - } - - if (next_period_node) { - if (next_period_node->start != -1) { - if (start >= next_period_node->start * GST_MSECOND) { - /* Invalid MPD file: duration would be negative or zero */ - goto syntax_error; - } - duration = next_period_node->start * GST_MSECOND - start; - } else if (period_node->duration != -1) { - if (period_node->duration <= 0) { - /* Invalid MPD file: duration would be negative or zero */ - goto syntax_error; - } - duration = period_node->duration * GST_MSECOND; - } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { - /* might be a live file, ignore unspecified duration */ - } else { - /* Invalid MPD file! */ - goto syntax_error; - } - } else if (client->mpd_node->mediaPresentationDuration != -1) { - /* last Period of the Media Presentation */ - if (client->mpd_node->mediaPresentationDuration * GST_MSECOND <= start) { - /* Invalid MPD file: duration would be negative or zero */ - goto syntax_error; - } - duration = - client->mpd_node->mediaPresentationDuration * GST_MSECOND - start; - } else if (period_node->duration != -1) { - duration = period_node->duration * GST_MSECOND; - } else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) { - /* might be a live file, ignore unspecified duration */ - } else { - /* Invalid MPD file! */ - goto syntax_error; - } - - stream_period = g_slice_new0 (GstStreamPeriod); - client->periods = g_list_append (client->periods, stream_period); - stream_period->period = period_node; - stream_period->number = idx++; - stream_period->start = start; - stream_period->duration = duration; - ret = TRUE; - GST_LOG (" - added Period %d start=%" GST_TIME_FORMAT " duration=%" - GST_TIME_FORMAT, idx, GST_TIME_ARGS (start), GST_TIME_ARGS (duration)); - - if ((time != GST_CLOCK_TIME_NONE - && stream_period->duration != GST_CLOCK_TIME_NONE - && stream_period->start + stream_period->duration >= time) - || (time != GST_CLOCK_TIME_NONE && stream_period->start >= time)) - break; - - if (period_idx != -1 && stream_period->number >= period_idx) - break; - - if (period_id != NULL && stream_period->period->id != NULL - && strcmp (stream_period->period->id, period_id) == 0) - break; - - list = list->next; - } - - GST_DEBUG - ("Found a total of %d valid Periods in the Media Presentation up to this point", - idx); - return ret; - -early: - GST_WARNING - ("Found an Early Available Period, skipping the rest of the Media Presentation"); - return ret; - -syntax_error: - GST_WARNING - ("Cannot get the duration of the Period %d, skipping the rest of the Media Presentation", - idx); - return ret; -} - -static GList * -gst_mpd_client_fetch_external_adaptation_set (GstMpdClient * client, - GstPeriodNode * period, GstAdaptationSetNode * adapt_set) -{ - GstFragment *download; - GstBuffer *adapt_set_buffer; - GstMapInfo map; - GError *err = NULL; - xmlDocPtr doc = NULL; - GstUri *base_uri, *uri; - gchar *query = NULL; - gchar *uri_string; - GList *new_adapt_sets = NULL; - - /* ISO/IEC 23009-1:2014 5.5.3 4) - * Remove nodes that resolve to nothing when resolving - */ - if (strcmp (adapt_set->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013") == 0) { - return NULL; - } - - if (!client->downloader) { - return NULL; - } - - /* Build absolute URI */ - - /* Get base URI at the MPD level */ - base_uri = - gst_uri_from_string (client-> - mpd_base_uri ? client->mpd_base_uri : client->mpd_uri); - - /* combine a BaseURL at the MPD level with the current base url */ - base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0); - - /* combine a BaseURL at the Period level with the current base url */ - base_uri = combine_urls (base_uri, period->BaseURLs, &query, 0); - - uri = gst_uri_from_string_with_base (base_uri, adapt_set->xlink_href); - if (query) - gst_uri_set_query_string (uri, query); - g_free (query); - uri_string = gst_uri_to_string (uri); - gst_uri_unref (base_uri); - gst_uri_unref (uri); - - download = - gst_uri_downloader_fetch_uri (client->downloader, - uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err); - g_free (uri_string); - - if (!download) { - GST_ERROR ("Failed to download external AdaptationSet node at '%s': %s", - adapt_set->xlink_href, err->message); - g_clear_error (&err); - return NULL; - } - - adapt_set_buffer = gst_fragment_get_buffer (download); - g_object_unref (download); - - gst_buffer_map (adapt_set_buffer, &map, GST_MAP_READ); - - doc = - xmlReadMemory ((const gchar *) map.data, map.size, "noname.xml", NULL, - XML_PARSE_NONET); - - gst_buffer_unmap (adapt_set_buffer, &map); - gst_buffer_unref (adapt_set_buffer); - - /* NOTE: ISO/IEC 23009-1:2014 5.3.3.2 is saying that exactly one AdaptationSet - * in external xml is allowed */ - if (doc) { - xmlNode *root_element = xmlDocGetRootElement (doc); - - if (root_element->type != XML_ELEMENT_NODE || - xmlStrcmp (root_element->name, (xmlChar *) "AdaptationSet") != 0) { - goto error; - } - - gst_mpdparser_parse_adaptation_set_node (&new_adapt_sets, root_element, - period); - } else { - goto error; - } - -done: - if (doc) - xmlFreeDoc (doc); - - return new_adapt_sets; - -error: - GST_ERROR ("Failed to parse adaptation set node XML"); - goto done; -} - -static GList * -gst_mpd_client_get_adaptation_sets_for_period (GstMpdClient * client, - GstStreamPeriod * period) -{ - GList *list; - - g_return_val_if_fail (period != NULL, NULL); - - /* Resolve all external adaptation sets of this period. Every user of - * the adaptation sets would need to know the content of all adaptation sets - * to decide which one to use, so we have to resolve them all here - */ - for (list = period->period->AdaptationSets; list; - /* advanced explicitly below */ ) { - GstAdaptationSetNode *adapt_set = (GstAdaptationSetNode *) list->data; - GList *new_adapt_sets = NULL, *prev, *next; - - if (!adapt_set->xlink_href) { - list = list->next; - continue; - } - - new_adapt_sets = - gst_mpd_client_fetch_external_adaptation_set (client, period->period, - adapt_set); - - prev = list->prev; - period->period->AdaptationSets = - g_list_delete_link (period->period->AdaptationSets, list); - gst_mpdparser_free_adaptation_set_node (adapt_set); - adapt_set = NULL; - - /* Get new next node, we will insert before this */ - if (prev) - next = prev->next; - else - next = period->period->AdaptationSets; - - while (new_adapt_sets) { - period->period->AdaptationSets = - g_list_insert_before (period->period->AdaptationSets, next, - new_adapt_sets->data); - new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets); - } - - /* Update our iterator to the first new adaptation set if any, or the next */ - if (prev) - list = prev->next; - else - list = period->period->AdaptationSets; - } - - return period->period->AdaptationSets; -} - -GList * -gst_mpd_client_get_adaptation_sets (GstMpdClient * client) -{ - GstStreamPeriod *stream_period; - - stream_period = gst_mpdparser_get_stream_period (client); - if (stream_period == NULL || stream_period->period == NULL) { - GST_DEBUG ("No more Period nodes in the MPD file, terminating..."); - return NULL; - } - - return gst_mpd_client_get_adaptation_sets_for_period (client, stream_period); -} - -gboolean -gst_mpd_client_setup_streaming (GstMpdClient * client, - GstAdaptationSetNode * adapt_set) -{ - GstRepresentationNode *representation; - GList *rep_list = NULL; - GstActiveStream *stream; - - rep_list = adapt_set->Representations; - if (!rep_list) { - GST_WARNING ("Can not retrieve any representation, aborting..."); - return FALSE; - } - - stream = g_slice_new0 (GstActiveStream); - gst_mpdparser_init_active_stream_segments (stream); - - stream->baseURL_idx = 0; - stream->cur_adapt_set = adapt_set; - - GST_DEBUG ("0. Current stream %p", stream); - -#if 0 - /* fast start */ - representation = - gst_mpdparser_get_representation_with_max_bandwidth (rep_list, - stream->max_bandwidth); - - if (!representation) { - GST_WARNING - ("Can not retrieve a representation with the requested bandwidth"); - representation = gst_mpdparser_get_lowest_representation (rep_list); - } -#else - /* slow start */ - representation = gst_mpdparser_get_lowest_representation (rep_list); -#endif - - if (!representation) { - GST_WARNING ("No valid representation in the MPD file, aborting..."); - gst_mpdparser_free_active_stream (stream); - return FALSE; - } - stream->mimeType = - gst_mpdparser_representation_get_mimetype (adapt_set, representation); - if (stream->mimeType == GST_STREAM_UNKNOWN) { - GST_WARNING ("Unknown mime type in the representation, aborting..."); - gst_mpdparser_free_active_stream (stream); - return FALSE; - } - - client->active_streams = g_list_append (client->active_streams, stream); - if (!gst_mpd_client_setup_representation (client, stream, representation)) { - GST_WARNING ("Failed to setup the representation, aborting..."); - return FALSE; - } - - GST_INFO ("Successfully setup the download pipeline for mimeType %d", - stream->mimeType); - - return TRUE; -} - -gboolean -gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * stream, - gboolean forward, GstSeekFlags flags, GstClockTime ts, - GstClockTime * final_ts) -{ - gint index = 0; - gint repeat_index = 0; - GstMediaSegment *selectedChunk = NULL; - - g_return_val_if_fail (stream != NULL, 0); - - if (stream->segments) { - for (index = 0; index < stream->segments->len; index++) { - gboolean in_segment = FALSE; - GstMediaSegment *segment = g_ptr_array_index (stream->segments, index); - GstClockTime end_time; - - GST_DEBUG ("Looking at fragment sequence chunk %d / %d", index, - stream->segments->len); - - end_time = - gst_mpdparser_get_segment_end_time (client, stream->segments, - segment, index); - - /* avoid downloading another fragment just for 1ns in reverse mode */ - if (forward) - in_segment = ts < end_time; - else - in_segment = ts <= end_time; - - if (in_segment) { - GstClockTime chunk_time; - - selectedChunk = segment; - repeat_index = (ts - segment->start) / segment->duration; - - chunk_time = segment->start + segment->duration * repeat_index; - - /* At the end of a segment in reverse mode, start from the previous fragment */ - if (!forward && repeat_index > 0 - && ((ts - segment->start) % segment->duration == 0)) - repeat_index--; - - if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) { - if (repeat_index + 1 < segment->repeat) { - if (ts - chunk_time > chunk_time + segment->duration - ts) - repeat_index++; - } else if (index + 1 < stream->segments->len) { - GstMediaSegment *next_segment = - g_ptr_array_index (stream->segments, index + 1); - - if (ts - chunk_time > next_segment->start - ts) { - repeat_index = 0; - selectedChunk = next_segment; - index++; - } - } - } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) || - (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) && - ts != chunk_time) { - - if (repeat_index + 1 < segment->repeat) { - repeat_index++; - } else { - repeat_index = 0; - if (index + 1 >= stream->segments->len) { - selectedChunk = NULL; - } else { - selectedChunk = g_ptr_array_index (stream->segments, ++index); - } - } - } - break; - } - } - - if (selectedChunk == NULL) { - stream->segment_index = stream->segments->len; - stream->segment_repeat_index = 0; - GST_DEBUG ("Seek to after last segment"); - return FALSE; - } - - if (final_ts) - *final_ts = selectedChunk->start + selectedChunk->duration * repeat_index; - } else { - GstClockTime duration = - gst_mpd_client_get_segment_duration (client, stream, NULL); - GstStreamPeriod *stream_period = gst_mpdparser_get_stream_period (client); - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); - GstClockTime index_time; - - g_return_val_if_fail (stream->cur_seg_template-> - MultSegBaseType->SegmentTimeline == NULL, FALSE); - if (!GST_CLOCK_TIME_IS_VALID (duration)) { - return FALSE; - } - - if (ts > stream_period->start) - ts -= stream_period->start; - else - ts = 0; - - index = ts / duration; - - /* At the end of a segment in reverse mode, start from the previous fragment */ - if (!forward && index > 0 && ts % duration == 0) - index--; - - index_time = index * duration; - - if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) { - if (ts - index_time > index_time + duration - ts) - index++; - } else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) || - (!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) - && ts != index_time) { - index++; - } - - if (segments_count > 0 && index >= segments_count) { - stream->segment_index = segments_count; - stream->segment_repeat_index = 0; - GST_DEBUG ("Seek to after last segment"); - return FALSE; - } - if (final_ts) - *final_ts = index * duration; - } - - stream->segment_repeat_index = repeat_index; - stream->segment_index = index; - - return TRUE; -} - -gint64 -gst_mpd_client_calculate_time_difference (const GstDateTime * t1, - const GstDateTime * t2) -{ - GDateTime *gdt1, *gdt2; - GTimeSpan diff; - - g_assert (t1 != NULL && t2 != NULL); - gdt1 = gst_date_time_to_g_date_time ((GstDateTime *) t1); - gdt2 = gst_date_time_to_g_date_time ((GstDateTime *) t2); - diff = g_date_time_difference (gdt2, gdt1); - g_date_time_unref (gdt1); - g_date_time_unref (gdt2); - return diff * GST_USECOND; + xmlFree (subrep_node->dependencyLevel); + g_strfreev (subrep_node->contentComponent); + g_slice_free (GstSubRepresentationNode, subrep_node); + } } -GstDateTime * -gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs) +static void +gst_mpdparser_free_s_node (GstSNode * s_node) { - GDateTime *gdt; - GDateTime *gdt2; - GstDateTime *rv; - - g_assert (t1 != NULL); - gdt = gst_date_time_to_g_date_time (t1); - g_assert (gdt != NULL); - gdt2 = g_date_time_add (gdt, usecs); - g_assert (gdt2 != NULL); - g_date_time_unref (gdt); - rv = gst_date_time_new_from_g_date_time (gdt2); - - /* Don't g_date_time_unref(gdt2) because gst_date_time_new_from_g_date_time takes - * ownership of the GDateTime pointer. - */ - - return rv; + if (s_node) { + g_slice_free (GstSNode, s_node); + } } -static GstDateTime * -gst_mpd_client_get_availability_start_time (GstMpdClient * client) +static GstSegmentTimelineNode * +gst_mpdparser_segment_timeline_node_new (void) { - GstDateTime *start_time; + GstSegmentTimelineNode *node = g_slice_new0 (GstSegmentTimelineNode); - if (client == NULL) - return (GstDateTime *) NULL; + g_queue_init (&node->S); - start_time = client->mpd_node->availabilityStartTime; - if (start_time) - gst_date_time_ref (start_time); - return start_time; + return node; } -gboolean -gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client, - guint stream_idx, GstClockTime * ts) +static void +gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * seg_timeline) { - GstActiveStream *stream; - gint segment_idx; - GstMediaSegment *currentChunk; - GstStreamPeriod *stream_period; - - GST_DEBUG ("Stream index: %i", stream_idx); - stream = g_list_nth_data (client->active_streams, stream_idx); - g_return_val_if_fail (stream != NULL, 0); - - if (!stream->segments) { - stream_period = gst_mpdparser_get_stream_period (client); - *ts = stream_period->start + stream_period->duration; - } else { - segment_idx = gst_mpd_client_get_segments_counts (client, stream) - 1; - if (segment_idx >= stream->segments->len) { - GST_WARNING ("Segment index %d is outside of segment list of length %d", - segment_idx, stream->segments->len); - return FALSE; - } - currentChunk = g_ptr_array_index (stream->segments, segment_idx); - - if (currentChunk->repeat >= 0) { - *ts = - currentChunk->start + (currentChunk->duration * (1 + - currentChunk->repeat)); - } else { - /* 5.3.9.6.1: negative repeat means repeat till the end of the - * period, or the next update of the MPD (which I think is - * implicit, as this will all get deleted/recreated), or the - * start of the next segment, if any. */ - stream_period = gst_mpdparser_get_stream_period (client); - *ts = stream_period->start + stream_period->duration; - } + if (seg_timeline) { + g_queue_foreach (&seg_timeline->S, (GFunc) gst_mpdparser_free_s_node, NULL); + g_queue_clear (&seg_timeline->S); + g_slice_free (GstSegmentTimelineNode, seg_timeline); } - - return TRUE; } -gboolean -gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, - guint stream_idx, GstClockTime * ts) +static void +gst_mpdparser_free_url_type_node (GstURLType * url_type_node) { - GstActiveStream *stream; - GstMediaSegment *currentChunk; - - GST_DEBUG ("Stream index: %i", stream_idx); - stream = g_list_nth_data (client->active_streams, stream_idx); - g_return_val_if_fail (stream != NULL, 0); - - if (stream->segments) { - GST_DEBUG ("Looking for fragment sequence chunk %d / %d", - stream->segment_index, stream->segments->len); - if (stream->segment_index >= stream->segments->len) - return FALSE; - currentChunk = g_ptr_array_index (stream->segments, stream->segment_index); - - *ts = - currentChunk->start + - (currentChunk->duration * stream->segment_repeat_index); - } else { - GstClockTime duration = - gst_mpd_client_get_segment_duration (client, stream, NULL); - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); - - g_return_val_if_fail (stream->cur_seg_template-> - MultSegBaseType->SegmentTimeline == NULL, FALSE); - if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 - && stream->segment_index >= segments_count)) { - return FALSE; - } - *ts = stream->segment_index * duration; + if (url_type_node) { + if (url_type_node->sourceURL) + xmlFree (url_type_node->sourceURL); + g_slice_free (GstXMLRange, url_type_node->range); + g_slice_free (GstURLType, url_type_node); } - - return TRUE; } -GstClockTime -gst_mpd_parser_get_stream_presentation_offset (GstMpdClient * client, - guint stream_idx) +static void +gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * seg_base_type) { - GstActiveStream *stream = NULL; - - g_return_val_if_fail (client != NULL, 0); - g_return_val_if_fail (client->active_streams != NULL, 0); - stream = g_list_nth_data (client->active_streams, stream_idx); - g_return_val_if_fail (stream != NULL, 0); - - return stream->presentationTimeOffset; + if (seg_base_type) { + if (seg_base_type->indexRange) + g_slice_free (GstXMLRange, seg_base_type->indexRange); + gst_mpdparser_free_url_type_node (seg_base_type->Initialization); + gst_mpdparser_free_url_type_node (seg_base_type->RepresentationIndex); + g_slice_free (GstSegmentBaseType, seg_base_type); + } } -GstClockTime -gst_mpd_parser_get_period_start_time (GstMpdClient * client) +static void +gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType * + mult_seg_base_type) { - GstStreamPeriod *stream_period = NULL; - - g_return_val_if_fail (client != NULL, 0); - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, 0); - - return stream_period->start; + if (mult_seg_base_type) { + /* SegmentBaseType extension */ + gst_mpdparser_free_seg_base_type_ext (mult_seg_base_type->SegBaseType); + gst_mpdparser_free_segment_timeline_node + (mult_seg_base_type->SegmentTimeline); + gst_mpdparser_free_url_type_node (mult_seg_base_type->BitstreamSwitching); + g_slice_free (GstMultSegmentBaseType, mult_seg_base_type); + } } -/** - * 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) +static void +gst_mpdparser_free_segment_url_node (GstSegmentURLNode * segment_url) { - 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; - } + if (segment_url) { + if (segment_url->media) + xmlFree (segment_url->media); + g_slice_free (GstXMLRange, segment_url->mediaRange); + if (segment_url->index) + xmlFree (segment_url->index); + g_slice_free (GstXMLRange, segment_url->indexRange); + g_slice_free (GstSegmentURLNode, segment_url); } - return NULL; } -gboolean -gst_mpd_client_get_next_fragment (GstMpdClient * client, - guint indexStream, GstMediaFragmentInfo * fragment) +static void +gst_mpdparser_free_descriptor_type_node (GstDescriptorType * descriptor_type) { - GstActiveStream *stream = NULL; - GstMediaSegment *currentChunk; - gchar *mediaURL = NULL; - gchar *indexURL = NULL; - GstUri *base_url, *frag_url; - - /* select stream */ - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->active_streams != NULL, FALSE); - stream = g_list_nth_data (client->active_streams, indexStream); - g_return_val_if_fail (stream != NULL, FALSE); - g_return_val_if_fail (stream->cur_representation != NULL, FALSE); - - if (stream->segments) { - GST_DEBUG ("Looking for fragment sequence chunk %d / %d", - stream->segment_index, stream->segments->len); - if (stream->segment_index >= stream->segments->len) - return FALSE; - } else { - GstClockTime duration = gst_mpd_client_get_segment_duration (client, - stream, NULL); - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); - - g_return_val_if_fail (stream->cur_seg_template-> - MultSegBaseType->SegmentTimeline == NULL, FALSE); - if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 - && stream->segment_index >= segments_count)) { - return FALSE; - } - fragment->duration = duration; - } - - /* FIXME rework discont checking */ - /* fragment->discontinuity = segment_idx != currentChunk.number; */ - fragment->range_start = 0; - fragment->range_end = -1; - fragment->index_uri = NULL; - fragment->index_range_start = 0; - fragment->index_range_end = -1; - - if (stream->segments) { - currentChunk = g_ptr_array_index (stream->segments, stream->segment_index); - - GST_DEBUG ("currentChunk->SegmentURL = %p", currentChunk->SegmentURL); - if (currentChunk->SegmentURL != NULL) { - mediaURL = - g_strdup (gst_mpdparser_get_mediaURL (stream, - currentChunk->SegmentURL)); - indexURL = g_strdup (currentChunk->SegmentURL->index); - } else if (stream->cur_seg_template != NULL) { - mediaURL = - gst_mpdparser_build_URL_from_template (stream-> - cur_seg_template->media, stream->cur_representation->id, - currentChunk->number + stream->segment_repeat_index, - stream->cur_representation->bandwidth, - currentChunk->scale_start + - stream->segment_repeat_index * currentChunk->scale_duration); - if (stream->cur_seg_template->index) { - indexURL = - gst_mpdparser_build_URL_from_template (stream-> - cur_seg_template->index, stream->cur_representation->id, - currentChunk->number + stream->segment_repeat_index, - stream->cur_representation->bandwidth, - currentChunk->scale_start + - stream->segment_repeat_index * currentChunk->scale_duration); - } - } - GST_DEBUG ("mediaURL = %s", mediaURL); - GST_DEBUG ("indexURL = %s", indexURL); - - fragment->timestamp = - currentChunk->start + - stream->segment_repeat_index * currentChunk->duration; - fragment->duration = currentChunk->duration; - if (currentChunk->SegmentURL) { - if (currentChunk->SegmentURL->mediaRange) { - fragment->range_start = - currentChunk->SegmentURL->mediaRange->first_byte_pos; - fragment->range_end = - currentChunk->SegmentURL->mediaRange->last_byte_pos; - } - if (currentChunk->SegmentURL->indexRange) { - fragment->index_range_start = - currentChunk->SegmentURL->indexRange->first_byte_pos; - fragment->index_range_end = - currentChunk->SegmentURL->indexRange->last_byte_pos; - } - } - } else { - if (stream->cur_seg_template != NULL) { - mediaURL = - gst_mpdparser_build_URL_from_template (stream-> - cur_seg_template->media, stream->cur_representation->id, - stream->segment_index + - stream->cur_seg_template->MultSegBaseType->startNumber, - stream->cur_representation->bandwidth, - stream->segment_index * fragment->duration); - if (stream->cur_seg_template->index) { - indexURL = - gst_mpdparser_build_URL_from_template (stream-> - cur_seg_template->index, stream->cur_representation->id, - stream->segment_index + - stream->cur_seg_template->MultSegBaseType->startNumber, - stream->cur_representation->bandwidth, - stream->segment_index * fragment->duration); - } - } else { - return FALSE; - } - - GST_DEBUG ("mediaURL = %s", mediaURL); - GST_DEBUG ("indexURL = %s", indexURL); - - fragment->timestamp = stream->segment_index * fragment->duration; + if (descriptor_type) { + if (descriptor_type->schemeIdUri) + xmlFree (descriptor_type->schemeIdUri); + if (descriptor_type->value) + xmlFree (descriptor_type->value); + g_slice_free (GstDescriptorType, descriptor_type); } +} - base_url = gst_uri_from_string (stream->baseURL); - frag_url = gst_uri_from_string_with_base (base_url, mediaURL); - g_free (mediaURL); - if (stream->queryURL) { - frag_url = gst_uri_make_writable (frag_url); - gst_uri_set_query_string (frag_url, stream->queryURL); - } - fragment->uri = gst_uri_to_string (frag_url); - gst_uri_unref (frag_url); - - if (indexURL != NULL) { - frag_url = gst_uri_make_writable (gst_uri_from_string_with_base (base_url, - indexURL)); - gst_uri_set_query_string (frag_url, stream->queryURL); - fragment->index_uri = gst_uri_to_string (frag_url); - gst_uri_unref (frag_url); - g_free (indexURL); - } else if (indexURL == NULL && (fragment->index_range_start - || fragment->index_range_end != -1)) { - /* index has no specific URL but has a range, we should only use this if - * the media also has a range, otherwise we are serving some data twice - * (in the media fragment and again in the index) */ - if (!(fragment->range_start || fragment->range_end != -1)) { - GST_WARNING ("Ignoring index ranges because there isn't a media range " - "and URIs would be the same"); - /* removing index information */ - fragment->index_range_start = 0; - fragment->index_range_end = -1; - } +static void +gst_mpdparser_free_content_component_node (GstContentComponentNode * + content_component_node) +{ + if (content_component_node) { + if (content_component_node->lang) + xmlFree (content_component_node->lang); + if (content_component_node->contentType) + xmlFree (content_component_node->contentType); + g_slice_free (GstXMLRatio, content_component_node->par); + g_list_free_full (content_component_node->Accessibility, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (content_component_node->Role, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (content_component_node->Rating, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (content_component_node->Viewpoint, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_slice_free (GstContentComponentNode, content_component_node); } - - gst_uri_unref (base_url); - - GST_DEBUG ("Loading chunk with URL %s", fragment->uri); - - return TRUE; } -gboolean -gst_mpd_client_has_next_segment (GstMpdClient * client, - GstActiveStream * stream, gboolean forward) +static void +gst_mpdparser_free_utctiming_node (GstUTCTimingNode * timing_type) { - if (forward) { - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); - - if (segments_count > 0 && stream->segments - && stream->segment_index + 1 == segments_count) { - GstMediaSegment *segment; - - segment = g_ptr_array_index (stream->segments, stream->segment_index); - if (segment->repeat >= 0 - && stream->segment_repeat_index >= segment->repeat) - return FALSE; - } else if (segments_count > 0 - && stream->segment_index + 1 >= segments_count) { - return FALSE; - } - } else { - if (stream->segment_index < 0) - return FALSE; + if (timing_type) { + if (timing_type->urls) + g_strfreev (timing_type->urls); + g_slice_free (GstUTCTimingNode, timing_type); } - - return TRUE; } -GstFlowReturn -gst_mpd_client_advance_segment (GstMpdClient * client, GstActiveStream * stream, - gboolean forward) +/* ISO/IEC 23009-1:2004 5.3.9.4.4 */ +static gboolean +validate_format (const gchar * format) { - GstMediaSegment *segment; - GstFlowReturn ret = GST_FLOW_OK; - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); - - GST_DEBUG ("Advancing segment. Current: %d / %d r:%d", stream->segment_index, - segments_count, stream->segment_repeat_index); - - /* handle special cases first */ - if (forward) { - if (segments_count > 0 && stream->segment_index >= segments_count) { - ret = GST_FLOW_EOS; - goto done; - } - - if (stream->segments == NULL) { - if (stream->segment_index < 0) { - stream->segment_index = 0; - } else { - stream->segment_index++; - if (segments_count > 0 && stream->segment_index >= segments_count) { - ret = GST_FLOW_EOS; - } - } - goto done; - } + const gchar *p = format; - /* special case for when playback direction is reverted right at * - * the end of the segment list */ - if (stream->segment_index < 0) { - stream->segment_index = 0; - goto done; - } - } else { - if (stream->segments == NULL) - stream->segment_index--; - if (stream->segment_index < 0) { - stream->segment_index = -1; - ret = GST_FLOW_EOS; - goto done; - } - if (stream->segments == NULL) - goto done; - - /* special case for when playback direction is reverted right at * - * the end of the segment list */ - if (stream->segment_index >= segments_count) { - stream->segment_index = segments_count - 1; - segment = g_ptr_array_index (stream->segments, stream->segment_index); - if (segment->repeat >= 0) { - stream->segment_repeat_index = segment->repeat; - } else { - GstClockTime start = segment->start; - GstClockTime end = - gst_mpdparser_get_segment_end_time (client, stream->segments, - segment, - stream->segment_index); - stream->segment_repeat_index = - (guint) (end - start) / segment->duration; - } - goto done; - } - } + /* Check if it starts with % */ + if (!p || p[0] != '%') + return FALSE; + p++; - /* for the normal cases we can get the segment safely here */ - segment = g_ptr_array_index (stream->segments, stream->segment_index); - if (forward) { - if (segment->repeat >= 0 && stream->segment_repeat_index >= segment->repeat) { - stream->segment_repeat_index = 0; - stream->segment_index++; - if (segments_count > 0 && stream->segment_index >= segments_count) { - ret = GST_FLOW_EOS; - goto done; - } - } else { - stream->segment_repeat_index++; - } - } else { - if (stream->segment_repeat_index == 0) { - stream->segment_index--; - if (stream->segment_index < 0) { - ret = GST_FLOW_EOS; - goto done; - } + /* the spec mandates a format like %0[width]d */ + /* Following the %, we must have a 0 */ + if (p[0] != '0') + return FALSE; - segment = g_ptr_array_index (stream->segments, stream->segment_index); - /* negative repeats only seem to make sense at the end of a list, - * so this one will probably not be. Needs some sanity checking - * when loading the XML data. */ - if (segment->repeat >= 0) { - stream->segment_repeat_index = segment->repeat; - } else { - GstClockTime start = segment->start; - GstClockTime end = - gst_mpdparser_get_segment_end_time (client, stream->segments, - segment, - stream->segment_index); - stream->segment_repeat_index = - (guint) (end - start) / segment->duration; - } - } else { - stream->segment_repeat_index--; - } - } + /* Following the % must be a number starting with 0 + */ + while (g_ascii_isdigit (*p)) + p++; -done: - GST_DEBUG ("Advanced to segment: %d / %d r:%d (ret: %s)", - stream->segment_index, segments_count, - stream->segment_repeat_index, gst_flow_get_name (ret)); - return ret; -} + /* After any 0 and alphanumeric values, there must be a d. + */ + if (p[0] != 'd') + return FALSE; + p++; -gboolean -gst_mpd_client_get_next_header (GstMpdClient * client, gchar ** uri, - guint stream_idx, gint64 * range_start, gint64 * range_end) -{ - GstActiveStream *stream; - GstStreamPeriod *stream_period; - - stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx); - g_return_val_if_fail (stream != NULL, FALSE); - g_return_val_if_fail (stream->cur_representation != NULL, FALSE); - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, FALSE); - g_return_val_if_fail (stream_period->period != NULL, FALSE); - - *range_start = 0; - *range_end = -1; - - GST_DEBUG ("Looking for current representation header"); - *uri = NULL; - if (stream->cur_segment_base) { - if (stream->cur_segment_base->Initialization) { - *uri = - g_strdup (gst_mpdparser_get_initializationURL (stream, - stream->cur_segment_base->Initialization)); - if (stream->cur_segment_base->Initialization->range) { - *range_start = - stream->cur_segment_base->Initialization->range->first_byte_pos; - *range_end = - stream->cur_segment_base->Initialization->range->last_byte_pos; - } - } else if (stream->cur_segment_base->indexRange) { - *uri = - g_strdup (gst_mpdparser_get_initializationURL (stream, - stream->cur_segment_base->Initialization)); - *range_start = 0; - *range_end = stream->cur_segment_base->indexRange->first_byte_pos - 1; - } - } else if (stream->cur_seg_template - && stream->cur_seg_template->initialization) { - *uri = - gst_mpdparser_build_URL_from_template (stream-> - cur_seg_template->initialization, stream->cur_representation->id, 0, - stream->cur_representation->bandwidth, 0); - } + /* And then potentially more characters without any + * further %, even if the spec does not mention this + */ + p = strchr (p, '%'); + if (p) + return FALSE; - return *uri == NULL ? FALSE : TRUE; + return TRUE; } -gboolean -gst_mpd_client_get_next_header_index (GstMpdClient * client, gchar ** uri, - guint stream_idx, gint64 * range_start, gint64 * range_end) +static gchar * +promote_format_to_uint64 (const gchar * format) { - GstActiveStream *stream; - GstStreamPeriod *stream_period; - - stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx); - g_return_val_if_fail (stream != NULL, FALSE); - g_return_val_if_fail (stream->cur_representation != NULL, FALSE); - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, FALSE); - g_return_val_if_fail (stream_period->period != NULL, FALSE); - - *range_start = 0; - *range_end = -1; - - GST_DEBUG ("Looking for current representation index"); - *uri = NULL; - if (stream->cur_segment_base && stream->cur_segment_base->indexRange) { - *uri = - g_strdup (gst_mpdparser_get_initializationURL (stream, - stream->cur_segment_base->RepresentationIndex)); - *range_start = stream->cur_segment_base->indexRange->first_byte_pos; - *range_end = stream->cur_segment_base->indexRange->last_byte_pos; - } else if (stream->cur_seg_template && stream->cur_seg_template->index) { - *uri = - gst_mpdparser_build_URL_from_template (stream->cur_seg_template->index, - stream->cur_representation->id, 0, - stream->cur_representation->bandwidth, 0); - } + const gchar *p = format; + gchar *promoted_format; - return *uri == NULL ? FALSE : TRUE; -} + /* Must be called with a validated format! */ + g_return_val_if_fail (validate_format (format), NULL); -GstClockTime -gst_mpd_client_get_next_fragment_duration (GstMpdClient * client, - GstActiveStream * stream) -{ - GstMediaSegment *media_segment = NULL; - gint seg_idx; + /* it starts with % */ + p++; - g_return_val_if_fail (stream != NULL, 0); + /* Following the % must be a 0, or any of d, x or u. + * x and u are not part of the spec, but don't hurt us + */ + if (p[0] == '0') { + p++; - seg_idx = stream->segment_index; + while (g_ascii_isdigit (*p)) + p++; + } - if (stream->segments) { - if (seg_idx < stream->segments->len && seg_idx >= 0) - media_segment = g_ptr_array_index (stream->segments, seg_idx); + /* After any 0 and alphanumeric values, there must be a d. + * Otherwise validation would have failed + */ + g_assert (p[0] == 'd'); - return media_segment == NULL ? 0 : media_segment->duration; - } else { - GstClockTime duration = - gst_mpd_client_get_segment_duration (client, stream, NULL); - guint segments_count = gst_mpd_client_get_segments_counts (client, stream); + promoted_format = + g_strdup_printf ("%.*s" G_GINT64_MODIFIER "%s", (gint) (p - format), + format, p); - g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType-> - SegmentTimeline == NULL, 0); + return promoted_format; +} - if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0 - && seg_idx >= segments_count)) { - return 0; +static gboolean +gst_mpdparser_validate_rfc1738_url (const char *s) +{ + while (*s) { + if (!strchr + (";:@&=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789$-_.+!*'(),%/", + *s)) + return FALSE; + if (*s == '%') { + /* g_ascii_isdigit returns FALSE for NUL, and || is a short circuiting + operator, so this is safe for strings ending before two hex digits */ + if (!g_ascii_isxdigit (s[1]) || !g_ascii_isxdigit (s[2])) + return FALSE; + s += 2; } - return duration; + s++; } + return TRUE; } -GstClockTime -gst_mpd_client_get_media_presentation_duration (GstMpdClient * client) + + +static GstURLType * +gst_mpdparser_clone_URL (GstURLType * url) { - GstClockTime duration; - g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); + GstURLType *clone = NULL; - if (client->mpd_node->mediaPresentationDuration != -1) { - duration = client->mpd_node->mediaPresentationDuration * GST_MSECOND; - } else { - /* We can only get the duration for on-demand streams */ - duration = GST_CLOCK_TIME_NONE; + if (url) { + clone = g_slice_new0 (GstURLType); + if (url->sourceURL) { + clone->sourceURL = xmlMemStrdup (url->sourceURL); + } + clone->range = gst_xml_helper_clone_range (url->range); } - return duration; + return clone; +} + +static void +gst_media_fragment_info_clear (GstMediaFragmentInfo * fragment) +{ + g_free (fragment->uri); + g_free (fragment->index_uri); } +/* API */ gboolean -gst_mpd_client_set_period_id (GstMpdClient * client, const gchar * period_id) +gst_mpdparser_get_mpd_node (GstMPDNode ** mpd_node, const gchar * data, + gint size) { - GstStreamPeriod *next_stream_period; gboolean ret = FALSE; - GList *iter; - guint period_idx; - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->periods != NULL, FALSE); - g_return_val_if_fail (period_id != NULL, FALSE); + if (data) { + xmlDocPtr doc; + xmlNode *root_element = NULL; - if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, -1, - period_id)) - return FALSE; + GST_DEBUG ("MPD file fully buffered, start parsing..."); + + /* parse the complete MPD file into a tree (using the libxml2 default parser API) */ + + /* this initialize the library and check potential ABI mismatches + * between the version it was compiled for and the actual shared + * library used + */ + LIBXML_TEST_VERSION; - for (period_idx = 0, iter = client->periods; iter; - period_idx++, iter = g_list_next (iter)) { - next_stream_period = iter->data; + /* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */ + doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); + if (doc == NULL) { + GST_ERROR ("failed to parse the MPD file"); + ret = FALSE; + } else { + /* get the root element node */ + root_element = xmlDocGetRootElement (doc); - if (next_stream_period->period->id - && strcmp (next_stream_period->period->id, period_id) == 0) { - ret = TRUE; - client->period_idx = period_idx; - break; + if (root_element->type != XML_ELEMENT_NODE + || xmlStrcmp (root_element->name, (xmlChar *) "MPD") != 0) { + GST_ERROR + ("can not find the root element MPD, failed to parse the MPD file"); + ret = FALSE; /* used to return TRUE before, but this seems wrong */ + } else { + /* now we can parse the MPD root node and all children nodes, recursively */ + ret = gst_mpdparser_parse_root_node (mpd_node, root_element); + } + /* free the document */ + xmlFreeDoc (doc); } } return ret; } -gboolean -gst_mpd_client_set_period_index (GstMpdClient * client, guint period_idx) +GstSegmentListNode * +gst_mpdparser_get_external_segment_list (const gchar * data, gint size, + GstSegmentListNode * parent) { - GstStreamPeriod *next_stream_period; - gboolean ret = FALSE; + xmlDocPtr doc = NULL; + GstSegmentListNode *new_segment_list = NULL; - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->periods != NULL, FALSE); + doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); - if (!gst_mpd_client_setup_media_presentation (client, -1, period_idx, NULL)) - return FALSE; - next_stream_period = g_list_nth_data (client->periods, period_idx); - if (next_stream_period != NULL) { - client->period_idx = period_idx; - ret = TRUE; - } + /* NOTE: ISO/IEC 23009-1:2014 5.3.9.3.2 is saying that one or multiple SegmentList + * in external xml is allowed, however, multiple SegmentList does not make sense + * because Period/AdaptationSet/Representation allow only one SegmentList */ + if (doc) { + xmlNode *root_element = xmlDocGetRootElement (doc); - return ret; -} -guint -gst_mpd_client_get_period_index (GstMpdClient * client) -{ - guint period_idx; + if (root_element->type == XML_ELEMENT_NODE && + xmlStrcmp (root_element->name, (xmlChar *) "SegmentList") == 0) { + gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element, + parent); + } + } - g_return_val_if_fail (client != NULL, 0); - period_idx = client->period_idx; + if (doc) + xmlFreeDoc (doc); - return period_idx; + return new_segment_list; } -const gchar * -gst_mpd_client_get_period_id (GstMpdClient * client) +GList * +gst_mpdparser_get_external_periods (const gchar * data, gint size) { - GstStreamPeriod *period; - gchar *period_id = NULL; - - g_return_val_if_fail (client != NULL, 0); - period = g_list_nth_data (client->periods, client->period_idx); - if (period && period->period) - period_id = period->period->id; + xmlDocPtr doc = NULL; + GList *new_periods = NULL; - return period_id; -} + doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); -gboolean -gst_mpd_client_has_previous_period (GstMpdClient * client) -{ - GList *next_stream_period; - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->periods != NULL, FALSE); - if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, - client->period_idx - 1, NULL)) - return FALSE; + if (doc) { + xmlNode *root_element = xmlDocGetRootElement (doc); + xmlNode *iter; - next_stream_period = - g_list_nth_data (client->periods, client->period_idx - 1); + for (iter = root_element->children; iter; iter = iter->next) { + if (iter->type == XML_ELEMENT_NODE) { + if (xmlStrcmp (iter->name, (xmlChar *) "Period") == 0) { + gst_mpdparser_parse_period_node (&new_periods, iter); + } else { + goto error; + } + } + } + } - return next_stream_period != NULL; -} +done: + if (doc) + xmlFreeDoc (doc); -gboolean -gst_mpd_client_has_next_period (GstMpdClient * client) -{ - GList *next_stream_period; - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->periods != NULL, FALSE); + return new_periods; - if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, - client->period_idx + 1, NULL)) - return FALSE; +error: + GST_ERROR ("Failed to parse period node XML"); - next_stream_period = - g_list_nth_data (client->periods, client->period_idx + 1); - return next_stream_period != NULL; + if (new_periods) { + g_list_free_full (new_periods, + (GDestroyNotify) gst_mpdparser_free_period_node); + new_periods = NULL; + } + goto done; } -void -gst_mpd_client_seek_to_first_segment (GstMpdClient * client) +GList * +gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size, + GstPeriodNode * period) { - GList *list; + xmlDocPtr doc = NULL; + GList *new_adaptation_sets = NULL; - g_return_if_fail (client != NULL); - g_return_if_fail (client->active_streams != NULL); + doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); - for (list = g_list_first (client->active_streams); list; - list = g_list_next (list)) { - GstActiveStream *stream = (GstActiveStream *) list->data; - if (stream) { - stream->segment_index = 0; - stream->segment_repeat_index = 0; + /* NOTE: ISO/IEC 23009-1:2014 5.3.3.2 is saying that exactly one AdaptationSet + * in external xml is allowed */ + if (doc) { + xmlNode *root_element = xmlDocGetRootElement (doc); + if (root_element->type == XML_ELEMENT_NODE && + xmlStrcmp (root_element->name, (xmlChar *) "AdaptationSet") == 0) { + gst_mpdparser_parse_adaptation_set_node (&new_adaptation_sets, + root_element, period); } } -} - -static guint -gst_mpd_client_get_segments_counts (GstMpdClient * client, - GstActiveStream * stream) -{ - GstStreamPeriod *stream_period; - - g_return_val_if_fail (stream != NULL, 0); - if (stream->segments) - return stream->segments->len; - g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType-> - SegmentTimeline == NULL, 0); - - stream_period = gst_mpdparser_get_stream_period (client); - if (stream_period->duration != -1) - return gst_util_uint64_scale_ceil (stream_period->duration, 1, - gst_mpd_client_get_segment_duration (client, stream, NULL)); + if (doc) + xmlFreeDoc (doc); - return 0; + return new_adaptation_sets; } -gboolean -gst_mpd_client_is_live (GstMpdClient * client) +void +gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node) { - g_return_val_if_fail (client != NULL, FALSE); - g_return_val_if_fail (client->mpd_node != NULL, FALSE); - - return client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC; + if (mpd_node) { + if (mpd_node->default_namespace) + xmlFree (mpd_node->default_namespace); + if (mpd_node->namespace_xsi) + xmlFree (mpd_node->namespace_xsi); + if (mpd_node->namespace_ext) + xmlFree (mpd_node->namespace_ext); + if (mpd_node->schemaLocation) + xmlFree (mpd_node->schemaLocation); + if (mpd_node->id) + xmlFree (mpd_node->id); + if (mpd_node->profiles) + xmlFree (mpd_node->profiles); + if (mpd_node->availabilityStartTime) + gst_date_time_unref (mpd_node->availabilityStartTime); + if (mpd_node->availabilityEndTime) + gst_date_time_unref (mpd_node->availabilityEndTime); + g_list_free_full (mpd_node->ProgramInfo, + (GDestroyNotify) gst_mpdparser_free_prog_info_node); + g_list_free_full (mpd_node->BaseURLs, + (GDestroyNotify) gst_mpdparser_free_base_url_node); + g_list_free_full (mpd_node->Locations, (GDestroyNotify) xmlFree); + g_list_free_full (mpd_node->Periods, + (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); + } } -guint -gst_mpdparser_get_nb_active_stream (GstMpdClient * client) +void +gst_mpdparser_free_period_node (GstPeriodNode * period_node) { - g_return_val_if_fail (client != NULL, 0); - - return g_list_length (client->active_streams); + if (period_node) { + if (period_node->id) + xmlFree (period_node->id); + gst_mpdparser_free_seg_base_type_ext (period_node->SegmentBase); + gst_mpdparser_free_segment_list_node (period_node->SegmentList); + gst_mpdparser_free_segment_template_node (period_node->SegmentTemplate); + g_list_free_full (period_node->AdaptationSets, + (GDestroyNotify) gst_mpdparser_free_adaptation_set_node); + g_list_free_full (period_node->Subsets, + (GDestroyNotify) gst_mpdparser_free_subset_node); + g_list_free_full (period_node->BaseURLs, + (GDestroyNotify) gst_mpdparser_free_base_url_node); + if (period_node->xlink_href) + xmlFree (period_node->xlink_href); + g_slice_free (GstPeriodNode, period_node); + } } -guint -gst_mpdparser_get_nb_adaptationSet (GstMpdClient * client) +void +gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * + adaptation_set_node) { - GstStreamPeriod *stream_period; - - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, 0); - g_return_val_if_fail (stream_period->period != NULL, 0); - - return g_list_length (stream_period->period->AdaptationSets); + if (adaptation_set_node) { + if (adaptation_set_node->lang) + xmlFree (adaptation_set_node->lang); + if (adaptation_set_node->contentType) + xmlFree (adaptation_set_node->contentType); + g_slice_free (GstXMLRatio, adaptation_set_node->par); + g_slice_free (GstXMLConditionalUintType, + adaptation_set_node->segmentAlignment); + g_slice_free (GstXMLConditionalUintType, + adaptation_set_node->subsegmentAlignment); + g_list_free_full (adaptation_set_node->Accessibility, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (adaptation_set_node->Role, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (adaptation_set_node->Rating, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + g_list_free_full (adaptation_set_node->Viewpoint, + (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); + gst_mpdparser_free_representation_base_type + (adaptation_set_node->RepresentationBase); + gst_mpdparser_free_seg_base_type_ext (adaptation_set_node->SegmentBase); + gst_mpdparser_free_segment_list_node (adaptation_set_node->SegmentList); + gst_mpdparser_free_segment_template_node + (adaptation_set_node->SegmentTemplate); + g_list_free_full (adaptation_set_node->BaseURLs, + (GDestroyNotify) gst_mpdparser_free_base_url_node); + g_list_free_full (adaptation_set_node->Representations, + (GDestroyNotify) gst_mpdparser_free_representation_node); + g_list_free_full (adaptation_set_node->ContentComponents, + (GDestroyNotify) gst_mpdparser_free_content_component_node); + if (adaptation_set_node->xlink_href) + xmlFree (adaptation_set_node->xlink_href); + g_slice_free (GstAdaptationSetNode, adaptation_set_node); + } } -GstActiveStream * -gst_mpdparser_get_active_stream_by_index (GstMpdClient * client, - guint stream_idx) +void +gst_mpdparser_free_segment_list_node (GstSegmentListNode * segment_list_node) { - g_return_val_if_fail (client != NULL, NULL); - g_return_val_if_fail (client->active_streams != NULL, NULL); - - return g_list_nth_data (client->active_streams, stream_idx); + if (segment_list_node) { + g_list_free_full (segment_list_node->SegmentURL, + (GDestroyNotify) gst_mpdparser_free_segment_url_node); + /* MultipleSegmentBaseType extension */ + gst_mpdparser_free_mult_seg_base_type_ext + (segment_list_node->MultSegBaseType); + if (segment_list_node->xlink_href) + xmlFree (segment_list_node->xlink_href); + g_slice_free (GstSegmentListNode, segment_list_node); + } } -gboolean -gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream) +void +gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node) { - const gchar *mimeType; - const gchar *adapt_set_codecs; - const gchar *rep_codecs; - - mimeType = stream->cur_representation->RepresentationBase->mimeType; - if (!mimeType) - mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; - - if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 || - g_strcmp0 (mimeType, "text/vtt") == 0) - return TRUE; - - adapt_set_codecs = stream->cur_adapt_set->RepresentationBase->codecs; - rep_codecs = stream->cur_representation->RepresentationBase->codecs; - - return (adapt_set_codecs && g_str_has_prefix (adapt_set_codecs, "stpp")) - || (rep_codecs && g_str_has_prefix (rep_codecs, "stpp")); + if (base_url_node) { + if (base_url_node->baseURL) + xmlFree (base_url_node->baseURL); + if (base_url_node->serviceLocation) + xmlFree (base_url_node->serviceLocation); + if (base_url_node->byteRange) + xmlFree (base_url_node->byteRange); + g_slice_free (GstBaseURL, base_url_node); + } } -static const gchar * -gst_mpdparser_mimetype_to_caps (const gchar * mimeType) +void +gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period) { - if (mimeType == NULL) - return NULL; - if (strcmp (mimeType, "video/mp2t") == 0) { - return "video/mpegts, systemstream=(bool) true"; - } else if (strcmp (mimeType, "video/mp4") == 0) { - return "video/quicktime"; - } else if (strcmp (mimeType, "audio/mp4") == 0) { - return "audio/x-m4a"; - } else if (strcmp (mimeType, "text/vtt") == 0) { - return "application/x-subtitle-vtt"; - } else - return mimeType; + if (stream_period) { + g_slice_free (GstStreamPeriod, stream_period); + } } -GstCaps * -gst_mpd_client_get_stream_caps (GstActiveStream * stream) +void +gst_mpdparser_free_media_segment (GstMediaSegment * media_segment) { - const gchar *mimeType, *caps_string; - GstCaps *ret = NULL; - - if (stream == NULL || stream->cur_adapt_set == NULL - || stream->cur_representation == NULL) - return NULL; - - mimeType = stream->cur_representation->RepresentationBase->mimeType; - if (mimeType == NULL) { - mimeType = stream->cur_adapt_set->RepresentationBase->mimeType; + if (media_segment) { + g_slice_free (GstMediaSegment, media_segment); } - - caps_string = gst_mpdparser_mimetype_to_caps (mimeType); - - if ((g_strcmp0 (caps_string, "application/mp4") == 0) - && gst_mpd_client_active_stream_contains_subtitles (stream)) - caps_string = "video/quicktime"; - - if (caps_string) - ret = gst_caps_from_string (caps_string); - - return ret; } -gboolean -gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream) +void +gst_mpdparser_init_active_stream_segments (GstActiveStream * stream) { - if (stream == NULL || stream->cur_adapt_set == NULL) - return FALSE; - - return stream->cur_adapt_set->bitstreamSwitching; + g_assert (stream->segments == NULL); + stream->segments = g_ptr_array_new (); + g_ptr_array_set_free_func (stream->segments, + (GDestroyNotify) gst_mpdparser_free_media_segment); } -guint -gst_mpd_client_get_video_stream_width (GstActiveStream * stream) +void +gst_mpdparser_free_active_stream (GstActiveStream * active_stream) { - guint width; - - if (stream == NULL || stream->cur_adapt_set == NULL - || stream->cur_representation == NULL) - return 0; - - width = stream->cur_representation->RepresentationBase->width; - if (width == 0) { - width = stream->cur_adapt_set->RepresentationBase->width; + if (active_stream) { + g_free (active_stream->baseURL); + active_stream->baseURL = NULL; + g_free (active_stream->queryURL); + active_stream->queryURL = NULL; + if (active_stream->segments) + g_ptr_array_unref (active_stream->segments); + g_slice_free (GstActiveStream, active_stream); } - - return width; } -guint -gst_mpd_client_get_video_stream_height (GstActiveStream * stream) +const gchar * +gst_mpdparser_get_initializationURL (GstActiveStream * stream, + GstURLType * InitializationURL) { - guint height; + const gchar *url_prefix; - if (stream == NULL || stream->cur_adapt_set == NULL - || stream->cur_representation == NULL) - return 0; + g_return_val_if_fail (stream != NULL, NULL); - height = stream->cur_representation->RepresentationBase->height; - if (height == 0) { - height = stream->cur_adapt_set->RepresentationBase->height; - } + url_prefix = (InitializationURL + && InitializationURL->sourceURL) ? InitializationURL-> + sourceURL : stream->baseURL; - return height; + return url_prefix; } -gboolean -gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream, - gint * fps_num, gint * fps_den) +gchar * +gst_mpdparser_get_mediaURL (GstActiveStream * stream, + GstSegmentURLNode * segmentURL) { - if (stream == NULL) - return FALSE; + const gchar *url_prefix; - if (stream->cur_adapt_set && - stream->cur_adapt_set->RepresentationBase->frameRate != NULL) { - *fps_num = stream->cur_adapt_set->RepresentationBase->frameRate->num; - *fps_den = stream->cur_adapt_set->RepresentationBase->frameRate->den; - return TRUE; - } + g_return_val_if_fail (stream != NULL, NULL); + g_return_val_if_fail (segmentURL != NULL, NULL); - if (stream->cur_adapt_set && - stream->cur_adapt_set->RepresentationBase->maxFrameRate != NULL) { - *fps_num = stream->cur_adapt_set->RepresentationBase->maxFrameRate->num; - *fps_den = stream->cur_adapt_set->RepresentationBase->maxFrameRate->den; - return TRUE; - } + url_prefix = segmentURL->media ? segmentURL->media : stream->baseURL; + g_return_val_if_fail (url_prefix != NULL, NULL); - if (stream->cur_representation && - stream->cur_representation->RepresentationBase->frameRate != NULL) { - *fps_num = stream->cur_representation->RepresentationBase->frameRate->num; - *fps_den = stream->cur_representation->RepresentationBase->frameRate->den; - return TRUE; - } + return segmentURL->media; +} - if (stream->cur_representation && - stream->cur_representation->RepresentationBase->maxFrameRate != NULL) { - *fps_num = - stream->cur_representation->RepresentationBase->maxFrameRate->num; - *fps_den = - stream->cur_representation->RepresentationBase->maxFrameRate->den; - return TRUE; +/* navigation functions */ +GstStreamMimeType +gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set, + GstRepresentationNode * rep) +{ + gchar *mime = NULL; + if (rep->RepresentationBase) + mime = rep->RepresentationBase->mimeType; + if (mime == NULL && adapt_set->RepresentationBase) { + mime = adapt_set->RepresentationBase->mimeType; } - return FALSE; + if (strncmp_ext (mime, "audio") == 0) + return GST_STREAM_AUDIO; + if (strncmp_ext (mime, "video") == 0) + return GST_STREAM_VIDEO; + if (strncmp_ext (mime, "application") == 0 || strncmp_ext (mime, "text") == 0) + return GST_STREAM_APPLICATION; + + return GST_STREAM_UNKNOWN; } -guint -gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream) +/* Helper methods */ +gchar * +gst_mpdparser_build_URL_from_template (const gchar * url_template, + const gchar * id, guint number, guint bandwidth, guint64 time) { - const gchar *rate; + static const gchar default_format[] = "%01d"; + gchar **tokens, *token, *ret; + const gchar *format; + gint i, num_tokens; - if (stream == NULL || stream->cur_adapt_set == NULL - || stream->cur_representation == NULL) - return 0; + g_return_val_if_fail (url_template != NULL, NULL); + tokens = g_strsplit_set (url_template, "$", -1); + if (!tokens) { + GST_WARNING ("Scan of URL template failed!"); + return NULL; + } + num_tokens = g_strv_length (tokens); - rate = stream->cur_representation->RepresentationBase->audioSamplingRate; - if (rate == NULL) { - rate = stream->cur_adapt_set->RepresentationBase->audioSamplingRate; + /* + * each identifier is guarded by 2 $, which means that we must have an odd number of tokens + * An even number of tokens means the string is not valid. + */ + if ((num_tokens & 1) == 0) { + GST_ERROR ("Invalid number of tokens (%d). url_template is '%s'", + num_tokens, url_template); + g_strfreev (tokens); + return NULL; } - return rate ? atoi (rate) : 0; -} + for (i = 0; i < num_tokens; i++) { + token = tokens[i]; + format = default_format; -guint -gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream) -{ - if (stream == NULL || stream->cur_adapt_set == NULL - || stream->cur_representation == NULL) - return 0; - /* TODO: here we have to parse the AudioChannelConfiguration descriptors */ - return 0; -} + /* the tokens to replace must be provided between $ characters, eg $token$ + * For a string like token0$token1$token2$token3$token4, only the odd number + * tokens (1,3,...) must be parsed. + * + * Skip even tokens + */ + if ((i & 1) == 0) + continue; -guint -gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient * client, - GList ** lang) -{ - GstStreamPeriod *stream_period; - GstAdaptationSetNode *adapt_set; - GList *adaptation_sets, *list; - const gchar *this_mimeType = "audio"; - gchar *mimeType = NULL; - guint nb_adaptation_set = 0; - - stream_period = gst_mpdparser_get_stream_period (client); - g_return_val_if_fail (stream_period != NULL, 0); - g_return_val_if_fail (stream_period->period != NULL, 0); - - adaptation_sets = - gst_mpd_client_get_adaptation_sets_for_period (client, stream_period); - for (list = adaptation_sets; list; list = g_list_next (list)) { - adapt_set = (GstAdaptationSetNode *) list->data; - if (adapt_set && adapt_set->lang) { - gchar *this_lang = adapt_set->lang; - GstRepresentationNode *rep; - rep = - gst_mpdparser_get_lowest_representation (adapt_set->Representations); - mimeType = NULL; - if (rep->RepresentationBase) - mimeType = rep->RepresentationBase->mimeType; - if (!mimeType && adapt_set->RepresentationBase) { - mimeType = adapt_set->RepresentationBase->mimeType; - } + if (!g_strcmp0 (token, "RepresentationID")) { + if (!gst_mpdparser_validate_rfc1738_url (id)) + goto invalid_representation_id; - if (strncmp_ext (mimeType, this_mimeType) == 0) { - nb_adaptation_set++; - *lang = g_list_append (*lang, this_lang); + tokens[i] = g_strdup_printf ("%s", id); + g_free (token); + } else if (!strncmp (token, "Number", 6)) { + if (strlen (token) > 6) { + format = token + 6; /* format tag */ } - } - } + if (!validate_format (format)) + goto invalid_format; - return nb_adaptation_set; -} + tokens[i] = g_strdup_printf (format, number); + g_free (token); + } else if (!strncmp (token, "Bandwidth", 9)) { + if (strlen (token) > 9) { + format = token + 9; /* format tag */ + } + if (!validate_format (format)) + goto invalid_format; + tokens[i] = g_strdup_printf (format, bandwidth); + g_free (token); + } else if (!strncmp (token, "Time", 4)) { + gchar *promoted_format; -GstDateTime * -gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client, - GstActiveStream * stream) -{ - GstDateTime *availability_start_time, *rv; - gint seg_idx; - GstMediaSegment *segment; - GstClockTime segmentEndTime; - const GstStreamPeriod *stream_period; - GstClockTime period_start = 0; - - g_return_val_if_fail (client != NULL, NULL); - g_return_val_if_fail (stream != NULL, NULL); + if (strlen (token) > 4) { + format = token + 4; /* format tag */ + } + if (!validate_format (format)) + goto invalid_format; - stream_period = gst_mpdparser_get_stream_period (client); - if (stream_period && stream_period->period) { - period_start = stream_period->start; + promoted_format = promote_format_to_uint64 (format); + tokens[i] = g_strdup_printf (promoted_format, time); + g_free (promoted_format); + g_free (token); + } else if (!g_strcmp0 (token, "")) { + tokens[i] = g_strdup_printf ("%s", "$"); + g_free (token); + } else { + /* unexpected identifier found between $ signs + * + * "If the URL contains unescaped $ symbols which do not enclose a valid + * identifier then the result of URL formation is undefined" + */ + goto invalid_format; + } } - seg_idx = stream->segment_index; + ret = g_strjoinv (NULL, tokens); - if (stream->segments) { - segment = g_ptr_array_index (stream->segments, seg_idx); + g_strfreev (tokens); - if (segment->repeat >= 0) { - segmentEndTime = segment->start + (stream->segment_repeat_index + 1) * - segment->duration; - } else if (seg_idx < stream->segments->len - 1) { - const GstMediaSegment *next_segment = - g_ptr_array_index (stream->segments, seg_idx + 1); - segmentEndTime = next_segment->start; - } else { - g_return_val_if_fail (stream_period != NULL, NULL); - segmentEndTime = period_start + stream_period->duration; - } - } else { - GstClockTime seg_duration; - seg_duration = gst_mpd_client_get_segment_duration (client, stream, NULL); - if (seg_duration == 0) - return NULL; - segmentEndTime = period_start + (1 + seg_idx) * seg_duration; - } + return ret; - availability_start_time = gst_mpd_client_get_availability_start_time (client); - if (availability_start_time == NULL) { - GST_WARNING_OBJECT (client, "Failed to get availability_start_time"); - return NULL; - } +invalid_format: + { + GST_ERROR ("Invalid format '%s' in '%s'", format, token); + + g_strfreev (tokens); - rv = gst_mpd_client_add_time_difference (availability_start_time, - segmentEndTime / GST_USECOND); - gst_date_time_unref (availability_start_time); - if (rv == NULL) { - GST_WARNING_OBJECT (client, "Failed to offset availability_start_time"); return NULL; } +invalid_representation_id: + { + GST_ERROR + ("Representation ID string '%s' has characters invalid in an RFC 1738 URL", + id); - return rv; -} + g_strfreev (tokens); -gboolean -gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time) -{ - GDateTime *start; - GTimeSpan ts_microseconds; - GstClockTime ts; - gboolean ret = TRUE; - GList *stream; - - g_return_val_if_fail (gst_mpd_client_is_live (client), FALSE); - g_return_val_if_fail (client->mpd_node->availabilityStartTime != NULL, FALSE); - - start = - gst_date_time_to_g_date_time (client->mpd_node->availabilityStartTime); - - ts_microseconds = g_date_time_difference (time, start); - g_date_time_unref (start); - - /* Clamp to availability start time, otherwise calculations wrap around */ - if (ts_microseconds < 0) - ts_microseconds = 0; - - ts = ts_microseconds * GST_USECOND; - for (stream = client->active_streams; stream; stream = g_list_next (stream)) { - ret = - ret & gst_mpd_client_stream_seek (client, stream->data, TRUE, 0, ts, - NULL); + return NULL; } - return ret; -} - -void -gst_media_fragment_info_clear (GstMediaFragmentInfo * fragment) -{ - g_free (fragment->uri); - g_free (fragment->index_uri); } -gboolean -gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient * client) +const gchar * +gst_mpdparser_mimetype_to_caps (const gchar * mimeType) { - return client->profile_isoff_ondemand; + if (mimeType == NULL) + return NULL; + if (strcmp (mimeType, "video/mp2t") == 0) { + return "video/mpegts, systemstream=(bool) true"; + } else if (strcmp (mimeType, "video/mp4") == 0) { + return "video/quicktime"; + } else if (strcmp (mimeType, "audio/mp4") == 0) { + return "audio/x-m4a"; + } else if (strcmp (mimeType, "text/vtt") == 0) { + return "application/x-subtitle-vtt"; + } else + return mimeType; } -/** - * gst_mpd_client_parse_default_presentation_delay: - * @client: #GstMpdClient that has a parsed manifest - * @default_presentation_delay: A string that specifies a time period - * in fragments (e.g. "5 f"), seconds ("12 s") or milliseconds - * ("12000 ms") - * Returns: the parsed string in milliseconds - * - * Since: 1.6 +/* + * Combine a base url with the current stream base url from the list of + * baseURLs. Takes ownership of base and returns a new base. */ -gint64 -gst_mpd_client_parse_default_presentation_delay (GstMpdClient * client, - const gchar * default_presentation_delay) -{ - gint64 value; - char *endptr = NULL; - - g_return_val_if_fail (client != NULL, 0); - g_return_val_if_fail (default_presentation_delay != NULL, 0); - value = strtol (default_presentation_delay, &endptr, 10); - if (endptr == default_presentation_delay || value == 0) { - return 0; - } - while (*endptr == ' ') - endptr++; - if (*endptr == 's' || *endptr == 'S') { - value *= 1000; /* convert to ms */ - } else if (*endptr == 'f' || *endptr == 'F') { - gint64 segment_duration; - g_assert (client->mpd_node != NULL); - segment_duration = client->mpd_node->maxSegmentDuration; - value *= segment_duration; - } else if (*endptr != 'm' && *endptr != 'M') { - GST_ERROR ("Unable to parse default presentation delay: %s", - default_presentation_delay); - value = 0; - } - return value; -} - -GstClockTime -gst_mpd_client_get_maximum_segment_duration (GstMpdClient * client) +GstUri * +combine_urls (GstUri * base, GList * list, gchar ** query, guint idx) { - GstClockTime ret = GST_CLOCK_TIME_NONE, dur; - GList *stream; + GstBaseURL *baseURL; + GstUri *ret = base; - g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE); - g_return_val_if_fail (client->mpd_node != NULL, GST_CLOCK_TIME_NONE); + if (list != NULL) { + baseURL = g_list_nth_data (list, idx); + if (!baseURL) { + baseURL = list->data; + } - if (client->mpd_node->maxSegmentDuration != GST_MPD_DURATION_NONE) { - return client->mpd_node->maxSegmentDuration * GST_MSECOND; - } + ret = gst_uri_from_string_with_base (base, baseURL->baseURL); + gst_uri_unref (base); - /* According to the DASH specification, if maxSegmentDuration is not present: - "If not present, then the maximum Segment duration shall be the maximum - duration of any Segment documented in this MPD" - */ - for (stream = client->active_streams; stream; stream = g_list_next (stream)) { - dur = gst_mpd_client_get_segment_duration (client, stream->data, NULL); - if (dur != GST_CLOCK_TIME_NONE && (dur > ret || ret == GST_CLOCK_TIME_NONE)) { - ret = dur; + if (ret && query) { + g_free (*query); + *query = gst_uri_get_query_string (ret); + if (*query) { + ret = gst_uri_make_writable (ret); + gst_uri_set_query_table (ret, NULL); + } } } + return ret; } + +/* comparison functions */ +int +strncmp_ext (const char *s1, const char *s2) +{ + if (s1 == NULL && s2 == NULL) + return 0; + if (s1 == NULL && s2 != NULL) + return 1; + if (s2 == NULL && s1 != NULL) + return 1; + return strncmp (s1, s2, strlen (s2)); +} diff --git a/ext/dash/gstmpdparser.h b/ext/dash/gstmpdparser.h index 9c78d3f..fb55704 100644 --- a/ext/dash/gstmpdparser.h +++ b/ext/dash/gstmpdparser.h @@ -30,10 +30,10 @@ #include #include #include +#include "gstmpdhelper.h" G_BEGIN_DECLS -typedef struct _GstMpdClient GstMpdClient; typedef struct _GstActiveStream GstActiveStream; typedef struct _GstStreamPeriod GstStreamPeriod; typedef struct _GstMediaFragmentInfo GstMediaFragmentInfo; @@ -50,10 +50,7 @@ typedef struct _GstSegmentListNode GstSegmentListNode; typedef struct _GstSegmentTemplateNode GstSegmentTemplateNode; typedef struct _GstSegmentURLNode GstSegmentURLNode; typedef struct _GstBaseURL GstBaseURL; -typedef struct _GstRange GstRange; -typedef struct _GstRatio GstRatio; -typedef struct _GstFrameRate GstFrameRate; -typedef struct _GstConditionalUintType GstConditionalUintType; + typedef struct _GstSubsetNode GstSubsetNode; typedef struct _GstProgramInformationNode GstProgramInformationNode; typedef struct _GstMetricsRangeNode GstMetricsRangeNode; @@ -65,8 +62,6 @@ typedef struct _GstSegmentBaseType GstSegmentBaseType; typedef struct _GstURLType GstURLType; typedef struct _GstMultSegmentBaseType GstMultSegmentBaseType; -#define GST_MPD_CLIENT_LOCK(c) g_mutex_lock (&c->lock); -#define GST_MPD_CLIENT_UNLOCK(c) g_mutex_unlock (&c->lock); #define GST_MPD_DURATION_NONE ((guint64)-1) @@ -78,22 +73,6 @@ typedef enum GST_STREAM_APPLICATION /* application stream (optional): for timed text/subtitles */ } GstStreamMimeType; -typedef enum -{ - GST_MPD_FILE_TYPE_STATIC, - GST_MPD_FILE_TYPE_DYNAMIC -} GstMPDFileType; - -typedef enum -{ - GST_SAP_TYPE_0 = 0, - GST_SAP_TYPE_1, - GST_SAP_TYPE_2, - GST_SAP_TYPE_3, - GST_SAP_TYPE_4, - GST_SAP_TYPE_5, - GST_SAP_TYPE_6 -} GstSAPType; typedef enum { @@ -120,30 +99,6 @@ struct _GstBaseURL gchar *byteRange; }; -struct _GstRange -{ - guint64 first_byte_pos; - guint64 last_byte_pos; -}; - -struct _GstRatio -{ - guint num; - guint den; -}; - -struct _GstFrameRate -{ - guint num; - guint den; -}; - -struct _GstConditionalUintType -{ - gboolean flag; - guint value; -}; - struct _GstSNode { guint64 t; @@ -160,14 +115,14 @@ struct _GstSegmentTimelineNode struct _GstURLType { gchar *sourceURL; - GstRange *range; + GstXMLRange *range; }; struct _GstSegmentBaseType { guint timescale; guint64 presentationTimeOffset; - GstRange *indexRange; + GstXMLRange *indexRange; gboolean indexRangeExact; /* Initialization node */ GstURLType *Initialization; @@ -211,9 +166,9 @@ struct _GstSegmentTemplateNode struct _GstSegmentURLNode { gchar *media; - GstRange *mediaRange; + GstXMLRange *mediaRange; gchar *index; - GstRange *indexRange; + GstXMLRange *indexRange; }; struct _GstRepresentationBaseType @@ -221,16 +176,16 @@ struct _GstRepresentationBaseType gchar *profiles; guint width; guint height; - GstRatio *sar; - GstFrameRate *minFrameRate; - GstFrameRate *maxFrameRate; - GstFrameRate *frameRate; + GstXMLRatio *sar; + GstXMLFrameRate *minFrameRate; + GstXMLFrameRate *maxFrameRate; + GstXMLFrameRate *frameRate; gchar *audioSamplingRate; gchar *mimeType; gchar *segmentProfiles; gchar *codecs; gdouble maximumSAPPeriod; - GstSAPType startWithSAP; + GstMPDSAPType startWithSAP; gdouble maxPlayoutRate; gboolean codingDependency; gchar *scanType; @@ -285,7 +240,7 @@ struct _GstContentComponentNode guint id; gchar *lang; /* LangVectorType RFC 5646 */ gchar *contentType; - GstRatio *par; + GstXMLRatio *par; /* list of Accessibility DescriptorType nodes */ GList *Accessibility; /* list of Role DescriptorType nodes */ @@ -302,16 +257,16 @@ struct _GstAdaptationSetNode guint group; gchar *lang; /* LangVectorType RFC 5646 */ gchar *contentType; - GstRatio *par; + GstXMLRatio *par; guint minBandwidth; guint maxBandwidth; guint minWidth; guint maxWidth; guint minHeight; guint maxHeight; - GstConditionalUintType *segmentAlignment; - GstConditionalUintType *subsegmentAlignment; - GstSAPType subsegmentStartsWithSAP; + GstXMLConditionalUintType *segmentAlignment; + GstXMLConditionalUintType *subsegmentAlignment; + GstMPDSAPType subsegmentStartsWithSAP; gboolean bitstreamSwitching; /* list of Accessibility DescriptorType nodes */ GList *Accessibility; @@ -502,106 +457,35 @@ struct _GstActiveStream GstClockTime presentationTimeOffset; /* presentation time offset of the current segment */ }; -struct _GstMpdClient -{ - GstMPDNode *mpd_node; /* active MPD manifest file */ - - GList *periods; /* list of GstStreamPeriod */ - guint period_idx; /* index of current Period */ - - GList *active_streams; /* list of GstActiveStream */ - - guint update_failed_count; - gchar *mpd_uri; /* manifest file URI */ - gchar *mpd_base_uri; /* base URI for resolving relative URIs. - * this will be different for redirects */ - - /* profiles */ - gboolean profile_isoff_ondemand; - - GstUriDownloader * downloader; -}; - -/* Basic initialization/deinitialization functions */ -GstMpdClient *gst_mpd_client_new (void); -void gst_active_streams_free (GstMpdClient * client); -void gst_mpd_client_free (GstMpdClient * client); -void gst_media_fragment_info_clear (GstMediaFragmentInfo * fragment); - -void gst_mpd_client_set_uri_downloader (GstMpdClient * client, GstUriDownloader * download); - /* MPD file parsing */ -gboolean gst_mpd_parse (GstMpdClient *client, const gchar *data, gint size); - -/* Streaming management */ -gboolean gst_mpd_client_setup_media_presentation (GstMpdClient *client, GstClockTime time, gint period_index, const gchar *period_id); -gboolean gst_mpd_client_setup_streaming (GstMpdClient * client, GstAdaptationSetNode * adapt_set); -gboolean gst_mpd_client_setup_representation (GstMpdClient *client, GstActiveStream *stream, GstRepresentationNode *representation); -GstClockTime gst_mpd_client_get_next_fragment_duration (GstMpdClient * client, GstActiveStream * stream); -GstClockTime gst_mpd_client_get_media_presentation_duration (GstMpdClient *client); -GstClockTime gst_mpd_client_get_maximum_segment_duration (GstMpdClient * client); -gboolean gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client, guint stream_idx, GstClockTime * ts); -gboolean gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client, guint stream_idx, GstClockTime * ts); -gboolean gst_mpd_client_get_next_fragment (GstMpdClient *client, guint indexStream, GstMediaFragmentInfo * fragment); -gboolean gst_mpd_client_get_next_header (GstMpdClient *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end); -gboolean gst_mpd_client_get_next_header_index (GstMpdClient *client, gchar **uri, guint stream_idx, gint64 * range_start, gint64 * range_end); -gboolean gst_mpd_client_is_live (GstMpdClient * client); -gboolean gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * stream, gboolean forward, GstSeekFlags flags, GstClockTime ts, GstClockTime * final_ts); -gboolean gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time); -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); -GstClockTime gst_mpd_parser_get_period_start_time (GstMpdClient *client); - -/* Period selection */ -guint gst_mpd_client_get_period_index_at_time (GstMpdClient * client, GstDateTime * time); -gboolean gst_mpd_client_set_period_index (GstMpdClient *client, guint period_idx); -gboolean gst_mpd_client_set_period_id (GstMpdClient *client, const gchar * period_id); -guint gst_mpd_client_get_period_index (GstMpdClient *client); -const gchar *gst_mpd_client_get_period_id (GstMpdClient *client); -gboolean gst_mpd_client_has_next_period (GstMpdClient *client); -gboolean gst_mpd_client_has_previous_period (GstMpdClient * client); - -/* Representation selection */ -gint gst_mpdparser_get_rep_idx_with_max_bandwidth (GList *Representations, gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint max_video_framerate_n, gint max_video_framerate_d); -gint gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations); - -/* URL management */ -const gchar *gst_mpdparser_get_baseURL (GstMpdClient *client, guint indexStream); - -/* Active stream */ -guint gst_mpdparser_get_nb_active_stream (GstMpdClient *client); -GstActiveStream *gst_mpdparser_get_active_stream_by_index (GstMpdClient *client, guint stream_idx); -gboolean gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream); - -/* AdaptationSet */ -guint gst_mpdparser_get_nb_adaptationSet (GstMpdClient *client); -GList * gst_mpd_client_get_adaptation_sets (GstMpdClient * client); - -/* Segment */ -gboolean gst_mpd_client_has_next_segment (GstMpdClient * client, GstActiveStream * stream, gboolean forward); -GstFlowReturn gst_mpd_client_advance_segment (GstMpdClient * client, GstActiveStream * stream, gboolean forward); -void gst_mpd_client_seek_to_first_segment (GstMpdClient * client); -GstDateTime *gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client, GstActiveStream * stream); - -/* Get audio/video stream parameters (caps, width, height, rate, number of channels) */ -GstCaps * gst_mpd_client_get_stream_caps (GstActiveStream * stream); -gboolean gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream); -guint gst_mpd_client_get_video_stream_width (GstActiveStream * stream); -guint gst_mpd_client_get_video_stream_height (GstActiveStream * stream); -gboolean gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream, gint * fps_num, gint * fps_den); -guint gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream); -guint gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream); - -/* Support multi language */ -guint gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient *client, GList **lang); - -gint64 gst_mpd_client_calculate_time_difference (const GstDateTime * t1, const GstDateTime * t2); -GstDateTime *gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs); -gint64 gst_mpd_client_parse_default_presentation_delay(GstMpdClient * client, const gchar * default_presentation_delay); - -/* profiles */ -gboolean gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient *client); - +gboolean gst_mpdparser_get_mpd_node (GstMPDNode ** mpd_node, const gchar * data, gint size); +GstSegmentListNode * gst_mpdparser_get_external_segment_list (const gchar * data, gint size, GstSegmentListNode * parent); +GList * gst_mpdparser_get_external_periods (const gchar * data, gint size); +GList * gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size, GstPeriodNode* period); + +/* navigation functions */ +GstStreamMimeType gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set, GstRepresentationNode * rep); + +/* Memory management */ +void gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node); +void gst_mpdparser_free_period_node (GstPeriodNode * period_node); +void gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode * adaptation_set_node); +void gst_mpdparser_free_segment_list_node (GstSegmentListNode * segment_list_node); +void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period); +void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment); +void gst_mpdparser_free_active_stream (GstActiveStream * active_stream); +void gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node); + +/* Active stream methods*/ +void gst_mpdparser_init_active_stream_segments (GstActiveStream * stream); +gchar *gst_mpdparser_get_mediaURL (GstActiveStream * stream, GstSegmentURLNode * segmentURL); +const gchar *gst_mpdparser_get_initializationURL (GstActiveStream * stream, GstURLType * InitializationURL); + +/*Helper methods */ +gchar *gst_mpdparser_build_URL_from_template (const gchar * url_template, const gchar * id, guint number, guint bandwidth, guint64 time); +const gchar *gst_mpdparser_mimetype_to_caps (const gchar * mimeType); +GstUri *combine_urls (GstUri * base, GList * list, gchar ** query, guint idx); +int strncmp_ext (const char *s1, const char *s2); G_END_DECLS #endif /* __GST_MPDPARSER_H__ */ diff --git a/ext/dash/gstxmlhelper.c b/ext/dash/gstxmlhelper.c new file mode 100644 index 0000000..ff97981 --- /dev/null +++ b/ext/dash/gstxmlhelper.c @@ -0,0 +1,1024 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "gstxmlhelper.h" + +/* static methods */ +/* this function computes decimals * 10 ^ (3 - pos) */ +static guint +_mpd_helper_convert_to_millisecs (guint decimals, gint pos) +{ + guint num = 1, den = 1; + gint i = 3 - pos; + + while (i < 0) { + den *= 10; + i++; + } + while (i > 0) { + num *= 10; + i--; + } + /* if i == 0 we have exactly 3 decimals and nothing to do */ + return decimals * num / den; +} + +static gboolean +_mpd_helper_accumulate (guint64 * v, guint64 mul, guint64 add) +{ + guint64 tmp; + + if (*v > G_MAXUINT64 / mul) + return FALSE; + tmp = *v * mul; + if (tmp > G_MAXUINT64 - add) + return FALSE; + *v = tmp + add; + return TRUE; +} + +/* + Duration Data Type + + The duration data type is used to specify a time interval. + + The time interval is specified in the following form "-PnYnMnDTnHnMnS" where: + + * - indicates the negative sign (optional) + * P indicates the period (required) + * nY indicates the number of years + * nM indicates the number of months + * nD indicates the number of days + * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds) + * nH indicates the number of hours + * nM indicates the number of minutes + * nS indicates the number of seconds +*/ +static gboolean +_mpd_helper_parse_duration (const char *str, guint64 * value) +{ + gint ret, len, pos, posT; + gint years = -1, months = -1, days = -1, hours = -1, minutes = -1, seconds = + -1, decimals = -1, read; + gboolean have_ms = FALSE; + guint64 tmp_value; + + len = strlen (str); + GST_TRACE ("duration: %s, len %d", str, len); + if (strspn (str, "PT0123456789., \tHMDSY") < len) { + GST_WARNING ("Invalid character found: '%s'", str); + goto error; + } + /* skip leading/trailing whitespace */ + while (g_ascii_isspace (str[0])) { + str++; + len--; + } + while (len > 0 && g_ascii_isspace (str[len - 1])) + --len; + + /* read "P" for period */ + if (str[0] != 'P') { + GST_WARNING ("P not found at the beginning of the string!"); + goto error; + } + str++; + len--; + + /* read "T" for time (if present) */ + posT = strcspn (str, "T"); + len -= posT; + if (posT > 0) { + /* there is some room between P and T, so there must be a period section */ + /* read years, months, days */ + do { + GST_TRACE ("parsing substring %s", str); + pos = strcspn (str, "YMD"); + ret = sscanf (str, "%u", &read); + if (ret != 1) { + GST_WARNING ("can not read integer value from string %s!", str); + goto error; + } + switch (str[pos]) { + case 'Y': + if (years != -1 || months != -1 || days != -1) { + GST_WARNING ("year, month or day was already set"); + goto error; + } + years = read; + break; + case 'M': + if (months != -1 || days != -1) { + GST_WARNING ("month or day was already set"); + goto error; + } + months = read; + if (months >= 12) { + GST_WARNING ("Month out of range"); + goto error; + } + break; + case 'D': + if (days != -1) { + GST_WARNING ("day was already set"); + goto error; + } + days = read; + if (days >= 31) { + GST_WARNING ("Day out of range"); + goto error; + } + break; + default: + GST_WARNING ("unexpected char %c!", str[pos]); + goto error; + break; + } + GST_TRACE ("read number %u type %c", read, str[pos]); + str += (pos + 1); + posT -= (pos + 1); + } while (posT > 0); + } + + if (years == -1) + years = 0; + if (months == -1) + months = 0; + if (days == -1) + days = 0; + + GST_TRACE ("Y:M:D=%d:%d:%d", years, months, days); + + /* read "T" for time (if present) */ + /* here T is at pos == 0 */ + str++; + len--; + pos = 0; + if (pos < len) { + /* T found, there is a time section */ + /* read hours, minutes, seconds, hundredths of second */ + do { + GST_TRACE ("parsing substring %s", str); + pos = strcspn (str, "HMS,."); + ret = sscanf (str, "%u", &read); + if (ret != 1) { + GST_WARNING ("can not read integer value from string %s!", str); + goto error; + } + switch (str[pos]) { + case 'H': + if (hours != -1 || minutes != -1 || seconds != -1) { + GST_WARNING ("hour, minute or second was already set"); + goto error; + } + hours = read; + if (hours >= 24) { + GST_WARNING ("Hour out of range"); + goto error; + } + break; + case 'M': + if (minutes != -1 || seconds != -1) { + GST_WARNING ("minute or second was already set"); + goto error; + } + minutes = read; + if (minutes >= 60) { + GST_WARNING ("Minute out of range"); + goto error; + } + break; + case 'S': + if (have_ms) { + /* we have read the decimal part of the seconds */ + decimals = _mpd_helper_convert_to_millisecs (read, pos); + GST_TRACE ("decimal number %u (%d digits) -> %d ms", read, pos, + decimals); + } else { + if (seconds != -1) { + GST_WARNING ("second was already set"); + goto error; + } + /* no decimals */ + seconds = read; + } + break; + case '.': + case ',': + /* we have read the integer part of a decimal number in seconds */ + if (seconds != -1) { + GST_WARNING ("second was already set"); + goto error; + } + seconds = read; + have_ms = TRUE; + break; + default: + GST_WARNING ("unexpected char %c!", str[pos]); + goto error; + break; + } + GST_TRACE ("read number %u type %c", read, str[pos]); + str += pos + 1; + len -= (pos + 1); + } while (len > 0); + } + + if (hours == -1) + hours = 0; + if (minutes == -1) + minutes = 0; + if (seconds == -1) + seconds = 0; + if (decimals == -1) + decimals = 0; + GST_TRACE ("H:M:S.MS=%d:%d:%d.%03d", hours, minutes, seconds, decimals); + + tmp_value = 0; + if (!_mpd_helper_accumulate (&tmp_value, 1, years) + || !_mpd_helper_accumulate (&tmp_value, 365, months * 30) + || !_mpd_helper_accumulate (&tmp_value, 1, days) + || !_mpd_helper_accumulate (&tmp_value, 24, hours) + || !_mpd_helper_accumulate (&tmp_value, 60, minutes) + || !_mpd_helper_accumulate (&tmp_value, 60, seconds) + || !_mpd_helper_accumulate (&tmp_value, 1000, decimals)) + goto error; + + /* ensure it can be converted from milliseconds to nanoseconds */ + if (tmp_value > G_MAXUINT64 / 1000000) + goto error; + + *value = tmp_value; + return TRUE; + +error: + return FALSE; +} + +static gboolean +_mpd_helper_validate_no_whitespace (const char *s) +{ + return !strpbrk (s, "\r\n\t "); +} + +/* API */ + +GstXMLRange * +gst_xml_helper_clone_range (GstXMLRange * range) +{ + GstXMLRange *clone = NULL; + + if (range) { + clone = g_slice_new0 (GstXMLRange); + clone->first_byte_pos = range->first_byte_pos; + clone->last_byte_pos = range->last_byte_pos; + } + + return clone; +} + +/* XML property get method */ +gboolean +gst_xml_helper_get_prop_validated_string (xmlNode * a_node, + const gchar * property_name, gchar ** property_value, + gboolean (*validate) (const char *)) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (validate && !(*validate) ((const char *) prop_string)) { + GST_WARNING ("Validation failure: %s", prop_string); + xmlFree (prop_string); + return FALSE; + } + *property_value = (gchar *) prop_string; + exists = TRUE; + GST_LOG (" - %s: %s", property_name, prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_ns_prop_string (xmlNode * a_node, + const gchar * ns_name, const gchar * property_name, gchar ** property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + prop_string = + xmlGetNsProp (a_node, (const xmlChar *) property_name, + (const xmlChar *) ns_name); + if (prop_string) { + *property_value = (gchar *) prop_string; + exists = TRUE; + GST_LOG (" - %s:%s: %s", ns_name, property_name, prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_string (xmlNode * a_node, + const gchar * property_name, gchar ** property_value) +{ + return gst_xml_helper_get_prop_validated_string (a_node, property_name, + property_value, NULL); +} + +gboolean +gst_xml_helper_get_prop_string_vector_type (xmlNode * a_node, + const gchar * property_name, gchar *** property_value) +{ + xmlChar *prop_string; + gchar **prop_string_vector = NULL; + guint i = 0; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + prop_string_vector = g_strsplit ((gchar *) prop_string, " ", -1); + if (prop_string_vector) { + exists = TRUE; + *property_value = prop_string_vector; + GST_LOG (" - %s:", property_name); + while (prop_string_vector[i]) { + GST_LOG (" %s", prop_string_vector[i]); + i++; + } + } else { + GST_WARNING ("Scan of string vector property failed!"); + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_signed_integer (xmlNode * a_node, + const gchar * property_name, gint default_val, gint * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + *property_value = default_val; + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (sscanf ((const gchar *) prop_string, "%d", property_value) == 1) { + exists = TRUE; + GST_LOG (" - %s: %d", property_name, *property_value); + } else { + GST_WARNING + ("failed to parse signed integer property %s from xml string %s", + property_name, prop_string); + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_unsigned_integer (xmlNode * a_node, + const gchar * property_name, guint default_val, guint * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + *property_value = default_val; + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%u", property_value) == 1 && + strstr ((gchar *) prop_string, "-") == NULL) { + exists = TRUE; + GST_LOG (" - %s: %u", property_name, *property_value); + } else { + GST_WARNING + ("failed to parse unsigned integer property %s from xml string %s", + property_name, prop_string); + /* sscanf might have written to *property_value. Restore to default */ + *property_value = default_val; + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_unsigned_integer_64 (xmlNode * a_node, + const gchar * property_name, guint64 default_val, guint64 * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + *property_value = default_val; + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%" G_GUINT64_FORMAT, + property_value) == 1 && + strstr ((gchar *) prop_string, "-") == NULL) { + exists = TRUE; + GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value); + } else { + GST_WARNING + ("failed to parse unsigned integer property %s from xml string %s", + property_name, prop_string); + /* sscanf might have written to *property_value. Restore to default */ + *property_value = default_val; + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_uint_vector_type (xmlNode * a_node, + const gchar * property_name, guint ** property_value, guint * value_size) +{ + xmlChar *prop_string; + gchar **str_vector; + guint *prop_uint_vector = NULL, i; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + str_vector = g_strsplit ((gchar *) prop_string, " ", -1); + if (str_vector) { + *value_size = g_strv_length (str_vector); + prop_uint_vector = g_malloc (*value_size * sizeof (guint)); + if (prop_uint_vector) { + exists = TRUE; + GST_LOG (" - %s:", property_name); + for (i = 0; i < *value_size; i++) { + if (sscanf ((gchar *) str_vector[i], "%u", &prop_uint_vector[i]) == 1 + && strstr (str_vector[i], "-") == NULL) { + GST_LOG (" %u", prop_uint_vector[i]); + } else { + GST_WARNING + ("failed to parse uint vector type property %s from xml string %s", + property_name, str_vector[i]); + /* there is no special value to put in prop_uint_vector[i] to + * signal it is invalid, so we just clean everything and return + * FALSE + */ + g_free (prop_uint_vector); + prop_uint_vector = NULL; + exists = FALSE; + break; + } + } + *property_value = prop_uint_vector; + } else { + GST_WARNING ("Array allocation failed!"); + } + } else { + GST_WARNING ("Scan of uint vector property failed!"); + } + xmlFree (prop_string); + g_strfreev (str_vector); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_double (xmlNode * a_node, + const gchar * property_name, gdouble * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (sscanf ((gchar *) prop_string, "%lf", property_value) == 1) { + exists = TRUE; + GST_LOG (" - %s: %lf", property_name, *property_value); + } else { + GST_WARNING ("failed to parse double property %s from xml string %s", + property_name, prop_string); + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_boolean (xmlNode * a_node, + const gchar * property_name, gboolean default_val, + gboolean * property_value) +{ + xmlChar *prop_string; + gboolean exists = FALSE; + + *property_value = default_val; + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + if (xmlStrcmp (prop_string, (xmlChar *) "false") == 0) { + exists = TRUE; + *property_value = FALSE; + GST_LOG (" - %s: false", property_name); + } else if (xmlStrcmp (prop_string, (xmlChar *) "true") == 0) { + exists = TRUE; + *property_value = TRUE; + GST_LOG (" - %s: true", property_name); + } else { + GST_WARNING ("failed to parse boolean property %s from xml string %s", + property_name, prop_string); + } + xmlFree (prop_string); + } + + return exists; +} + +gboolean +gst_xml_helper_get_prop_range (xmlNode * a_node, + const gchar * property_name, GstXMLRange ** property_value) +{ + xmlChar *prop_string; + guint64 first_byte_pos = 0, last_byte_pos = -1; + guint len, pos; + gchar *str; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("range: %s, len %d", str, len); + + /* read "-" */ + pos = strcspn (str, "-"); + if (pos >= len) { + GST_TRACE ("pos %d >= len %d", pos, len); + goto error; + } + /* read first_byte_pos */ + if (pos != 0) { + /* replace str[pos] with '\0' to allow sscanf to not be confused by + * the minus sign (eg " -1" (observe the space before -) would otherwise + * be interpreted as range -1 to 1) + */ + str[pos] = 0; + if (sscanf (str, "%" G_GUINT64_FORMAT, &first_byte_pos) != 1 || + strstr (str, "-") != NULL) { + /* sscanf failed or it found a negative number */ + /* restore the '-' sign */ + str[pos] = '-'; + goto error; + } + /* restore the '-' sign */ + str[pos] = '-'; + } + /* read last_byte_pos */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%" G_GUINT64_FORMAT, &last_byte_pos) != 1 || + strstr (str + pos + 1, "-") != NULL) { + goto error; + } + } + /* malloc return data structure */ + *property_value = g_slice_new0 (GstXMLRange); + exists = TRUE; + (*property_value)->first_byte_pos = first_byte_pos; + (*property_value)->last_byte_pos = last_byte_pos; + xmlFree (prop_string); + GST_LOG (" - %s: %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, + property_name, first_byte_pos, last_byte_pos); + } + + return exists; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property_name, + prop_string); + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_prop_ratio (xmlNode * a_node, + const gchar * property_name, GstXMLRatio ** property_value) +{ + xmlChar *prop_string; + guint num = 0, den = 1; + guint len, pos; + gchar *str; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("ratio: %s, len %d", str, len); + + /* read ":" */ + pos = strcspn (str, ":"); + if (pos >= len) { + GST_TRACE ("pos %d >= len %d", pos, len); + goto error; + } + /* search for negative sign */ + if (strstr (str, "-") != NULL) { + goto error; + } + /* read num */ + if (pos != 0) { + if (sscanf (str, "%u", &num) != 1) { + goto error; + } + } + /* read den */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%u", &den) != 1) { + goto error; + } + } + /* malloc return data structure */ + *property_value = g_slice_new0 (GstXMLRatio); + exists = TRUE; + (*property_value)->num = num; + (*property_value)->den = den; + xmlFree (prop_string); + GST_LOG (" - %s: %u:%u", property_name, num, den); + } + + return exists; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property_name, + prop_string); + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_prop_framerate (xmlNode * a_node, + const gchar * property_name, GstXMLFrameRate ** property_value) +{ + xmlChar *prop_string; + guint num = 0, den = 1; + guint len, pos; + gchar *str; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + len = xmlStrlen (prop_string); + str = (gchar *) prop_string; + GST_TRACE ("framerate: %s, len %d", str, len); + + /* search for negative sign */ + if (strstr (str, "-") != NULL) { + goto error; + } + + /* read "/" if available */ + pos = strcspn (str, "/"); + /* read num */ + if (pos != 0) { + if (sscanf (str, "%u", &num) != 1) { + goto error; + } + } + /* read den (if available) */ + if (pos < (len - 1)) { + if (sscanf (str + pos + 1, "%u", &den) != 1) { + goto error; + } + } + /* alloc return data structure */ + *property_value = g_slice_new0 (GstXMLFrameRate); + exists = TRUE; + (*property_value)->num = num; + (*property_value)->den = den; + xmlFree (prop_string); + if (den == 1) + GST_LOG (" - %s: %u", property_name, num); + else + GST_LOG (" - %s: %u/%u", property_name, num, den); + } + + return exists; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property_name, + prop_string); + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_prop_cond_uint (xmlNode * a_node, + const gchar * property_name, GstXMLConditionalUintType ** property_value) +{ + xmlChar *prop_string; + gchar *str; + gboolean flag; + guint val; + gboolean exists = FALSE; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + str = (gchar *) prop_string; + GST_TRACE ("conditional uint: %s", str); + + if (strcmp (str, "false") == 0) { + flag = FALSE; + val = 0; + } else if (strcmp (str, "true") == 0) { + flag = TRUE; + val = 0; + } else { + flag = TRUE; + if (sscanf (str, "%u", &val) != 1 || strstr (str, "-") != NULL) + goto error; + } + + /* alloc return data structure */ + *property_value = g_slice_new0 (GstXMLConditionalUintType); + exists = TRUE; + (*property_value)->flag = flag; + (*property_value)->value = val; + xmlFree (prop_string); + GST_LOG (" - %s: flag=%s val=%u", property_name, flag ? "true" : "false", + val); + } + + return exists; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property_name, + prop_string); + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_prop_dateTime (xmlNode * a_node, + const gchar * property_name, GstDateTime ** property_value) +{ + xmlChar *prop_string; + gchar *str; + gint ret, pos; + gint year, month, day, hour, minute; + gdouble second; + gboolean exists = FALSE; + gfloat tzoffset = 0.0; + gint gmt_offset_hour = -99, gmt_offset_min = -99; + + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + str = (gchar *) prop_string; + GST_TRACE ("dateTime: %s, len %d", str, xmlStrlen (prop_string)); + /* parse year */ + ret = sscanf (str, "%d", &year); + if (ret != 1 || year <= 0) + goto error; + pos = strcspn (str, "-"); + str += (pos + 1); + GST_TRACE (" - year %d", year); + /* parse month */ + ret = sscanf (str, "%d", &month); + if (ret != 1 || month <= 0) + goto error; + pos = strcspn (str, "-"); + str += (pos + 1); + GST_TRACE (" - month %d", month); + /* parse day */ + ret = sscanf (str, "%d", &day); + if (ret != 1 || day <= 0) + goto error; + pos = strcspn (str, "T"); + str += (pos + 1); + GST_TRACE (" - day %d", day); + /* parse hour */ + ret = sscanf (str, "%d", &hour); + if (ret != 1 || hour < 0) + goto error; + pos = strcspn (str, ":"); + str += (pos + 1); + GST_TRACE (" - hour %d", hour); + /* parse minute */ + ret = sscanf (str, "%d", &minute); + if (ret != 1 || minute < 0) + goto error; + pos = strcspn (str, ":"); + str += (pos + 1); + GST_TRACE (" - minute %d", minute); + /* parse second */ + ret = sscanf (str, "%lf", &second); + if (ret != 1 || second < 0) + goto error; + GST_TRACE (" - second %lf", second); + + GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%09.6lf", property_name, + year, month, day, hour, minute, second); + + if (strrchr (str, '+') || strrchr (str, '-')) { + /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */ + gint gmt_offset = -1; + gchar *plus_pos = NULL; + gchar *neg_pos = NULL; + gchar *pos = NULL; + + GST_LOG ("Checking for timezone information"); + + /* check if there is timezone info */ + plus_pos = strrchr (str, '+'); + neg_pos = strrchr (str, '-'); + if (plus_pos) + pos = plus_pos + 1; + else if (neg_pos) + pos = neg_pos + 1; + + if (pos && strlen (pos) >= 3) { + gint ret_tz; + if (pos[2] == ':') + ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min); + else + ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min); + + GST_DEBUG ("Parsing timezone: %s", pos); + + if (ret_tz == 2) { + if (neg_pos != NULL && neg_pos + 1 == pos) { + gmt_offset_hour *= -1; + gmt_offset_min *= -1; + } + gmt_offset = gmt_offset_hour * 60 + gmt_offset_min; + + tzoffset = gmt_offset / 60.0; + + GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset); + } else + GST_WARNING ("Failed to parse timezone information"); + } + } + + exists = TRUE; + *property_value = + gst_date_time_new (tzoffset, year, month, day, hour, minute, second); + xmlFree (prop_string); + } + + return exists; + +error: + GST_WARNING ("failed to parse property %s from xml string %s", property_name, + prop_string); + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_prop_duration (xmlNode * a_node, + const gchar * property_name, guint64 default_value, + guint64 * property_value) +{ + xmlChar *prop_string; + gchar *str; + gboolean exists = FALSE; + + *property_value = default_value; + prop_string = xmlGetProp (a_node, (const xmlChar *) property_name); + if (prop_string) { + str = (gchar *) prop_string; + if (!_mpd_helper_parse_duration (str, property_value)) + goto error; + GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value); + xmlFree (prop_string); + exists = TRUE; + } + return exists; + +error: + xmlFree (prop_string); + return FALSE; +} + +gboolean +gst_xml_helper_get_node_content (xmlNode * a_node, gchar ** content) +{ + xmlChar *node_content = NULL; + gboolean exists = FALSE; + + node_content = xmlNodeGetContent (a_node); + if (node_content) { + exists = TRUE; + *content = (gchar *) node_content; + GST_LOG (" - %s: %s", a_node->name, *content); + } + + return exists; +} + +gboolean +gst_xml_helper_get_node_as_string (xmlNode * a_node, gchar ** content) +{ + gboolean exists = FALSE; + const char *txt_encoding; + xmlOutputBufferPtr out_buf; + + txt_encoding = (const char *) a_node->doc->encoding; + out_buf = xmlAllocOutputBuffer (NULL); + g_assert (out_buf != NULL); + xmlNodeDumpOutput (out_buf, a_node->doc, a_node, 0, 0, txt_encoding); + xmlOutputBufferFlush (out_buf); +#ifdef LIBXML2_NEW_BUFFER + if (xmlOutputBufferGetSize (out_buf) > 0) { + *content = + (gchar *) xmlStrndup (xmlOutputBufferGetContent (out_buf), + xmlOutputBufferGetSize (out_buf)); + exists = TRUE; + } +#else + if (out_buf->conv && out_buf->conv->use > 0) { + *content = + (gchar *) xmlStrndup (out_buf->conv->content, out_buf->conv->use); + exists = TRUE; + } else if (out_buf->buffer && out_buf->buffer->use > 0) { + *content = + (gchar *) xmlStrndup (out_buf->buffer->content, out_buf->buffer->use); + exists = TRUE; + } +#endif // LIBXML2_NEW_BUFFER + (void) xmlOutputBufferClose (out_buf); + + if (exists) { + GST_LOG (" - %s: %s", a_node->name, *content); + } + return exists; +} + +gchar * +gst_xml_helper_get_node_namespace (xmlNode * a_node, const gchar * prefix) +{ + xmlNs *curr_ns; + gchar *namespace = NULL; + + if (prefix == NULL) { + /* return the default namespace */ + if (a_node->ns) { + namespace = xmlMemStrdup ((const gchar *) a_node->ns->href); + if (namespace) { + GST_LOG (" - default namespace: %s", namespace); + } + } + } else { + /* look for the specified prefix in the namespace list */ + for (curr_ns = a_node->ns; curr_ns; curr_ns = curr_ns->next) { + if (xmlStrcmp (curr_ns->prefix, (xmlChar *) prefix) == 0) { + namespace = xmlMemStrdup ((const gchar *) curr_ns->href); + if (namespace) { + GST_LOG (" - %s namespace: %s", curr_ns->prefix, curr_ns->href); + } + } + } + } + + return namespace; +} + +gboolean +gst_xml_helper_get_prop_string_stripped (xmlNode * a_node, + const gchar * property_name, gchar ** property_value) +{ + gboolean ret; + ret = gst_xml_helper_get_prop_string (a_node, property_name, property_value); + if (ret) + *property_value = g_strstrip (*property_value); + return ret; +} + +gboolean +gst_xml_helper_get_prop_string_no_whitespace (xmlNode * a_node, + const gchar * property_name, gchar ** property_value) +{ + return gst_xml_helper_get_prop_validated_string (a_node, property_name, + property_value, _mpd_helper_validate_no_whitespace); +} diff --git a/ext/dash/gstxmlhelper.h b/ext/dash/gstxmlhelper.h new file mode 100644 index 0000000..ce9de9a --- /dev/null +++ b/ext/dash/gstxmlhelper.h @@ -0,0 +1,113 @@ +/* GStreamer + * + * Copyright (C) 2019 Collabora Ltd. + * Author: Stéphane Cerveau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library (COPYING); if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __GST_XMLHELPER_H__ +#define __GST_XMLHELPER_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstXMLRange GstXMLRange; +typedef struct _GstXMLRatio GstXMLRatio; +typedef struct _GstXMLFrameRate GstXMLFrameRate; +typedef struct _GstXMLConditionalUintType GstXMLConditionalUintType; + +struct _GstXMLRange +{ + guint64 first_byte_pos; + guint64 last_byte_pos; +}; + +struct _GstXMLRatio +{ + guint num; + guint den; +}; + +struct _GstXMLFrameRate +{ + guint num; + guint den; +}; + +struct _GstXMLConditionalUintType +{ + gboolean flag; + guint value; +}; + +GstXMLRange *gst_xml_helper_clone_range (GstXMLRange * range); + +/* XML property get method */ +gboolean gst_xml_helper_get_prop_validated_string (xmlNode * a_node, + const gchar * property_name, gchar ** property_value, + gboolean (*validator) (const char *)); +gboolean gst_xml_helper_get_prop_string (xmlNode * a_node, + const gchar * property_name, gchar ** property_value); +gboolean gst_xml_helper_get_prop_string_stripped (xmlNode * a_node, + const gchar * property_name, gchar ** property_value); +gboolean gst_xml_helper_get_ns_prop_string (xmlNode * a_node, + const gchar * ns_name, const gchar * property_name, + gchar ** property_value); +gboolean gst_xml_helper_get_prop_string_vector_type (xmlNode * a_node, + const gchar * property_name, gchar *** property_value); +gboolean gst_xml_helper_get_prop_signed_integer (xmlNode * a_node, + const gchar * property_name, gint default_val, gint * property_value); +gboolean gst_xml_helper_get_prop_unsigned_integer (xmlNode * a_node, + const gchar * property_name, guint default_val, guint * property_value); +gboolean gst_xml_helper_get_prop_unsigned_integer_64 (xmlNode * + a_node, const gchar * property_name, guint64 default_val, + guint64 * property_value); +gboolean gst_xml_helper_get_prop_uint_vector_type (xmlNode * a_node, + const gchar * property_name, guint ** property_value, guint * value_size); +gboolean gst_xml_helper_get_prop_double (xmlNode * a_node, + const gchar * property_name, gdouble * property_value); +gboolean gst_xml_helper_get_prop_boolean (xmlNode * a_node, + const gchar * property_name, gboolean default_val, + gboolean * property_value); +gboolean gst_xml_helper_get_prop_range (xmlNode * a_node, + const gchar * property_name, GstXMLRange ** property_value); +gboolean gst_xml_helper_get_prop_ratio (xmlNode * a_node, + const gchar * property_name, GstXMLRatio ** property_value); +gboolean gst_xml_helper_get_prop_framerate (xmlNode * a_node, + const gchar * property_name, GstXMLFrameRate ** property_value); +gboolean gst_xml_helper_get_prop_cond_uint (xmlNode * a_node, + const gchar * property_name, GstXMLConditionalUintType ** property_value); +gboolean gst_xml_helper_get_prop_dateTime (xmlNode * a_node, + const gchar * property_name, GstDateTime ** property_value); +gboolean gst_xml_helper_get_prop_duration (xmlNode * a_node, + const gchar * property_name, guint64 default_value, + guint64 * property_value); +gboolean gst_xml_helper_get_prop_string_no_whitespace (xmlNode * a_node, + const gchar * property_name, gchar ** property_value); + +/* XML node get method */ +gboolean gst_xml_helper_get_node_content (xmlNode * a_node, + gchar ** content); +gchar *gst_xml_helper_get_node_namespace (xmlNode * a_node, + const gchar * prefix); +gboolean gst_xml_helper_get_node_as_string (xmlNode * a_node, + gchar ** content); + +G_END_DECLS +#endif /* __GST_XMLHELPER_H__ */ diff --git a/ext/dash/meson.build b/ext/dash/meson.build index a6f1b5d..c3e1f09 100644 --- a/ext/dash/meson.build +++ b/ext/dash/meson.build @@ -1,6 +1,9 @@ dash_sources = [ 'gstdashdemux.c', + 'gstxmlhelper.c', + 'gstmpdhelper.c', 'gstmpdparser.c', + 'gstmpdclient.c', 'gstplugin.c', ] diff --git a/tests/check/elements/dash_mpd.c b/tests/check/elements/dash_mpd.c index 7b88ce5..c6c9697 100644 --- a/tests/check/elements/dash_mpd.c +++ b/tests/check/elements/dash_mpd.c @@ -19,6 +19,9 @@ */ #include "../../ext/dash/gstmpdparser.c" +#include "../../ext/dash/gstxmlhelper.c" +#include "../../ext/dash/gstmpdhelper.c" +#include "../../ext/dash/gstmpdclient.c" #undef GST_CAT_DEFAULT #include @@ -30,7 +33,7 @@ GST_DEBUG_CATEGORY (gst_dash_demux_debug); * year, month, day, hour, minute, second, millisecond * * This function must use the same conversion algorithm implemented in - * gst_mpdparser_get_xml_prop_duration from gstmpdparser.c file. + * gst_xml_helper_get_prop_duration from gstmpdparser.c file. */ static guint64 duration_to_ms (guint year, guint month, guint day, guint hour, guint minute, @@ -66,7 +69,7 @@ GST_START_TEST (dash_mpdparser_validsimplempd) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* check that unset elements with default values are properly configured */ @@ -107,7 +110,7 @@ GST_START_TEST (dash_mpdparser_mpd) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); assert_equals_string (mpdclient->mpd_node->default_namespace, @@ -186,7 +189,7 @@ GST_START_TEST (dash_mpdparser_programInformation) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); program = @@ -219,7 +222,7 @@ GST_START_TEST (dash_mpdparser_baseURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); baseURL = (GstBaseURL *) mpdclient->mpd_node->BaseURLs->data; @@ -248,7 +251,7 @@ GST_START_TEST (dash_mpdparser_location) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); location = (gchar *) mpdclient->mpd_node->Locations->data; @@ -275,7 +278,7 @@ GST_START_TEST (dash_mpdparser_metrics) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); metricsNode = (GstMetricsNode *) mpdclient->mpd_node->Metrics->data; @@ -306,7 +309,7 @@ GST_START_TEST (dash_mpdparser_metrics_range) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); metricsNode = (GstMetricsNode *) mpdclient->mpd_node->Metrics->data; @@ -338,7 +341,7 @@ GST_START_TEST (dash_mpdparser_metrics_reporting) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); metricsNode = (GstMetricsNode *) mpdclient->mpd_node->Metrics->data; @@ -368,7 +371,7 @@ GST_START_TEST (dash_mpdparser_period) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -404,7 +407,7 @@ GST_START_TEST (dash_mpdparser_period_baseURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -440,7 +443,7 @@ GST_START_TEST (dash_mpdparser_period_segmentBase) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -478,7 +481,7 @@ GST_START_TEST (dash_mpdparser_period_segmentBase_initialization) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -515,7 +518,7 @@ GST_START_TEST (dash_mpdparser_period_segmentBase_representationIndex) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -547,7 +550,7 @@ GST_START_TEST (dash_mpdparser_period_segmentList) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -580,7 +583,7 @@ GST_START_TEST (dash_mpdparser_period_segmentList_multipleSegmentBaseType) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -620,7 +623,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -661,7 +664,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -700,7 +703,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -741,7 +744,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -781,7 +784,7 @@ GST_START_TEST (dash_mpdparser_period_segmentList_segmentURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -822,7 +825,7 @@ GST_START_TEST (dash_mpdparser_period_segmentTemplate) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -878,7 +881,7 @@ GST_START_TEST (dash_mpdparser_period_segmentTemplateWithPresentationTimeOffset) GstSegmentTemplateNode *segmentTemplate; mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); ret = @@ -899,7 +902,7 @@ GST_START_TEST (dash_mpdparser_period_segmentTemplateWithPresentationTimeOffset) fail_if (adapt_set == NULL); ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); segmentTemplate = adapt_set->SegmentTemplate; @@ -950,7 +953,7 @@ GST_START_TEST (dash_mpdparser_period_segmentTemplate_multipleSegmentBaseType) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -990,7 +993,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1031,7 +1034,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1070,7 +1073,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1111,7 +1114,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1162,7 +1165,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1232,7 +1235,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_representationBase) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1285,7 +1288,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1324,7 +1327,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1364,7 +1367,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1410,7 +1413,7 @@ GST_START_TEST (dash_mpdparser_contentProtection_no_value) GstMpdClient *mpdclient = gst_mpd_client_new (); gchar *str; - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1469,7 +1472,7 @@ GST_START_TEST (dash_mpdparser_contentProtection_no_value_no_encoding) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1509,7 +1512,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_accessibility) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1545,7 +1548,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_role) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1581,7 +1584,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_rating) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1617,7 +1620,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_viewpoint) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1655,7 +1658,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_contentComponent) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1698,7 +1701,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1738,7 +1741,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_contentComponent_role) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1779,7 +1782,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_contentComponent_rating) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1820,7 +1823,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_contentComponent_viewpoint) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1858,7 +1861,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_baseURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1897,7 +1900,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_segmentBase) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1938,7 +1941,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_segmentBase_initialization) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -1979,7 +1982,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2015,7 +2018,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_segmentList) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2053,7 +2056,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_segmentTemplate) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2096,7 +2099,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2142,7 +2145,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2184,7 +2187,7 @@ GST_START_TEST (dash_mpdparser_adapt_repr_segmentTemplate_inherit) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2233,7 +2236,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_segmentTemplate_inherit) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2276,7 +2279,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_representation) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2319,7 +2322,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2359,7 +2362,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_representation_baseURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2403,7 +2406,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2453,7 +2456,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2495,7 +2498,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_representation_segmentBase) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2534,7 +2537,7 @@ GST_START_TEST (dash_mpdparser_period_adaptationSet_representation_segmentList) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2573,7 +2576,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2605,7 +2608,7 @@ GST_START_TEST (dash_mpdparser_period_subset) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -2639,7 +2642,7 @@ GST_START_TEST (dash_mpdparser_utctiming) GstMPDUTCTimingType selected_method; gchar **urls; - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); fail_if (mpdclient->mpd_node == NULL); @@ -2708,7 +2711,7 @@ GST_START_TEST (dash_mpdparser_utctiming_invalid_value) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); fail_if (mpdclient->mpd_node == NULL); @@ -2734,7 +2737,7 @@ GST_START_TEST (dash_mpdparser_type_dynamic) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); isLive = gst_mpd_client_is_live (mpdclient); @@ -2827,7 +2830,7 @@ GST_START_TEST (dash_mpdparser_isoff_ondemand_profile) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); hasOnDemandProfile = gst_mpd_client_has_isoff_ondemand_profile (mpdclient); @@ -2915,7 +2918,7 @@ GST_START_TEST (dash_mpdparser_bitstreamSwitching_inheritance) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -2940,11 +2943,11 @@ GST_START_TEST (dash_mpdparser_bitstreamSwitching_inheritance) assert_equals_int (ret, TRUE); /* 2 active streams */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 2); /* get details of the first active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_VIDEO); @@ -2960,7 +2963,7 @@ GST_START_TEST (dash_mpdparser_bitstreamSwitching_inheritance) assert_equals_int (bitstreamSwitchingFlag, TRUE); /* get details of the second active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 1); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 1); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_AUDIO); @@ -3006,7 +3009,7 @@ GST_START_TEST (dash_mpdparser_various_duration_formats) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); ret = @@ -3083,7 +3086,7 @@ GST_START_TEST (dash_mpdparser_setup_media_presentation) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3120,7 +3123,7 @@ GST_START_TEST (dash_mpdparser_setup_streaming) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3165,7 +3168,7 @@ GST_START_TEST (dash_mpdparser_period_selection) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* period_idx should be 0 and we should have no active periods */ @@ -3237,7 +3240,7 @@ GST_START_TEST (dash_mpdparser_get_period_at_time) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3306,7 +3309,7 @@ GST_START_TEST (dash_mpdparser_adaptationSet_handling) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3319,13 +3322,13 @@ GST_START_TEST (dash_mpdparser_adaptationSet_handling) fail_unless (mpdclient->periods != NULL); periodName = gst_mpd_client_get_period_id (mpdclient); assert_equals_string (periodName, "Period0"); - adaptation_sets_count = gst_mpdparser_get_nb_adaptationSet (mpdclient); + adaptation_sets_count = gst_mpd_client_get_nb_adaptationSet (mpdclient); assert_equals_int (adaptation_sets_count, 1); /* period1 has 2 adaptation set */ ret = gst_mpd_client_set_period_id (mpdclient, "Period1"); assert_equals_int (ret, TRUE); - adaptation_sets_count = gst_mpdparser_get_nb_adaptationSet (mpdclient); + adaptation_sets_count = gst_mpd_client_get_nb_adaptationSet (mpdclient); assert_equals_int (adaptation_sets_count, 2); /* check the id for the 2 adaptation sets from period 1 */ @@ -3370,7 +3373,7 @@ GST_START_TEST (dash_mpdparser_representation_selection) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3390,26 +3393,26 @@ GST_START_TEST (dash_mpdparser_representation_selection) fail_if (representations == NULL); represendationIndex = - gst_mpdparser_get_rep_idx_with_min_bandwidth (representations); + gst_mpd_client_get_rep_idx_with_min_bandwidth (representations); assert_equals_int (represendationIndex, 1); represendationIndex = - gst_mpdparser_get_rep_idx_with_max_bandwidth (representations, 0, 0, 0, 0, - 1); + gst_mpd_client_get_rep_idx_with_max_bandwidth (representations, 0, 0, 0, + 0, 1); assert_equals_int (represendationIndex, 1); represendationIndex = - gst_mpdparser_get_rep_idx_with_max_bandwidth (representations, 100000, 0, + gst_mpd_client_get_rep_idx_with_max_bandwidth (representations, 100000, 0, 0, 0, 1); assert_equals_int (represendationIndex, -1); represendationIndex = - gst_mpdparser_get_rep_idx_with_max_bandwidth (representations, 300000, 0, + gst_mpd_client_get_rep_idx_with_max_bandwidth (representations, 300000, 0, 0, 0, 1); assert_equals_int (represendationIndex, 1); represendationIndex = - gst_mpdparser_get_rep_idx_with_max_bandwidth (representations, 500000, 0, + gst_mpd_client_get_rep_idx_with_max_bandwidth (representations, 500000, 0, 0, 0, 1); assert_equals_int (represendationIndex, 0); @@ -3449,7 +3452,7 @@ GST_START_TEST (dash_mpdparser_activeStream_selection) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3463,7 +3466,7 @@ GST_START_TEST (dash_mpdparser_activeStream_selection) fail_if (adaptationSets == NULL); /* no active streams yet */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 0); /* setup streaming from the first adaptation set */ @@ -3473,7 +3476,7 @@ GST_START_TEST (dash_mpdparser_activeStream_selection) assert_equals_int (ret, TRUE); /* 1 active streams */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 1); /* setup streaming from the second adaptation set */ @@ -3483,7 +3486,7 @@ GST_START_TEST (dash_mpdparser_activeStream_selection) assert_equals_int (ret, TRUE); /* 2 active streams */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 2); /* setup streaming from the third adaptation set */ @@ -3493,21 +3496,21 @@ GST_START_TEST (dash_mpdparser_activeStream_selection) assert_equals_int (ret, TRUE); /* 3 active streams */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 3); /* get details of the first active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_VIDEO); /* get details of the second active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 1); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 1); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_AUDIO); /* get details of the third active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 2); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 2); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_APPLICATION); @@ -3552,7 +3555,7 @@ GST_START_TEST (dash_mpdparser_activeStream_parameters) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3572,11 +3575,11 @@ GST_START_TEST (dash_mpdparser_activeStream_parameters) assert_equals_int (ret, TRUE); /* 1 active streams */ - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, 1); /* get details of the first active stream */ - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); assert_equals_int (activeStream->mimeType, GST_STREAM_VIDEO); @@ -3642,7 +3645,7 @@ GST_START_TEST (dash_mpdparser_get_audio_languages) GstMpdClient *mpdclient = gst_mpd_client_new (); gint i; - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3656,18 +3659,18 @@ GST_START_TEST (dash_mpdparser_get_audio_languages) fail_if (adaptationSets == NULL); /* setup streaming from all adaptation sets */ - adaptationSetsCount = gst_mpdparser_get_nb_adaptationSet (mpdclient); + adaptationSetsCount = gst_mpd_client_get_nb_adaptationSet (mpdclient); for (i = 0; i < adaptationSetsCount; i++) { adapt_set = (GstAdaptationSetNode *) g_list_nth_data (adaptationSets, i); fail_if (adapt_set == NULL); ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); } - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, adaptationSetsCount); languagesCount = - gst_mpdparser_get_list_and_nb_of_audio_language (mpdclient, &languages); + gst_mpd_client_get_list_and_nb_of_audio_language (mpdclient, &languages); assert_equals_int (languagesCount, 2); assert_equals_string ((gchar *) g_list_nth_data (languages, 0), "en"); assert_equals_string ((gchar *) g_list_nth_data (languages, 1), "fr"); @@ -3694,7 +3697,7 @@ setup_mpd_client (const gchar * xml) GstMpdClient *mpdclient = gst_mpd_client_new (); gint i; - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -3708,14 +3711,14 @@ setup_mpd_client (const gchar * xml) fail_if (adaptationSets == NULL); /* setup streaming from all adaptation sets */ - adaptationSetsCount = gst_mpdparser_get_nb_adaptationSet (mpdclient); + adaptationSetsCount = gst_mpd_client_get_nb_adaptationSet (mpdclient); for (i = 0; i < adaptationSetsCount; i++) { adapt_set = (GstAdaptationSetNode *) g_list_nth_data (adaptationSets, i); fail_if (adapt_set == NULL); ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); } - activeStreams = gst_mpdparser_get_nb_active_stream (mpdclient); + activeStreams = gst_mpd_client_get_nb_active_stream (mpdclient); assert_equals_int (activeStreams, adaptationSetsCount); return mpdclient; @@ -3736,7 +3739,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL1) GstMpdClient *mpdclient = setup_mpd_client (xml); - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "http://example.com/"); @@ -3778,7 +3781,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL2) * value does not end in /, everything after the last / will be overwritten. * baseURL becomes "/period_base_url/representation_base_url" */ - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "/period_base_url/representation_base_url"); @@ -3818,7 +3821,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL3) * - update the value with BaseURL element from Representation. Because this * is an absolute url, it will replace everything again" */ - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "/representation_base_url"); @@ -3858,7 +3861,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL4) * - update the value with BaseURL element from Representation. Because this * is an relative url, it will update the current value." */ - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "/period_base_url/adaptation_base_url/representation_base_url/"); @@ -3954,7 +3957,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL5) * - update the value with BaseURL element from Representation. Because this * is an relative url, it will update the current value." */ - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "/mpd_base_url1/period_base_url1/adaptation_base_url1/representation_base_url1/"); @@ -3979,7 +3982,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL6) GstMpdClient *mpdclient = setup_mpd_client (xml); - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, ""); @@ -4006,7 +4009,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL7) mpdclient = setup_mpd_client (xml); - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "/x/example.com/"); @@ -4032,7 +4035,7 @@ GST_START_TEST (dash_mpdparser_get_baseURL8) GstMpdClient *mpdclient = setup_mpd_client (xml); - baseURL = gst_mpdparser_get_baseURL (mpdclient, 0); + baseURL = gst_mpd_client_get_baseURL (mpdclient, 0); fail_if (baseURL == NULL); assert_equals_string (baseURL, "x:y/example.com/"); @@ -4058,7 +4061,7 @@ GST_START_TEST (dash_mpdparser_get_mediaPresentationDuration) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); mediaPresentationDuration = @@ -4094,7 +4097,7 @@ GST_START_TEST (dash_mpdparser_get_streamPresentationOffset) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4114,7 +4117,7 @@ GST_START_TEST (dash_mpdparser_get_streamPresentationOffset) assert_equals_int (ret, TRUE); /* test the stream presentation time offset */ - offset = gst_mpd_parser_get_stream_presentation_offset (mpdclient, 0); + offset = gst_mpd_client_get_stream_presentation_offset (mpdclient, 0); /* seems to be set only for template segments, so here it is 0 */ assert_equals_int (offset, 0); @@ -4165,7 +4168,7 @@ GST_START_TEST (dash_mpdparser_segments) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4184,7 +4187,7 @@ GST_START_TEST (dash_mpdparser_segments) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* segment_index 0, segment_count 2. @@ -4300,7 +4303,7 @@ GST_START_TEST (dash_mpdparser_headers) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4376,7 +4379,7 @@ GST_START_TEST (dash_mpdparser_fragments) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4394,7 +4397,7 @@ GST_START_TEST (dash_mpdparser_fragments) fail_if (adapt_set == NULL); ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* expected duration of the next fragment */ @@ -4411,7 +4414,7 @@ GST_START_TEST (dash_mpdparser_fragments) assert_equals_uint64 (fragment.timestamp, expectedTimestamp * GST_MSECOND); gst_media_fragment_info_clear (&fragment); - periodStartTime = gst_mpd_parser_get_period_start_time (mpdclient); + periodStartTime = gst_mpd_client_get_period_start_time (mpdclient); assert_equals_uint64 (periodStartTime, 10 * GST_SECOND); nextFragmentDuration = @@ -4462,7 +4465,7 @@ GST_START_TEST (dash_mpdparser_inherited_segmentBase) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -4525,7 +4528,7 @@ GST_START_TEST (dash_mpdparser_inherited_segmentURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4544,7 +4547,7 @@ GST_START_TEST (dash_mpdparser_inherited_segmentURL) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* expected duration of the next fragment @@ -4611,7 +4614,7 @@ GST_START_TEST (dash_mpdparser_segment_list) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4630,7 +4633,7 @@ GST_START_TEST (dash_mpdparser_segment_list) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* expected duration of the next fragment @@ -4692,7 +4695,7 @@ GST_START_TEST (dash_mpdparser_segment_template) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4711,7 +4714,7 @@ GST_START_TEST (dash_mpdparser_segment_template) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* expected duration of the next fragment @@ -4740,10 +4743,10 @@ GST_START_TEST (dash_mpdparser_segment_template) assert_equals_uint64 (fragment.duration, expectedDuration * GST_MSECOND); assert_equals_uint64 (fragment.timestamp, expectedTimestamp * GST_MSECOND); - periodStartTime = gst_mpd_parser_get_period_start_time (mpdclient); + periodStartTime = gst_mpd_client_get_period_start_time (mpdclient); assert_equals_uint64 (periodStartTime, 10 * GST_SECOND); - offset = gst_mpd_parser_get_stream_presentation_offset (mpdclient, 0); + offset = gst_mpd_client_get_stream_presentation_offset (mpdclient, 0); assert_equals_uint64 (offset, 15 * GST_SECOND); gst_media_fragment_info_clear (&fragment); @@ -4810,7 +4813,7 @@ GST_START_TEST (dash_mpdparser_segment_timeline) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -4829,7 +4832,7 @@ GST_START_TEST (dash_mpdparser_segment_timeline) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); /* expected duration of the next fragment */ @@ -4995,7 +4998,7 @@ GST_START_TEST (dash_mpdparser_multiple_inherited_segmentURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -5013,7 +5016,7 @@ GST_START_TEST (dash_mpdparser_multiple_inherited_segmentURL) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); expectedDuration = duration_to_ms (0, 0, 0, 0, 0, 8, 0); @@ -5112,7 +5115,7 @@ GST_START_TEST (dash_mpdparser_multipleSegmentURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -5131,7 +5134,7 @@ GST_START_TEST (dash_mpdparser_multipleSegmentURL) ret = gst_mpd_client_setup_streaming (mpdclient, adapt_set); assert_equals_int (ret, TRUE); - activeStream = gst_mpdparser_get_active_stream_by_index (mpdclient, 0); + activeStream = gst_mpd_client_get_active_stream_by_index (mpdclient, 0); fail_if (activeStream == NULL); expectedDuration = duration_to_ms (0, 0, 0, 0, 0, 20, 0); @@ -5174,7 +5177,7 @@ GST_START_TEST (dash_mpdparser_missing_xml) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, FALSE); gst_mpd_client_free (mpdclient); @@ -5193,7 +5196,7 @@ GST_START_TEST (dash_mpdparser_missing_mpd) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, FALSE); gst_mpd_client_free (mpdclient); @@ -5214,7 +5217,7 @@ GST_START_TEST (dash_mpdparser_no_end_tag) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, FALSE); gst_mpd_client_free (mpdclient); @@ -5234,7 +5237,7 @@ GST_START_TEST (dash_mpdparser_no_default_namespace) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, strlen (xml)); assert_equals_int (ret, TRUE); gst_mpd_client_free (mpdclient); @@ -5263,7 +5266,7 @@ GST_START_TEST (dash_mpdparser_wrong_period_duration_inferred_from_next_period) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* period_idx should be 0 and we should have no active periods */ @@ -5308,7 +5311,7 @@ GST_START_TEST gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* period_idx should be 0 and we should have no active periods */ @@ -5330,13 +5333,13 @@ GST_END_TEST; GST_START_TEST (dash_mpdparser_whitespace_strings) { - fail_unless (gst_mpdparser_validate_no_whitespace ("") == TRUE); - fail_unless (gst_mpdparser_validate_no_whitespace ("/") == TRUE); - fail_unless (gst_mpdparser_validate_no_whitespace (" ") == FALSE); - fail_unless (gst_mpdparser_validate_no_whitespace ("aaaaaaaa ") == FALSE); - fail_unless (gst_mpdparser_validate_no_whitespace ("a\ta") == FALSE); - fail_unless (gst_mpdparser_validate_no_whitespace ("a\ra") == FALSE); - fail_unless (gst_mpdparser_validate_no_whitespace ("a\na") == FALSE); + fail_unless (_mpd_helper_validate_no_whitespace ("") == TRUE); + fail_unless (_mpd_helper_validate_no_whitespace ("/") == TRUE); + fail_unless (_mpd_helper_validate_no_whitespace (" ") == FALSE); + fail_unless (_mpd_helper_validate_no_whitespace ("aaaaaaaa ") == FALSE); + fail_unless (_mpd_helper_validate_no_whitespace ("a\ta") == FALSE); + fail_unless (_mpd_helper_validate_no_whitespace ("a\ra") == FALSE); + fail_unless (_mpd_helper_validate_no_whitespace ("a\na") == FALSE); } GST_END_TEST; @@ -5388,7 +5391,7 @@ GST_START_TEST (dash_mpdparser_negative_period_duration) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data @@ -5436,7 +5439,7 @@ GST_START_TEST (dash_mpdparser_read_unsigned_from_negative_values) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); periodNode = (GstPeriodNode *) mpdclient->mpd_node->Periods->data; @@ -5494,7 +5497,7 @@ GST_START_TEST (dash_mpdparser_negative_mediaPresentationDuration) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data @@ -5521,7 +5524,7 @@ GST_START_TEST (dash_mpdparser_no_profiles) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, strlen (xml)); assert_equals_int (ret, TRUE); @@ -5562,7 +5565,7 @@ GST_START_TEST (dash_mpdparser_unmatched_segmentTimeline_segmentURL) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); /* process the xml data */ @@ -5605,7 +5608,7 @@ GST_START_TEST (dash_mpdparser_default_presentation_delay) GstMpdClient *mpdclient = gst_mpd_client_new (); gint64 value; - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); value = gst_mpd_client_parse_default_presentation_delay (mpdclient, "5s"); assert_equals_int64 (value, 5000); @@ -5638,54 +5641,54 @@ GST_START_TEST (dash_mpdparser_duration) { guint64 v; - fail_unless (gst_mpdparser_parse_duration ("", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration (" ", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("0", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("D-1", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("T", &v) == FALSE); - - fail_unless (gst_mpdparser_parse_duration ("P", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("PT", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("PX", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PPT", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PTT", &v) == FALSE); - - fail_unless (gst_mpdparser_parse_duration ("P1D", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("P1D1D", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P1D1M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P1M1D", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("P1M1D1M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P1M1D1D", &v) == FALSE); - - fail_unless (gst_mpdparser_parse_duration ("P0M0D", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("P-1M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P15M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P-1D", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P35D", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P-1Y", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT-1H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT25H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT-1M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT65M", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT-1S", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration (" ", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("0", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("D-1", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("T", &v) == FALSE); + + fail_unless (_mpd_helper_parse_duration ("P", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("PT", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("PX", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PPT", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PTT", &v) == FALSE); + + fail_unless (_mpd_helper_parse_duration ("P1D", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("P1D1D", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P1D1M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P1M1D", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("P1M1D1M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P1M1D1D", &v) == FALSE); + + fail_unless (_mpd_helper_parse_duration ("P0M0D", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("P-1M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P15M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P-1D", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P35D", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P-1Y", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT-1H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT25H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT-1M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT65M", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT-1S", &v) == FALSE); /* seconds are allowed to be larger than 60 */ - fail_unless (gst_mpdparser_parse_duration ("PT65S", &v) == TRUE); - - fail_unless (gst_mpdparser_parse_duration ("PT1.1H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT1-1H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT1-H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT-H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PTH", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT0", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("PT1.1S", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("PT1.1.1S", &v) == FALSE); - - fail_unless (gst_mpdparser_parse_duration ("P585Y", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P584Y", &v) == TRUE); - - fail_unless (gst_mpdparser_parse_duration (" P10DT8H", &v) == TRUE); - fail_unless (gst_mpdparser_parse_duration ("P10D T8H", &v) == FALSE); - fail_unless (gst_mpdparser_parse_duration ("P10DT8H ", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("PT65S", &v) == TRUE); + + fail_unless (_mpd_helper_parse_duration ("PT1.1H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT1-1H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT1-H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT-H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PTH", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT0", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("PT1.1S", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("PT1.1.1S", &v) == FALSE); + + fail_unless (_mpd_helper_parse_duration ("P585Y", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P584Y", &v) == TRUE); + + fail_unless (_mpd_helper_parse_duration (" P10DT8H", &v) == TRUE); + fail_unless (_mpd_helper_parse_duration ("P10D T8H", &v) == FALSE); + fail_unless (_mpd_helper_parse_duration ("P10DT8H ", &v) == TRUE); } GST_END_TEST; @@ -5721,7 +5724,7 @@ GST_START_TEST (dash_mpdparser_maximum_segment_duration) xml = g_strdup_printf (xml_template, "maxSegmentDuration=\"PT4.5S\""); mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); g_free (xml); assert_equals_int (ret, TRUE); @@ -5737,7 +5740,7 @@ GST_START_TEST (dash_mpdparser_maximum_segment_duration) */ xml = g_strdup_printf (xml_template, ""); mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); g_free (xml); assert_equals_int (ret, TRUE); ret = @@ -5821,7 +5824,8 @@ GST_START_TEST (dash_mpdparser_xlink_period) xml_uri_front, (const char *) file_uri_double_period, xml_uri_rear, xml_frag_end, NULL); - ret = gst_mpd_parse (mpdclient, xml_joined, (gint) strlen (xml_joined)); + ret = + gst_mpd_client_parse (mpdclient, xml_joined, (gint) strlen (xml_joined)); assert_equals_int (ret, TRUE); period_list = mpdclient->mpd_node->Periods; @@ -5892,7 +5896,7 @@ GST_START_TEST (dash_mpdparser_datetime_with_tz_offset) gboolean ret; GstMpdClient *mpdclient = gst_mpd_client_new (); - ret = gst_mpd_parse (mpdclient, xml, (gint) strlen (xml)); + ret = gst_mpd_client_parse (mpdclient, xml, (gint) strlen (xml)); assert_equals_int (ret, TRUE); availabilityStartTime = mpdclient->mpd_node->availabilityStartTime; -- 2.7.4