| /* |
| * DASH MPD parsing library |
| * |
| * gstmpdparser.c |
| * |
| * Copyright (C) 2012 STMicroelectronics |
| * |
| * Authors: |
| * Gianluca Gennari <gennarone@gmail.com> |
| * |
| * 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 <string.h> |
| #include <libxml/parser.h> |
| #include <libxml/tree.h> |
| #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, |
|