/* * 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 "gstmpdparser.h" #include "gstdash_debug.h" #define GST_CAT_DEFAULT gst_dash_demux2_debug /* XML node parsing */ static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node); static void gst_mpdparser_parse_descriptor_type (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 (GstMPDURLTypeNode ** pointer, xmlNode * a_node); static void gst_mpdparser_parse_seg_base_type_ext (GstMPDSegmentBaseNode ** pointer, xmlNode * a_node, GstMPDSegmentBaseNode * parent); static void gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node); static void gst_mpdparser_parse_segment_timeline_node (GstMPDSegmentTimelineNode ** pointer, xmlNode * a_node); static gboolean gst_mpdparser_parse_mult_seg_base_node (GstMPDMultSegmentBaseNode * pointer, xmlNode * a_node, GstMPDMultSegmentBaseNode * parent); static gboolean gst_mpdparser_parse_segment_list_node (GstMPDSegmentListNode ** pointer, xmlNode * a_node, GstMPDSegmentListNode * parent); static void gst_mpdparser_parse_representation_base (GstMPDRepresentationBaseNode * pointer, xmlNode * a_node); static gboolean gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node, GstMPDAdaptationSetNode * parent, GstMPDPeriodNode * period_node); static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, GstMPDPeriodNode * parent); static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node); static gboolean gst_mpdparser_parse_segment_template_node (GstMPDSegmentTemplateNode ** pointer, xmlNode * a_node, GstMPDSegmentTemplateNode * 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 (GstMPDRootNode ** pointer, xmlNode * a_node); static void gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node); /* Duration Data Type The duration data type is used to specify a time interval. The time interval is specified in the following form "-PnYnMnDTnHnMnS" where: * - indicates the negative sign (optional) * P indicates the period (required) * nY indicates the number of years * nM indicates the number of months * nD indicates the number of days * T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds) * nH indicates the number of hours * nM indicates the number of minutes * nS indicates the number of seconds */ static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node) { GstMPDBaseURLNode *new_base_url; new_base_url = gst_mpd_baseurl_node_new (); *list = g_list_append (*list, new_base_url); GST_LOG ("content of BaseURL node:"); gst_xml_helper_get_node_content (a_node, &new_base_url->baseURL); GST_LOG ("attributes of BaseURL node:"); gst_xml_helper_get_prop_string (a_node, "serviceLocation", &new_base_url->serviceLocation); gst_xml_helper_get_prop_string (a_node, "byteRange", &new_base_url->byteRange); } static void gst_mpdparser_parse_descriptor_type (GList ** list, xmlNode * a_node) { GstMPDDescriptorTypeNode *new_descriptor; new_descriptor = gst_mpd_descriptor_type_node_new ((const gchar *) a_node->name); *list = g_list_append (*list, new_descriptor); GST_LOG ("attributes of %s node:", a_node->name); gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri", &new_descriptor->schemeIdUri); if (!gst_xml_helper_get_prop_string (a_node, "value", &new_descriptor->value)) { /* if no value attribute, use XML string representation of the node */ gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value); } } static void gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstMPDContentComponentNode *new_content_component; new_content_component = gst_mpd_content_component_node_new (); *list = g_list_append (*list, new_content_component); GST_LOG ("attributes of ContentComponent node:"); gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, &new_content_component->id); gst_xml_helper_get_prop_string (a_node, "lang", &new_content_component->lang); gst_xml_helper_get_prop_string (a_node, "contentType", &new_content_component->contentType); gst_xml_helper_get_prop_ratio (a_node, "par", &new_content_component->par); /* 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 (&new_content_component->Accessibility, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { gst_mpdparser_parse_descriptor_type (&new_content_component->Role, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { gst_mpdparser_parse_descriptor_type (&new_content_component->Rating, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { gst_mpdparser_parse_descriptor_type (&new_content_component->Viewpoint, cur_node); } } } } static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node) { gchar *location = NULL; GstMPDLocationNode *locationNode; GST_LOG ("content of Location node:"); if (gst_xml_helper_get_node_content (a_node, &location)) { locationNode = gst_mpd_location_node_new (); locationNode->location = location; *list = g_list_append (*list, locationNode); } } static void gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node) { GstMPDSubRepresentationNode *new_subrep; new_subrep = gst_mpd_sub_representation_node_new (); *list = g_list_append (*list, new_subrep); GST_LOG ("attributes of SubRepresentation node:"); gst_xml_helper_get_prop_unsigned_integer (a_node, "level", 0, &new_subrep->level); gst_xml_helper_get_prop_uint_vector_type (a_node, "dependencyLevel", &new_subrep->dependencyLevel, &new_subrep->dependencyLevel_size); gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0, &new_subrep->bandwidth); gst_xml_helper_get_prop_string_vector_type (a_node, "contentComponent", &new_subrep->contentComponent); /* RepresentationBase extension */ gst_mpdparser_parse_representation_base (GST_MPD_REPRESENTATION_BASE_NODE (new_subrep), a_node); } static void gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node) { GstMPDSegmentURLNode *new_segment_url; new_segment_url = gst_mpd_segment_url_node_new (); *list = g_list_append (*list, new_segment_url); GST_LOG ("attributes of SegmentURL node:"); gst_xml_helper_get_prop_string (a_node, "media", &new_segment_url->media); gst_xml_helper_get_prop_range (a_node, "mediaRange", &new_segment_url->mediaRange); gst_xml_helper_get_prop_string (a_node, "index", &new_segment_url->index); gst_xml_helper_get_prop_range (a_node, "indexRange", &new_segment_url->indexRange); } static void gst_mpdparser_parse_url_type_node (GstMPDURLTypeNode ** pointer, xmlNode * a_node) { GstMPDURLTypeNode *new_url_type; gst_mpd_url_type_node_free (*pointer); *pointer = new_url_type = gst_mpd_url_type_node_new ((const gchar *) a_node->name); GST_LOG ("attributes of URLType node:"); gst_xml_helper_get_prop_string (a_node, "sourceURL", &new_url_type->sourceURL); gst_xml_helper_get_prop_range (a_node, "range", &new_url_type->range); } static void gst_mpdparser_parse_seg_base_type_ext (GstMPDSegmentBaseNode ** pointer, xmlNode * a_node, GstMPDSegmentBaseNode * parent) { xmlNode *cur_node; GstMPDSegmentBaseNode *seg_base_type; guint intval; guint64 int64val; gboolean boolval; GstXMLRange *rangeval; gst_mpd_segment_base_node_free (*pointer); *pointer = seg_base_type = gst_mpd_segment_base_node_new (); /* 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_xml_helper_clone_range (parent->indexRange); seg_base_type->indexRangeExact = parent->indexRangeExact; seg_base_type->Initialization = gst_mpd_url_type_node_clone (parent->Initialization); seg_base_type->RepresentationIndex = gst_mpd_url_type_node_clone (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_xml_helper_get_prop_unsigned_integer (a_node, "timescale", 1, &intval)) { seg_base_type->timescale = intval; } if (gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "presentationTimeOffset", 0, &int64val)) { seg_base_type->presentationTimeOffset = int64val; } if (gst_xml_helper_get_prop_range (a_node, "indexRange", &rangeval)) { if (seg_base_type->indexRange) { g_free (seg_base_type->indexRange); } seg_base_type->indexRange = rangeval; } if (gst_xml_helper_get_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 void gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node) { GstMPDSNode *new_s_node; new_s_node = gst_mpd_s_node_new (); g_queue_push_tail (queue, new_s_node); GST_LOG ("attributes of S node:"); gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "t", 0, &new_s_node->t); gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "d", 0, &new_s_node->d); gst_xml_helper_get_prop_signed_integer (a_node, "r", 0, &new_s_node->r); } static void gst_mpdparser_parse_segment_timeline_node (GstMPDSegmentTimelineNode ** pointer, xmlNode * a_node) { xmlNode *cur_node; GstMPDSegmentTimelineNode *new_seg_timeline; gst_mpd_segment_timeline_node_free (*pointer); *pointer = new_seg_timeline = gst_mpd_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_node (GstMPDMultSegmentBaseNode * mult_seg_base_node, xmlNode * a_node, GstMPDMultSegmentBaseNode * parent) { xmlNode *cur_node; guint intval; gboolean has_timeline = FALSE, has_duration = FALSE; mult_seg_base_node->duration = 0; mult_seg_base_node->startNumber = 1; /* Inherit attribute values from parent */ if (parent) { mult_seg_base_node->duration = parent->duration; mult_seg_base_node->startNumber = parent->startNumber; mult_seg_base_node->SegmentTimeline = gst_mpd_segment_timeline_node_clone (parent->SegmentTimeline); mult_seg_base_node->BitstreamSwitching = gst_mpd_url_type_node_clone (parent->BitstreamSwitching); } GST_LOG ("attributes of MultipleSegmentBaseType extension:"); if (gst_xml_helper_get_prop_unsigned_integer (a_node, "duration", 0, &intval)) { mult_seg_base_node->duration = intval; } /* duration might be specified from parent */ if (mult_seg_base_node->duration) has_duration = TRUE; if (gst_xml_helper_get_prop_unsigned_integer (a_node, "startNumber", 1, &intval)) { mult_seg_base_node->startNumber = intval; } GST_LOG ("extension of MultipleSegmentBaseType extension:"); gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_node->SegmentBase, a_node, (parent ? parent->SegmentBase : 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_node->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_node->BitstreamSwitching, cur_node); } } } has_timeline = mult_seg_base_node->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"); } return TRUE; } static gboolean gst_mpdparser_parse_segment_list_node (GstMPDSegmentListNode ** pointer, xmlNode * a_node, GstMPDSegmentListNode * parent) { xmlNode *cur_node; GstMPDSegmentListNode *new_segment_list; gchar *actuate; gboolean segment_urls_inherited_from_parent = FALSE; gst_mpd_segment_list_node_free (*pointer); new_segment_list = gst_mpd_segment_list_node_new (); /* Inherit attribute values from parent */ if (parent) { GList *list; GstMPDSegmentURLNode *seg_url; for (list = g_list_first (parent->SegmentURL); list; list = g_list_next (list)) { seg_url = (GstMPDSegmentURLNode *) list->data; new_segment_list->SegmentURL = g_list_append (new_segment_list->SegmentURL, gst_mpd_segment_url_node_clone (seg_url)); segment_urls_inherited_from_parent = TRUE; } } new_segment_list->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST; if (gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href) && gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "actuate", &actuate)) { if (strcmp (actuate, GST_MPD_XLINK_ACTUATE_ON_LOAD_STR) == 0) new_segment_list->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD; xmlFree (actuate); } GST_LOG ("extension of SegmentList node:"); if (!gst_mpdparser_parse_mult_seg_base_node (GST_MPD_MULT_SEGMENT_BASE_NODE (new_segment_list), a_node, (parent ? GST_MPD_MULT_SEGMENT_BASE_NODE (parent) : 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_mpd_segment_url_node_free); 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_mpd_segment_list_node_free (new_segment_list); return FALSE; } static void gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node) { gchar *value = NULL; if (gst_xml_helper_get_prop_string (a_node, "value", &value)) { if (!g_strcmp0 (value, "MSPR 2.0")) { xmlNode *cur_node; for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) { GstMPDDescriptorTypeNode *new_descriptor; new_descriptor = gst_mpd_descriptor_type_node_new ((const gchar *) cur_node->name); *list = g_list_append (*list, new_descriptor); gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri", &new_descriptor->schemeIdUri); gst_xml_helper_get_node_content (cur_node, &new_descriptor->value); goto beach; } } } } else { gst_mpdparser_parse_descriptor_type (list, a_node); } } else { gst_mpdparser_parse_descriptor_type (list, a_node); } beach: if (value) g_free (value); } static void gst_mpdparser_parse_representation_base (GstMPDRepresentationBaseNode * representation_base, xmlNode * a_node) { xmlNode *cur_node; GST_LOG ("attributes of RepresentationBaseType extension:"); gst_xml_helper_get_prop_string (a_node, "profiles", &representation_base->profiles); gst_xml_helper_get_prop_unsigned_integer (a_node, "width", 0, &representation_base->width); gst_xml_helper_get_prop_unsigned_integer (a_node, "height", 0, &representation_base->height); gst_xml_helper_get_prop_ratio (a_node, "sar", &representation_base->sar); gst_xml_helper_get_prop_framerate (a_node, "frameRate", &representation_base->frameRate); gst_xml_helper_get_prop_framerate (a_node, "minFrameRate", &representation_base->minFrameRate); gst_xml_helper_get_prop_framerate (a_node, "maxFrameRate", &representation_base->maxFrameRate); gst_xml_helper_get_prop_string (a_node, "audioSamplingRate", &representation_base->audioSamplingRate); gst_xml_helper_get_prop_string (a_node, "mimeType", &representation_base->mimeType); gst_xml_helper_get_prop_string (a_node, "segmentProfiles", &representation_base->segmentProfiles); gst_xml_helper_get_prop_string (a_node, "codecs", &representation_base->codecs); if (representation_base->codecs) { GST_DEBUG ("Getting caps "); representation_base->caps = gst_codec_utils_caps_from_mime_codec (representation_base->codecs); } else { representation_base->caps = gst_mpd_helper_mimetype_to_codec_caps (representation_base->mimeType); GST_DEBUG ("Getting caps from mime type gave %" GST_PTR_FORMAT, representation_base->caps); } gst_xml_helper_get_prop_double (a_node, "maximumSAPPeriod", &representation_base->maximumSAPPeriod); gst_mpd_helper_get_SAP_type (a_node, "startWithSAP", &representation_base->startWithSAP); gst_xml_helper_get_prop_double (a_node, "maxPlayoutRate", &representation_base->maxPlayoutRate); gst_xml_helper_get_prop_boolean (a_node, "codingDependency", FALSE, &representation_base->codingDependency); gst_xml_helper_get_prop_string (a_node, "scanType", &representation_base->scanType); /* explore children nodes */ for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) { gst_mpdparser_parse_descriptor_type (&representation_base->FramePacking, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "AudioChannelConfiguration") == 0) { gst_mpdparser_parse_descriptor_type (&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, GstMPDAdaptationSetNode * parent, GstMPDPeriodNode * period_node) { xmlNode *cur_node; GstMPDRepresentationNode *new_representation; new_representation = gst_mpd_representation_node_new (); GST_LOG ("attributes of Representation node:"); if (!gst_xml_helper_get_prop_string (a_node, "id", &new_representation->id)) { GST_ERROR ("Cannot parse Representation id, invalid manifest"); goto error; } if (!gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0, &new_representation->bandwidth)) { GST_ERROR ("Cannot parse Representation bandwidth, invalid manifest"); goto error; } gst_xml_helper_get_prop_unsigned_integer (a_node, "qualityRanking", 0, &new_representation->qualityRanking); gst_xml_helper_get_prop_string_vector_type (a_node, "dependencyId", &new_representation->dependencyId); gst_xml_helper_get_prop_string_vector_type (a_node, "mediaStreamStructureId", &new_representation->mediaStreamStructureId); /* RepresentationBase extension */ gst_mpdparser_parse_representation_base (GST_MPD_REPRESENTATION_BASE_NODE (new_representation), 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_mpd_representation_node_free (new_representation); return FALSE; } static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node, GstMPDPeriodNode * parent) { xmlNode *cur_node; GstMPDAdaptationSetNode *new_adap_set; gchar *actuate; new_adap_set = gst_mpd_adaptation_set_node_new (); GST_LOG ("attributes of AdaptationSet node:"); new_adap_set->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST; if (gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "href", &new_adap_set->xlink_href) && gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "actuate", &actuate)) { if (strcmp (actuate, "onLoad") == 0) new_adap_set->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD; xmlFree (actuate); } gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, &new_adap_set->id); gst_xml_helper_get_prop_unsigned_integer (a_node, "group", 0, &new_adap_set->group); gst_xml_helper_get_prop_string (a_node, "lang", &new_adap_set->lang); gst_xml_helper_get_prop_string (a_node, "contentType", &new_adap_set->contentType); gst_xml_helper_get_prop_ratio (a_node, "par", &new_adap_set->par); gst_xml_helper_get_prop_unsigned_integer (a_node, "minBandwidth", 0, &new_adap_set->minBandwidth); gst_xml_helper_get_prop_unsigned_integer (a_node, "maxBandwidth", 0, &new_adap_set->maxBandwidth); gst_xml_helper_get_prop_unsigned_integer (a_node, "minWidth", 0, &new_adap_set->minWidth); gst_xml_helper_get_prop_unsigned_integer (a_node, "maxWidth", 0, &new_adap_set->maxWidth); gst_xml_helper_get_prop_unsigned_integer (a_node, "minHeight", 0, &new_adap_set->minHeight); gst_xml_helper_get_prop_unsigned_integer (a_node, "maxHeight", 0, &new_adap_set->maxHeight); gst_xml_helper_get_prop_cond_uint (a_node, "segmentAlignment", &new_adap_set->segmentAlignment); gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching", parent->bitstreamSwitching, &new_adap_set->bitstreamSwitching); if (parent->bitstreamSwitching && !new_adap_set->bitstreamSwitching) { /* according to the standard, if the Period's bitstreamSwitching attribute * 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_xml_helper_get_prop_cond_uint (a_node, "subsegmentAlignment", &new_adap_set->subsegmentAlignment); gst_mpd_helper_get_SAP_type (a_node, "subsegmentStartsWithSAP", &new_adap_set->subsegmentStartsWithSAP); /* RepresentationBase extension */ gst_mpdparser_parse_representation_base (GST_MPD_REPRESENTATION_BASE_NODE (new_adap_set), 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 (&new_adap_set->Accessibility, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) { gst_mpdparser_parse_descriptor_type (&new_adap_set->Role, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) { gst_mpdparser_parse_descriptor_type (&new_adap_set->Rating, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) { gst_mpdparser_parse_descriptor_type (&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_mpd_adaptation_set_node_free (new_adap_set); return FALSE; } static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node) { GstMPDSubsetNode *new_subset; new_subset = gst_mpd_subset_node_new (); *list = g_list_append (*list, new_subset); GST_LOG ("attributes of Subset node:"); gst_xml_helper_get_prop_uint_vector_type (a_node, "contains", &new_subset->contains, &new_subset->contains_size); } static gboolean gst_mpdparser_parse_segment_template_node (GstMPDSegmentTemplateNode ** pointer, xmlNode * a_node, GstMPDSegmentTemplateNode * parent) { GstMPDSegmentTemplateNode *new_segment_template; gchar *strval; gst_mpd_segment_template_node_free (*pointer); new_segment_template = gst_mpd_segment_template_node_new (); GST_LOG ("extension of SegmentTemplate node:"); if (!gst_mpdparser_parse_mult_seg_base_node (GST_MPD_MULT_SEGMENT_BASE_NODE (new_segment_template), a_node, (parent ? GST_MPD_MULT_SEGMENT_BASE_NODE (parent) : NULL))) goto error; /* Inherit attribute values from parent when the value isn't found */ GST_LOG ("attributes of SegmentTemplate node:"); if (gst_xml_helper_get_prop_string (a_node, "media", &strval)) { new_segment_template->media = strval; } else if (parent) { new_segment_template->media = xmlMemStrdup (parent->media); } if (gst_xml_helper_get_prop_string (a_node, "index", &strval)) { new_segment_template->index = strval; } else if (parent) { new_segment_template->index = xmlMemStrdup (parent->index); } if (gst_xml_helper_get_prop_string (a_node, "initialization", &strval)) { new_segment_template->initialization = strval; } else if (parent) { new_segment_template->initialization = xmlMemStrdup (parent->initialization); } if (gst_xml_helper_get_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_mpd_segment_template_node_free (new_segment_template); return FALSE; } static gboolean gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstMPDPeriodNode *new_period; gchar *actuate; new_period = gst_mpd_period_node_new (); GST_LOG ("attributes of Period node:"); new_period->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST; if (gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "href", &new_period->xlink_href) && gst_xml_helper_get_ns_prop_string (a_node, "http://www.w3.org/1999/xlink", "actuate", &actuate)) { if (strcmp (actuate, "onLoad") == 0) new_period->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD; xmlFree (actuate); } gst_xml_helper_get_prop_string (a_node, "id", &new_period->id); gst_xml_helper_get_prop_duration (a_node, "start", GST_MPD_DURATION_NONE, &new_period->start); gst_xml_helper_get_prop_duration (a_node, "duration", GST_MPD_DURATION_NONE, &new_period->duration); gst_xml_helper_get_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_mpd_period_node_free (new_period); return FALSE; } static void gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node) { xmlNode *cur_node; GstMPDProgramInformationNode *new_prog_info; new_prog_info = gst_mpd_program_information_node_new (); *list = g_list_append (*list, new_prog_info); GST_LOG ("attributes of ProgramInformation node:"); gst_xml_helper_get_prop_string (a_node, "lang", &new_prog_info->lang); gst_xml_helper_get_prop_string (a_node, "moreInformationURL", &new_prog_info->moreInformationURL); /* explore children nodes */ 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_xml_helper_get_node_content (cur_node, &new_prog_info->Title); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) { gst_xml_helper_get_node_content (cur_node, &new_prog_info->Source); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) { gst_xml_helper_get_node_content (cur_node, &new_prog_info->Copyright); } } } } static void gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node) { GstMPDMetricsRangeNode *new_metrics_range; new_metrics_range = gst_mpd_metrics_range_node_new (); *list = g_list_append (*list, new_metrics_range); GST_LOG ("attributes of Metrics Range node:"); gst_xml_helper_get_prop_duration (a_node, "starttime", GST_MPD_DURATION_NONE, &new_metrics_range->starttime); gst_xml_helper_get_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; GstMPDMetricsNode *new_metrics; new_metrics = gst_mpd_metrics_node_new (); *list = g_list_append (*list, new_metrics); GST_LOG ("attributes of Metrics node:"); gst_xml_helper_get_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) { GstMPDUTCTimingNode *new_timing; gchar *method = NULL; gchar *value = NULL; new_timing = gst_mpd_utctiming_node_new (); GST_LOG ("attributes of UTCTiming node:"); if (gst_xml_helper_get_prop_string (a_node, "schemeIdUri", &method)) { new_timing->method = gst_mpd_utctiming_get_method (method); xmlFree (method); } if (gst_xml_helper_get_prop_string (a_node, "value", &value)) { int max_tokens = 0; if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) { /* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case * 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_mpd_utctiming_node_free (new_timing); } } static gboolean gst_mpdparser_parse_root_node (GstMPDRootNode ** pointer, xmlNode * a_node) { xmlNode *cur_node; GstMPDRootNode *new_mpd_root; gst_mpd_root_node_free (*pointer); *pointer = NULL; new_mpd_root = gst_mpd_root_node_new (); GST_LOG ("namespaces of root MPD node:"); new_mpd_root->default_namespace = gst_xml_helper_get_node_namespace (a_node, NULL); new_mpd_root->namespace_xsi = gst_xml_helper_get_node_namespace (a_node, "xsi"); new_mpd_root->namespace_ext = gst_xml_helper_get_node_namespace (a_node, "ext"); GST_LOG ("attributes of root MPD node:"); gst_xml_helper_get_prop_string (a_node, "schemaLocation", &new_mpd_root->schemaLocation); gst_xml_helper_get_prop_string (a_node, "id", &new_mpd_root->id); gst_xml_helper_get_prop_string (a_node, "profiles", &new_mpd_root->profiles); gst_mpd_helper_get_mpd_type (a_node, "type", &new_mpd_root->type); gst_xml_helper_get_prop_dateTime (a_node, "availabilityStartTime", &new_mpd_root->availabilityStartTime); gst_xml_helper_get_prop_dateTime (a_node, "availabilityEndTime", &new_mpd_root->availabilityEndTime); gst_xml_helper_get_prop_duration (a_node, "mediaPresentationDuration", GST_MPD_DURATION_NONE, &new_mpd_root->mediaPresentationDuration); gst_xml_helper_get_prop_duration (a_node, "minimumUpdatePeriod", GST_MPD_DURATION_NONE, &new_mpd_root->minimumUpdatePeriod); gst_xml_helper_get_prop_duration (a_node, "minBufferTime", GST_MPD_DURATION_NONE, &new_mpd_root->minBufferTime); gst_xml_helper_get_prop_duration (a_node, "timeShiftBufferDepth", GST_MPD_DURATION_NONE, &new_mpd_root->timeShiftBufferDepth); gst_xml_helper_get_prop_duration (a_node, "suggestedPresentationDelay", GST_MPD_DURATION_NONE, &new_mpd_root->suggestedPresentationDelay); gst_xml_helper_get_prop_duration (a_node, "maxSegmentDuration", GST_MPD_DURATION_NONE, &new_mpd_root->maxSegmentDuration); gst_xml_helper_get_prop_duration (a_node, "maxSubsegmentDuration", GST_MPD_DURATION_NONE, &new_mpd_root->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_root->Periods, cur_node)) goto error; } else if (xmlStrcmp (cur_node->name, (xmlChar *) "ProgramInformation") == 0) { gst_mpdparser_parse_program_info_node (&new_mpd_root->ProgramInfos, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) { gst_mpdparser_parse_baseURL_node (&new_mpd_root->BaseURLs, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Location") == 0) { gst_mpdparser_parse_location_node (&new_mpd_root->Locations, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) { gst_mpdparser_parse_metrics_node (&new_mpd_root->Metrics, cur_node); } else if (xmlStrcmp (cur_node->name, (xmlChar *) "UTCTiming") == 0) { gst_mpdparser_parse_utctiming_node (&new_mpd_root->UTCTimings, cur_node); } } } *pointer = new_mpd_root; return TRUE; error: gst_mpd_root_node_free (new_mpd_root); return FALSE; } /* internal memory management functions */ /* 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; } void gst_mpdparser_media_fragment_info_clear (GstMediaFragmentInfo * fragment) { g_free (fragment->uri); g_free (fragment->index_uri); } /* API */ gboolean gst_mpdparser_get_mpd_root_node (GstMPDRootNode ** mpd_root_node, 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 (mpd_root_node, root_element); } /* free the document */ xmlFreeDoc (doc); } } return ret; } GstMPDSegmentListNode * gst_mpdparser_get_external_segment_list (const gchar * data, gint size, GstMPDSegmentListNode * parent) { xmlDocPtr doc = NULL; GstMPDSegmentListNode *new_segment_list = NULL; doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); /* 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) { gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element, parent); } } if (doc) xmlFreeDoc (doc); return new_segment_list; } GList * gst_mpdparser_get_external_periods (const gchar * data, gint size) { xmlDocPtr doc = NULL; GList *new_periods = NULL; doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); if (doc) { xmlNode *root_element = xmlDocGetRootElement (doc); xmlNode *iter; 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; } } } } 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_mpd_period_node_free); new_periods = NULL; } goto done; } GList * gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size, GstMPDPeriodNode * period) { xmlDocPtr doc = NULL; GList *new_adaptation_sets = NULL; doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET); /* NOTE: ISO/IEC 23009-1:2014 5.3.3.2 is saying that exactly one AdaptationSet * in external xml is allowed */ if (doc) { xmlNode *root_element = xmlDocGetRootElement (doc); if (root_element->type == XML_ELEMENT_NODE && xmlStrcmp (root_element->name, (xmlChar *) "AdaptationSet") == 0) { gst_mpdparser_parse_adaptation_set_node (&new_adaptation_sets, root_element, period); } } if (doc) xmlFreeDoc (doc); return new_adaptation_sets; } void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period) { if (stream_period) { g_free (stream_period); } } void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment) { if (media_segment) { g_free (media_segment); } } 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); } 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_free (active_stream); } } static gchar * get_base_url_with_query (GstActiveStream * stream) { GstUri *uri; gchar *uri_str; if (!stream->queryURL) return g_strdup (stream->baseURL); uri = gst_uri_from_string (stream->baseURL); gst_uri_set_query_string (uri, stream->queryURL); uri_str = gst_uri_to_string (uri); gst_uri_unref (uri); return uri_str; } /* * gst_mpdparser_get_initializationURL: * * Returns: (transfer full): stream initializationURL if available, * baseURL combined with queryURL otherwise. */ gchar * gst_mpdparser_get_initializationURL (GstActiveStream * stream, GstMPDURLTypeNode * InitializationURL) { g_return_val_if_fail (stream != NULL, NULL); return (InitializationURL && InitializationURL->sourceURL) ? g_strdup (InitializationURL->sourceURL) : get_base_url_with_query (stream); } /* * gst_mpdparser_get_mediaURL: * * Returns: (transfer full): stream mediaURL if available, * baseURL combined with queryURL otherwise. */ gchar * gst_mpdparser_get_mediaURL (GstActiveStream * stream, GstMPDSegmentURLNode * segmentURL) { g_return_val_if_fail (stream != NULL, NULL); g_return_val_if_fail (segmentURL != NULL, NULL); return (segmentURL->media) ? g_strdup (segmentURL->media) : get_base_url_with_query (stream); } /* navigation functions */ GstStreamMimeType gst_mpdparser_representation_get_mimetype (GstMPDAdaptationSetNode * adapt_set, GstMPDRepresentationNode * rep) { gchar *mime = NULL; if (rep) mime = GST_MPD_REPRESENTATION_BASE_NODE (rep)->mimeType; if (mime == NULL) { mime = GST_MPD_REPRESENTATION_BASE_NODE (adapt_set)->mimeType; } if (gst_mpd_helper_strncmp_ext (mime, "audio") == 0) return GST_STREAM_AUDIO; if (gst_mpd_helper_strncmp_ext (mime, "video") == 0) return GST_STREAM_VIDEO; if (gst_mpd_helper_strncmp_ext (mime, "application") == 0 || gst_mpd_helper_strncmp_ext (mime, "text") == 0) return GST_STREAM_APPLICATION; return GST_STREAM_UNKNOWN; } /* Helper methods */ 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; } }