/* * DASH MPD parsing library * * gstmpdparser.c * * Copyright (C) 2012 STMicroelectronics * * Authors: * Gianluca Gennari * * 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. */ #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); static void gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_url_type_node (GstURLType ** pointer, xmlNode * a_node); static void gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer, xmlNode * a_node, GstSegmentBaseType * parent); static void gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node); static void gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer, xmlNode * a_node); static gboolean gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer, xmlNode * a_node, GstMultSegmentBaseType * parent); static gboolean gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** pointer, xmlNode * a_node, GstSegmentListNode * parent); static void gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType ** pointer, xmlNode * a_node); static gboolean gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node, GstAdaptationSetNode * parent, GstPeriodNode * period_node); static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, GstPeriodNode * parent); static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node); static gboolean gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer, xmlNode * a_node, GstSegmentTemplateNode * parent); static gboolean gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node); static gboolean gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node); static void gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node); /* Helper functions */ static 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 * subrep_node); static void gst_mpdparser_free_s_node (GstSNode * s_node); static void gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * seg_timeline); static void gst_mpdparser_free_url_type_node (GstURLType * url_type_node); 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 { const gchar *name; GstMPDUTCTimingType method; }; static const struct GstMpdParserUtcTimingMethod gst_mpdparser_utc_timing_methods[] = { {"urn:mpeg:dash:utc:ntp:2014", GST_MPD_UTCTIMING_TYPE_NTP}, {"urn:mpeg:dash:utc:sntp:2014", GST_MPD_UTCTIMING_TYPE_SNTP}, {"urn:mpeg:dash:utc:http-head:2014", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD}, {"urn:mpeg:dash:utc:http-xsdate:2014", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE}, {"urn:mpeg:dash:utc:http-iso:2014", GST_MPD_UTCTIMING_TYPE_HTTP_ISO}, {"urn:mpeg:dash:utc:http-ntp:2014", GST_MPD_UTCTIMING_TYPE_HTTP_NTP}, {"urn:mpeg:dash:utc:direct:2014", GST_MPD_UTCTIMING_TYPE_DIRECT}, /* * Early working drafts used the :2012 namespace and this namespace is * used by some DASH packagers. To work-around these packagers, we also * accept the early draft scheme names. */ {"urn:mpeg:dash:utc:ntp:2012", GST_MPD_UTCTIMING_TYPE_NTP}, {"urn:mpeg:dash:utc:sntp:2012", GST_MPD_UTCTIMING_TYPE_SNTP}, {"urn:mpeg:dash:utc:http-head:2012", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD}, {"urn:mpeg:dash:utc:http-xsdate:2012", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE}, {"urn:mpeg:dash:utc:http-iso:2012", GST_MPD_UTCTIMING_TYPE_HTTP_ISO}, {"urn:mpeg:dash:utc:http-ntp:2012", GST_MPD_UTCTIMING_TYPE_HTTP_NTP}, {"urn:mpeg:dash:utc:direct:2012", GST_MPD_UTCTIMING_TYPE_DIRECT}, {NULL, 0} }; /* functions to parse node namespaces, content and properties */ static gboolean gst_mpdparser_get_xml_prop_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; } 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); } 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; } static gboolean gst_mpdparser_validate_no_whitespace (const char *s) { return !strpbrk (s, "\r\n\t "); } 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) { 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; } 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); } return exists; } static gboolean gst_mpdparser_get_xml_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; } 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); } return exists; } static gboolean gst_mpdparser_get_xml_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; } 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); } return exists; } 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); } xmlFree (prop_string); } return exists; } static gboolean gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node, const gchar * property_name, GstSAPType * 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 = (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); } return exists; } static gboolean gst_mpdparser_get_xml_prop_range (xmlNode * a_node, const gchar * property_name, GstRange ** 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 (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; } 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); } return exists; error: GST_WARNING ("failed to parse property %s from xml string %s", property_name, prop_string); xmlFree (prop_string); return FALSE; } static gboolean gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node, const gchar * property_name, GstFrameRate ** 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 (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); } return exists; error: GST_WARNING ("failed to parse property %s from xml string %s", property_name, prop_string); xmlFree (prop_string); return FALSE; } static gboolean gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node, const gchar * property_name, GstConditionalUintType ** 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 (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); } return exists; error: GST_WARNING ("failed to parse property %s from xml string %s", property_name, prop_string); xmlFree (prop_string); return FALSE; } /* DateTime Data Type The dateTime data type is used to specify a date and a time. The lexical form of xs:dateTime is YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] * 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 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) { 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; } /* 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; } /* 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 = 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 (!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) { 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; } 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); } return exists; } static gboolean gst_mpdparser_get_xml_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; } static gchar * gst_mpdparser_get_xml_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; } static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node) { GstBaseURL *new_base_url; new_base_url = g_slice_new0 (GstBaseURL); *list = g_list_append (*list, new_base_url); GST_LOG ("content of BaseURL node:"); gst_mpdparser_get_xml_node_content (a_node, &new_base_url->baseURL); 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); } static void gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node) { GstDescriptorType *new_descriptor; new_descriptor = g_slice_new0 (GstDescriptorType); *list = g_list_append (*list, new_descriptor); 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); } } static void gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstContentComponentNode *new_content_component; new_content_component = g_slice_new0 (GstContentComponentNode); *list = g_list_append (*list, new_content_component); 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); /* 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); } } } } static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node) { gchar *location = NULL; GST_LOG ("content of Location node:"); if (gst_mpdparser_get_xml_node_content (a_node, &location)) *list = g_list_append (*list, location); } static void gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node) { GstSubRepresentationNode *new_subrep; 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); } static void gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer, xmlNode * a_node, GstSegmentBaseType * 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); /* Initialize values that have defaults */ seg_base_type->indexRangeExact = FALSE; seg_base_type->timescale = 1; /* 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); } seg_base_type->indexRange = rangeval; } if (gst_mpdparser_get_xml_prop_boolean (a_node, "indexRangeExact", FALSE, &boolval)) { seg_base_type->indexRangeExact = boolval; } /* 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); } } } } 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; } return clone; } static void gst_mpdparser_parse_s_node (GQueue * queue, 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); } static GstSegmentTimelineNode * gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer) { 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 clone; } static void gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer, xmlNode * a_node) { xmlNode *cur_node; GstSegmentTimelineNode *new_seg_timeline; 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; } /* 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 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) { xmlNode *cur_node; GstRepresentationNode *new_representation; new_representation = g_slice_new0 (GstRepresentationNode); GST_LOG ("attributes of Representation node:"); if (!gst_mpdparser_get_xml_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, &new_representation->bandwidth)) { GST_ERROR ("Cannot parse Representation bandwidth, invalid manifest"); goto error; } gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "qualityRanking", 0, &new_representation->qualityRanking); gst_mpdparser_get_xml_prop_string_vector_type (a_node, "dependencyId", &new_representation->dependencyId); gst_mpdparser_get_xml_prop_string_vector_type (a_node, "mediaStreamStructureId", &new_representation->mediaStreamStructureId); /* RepresentationBase extension */ gst_mpdparser_parse_representation_base_type (&new_representation->RepresentationBase, a_node); /* 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 *) "SegmentBase") == 0) { gst_mpdparser_parse_seg_base_type_ext (&new_representation->SegmentBase, cur_node, parent->SegmentBase ? parent->SegmentBase : period_node->SegmentBase); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { if (!gst_mpdparser_parse_segment_template_node (&new_representation->SegmentTemplate, cur_node, parent->SegmentTemplate ? parent->SegmentTemplate : period_node->SegmentTemplate)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { if (!gst_mpdparser_parse_segment_list_node (&new_representation->SegmentList, cur_node, parent->SegmentList ? parent-> SegmentList : period_node->SegmentList)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { gst_mpdparser_parse_baseURL_node (&new_representation->BaseURLs, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SubRepresentation") == 0) { gst_mpdparser_parse_subrepresentation_node (&new_representation->SubRepresentations, cur_node); } } } /* some sanity checking */ *list = g_list_append (*list, new_representation); return TRUE; error: gst_mpdparser_free_representation_node (new_representation); return FALSE; } static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, GstPeriodNode * parent) { xmlNode *cur_node; GstAdaptationSetNode *new_adap_set; gchar *actuate; new_adap_set = g_slice_new0 (GstAdaptationSetNode); 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, "http://www.w3.org/1999/xlink", "href", &new_adap_set->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_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, &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", &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, &new_adap_set->minBandwidth); gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxBandwidth", 0, &new_adap_set->maxBandwidth); gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minWidth", 0, &new_adap_set->minWidth); gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxWidth", 0, &new_adap_set->maxWidth); gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minHeight", 0, &new_adap_set->minHeight); gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxHeight", 0, &new_adap_set->maxHeight); gst_mpdparser_get_xml_prop_cond_uint (a_node, "segmentAlignment", &new_adap_set->segmentAlignment); gst_mpdparser_get_xml_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 * is true, the AdaptationSet should not have the bitstreamSwitching * attribute set to false. * We should return a parsing error, but we are generous and ignore the * standard violation. */ new_adap_set->bitstreamSwitching = parent->bitstreamSwitching; } gst_mpdparser_get_xml_prop_cond_uint (a_node, "subsegmentAlignment", &new_adap_set->subsegmentAlignment); gst_mpdparser_get_xml_prop_SAP_type (a_node, "subsegmentStartsWithSAP", &new_adap_set->subsegmentStartsWithSAP); /* RepresentationBase extension */ gst_mpdparser_parse_representation_base_type (&new_adap_set->RepresentationBase, a_node); /* 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_adap_set->Accessibility, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Role, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Rating, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Viewpoint, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { gst_mpdparser_parse_baseURL_node (&new_adap_set->BaseURLs, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) { gst_mpdparser_parse_seg_base_type_ext (&new_adap_set->SegmentBase, cur_node, parent->SegmentBase); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { if (!gst_mpdparser_parse_segment_list_node (&new_adap_set->SegmentList, cur_node, parent->SegmentList)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "ContentComponent") == 0) { gst_mpdparser_parse_content_component_node (&new_adap_set->ContentComponents, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { if (!gst_mpdparser_parse_segment_template_node (&new_adap_set->SegmentTemplate, cur_node, parent->SegmentTemplate)) goto error; } } } /* We must parse Representation after everything else in the AdaptationSet * has been parsed because certain Representation child elements can inherit * attributes specified by the same element in the AdaptationSet */ 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 *) "Representation") == 0) { if (!gst_mpdparser_parse_representation_node (&new_adap_set->Representations, cur_node, new_adap_set, parent)) goto error; } } } *list = g_list_append (*list, new_adap_set); return TRUE; error: gst_mpdparser_free_adaptation_set_node (new_adap_set); return FALSE; } static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node) { GstSubsetNode *new_subset; new_subset = g_slice_new0 (GstSubsetNode); *list = g_list_append (*list, new_subset); GST_LOG ("attributes of Subset node:"); gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "contains", &new_subset->contains, &new_subset->size); } static gboolean gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer, xmlNode * a_node, GstSegmentTemplateNode * parent) { GstSegmentTemplateNode *new_segment_template; gchar *strval; gst_mpdparser_free_segment_template_node (*pointer); new_segment_template = g_slice_new0 (GstSegmentTemplateNode); GST_LOG ("extension of SegmentTemplate node:"); if (!gst_mpdparser_parse_mult_seg_base_type_ext (&new_segment_template->MultSegBaseType, a_node, (parent ? parent->MultSegBaseType : NULL))) goto error; /* 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)) { 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)) { 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)) { 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)) { new_segment_template->bitstreamSwitching = strval; } else if (parent) { new_segment_template->bitstreamSwitching = xmlMemStrdup (parent->bitstreamSwitching); } *pointer = new_segment_template; return TRUE; error: gst_mpdparser_free_segment_template_node (new_segment_template); return FALSE; } static gboolean gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstPeriodNode *new_period; gchar *actuate; new_period = g_slice_new0 (GstPeriodNode); GST_LOG ("attributes of Period node:"); new_period->actuate = GST_XLINK_ACTUATE_ON_REQUEST; if (gst_mpdparser_get_xml_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, "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, &new_period->start); gst_mpdparser_get_xml_prop_duration (a_node, "duration", GST_MPD_DURATION_NONE, &new_period->duration); gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching", FALSE, &new_period->bitstreamSwitching); /* 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 *) "SegmentBase") == 0) { gst_mpdparser_parse_seg_base_type_ext (&new_period->SegmentBase, cur_node, NULL); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) { if (!gst_mpdparser_parse_segment_list_node (&new_period->SegmentList, cur_node, NULL)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) { if (!gst_mpdparser_parse_segment_template_node (&new_period->SegmentTemplate, cur_node, NULL)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Subset") == 0) { gst_mpdparser_parse_subset_node (&new_period->Subsets, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { gst_mpdparser_parse_baseURL_node (&new_period->BaseURLs, cur_node); } } } /* We must parse AdaptationSet after everything else in the Period has been * parsed because certain AdaptationSet child elements can inherit attributes * specified by the same element in the Period */ 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 *) "AdaptationSet") == 0) { if (!gst_mpdparser_parse_adaptation_set_node (&new_period->AdaptationSets, cur_node, new_period)) goto error; } } } *list = g_list_append (*list, new_period); return TRUE; error: gst_mpdparser_free_period_node (new_period); return FALSE; } static void gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstProgramInformationNode *new_prog_info; new_prog_info = g_slice_new0 (GstProgramInformationNode); *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", &new_prog_info->moreInformationURL); /* explore children nodes */ GST_LOG ("children of ProgramInformation 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); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) { gst_mpdparser_get_xml_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); } } } } static void gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node) { GstMetricsRangeNode *new_metrics_range; new_metrics_range = g_slice_new0 (GstMetricsRangeNode); *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_MPD_DURATION_NONE, &new_metrics_range->starttime); gst_mpdparser_get_xml_prop_duration (a_node, "duration", GST_MPD_DURATION_NONE, &new_metrics_range->duration); } static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstMetricsNode *new_metrics; new_metrics = g_slice_new0 (GstMetricsNode); *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); /* explore children nodes */ GST_LOG ("children of Metrics 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 *) "Range") == 0) { gst_mpdparser_parse_metrics_range_node (&new_metrics->MetricsRanges, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Reporting") == 0) { /* No reporting scheme is specified in this part of ISO/IEC 23009. * It is expected that external specifications may define formats * and delivery for the reporting data. */ GST_LOG (" - Reporting node found (unknown structure)"); } } } } /* The UTCTiming element is defined in * ISO/IEC 23009-1:2014/PDAM 1 "Information technology — Dynamic adaptive streaming over HTTP (DASH) — Part 1: Media presentation description and segment formats / Amendment 1: High Profile and Availability Time Synchronization" */ static void gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node) { GstUTCTimingNode *new_timing; gchar *method = NULL; gchar *value = NULL; new_timing = g_slice_new0 (GstUTCTimingNode); GST_LOG ("attributes of UTCTiming node:"); if (gst_mpdparser_get_xml_prop_string (a_node, "schemeIdUri", &method)) { int i; for (i = 0; gst_mpdparser_utc_timing_methods[i].name; ++i) { if (g_ascii_strncasecmp (gst_mpdparser_utc_timing_methods[i].name, method, strlen (gst_mpdparser_utc_timing_methods[i].name)) == 0) { new_timing->method = gst_mpdparser_utc_timing_methods[i].method; break; } } xmlFree (method); } if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) { int max_tokens = 0; if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) { /* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case * that is not a space separated list. */ max_tokens = 1; } new_timing->urls = g_strsplit (value, " ", max_tokens); xmlFree (value); } /* append to list only if both method and urls were set */ if (new_timing->method != 0 && new_timing->urls != NULL && g_strv_length (new_timing->urls) != 0) { *list = g_list_append (*list, new_timing); } else { gst_mpdparser_free_utctiming_node (new_timing); } } static gboolean gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node) { xmlNode *cur_node; GstMPDNode *new_mpd; gst_mpdparser_free_mpd_node (*pointer); *pointer = NULL; 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"); GST_LOG ("attributes of root MPD node:"); gst_mpdparser_get_xml_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", &new_mpd->availabilityStartTime); gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityEndTime", &new_mpd->availabilityEndTime); gst_mpdparser_get_xml_prop_duration (a_node, "mediaPresentationDuration", GST_MPD_DURATION_NONE, &new_mpd->mediaPresentationDuration); gst_mpdparser_get_xml_prop_duration (a_node, "minimumUpdatePeriod", GST_MPD_DURATION_NONE, &new_mpd->minimumUpdatePeriod); gst_mpdparser_get_xml_prop_duration (a_node, "minBufferTime", GST_MPD_DURATION_NONE, &new_mpd->minBufferTime); gst_mpdparser_get_xml_prop_duration (a_node, "timeShiftBufferDepth", GST_MPD_DURATION_NONE, &new_mpd->timeShiftBufferDepth); gst_mpdparser_get_xml_prop_duration (a_node, "suggestedPresentationDelay", GST_MPD_DURATION_NONE, &new_mpd->suggestedPresentationDelay); gst_mpdparser_get_xml_prop_duration (a_node, "maxSegmentDuration", GST_MPD_DURATION_NONE, &new_mpd->maxSegmentDuration); gst_mpdparser_get_xml_prop_duration (a_node, "maxSubsegmentDuration", GST_MPD_DURATION_NONE, &new_mpd->maxSubsegmentDuration); /* explore children Period 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 *) "Period") == 0) { if (!gst_mpdparser_parse_period_node (&new_mpd->Periods, cur_node)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "ProgramInformation") == 0) { gst_mpdparser_parse_program_info_node (&new_mpd->ProgramInfo, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { gst_mpdparser_parse_baseURL_node (&new_mpd->BaseURLs, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Location") == 0) { gst_mpdparser_parse_location_node (&new_mpd->Locations, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) { gst_mpdparser_parse_metrics_node (&new_mpd->Metrics, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "UTCTiming") == 0) { gst_mpdparser_parse_utctiming_node (&new_mpd->UTCTiming, cur_node); } } } *pointer = new_mpd; return TRUE; error: gst_mpdparser_free_mpd_node (new_mpd); return FALSE; } /* comparison functions */ static 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)); } /* 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) { if (metrics_node) { if (metrics_node->metrics) xmlFree (metrics_node->metrics); g_list_free_full (metrics_node->MetricsRanges, (GDestroyNotify) gst_mpdparser_free_metrics_range_node); g_slice_free (GstMetricsNode, metrics_node); } } static void gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * metrics_range_node) { if (metrics_range_node) { g_slice_free (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) { if (subset_node->contains) xmlFree (subset_node->contains); g_slice_free (GstSubsetNode, subset_node); } } static void gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode * segment_template_node) { if (segment_template_node) { if (segment_template_node->media) xmlFree (segment_template_node->media); if (segment_template_node->index) xmlFree (segment_template_node->index); if (segment_template_node->initialization) xmlFree (segment_template_node->initialization); if (segment_template_node->bitstreamSwitching) xmlFree (segment_template_node->bitstreamSwitching); /* MultipleSegmentBaseType extension */ gst_mpdparser_free_mult_seg_base_type_ext (segment_template_node->MultSegBaseType); g_slice_free (GstSegmentTemplateNode, segment_template_node); } } static void gst_mpdparser_free_representation_base_type (GstRepresentationBaseType * representation_base) { 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); if (representation_base->audioSamplingRate) xmlFree (representation_base->audioSamplingRate); if (representation_base->mimeType) xmlFree (representation_base->mimeType); if (representation_base->segmentProfiles) xmlFree (representation_base->segmentProfiles); if (representation_base->codecs) xmlFree (representation_base->codecs); if (representation_base->scanType) xmlFree (representation_base->scanType); g_list_free_full (representation_base->FramePacking, (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); g_list_free_full (representation_base->AudioChannelConfiguration, (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); g_list_free_full (representation_base->ContentProtection, (GDestroyNotify) gst_mpdparser_free_descriptor_type_node); g_slice_free (GstRepresentationBaseType, representation_base); } } 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) { if (representation_node) { if (representation_node->id) xmlFree (representation_node->id); g_strfreev (representation_node->dependencyId); g_strfreev (representation_node->mediaStreamStructureId); gst_mpdparser_free_representation_base_type (representation_node->RepresentationBase); g_list_free_full (representation_node->SubRepresentations, (GDestroyNotify) gst_mpdparser_free_subrepresentation_node); gst_mpdparser_free_seg_base_type_ext (representation_node->SegmentBase); gst_mpdparser_free_segment_template_node (representation_node->SegmentTemplate); gst_mpdparser_free_segment_list_node (representation_node->SegmentList); g_list_free_full (representation_node->BaseURLs, (GDestroyNotify) gst_mpdparser_free_base_url_node); g_slice_free (GstRepresentationNode, representation_node); } } static void gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode * subrep_node) { if (subrep_node) { 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 explicitely 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; } 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; } static 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; } 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_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; } } 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_parser_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_parser_get_period_start_time (GstMpdClient * client) { 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; } /** * 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_mpdparser_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_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--; } } 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_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); } 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_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); } 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_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; } 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; } 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_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)); 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_mpdparser_get_nb_active_stream (GstMpdClient * client) { g_return_val_if_fail (client != NULL, 0); return g_list_length (client->active_streams); } guint gst_mpdparser_get_nb_adaptationSet (GstMpdClient * client) { 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); } GstActiveStream * gst_mpdparser_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")); } static const gchar * gst_mpdparser_mimetype_to_caps (const gchar * mimeType) { 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; } 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_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 (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_mpdparser_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; } 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) { 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; }