blob: 15d6d98894d1439a61e14d70461b7693af6f5bfe [file] [log] [blame]
/*
* 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, gboolean * error);
static GList *gst_mpd_client_fetch_external_adaptation_set (GstMpdClient *
client, GstPeriodNode * period, GstAdaptationSetNode * adapt_set,
gboolean * error);
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 dateTime is specified in the following form "YYYY-MM-DDThh:mm:ss" where:
* 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
Note: All components are required!
*/
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;
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);
exists = TRUE;
*property_value =
gst_date_time_new (0, 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;
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);
has_timeline = TRUE;
} 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;
if (!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_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_descriptor_type_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:");
gst_mpdparser_get_xml_prop_string_no_whitespace (a_node, "id",
&new_representation->id);
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0,
&new_representation->bandwidth);
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);
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
if (!gst_mpdparser_parse_segment_template_node
(&new_representation->SegmentTemplate, cur_node,
parent->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)) {
for (int i = 0; gst_mpdparser_utc_timing_methods[i].name; ++i) {
if (g_ascii_strncasecmp (gst_mpdparser_utc_timing_methods[i].name,
method, strlen (gst_mpdparser_utc_timing_methods[i].name)) == 0) {
new_timing->method = gst_mpdparser_utc_timing_methods[i].method;
break;
}
}
xmlFree (method);
}
if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) {
int max_tokens = 0;
if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) {
/* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case
* that is not a space separated list.
*/
max_tokens = 1;
}
new_timing->urls = g_strsplit (value, " ", max_tokens);
xmlFree (value);
}
/* 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);
}
}
}
gst_mpdparser_free_mpd_node (*pointer);
*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)
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,
gint 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 = %i", 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,
gboolean * error)
{
GstFragment *download;
GstBuffer *segment_list_buffer;
GstMapInfo map;
GError *err = NULL;
xmlDocPtr doc;
GstUri *base_uri, *uri;
gchar *query = NULL;
gchar *uri_string;
GstSegmentListNode *new_segment_list = NULL;
*error = FALSE;
/* 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) {
*error = TRUE;
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) {