mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 08:11:16 +00:00
86b251b7d1
provide a separate namespace for mpd helper for xml parsing and the real mpd parsing.
2159 lines
73 KiB
C
2159 lines
73 KiB
C
/*
|
|
* 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 "gstmpdparser.h"
|
|
#include "gstdash_debug.h"
|
|
|
|
#define GST_CAT_DEFAULT gst_dash_demux_debug
|
|
|
|
|
|
/* 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);
|
|
|
|
static GstSNode *gst_mpdparser_clone_s_node (GstSNode * pointer);
|
|
static GstSegmentTimelineNode
|
|
* gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer);
|
|
|
|
static GstURLType *gst_mpdparser_clone_URL (GstURLType * url);
|
|
static GstSegmentURLNode *gst_mpdparser_clone_segment_url (GstSegmentURLNode *
|
|
seg_url);
|
|
|
|
/* Memory management */
|
|
static GstSegmentTimelineNode *gst_mpdparser_segment_timeline_node_new (void);
|
|
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_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_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_url_node (GstSegmentURLNode *
|
|
segment_url);
|
|
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);
|
|
|
|
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}
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
Duration Data Type
|
|
|
|
The duration data type is used to specify a time interval.
|
|
|
|
The time interval is specified in the following form "-PnYnMnDTnHnMnS" where:
|
|
|
|
* - indicates the negative sign (optional)
|
|
* P indicates the period (required)
|
|
* nY indicates the number of years
|
|
* nM indicates the number of months
|
|
* nD indicates the number of days
|
|
* T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds)
|
|
* nH indicates the number of hours
|
|
* nM indicates the number of minutes
|
|
* nS indicates the number of seconds
|
|
*/
|
|
|
|
|
|
|
|
|
|
static void
|
|
gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
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_xml_helper_get_node_content (a_node, &new_base_url->baseURL);
|
|
|
|
GST_LOG ("attributes of BaseURL node:");
|
|
gst_xml_helper_get_prop_string (a_node, "serviceLocation",
|
|
&new_base_url->serviceLocation);
|
|
gst_xml_helper_get_prop_string (a_node, "byteRange",
|
|
&new_base_url->byteRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_descriptor_type_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_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
if (!gst_xml_helper_get_prop_string (a_node, "value", &new_descriptor->value)) {
|
|
/* if no value attribute, use XML string representation of the node */
|
|
gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
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_xml_helper_get_prop_unsigned_integer (a_node, "id", 0,
|
|
&new_content_component->id);
|
|
gst_xml_helper_get_prop_string (a_node, "lang", &new_content_component->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "contentType",
|
|
&new_content_component->contentType);
|
|
gst_xml_helper_get_prop_ratio (a_node, "par", &new_content_component->par);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_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_xml_helper_get_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_xml_helper_get_prop_unsigned_integer (a_node, "level", 0,
|
|
&new_subrep->level);
|
|
gst_xml_helper_get_prop_uint_vector_type (a_node, "dependencyLevel",
|
|
&new_subrep->dependencyLevel, &new_subrep->size);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0,
|
|
&new_subrep->bandwidth);
|
|
gst_xml_helper_get_prop_string_vector_type (a_node,
|
|
"contentComponent", &new_subrep->contentComponent);
|
|
|
|
/* 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_xml_helper_clone_range (seg_url->mediaRange);
|
|
clone->index = xmlMemStrdup (seg_url->index);
|
|
clone->indexRange = gst_xml_helper_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_xml_helper_get_prop_string (a_node, "media", &new_segment_url->media);
|
|
gst_xml_helper_get_prop_range (a_node, "mediaRange",
|
|
&new_segment_url->mediaRange);
|
|
gst_xml_helper_get_prop_string (a_node, "index", &new_segment_url->index);
|
|
gst_xml_helper_get_prop_range (a_node, "indexRange",
|
|
&new_segment_url->indexRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_url_type_node (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_xml_helper_get_prop_string (a_node, "sourceURL",
|
|
&new_url_type->sourceURL);
|
|
gst_xml_helper_get_prop_range (a_node, "range", &new_url_type->range);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer,
|
|
xmlNode * a_node, GstSegmentBaseType * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstSegmentBaseType *seg_base_type;
|
|
guint intval;
|
|
guint64 int64val;
|
|
gboolean boolval;
|
|
GstXMLRange *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_xml_helper_clone_range (parent->indexRange);
|
|
seg_base_type->indexRangeExact = parent->indexRangeExact;
|
|
seg_base_type->Initialization =
|
|
gst_mpdparser_clone_URL (parent->Initialization);
|
|
seg_base_type->RepresentationIndex =
|
|
gst_mpdparser_clone_URL (parent->RepresentationIndex);
|
|
}
|
|
|
|
/* We must retrieve each value first to see if it exists. If it does not
|
|
* exist, we do not want to overwrite an inherited value */
|
|
GST_LOG ("attributes of SegmentBaseType extension:");
|
|
if (gst_xml_helper_get_prop_unsigned_integer (a_node, "timescale", 1,
|
|
&intval)) {
|
|
seg_base_type->timescale = intval;
|
|
}
|
|
if (gst_xml_helper_get_prop_unsigned_integer_64 (a_node,
|
|
"presentationTimeOffset", 0, &int64val)) {
|
|
seg_base_type->presentationTimeOffset = int64val;
|
|
}
|
|
if (gst_xml_helper_get_prop_range (a_node, "indexRange", &rangeval)) {
|
|
if (seg_base_type->indexRange) {
|
|
g_slice_free (GstXMLRange, seg_base_type->indexRange);
|
|
}
|
|
seg_base_type->indexRange = rangeval;
|
|
}
|
|
if (gst_xml_helper_get_prop_boolean (a_node, "indexRangeExact",
|
|
FALSE, &boolval)) {
|
|
seg_base_type->indexRangeExact = boolval;
|
|
}
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Initialization") == 0 ||
|
|
xmlStrcmp (cur_node->name, (xmlChar *) "Initialisation") == 0) {
|
|
/* parse will free the previous pointer to create a new one */
|
|
gst_mpdparser_parse_url_type_node (&seg_base_type->Initialization,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "RepresentationIndex") == 0) {
|
|
/* parse will free the previous pointer to create a new one */
|
|
gst_mpdparser_parse_url_type_node (&seg_base_type->RepresentationIndex,
|
|
cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static 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_xml_helper_get_prop_unsigned_integer_64 (a_node, "t", 0, &new_s_node->t);
|
|
gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "d", 0, &new_s_node->d);
|
|
gst_xml_helper_get_prop_signed_integer (a_node, "r", 0, &new_s_node->r);
|
|
}
|
|
|
|
static 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_xml_helper_get_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_xml_helper_get_prop_unsigned_integer (a_node, "startNumber", 1,
|
|
&intval)) {
|
|
mult_seg_base_type->startNumber = intval;
|
|
}
|
|
|
|
GST_LOG ("extension of MultipleSegmentBaseType extension:");
|
|
gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_type->SegBaseType,
|
|
a_node, (parent ? parent->SegBaseType : NULL));
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "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_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_segment_list->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
GST_LOG ("extension of SegmentList node:");
|
|
if (!gst_mpdparser_parse_mult_seg_base_type_ext
|
|
(&new_segment_list->MultSegBaseType, a_node,
|
|
(parent ? parent->MultSegBaseType : NULL)))
|
|
goto error;
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "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_xml_helper_get_prop_string (a_node, "value", &value)) {
|
|
if (!g_strcmp0 (value, "MSPR 2.0")) {
|
|
xmlNode *cur_node;
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) {
|
|
GstDescriptorType *new_descriptor;
|
|
new_descriptor = g_slice_new0 (GstDescriptorType);
|
|
*list = g_list_append (*list, new_descriptor);
|
|
|
|
gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
|
|
gst_xml_helper_get_node_content (cur_node, &new_descriptor->value);
|
|
goto beach;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
gst_mpdparser_parse_descriptor_type_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_xml_helper_get_prop_string (a_node, "profiles",
|
|
&representation_base->profiles);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "width", 0,
|
|
&representation_base->width);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "height", 0,
|
|
&representation_base->height);
|
|
gst_xml_helper_get_prop_ratio (a_node, "sar", &representation_base->sar);
|
|
gst_xml_helper_get_prop_framerate (a_node, "frameRate",
|
|
&representation_base->frameRate);
|
|
gst_xml_helper_get_prop_framerate (a_node, "minFrameRate",
|
|
&representation_base->minFrameRate);
|
|
gst_xml_helper_get_prop_framerate (a_node, "maxFrameRate",
|
|
&representation_base->maxFrameRate);
|
|
gst_xml_helper_get_prop_string (a_node, "audioSamplingRate",
|
|
&representation_base->audioSamplingRate);
|
|
gst_xml_helper_get_prop_string (a_node, "mimeType",
|
|
&representation_base->mimeType);
|
|
gst_xml_helper_get_prop_string (a_node, "segmentProfiles",
|
|
&representation_base->segmentProfiles);
|
|
gst_xml_helper_get_prop_string (a_node, "codecs",
|
|
&representation_base->codecs);
|
|
gst_xml_helper_get_prop_double (a_node, "maximumSAPPeriod",
|
|
&representation_base->maximumSAPPeriod);
|
|
gst_mpd_helper_get_SAP_type (a_node, "startWithSAP",
|
|
&representation_base->startWithSAP);
|
|
gst_xml_helper_get_prop_double (a_node, "maxPlayoutRate",
|
|
&representation_base->maxPlayoutRate);
|
|
gst_xml_helper_get_prop_boolean (a_node, "codingDependency",
|
|
FALSE, &representation_base->codingDependency);
|
|
gst_xml_helper_get_prop_string (a_node, "scanType",
|
|
&representation_base->scanType);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "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_xml_helper_get_prop_string_no_whitespace (a_node, "id",
|
|
&new_representation->id)) {
|
|
GST_ERROR ("Cannot parse Representation id, invalid manifest");
|
|
goto error;
|
|
}
|
|
if (!gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0,
|
|
&new_representation->bandwidth)) {
|
|
GST_ERROR ("Cannot parse Representation bandwidth, invalid manifest");
|
|
goto error;
|
|
}
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "qualityRanking", 0,
|
|
&new_representation->qualityRanking);
|
|
gst_xml_helper_get_prop_string_vector_type (a_node, "dependencyId",
|
|
&new_representation->dependencyId);
|
|
gst_xml_helper_get_prop_string_vector_type (a_node,
|
|
"mediaStreamStructureId", &new_representation->mediaStreamStructureId);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base_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_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_adap_set->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_adap_set->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, &new_adap_set->id);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "group", 0,
|
|
&new_adap_set->group);
|
|
gst_xml_helper_get_prop_string (a_node, "lang", &new_adap_set->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "contentType",
|
|
&new_adap_set->contentType);
|
|
gst_xml_helper_get_prop_ratio (a_node, "par", &new_adap_set->par);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minBandwidth", 0,
|
|
&new_adap_set->minBandwidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxBandwidth", 0,
|
|
&new_adap_set->maxBandwidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minWidth", 0,
|
|
&new_adap_set->minWidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxWidth", 0,
|
|
&new_adap_set->maxWidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minHeight", 0,
|
|
&new_adap_set->minHeight);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxHeight", 0,
|
|
&new_adap_set->maxHeight);
|
|
gst_xml_helper_get_prop_cond_uint (a_node, "segmentAlignment",
|
|
&new_adap_set->segmentAlignment);
|
|
gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching",
|
|
parent->bitstreamSwitching, &new_adap_set->bitstreamSwitching);
|
|
if (parent->bitstreamSwitching && !new_adap_set->bitstreamSwitching) {
|
|
/* according to the standard, if the Period's bitstreamSwitching attribute
|
|
* is true, the AdaptationSet should not have the bitstreamSwitching
|
|
* attribute set to false.
|
|
* We should return a parsing error, but we are generous and ignore the
|
|
* standard violation.
|
|
*/
|
|
new_adap_set->bitstreamSwitching = parent->bitstreamSwitching;
|
|
}
|
|
gst_xml_helper_get_prop_cond_uint (a_node, "subsegmentAlignment",
|
|
&new_adap_set->subsegmentAlignment);
|
|
gst_mpd_helper_get_SAP_type (a_node, "subsegmentStartsWithSAP",
|
|
&new_adap_set->subsegmentStartsWithSAP);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base_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_xml_helper_get_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_xml_helper_get_prop_string (a_node, "media", &strval)) {
|
|
new_segment_template->media = strval;
|
|
} else if (parent) {
|
|
new_segment_template->media = xmlMemStrdup (parent->media);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "index", &strval)) {
|
|
new_segment_template->index = strval;
|
|
} else if (parent) {
|
|
new_segment_template->index = xmlMemStrdup (parent->index);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "initialization", &strval)) {
|
|
new_segment_template->initialization = strval;
|
|
} else if (parent) {
|
|
new_segment_template->initialization =
|
|
xmlMemStrdup (parent->initialization);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "bitstreamSwitching", &strval)) {
|
|
new_segment_template->bitstreamSwitching = strval;
|
|
} else if (parent) {
|
|
new_segment_template->bitstreamSwitching =
|
|
xmlMemStrdup (parent->bitstreamSwitching);
|
|
}
|
|
|
|
*pointer = new_segment_template;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_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_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_period->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_period->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_xml_helper_get_prop_string (a_node, "id", &new_period->id);
|
|
gst_xml_helper_get_prop_duration (a_node, "start", GST_MPD_DURATION_NONE,
|
|
&new_period->start);
|
|
gst_xml_helper_get_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_period->duration);
|
|
gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching", FALSE,
|
|
&new_period->bitstreamSwitching);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_period->SegmentBase,
|
|
cur_node, NULL);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node (&new_period->SegmentList,
|
|
cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_period->SegmentTemplate, cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Subset") == 0) {
|
|
gst_mpdparser_parse_subset_node (&new_period->Subsets, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_period->BaseURLs, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must parse AdaptationSet after everything else in the Period has been
|
|
* parsed because certain AdaptationSet child elements can inherit attributes
|
|
* specified by the same element in the Period
|
|
*/
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "AdaptationSet") == 0) {
|
|
if (!gst_mpdparser_parse_adaptation_set_node
|
|
(&new_period->AdaptationSets, cur_node, new_period))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
*list = g_list_append (*list, new_period);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_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_xml_helper_get_prop_string (a_node, "lang", &new_prog_info->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "moreInformationURL",
|
|
&new_prog_info->moreInformationURL);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of ProgramInformation node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Title") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Title);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Source);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Copyright);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
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_xml_helper_get_prop_duration (a_node, "starttime",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->starttime);
|
|
gst_xml_helper_get_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->duration);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMetricsNode *new_metrics;
|
|
|
|
new_metrics = g_slice_new0 (GstMetricsNode);
|
|
*list = g_list_append (*list, new_metrics);
|
|
|
|
GST_LOG ("attributes of Metrics node:");
|
|
gst_xml_helper_get_prop_string (a_node, "metrics", &new_metrics->metrics);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of Metrics node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Range") == 0) {
|
|
gst_mpdparser_parse_metrics_range_node (&new_metrics->MetricsRanges,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Reporting") == 0) {
|
|
/* No reporting scheme is specified in this part of ISO/IEC 23009.
|
|
* It is expected that external specifications may define formats
|
|
* and delivery for the reporting data. */
|
|
GST_LOG (" - Reporting node found (unknown structure)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The UTCTiming element is defined in
|
|
* ISO/IEC 23009-1:2014/PDAM 1 "Information technology — Dynamic adaptive streaming over HTTP (DASH) — Part 1: Media presentation description and segment formats / Amendment 1: High Profile and Availability Time Synchronization"
|
|
*/
|
|
static void
|
|
gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstUTCTimingNode *new_timing;
|
|
gchar *method = NULL;
|
|
gchar *value = NULL;
|
|
|
|
new_timing = g_slice_new0 (GstUTCTimingNode);
|
|
|
|
GST_LOG ("attributes of UTCTiming node:");
|
|
if (gst_xml_helper_get_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_xml_helper_get_prop_string (a_node, "value", &value)) {
|
|
int max_tokens = 0;
|
|
if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) {
|
|
/* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case
|
|
* that is not a space separated list.
|
|
*/
|
|
max_tokens = 1;
|
|
}
|
|
new_timing->urls = g_strsplit (value, " ", max_tokens);
|
|
xmlFree (value);
|
|
}
|
|
|
|
/* append to list only if both method and urls were set */
|
|
if (new_timing->method != 0 && new_timing->urls != NULL &&
|
|
g_strv_length (new_timing->urls) != 0) {
|
|
*list = g_list_append (*list, new_timing);
|
|
} else {
|
|
gst_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_xml_helper_get_node_namespace (a_node, NULL);
|
|
new_mpd->namespace_xsi = gst_xml_helper_get_node_namespace (a_node, "xsi");
|
|
new_mpd->namespace_ext = gst_xml_helper_get_node_namespace (a_node, "ext");
|
|
|
|
GST_LOG ("attributes of root MPD node:");
|
|
gst_xml_helper_get_prop_string (a_node, "schemaLocation",
|
|
&new_mpd->schemaLocation);
|
|
gst_xml_helper_get_prop_string (a_node, "id", &new_mpd->id);
|
|
gst_xml_helper_get_prop_string (a_node, "profiles", &new_mpd->profiles);
|
|
gst_mpd_helper_get_mpd_type (a_node, "type", &new_mpd->type);
|
|
gst_xml_helper_get_prop_dateTime (a_node, "availabilityStartTime",
|
|
&new_mpd->availabilityStartTime);
|
|
gst_xml_helper_get_prop_dateTime (a_node, "availabilityEndTime",
|
|
&new_mpd->availabilityEndTime);
|
|
gst_xml_helper_get_prop_duration (a_node, "mediaPresentationDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd->mediaPresentationDuration);
|
|
gst_xml_helper_get_prop_duration (a_node, "minimumUpdatePeriod",
|
|
GST_MPD_DURATION_NONE, &new_mpd->minimumUpdatePeriod);
|
|
gst_xml_helper_get_prop_duration (a_node, "minBufferTime",
|
|
GST_MPD_DURATION_NONE, &new_mpd->minBufferTime);
|
|
gst_xml_helper_get_prop_duration (a_node, "timeShiftBufferDepth",
|
|
GST_MPD_DURATION_NONE, &new_mpd->timeShiftBufferDepth);
|
|
gst_xml_helper_get_prop_duration (a_node, "suggestedPresentationDelay",
|
|
GST_MPD_DURATION_NONE, &new_mpd->suggestedPresentationDelay);
|
|
gst_xml_helper_get_prop_duration (a_node, "maxSegmentDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd->maxSegmentDuration);
|
|
gst_xml_helper_get_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;
|
|
}
|
|
|
|
/* internal memory management functions */
|
|
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_subset_node (GstSubsetNode * subset_node)
|
|
{
|
|
if (subset_node) {
|
|
if (subset_node->contains)
|
|
xmlFree (subset_node->contains);
|
|
g_slice_free (GstSubsetNode, subset_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode *
|
|
segment_template_node)
|
|
{
|
|
if (segment_template_node) {
|
|
if (segment_template_node->media)
|
|
xmlFree (segment_template_node->media);
|
|
if (segment_template_node->index)
|
|
xmlFree (segment_template_node->index);
|
|
if (segment_template_node->initialization)
|
|
xmlFree (segment_template_node->initialization);
|
|
if (segment_template_node->bitstreamSwitching)
|
|
xmlFree (segment_template_node->bitstreamSwitching);
|
|
/* MultipleSegmentBaseType extension */
|
|
gst_mpdparser_free_mult_seg_base_type_ext
|
|
(segment_template_node->MultSegBaseType);
|
|
g_slice_free (GstSegmentTemplateNode, segment_template_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_representation_base_type (GstRepresentationBaseType *
|
|
representation_base)
|
|
{
|
|
if (representation_base) {
|
|
if (representation_base->profiles)
|
|
xmlFree (representation_base->profiles);
|
|
g_slice_free (GstXMLRatio, representation_base->sar);
|
|
g_slice_free (GstXMLFrameRate, representation_base->frameRate);
|
|
g_slice_free (GstXMLFrameRate, representation_base->minFrameRate);
|
|
g_slice_free (GstXMLFrameRate, representation_base->maxFrameRate);
|
|
if (representation_base->audioSamplingRate)
|
|
xmlFree (representation_base->audioSamplingRate);
|
|
if (representation_base->mimeType)
|
|
xmlFree (representation_base->mimeType);
|
|
if (representation_base->segmentProfiles)
|
|
xmlFree (representation_base->segmentProfiles);
|
|
if (representation_base->codecs)
|
|
xmlFree (representation_base->codecs);
|
|
if (representation_base->scanType)
|
|
xmlFree (representation_base->scanType);
|
|
g_list_free_full (representation_base->FramePacking,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (representation_base->AudioChannelConfiguration,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (representation_base->ContentProtection,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_slice_free (GstRepresentationBaseType, representation_base);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_representation_node (GstRepresentationNode *
|
|
representation_node)
|
|
{
|
|
if (representation_node) {
|
|
if (representation_node->id)
|
|
xmlFree (representation_node->id);
|
|
g_strfreev (representation_node->dependencyId);
|
|
g_strfreev (representation_node->mediaStreamStructureId);
|
|
gst_mpdparser_free_representation_base_type
|
|
(representation_node->RepresentationBase);
|
|
g_list_free_full (representation_node->SubRepresentations,
|
|
(GDestroyNotify) gst_mpdparser_free_subrepresentation_node);
|
|
gst_mpdparser_free_seg_base_type_ext (representation_node->SegmentBase);
|
|
gst_mpdparser_free_segment_template_node
|
|
(representation_node->SegmentTemplate);
|
|
gst_mpdparser_free_segment_list_node (representation_node->SegmentList);
|
|
g_list_free_full (representation_node->BaseURLs,
|
|
(GDestroyNotify) gst_mpdparser_free_base_url_node);
|
|
g_slice_free (GstRepresentationNode, representation_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode *
|
|
subrep_node)
|
|
{
|
|
if (subrep_node) {
|
|
gst_mpdparser_free_representation_base_type
|
|
(subrep_node->RepresentationBase);
|
|
if (subrep_node->dependencyLevel)
|
|
xmlFree (subrep_node->dependencyLevel);
|
|
g_strfreev (subrep_node->contentComponent);
|
|
g_slice_free (GstSubRepresentationNode, subrep_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_s_node (GstSNode * s_node)
|
|
{
|
|
if (s_node) {
|
|
g_slice_free (GstSNode, s_node);
|
|
}
|
|
}
|
|
|
|
static GstSegmentTimelineNode *
|
|
gst_mpdparser_segment_timeline_node_new (void)
|
|
{
|
|
GstSegmentTimelineNode *node = g_slice_new0 (GstSegmentTimelineNode);
|
|
|
|
g_queue_init (&node->S);
|
|
|
|
return node;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode * seg_timeline)
|
|
{
|
|
if (seg_timeline) {
|
|
g_queue_foreach (&seg_timeline->S, (GFunc) gst_mpdparser_free_s_node, NULL);
|
|
g_queue_clear (&seg_timeline->S);
|
|
g_slice_free (GstSegmentTimelineNode, seg_timeline);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_url_type_node (GstURLType * url_type_node)
|
|
{
|
|
if (url_type_node) {
|
|
if (url_type_node->sourceURL)
|
|
xmlFree (url_type_node->sourceURL);
|
|
g_slice_free (GstXMLRange, url_type_node->range);
|
|
g_slice_free (GstURLType, url_type_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType * seg_base_type)
|
|
{
|
|
if (seg_base_type) {
|
|
if (seg_base_type->indexRange)
|
|
g_slice_free (GstXMLRange, seg_base_type->indexRange);
|
|
gst_mpdparser_free_url_type_node (seg_base_type->Initialization);
|
|
gst_mpdparser_free_url_type_node (seg_base_type->RepresentationIndex);
|
|
g_slice_free (GstSegmentBaseType, seg_base_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType *
|
|
mult_seg_base_type)
|
|
{
|
|
if (mult_seg_base_type) {
|
|
/* SegmentBaseType extension */
|
|
gst_mpdparser_free_seg_base_type_ext (mult_seg_base_type->SegBaseType);
|
|
gst_mpdparser_free_segment_timeline_node
|
|
(mult_seg_base_type->SegmentTimeline);
|
|
gst_mpdparser_free_url_type_node (mult_seg_base_type->BitstreamSwitching);
|
|
g_slice_free (GstMultSegmentBaseType, mult_seg_base_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_segment_url_node (GstSegmentURLNode * segment_url)
|
|
{
|
|
if (segment_url) {
|
|
if (segment_url->media)
|
|
xmlFree (segment_url->media);
|
|
g_slice_free (GstXMLRange, segment_url->mediaRange);
|
|
if (segment_url->index)
|
|
xmlFree (segment_url->index);
|
|
g_slice_free (GstXMLRange, segment_url->indexRange);
|
|
g_slice_free (GstSegmentURLNode, segment_url);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_descriptor_type_node (GstDescriptorType * descriptor_type)
|
|
{
|
|
if (descriptor_type) {
|
|
if (descriptor_type->schemeIdUri)
|
|
xmlFree (descriptor_type->schemeIdUri);
|
|
if (descriptor_type->value)
|
|
xmlFree (descriptor_type->value);
|
|
g_slice_free (GstDescriptorType, descriptor_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_content_component_node (GstContentComponentNode *
|
|
content_component_node)
|
|
{
|
|
if (content_component_node) {
|
|
if (content_component_node->lang)
|
|
xmlFree (content_component_node->lang);
|
|
if (content_component_node->contentType)
|
|
xmlFree (content_component_node->contentType);
|
|
g_slice_free (GstXMLRatio, content_component_node->par);
|
|
g_list_free_full (content_component_node->Accessibility,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (content_component_node->Role,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (content_component_node->Rating,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (content_component_node->Viewpoint,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_slice_free (GstContentComponentNode, content_component_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_utctiming_node (GstUTCTimingNode * timing_type)
|
|
{
|
|
if (timing_type) {
|
|
if (timing_type->urls)
|
|
g_strfreev (timing_type->urls);
|
|
g_slice_free (GstUTCTimingNode, timing_type);
|
|
}
|
|
}
|
|
|
|
/* ISO/IEC 23009-1:2004 5.3.9.4.4 */
|
|
static gboolean
|
|
validate_format (const gchar * format)
|
|
{
|
|
const gchar *p = format;
|
|
|
|
/* Check if it starts with % */
|
|
if (!p || p[0] != '%')
|
|
return FALSE;
|
|
p++;
|
|
|
|
/* the spec mandates a format like %0[width]d */
|
|
/* Following the %, we must have a 0 */
|
|
if (p[0] != '0')
|
|
return FALSE;
|
|
|
|
/* Following the % must be a number starting with 0
|
|
*/
|
|
while (g_ascii_isdigit (*p))
|
|
p++;
|
|
|
|
/* After any 0 and alphanumeric values, there must be a d.
|
|
*/
|
|
if (p[0] != 'd')
|
|
return FALSE;
|
|
p++;
|
|
|
|
/* And then potentially more characters without any
|
|
* further %, even if the spec does not mention this
|
|
*/
|
|
p = strchr (p, '%');
|
|
if (p)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
promote_format_to_uint64 (const gchar * format)
|
|
{
|
|
const gchar *p = format;
|
|
gchar *promoted_format;
|
|
|
|
/* Must be called with a validated format! */
|
|
g_return_val_if_fail (validate_format (format), NULL);
|
|
|
|
/* it starts with % */
|
|
p++;
|
|
|
|
/* Following the % must be a 0, or any of d, x or u.
|
|
* x and u are not part of the spec, but don't hurt us
|
|
*/
|
|
if (p[0] == '0') {
|
|
p++;
|
|
|
|
while (g_ascii_isdigit (*p))
|
|
p++;
|
|
}
|
|
|
|
/* After any 0 and alphanumeric values, there must be a d.
|
|
* Otherwise validation would have failed
|
|
*/
|
|
g_assert (p[0] == 'd');
|
|
|
|
promoted_format =
|
|
g_strdup_printf ("%.*s" G_GINT64_MODIFIER "%s", (gint) (p - format),
|
|
format, p);
|
|
|
|
return promoted_format;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_validate_rfc1738_url (const char *s)
|
|
{
|
|
while (*s) {
|
|
if (!strchr
|
|
(";:@&=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789$-_.+!*'(),%/",
|
|
*s))
|
|
return FALSE;
|
|
if (*s == '%') {
|
|
/* g_ascii_isdigit returns FALSE for NUL, and || is a short circuiting
|
|
operator, so this is safe for strings ending before two hex digits */
|
|
if (!g_ascii_isxdigit (s[1]) || !g_ascii_isxdigit (s[2]))
|
|
return FALSE;
|
|
s += 2;
|
|
}
|
|
s++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static GstURLType *
|
|
gst_mpdparser_clone_URL (GstURLType * url)
|
|
{
|
|
|
|
GstURLType *clone = NULL;
|
|
|
|
if (url) {
|
|
clone = g_slice_new0 (GstURLType);
|
|
if (url->sourceURL) {
|
|
clone->sourceURL = xmlMemStrdup (url->sourceURL);
|
|
}
|
|
clone->range = gst_xml_helper_clone_range (url->range);
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
static void
|
|
gst_media_fragment_info_clear (GstMediaFragmentInfo * fragment)
|
|
{
|
|
g_free (fragment->uri);
|
|
g_free (fragment->index_uri);
|
|
}
|
|
|
|
/* API */
|
|
gboolean
|
|
gst_mpdparser_get_mpd_node (GstMPDNode ** mpd_node, const gchar * data,
|
|
gint size)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (data) {
|
|
xmlDocPtr doc;
|
|
xmlNode *root_element = NULL;
|
|
|
|
GST_DEBUG ("MPD file fully buffered, start parsing...");
|
|
|
|
/* parse the complete MPD file into a tree (using the libxml2 default parser API) */
|
|
|
|
/* this initialize the library and check potential ABI mismatches
|
|
* between the version it was compiled for and the actual shared
|
|
* library used
|
|
*/
|
|
LIBXML_TEST_VERSION;
|
|
|
|
/* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
if (doc == NULL) {
|
|
GST_ERROR ("failed to parse the MPD file");
|
|
ret = FALSE;
|
|
} else {
|
|
/* get the root element node */
|
|
root_element = xmlDocGetRootElement (doc);
|
|
|
|
if (root_element->type != XML_ELEMENT_NODE
|
|
|| xmlStrcmp (root_element->name, (xmlChar *) "MPD") != 0) {
|
|
GST_ERROR
|
|
("can not find the root element MPD, failed to parse the MPD file");
|
|
ret = FALSE; /* used to return TRUE before, but this seems wrong */
|
|
} else {
|
|
/* now we can parse the MPD root node and all children nodes, recursively */
|
|
ret = gst_mpdparser_parse_root_node (mpd_node, root_element);
|
|
}
|
|
/* free the document */
|
|
xmlFreeDoc (doc);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GstSegmentListNode *
|
|
gst_mpdparser_get_external_segment_list (const gchar * data, gint size,
|
|
GstSegmentListNode * parent)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GstSegmentListNode *new_segment_list = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
|
|
/* NOTE: ISO/IEC 23009-1:2014 5.3.9.3.2 is saying that one or multiple SegmentList
|
|
* in external xml is allowed, however, multiple SegmentList does not make sense
|
|
* because Period/AdaptationSet/Representation allow only one SegmentList */
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
|
|
|
|
if (root_element->type == XML_ELEMENT_NODE &&
|
|
xmlStrcmp (root_element->name, (xmlChar *) "SegmentList") == 0) {
|
|
gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element,
|
|
parent);
|
|
}
|
|
}
|
|
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_segment_list;
|
|
}
|
|
|
|
GList *
|
|
gst_mpdparser_get_external_periods (const gchar * data, gint size)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GList *new_periods = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
xmlNode *iter;
|
|
|
|
for (iter = root_element->children; iter; iter = iter->next) {
|
|
if (iter->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (iter->name, (xmlChar *) "Period") == 0) {
|
|
gst_mpdparser_parse_period_node (&new_periods, iter);
|
|
} else {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_periods;
|
|
|
|
error:
|
|
GST_ERROR ("Failed to parse period node XML");
|
|
|
|
if (new_periods) {
|
|
g_list_free_full (new_periods,
|
|
(GDestroyNotify) gst_mpdparser_free_period_node);
|
|
new_periods = NULL;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
GList *
|
|
gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size,
|
|
GstPeriodNode * period)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GList *new_adaptation_sets = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
/* NOTE: ISO/IEC 23009-1:2014 5.3.3.2 is saying that exactly one AdaptationSet
|
|
* in external xml is allowed */
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
if (root_element->type == XML_ELEMENT_NODE &&
|
|
xmlStrcmp (root_element->name, (xmlChar *) "AdaptationSet") == 0) {
|
|
gst_mpdparser_parse_adaptation_set_node (&new_adaptation_sets,
|
|
root_element, period);
|
|
}
|
|
}
|
|
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_adaptation_sets;
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_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);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_period_node (GstPeriodNode * period_node)
|
|
{
|
|
if (period_node) {
|
|
if (period_node->id)
|
|
xmlFree (period_node->id);
|
|
gst_mpdparser_free_seg_base_type_ext (period_node->SegmentBase);
|
|
gst_mpdparser_free_segment_list_node (period_node->SegmentList);
|
|
gst_mpdparser_free_segment_template_node (period_node->SegmentTemplate);
|
|
g_list_free_full (period_node->AdaptationSets,
|
|
(GDestroyNotify) gst_mpdparser_free_adaptation_set_node);
|
|
g_list_free_full (period_node->Subsets,
|
|
(GDestroyNotify) gst_mpdparser_free_subset_node);
|
|
g_list_free_full (period_node->BaseURLs,
|
|
(GDestroyNotify) gst_mpdparser_free_base_url_node);
|
|
if (period_node->xlink_href)
|
|
xmlFree (period_node->xlink_href);
|
|
g_slice_free (GstPeriodNode, period_node);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode *
|
|
adaptation_set_node)
|
|
{
|
|
if (adaptation_set_node) {
|
|
if (adaptation_set_node->lang)
|
|
xmlFree (adaptation_set_node->lang);
|
|
if (adaptation_set_node->contentType)
|
|
xmlFree (adaptation_set_node->contentType);
|
|
g_slice_free (GstXMLRatio, adaptation_set_node->par);
|
|
g_slice_free (GstXMLConditionalUintType,
|
|
adaptation_set_node->segmentAlignment);
|
|
g_slice_free (GstXMLConditionalUintType,
|
|
adaptation_set_node->subsegmentAlignment);
|
|
g_list_free_full (adaptation_set_node->Accessibility,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (adaptation_set_node->Role,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (adaptation_set_node->Rating,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
g_list_free_full (adaptation_set_node->Viewpoint,
|
|
(GDestroyNotify) gst_mpdparser_free_descriptor_type_node);
|
|
gst_mpdparser_free_representation_base_type
|
|
(adaptation_set_node->RepresentationBase);
|
|
gst_mpdparser_free_seg_base_type_ext (adaptation_set_node->SegmentBase);
|
|
gst_mpdparser_free_segment_list_node (adaptation_set_node->SegmentList);
|
|
gst_mpdparser_free_segment_template_node
|
|
(adaptation_set_node->SegmentTemplate);
|
|
g_list_free_full (adaptation_set_node->BaseURLs,
|
|
(GDestroyNotify) gst_mpdparser_free_base_url_node);
|
|
g_list_free_full (adaptation_set_node->Representations,
|
|
(GDestroyNotify) gst_mpdparser_free_representation_node);
|
|
g_list_free_full (adaptation_set_node->ContentComponents,
|
|
(GDestroyNotify) gst_mpdparser_free_content_component_node);
|
|
if (adaptation_set_node->xlink_href)
|
|
xmlFree (adaptation_set_node->xlink_href);
|
|
g_slice_free (GstAdaptationSetNode, adaptation_set_node);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_segment_list_node (GstSegmentListNode * segment_list_node)
|
|
{
|
|
if (segment_list_node) {
|
|
g_list_free_full (segment_list_node->SegmentURL,
|
|
(GDestroyNotify) gst_mpdparser_free_segment_url_node);
|
|
/* MultipleSegmentBaseType extension */
|
|
gst_mpdparser_free_mult_seg_base_type_ext
|
|
(segment_list_node->MultSegBaseType);
|
|
if (segment_list_node->xlink_href)
|
|
xmlFree (segment_list_node->xlink_href);
|
|
g_slice_free (GstSegmentListNode, segment_list_node);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node)
|
|
{
|
|
if (base_url_node) {
|
|
if (base_url_node->baseURL)
|
|
xmlFree (base_url_node->baseURL);
|
|
if (base_url_node->serviceLocation)
|
|
xmlFree (base_url_node->serviceLocation);
|
|
if (base_url_node->byteRange)
|
|
xmlFree (base_url_node->byteRange);
|
|
g_slice_free (GstBaseURL, base_url_node);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period)
|
|
{
|
|
if (stream_period) {
|
|
g_slice_free (GstStreamPeriod, stream_period);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_media_segment (GstMediaSegment * media_segment)
|
|
{
|
|
if (media_segment) {
|
|
g_slice_free (GstMediaSegment, media_segment);
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_init_active_stream_segments (GstActiveStream * stream)
|
|
{
|
|
g_assert (stream->segments == NULL);
|
|
stream->segments = g_ptr_array_new ();
|
|
g_ptr_array_set_free_func (stream->segments,
|
|
(GDestroyNotify) gst_mpdparser_free_media_segment);
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_active_stream (GstActiveStream * active_stream)
|
|
{
|
|
if (active_stream) {
|
|
g_free (active_stream->baseURL);
|
|
active_stream->baseURL = NULL;
|
|
g_free (active_stream->queryURL);
|
|
active_stream->queryURL = NULL;
|
|
if (active_stream->segments)
|
|
g_ptr_array_unref (active_stream->segments);
|
|
g_slice_free (GstActiveStream, active_stream);
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
gst_mpdparser_get_initializationURL (GstActiveStream * stream,
|
|
GstURLType * InitializationURL)
|
|
{
|
|
const gchar *url_prefix;
|
|
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
|
|
url_prefix = (InitializationURL
|
|
&& InitializationURL->sourceURL) ? InitializationURL->
|
|
sourceURL : stream->baseURL;
|
|
|
|
return url_prefix;
|
|
}
|
|
|
|
gchar *
|
|
gst_mpdparser_get_mediaURL (GstActiveStream * stream,
|
|
GstSegmentURLNode * segmentURL)
|
|
{
|
|
const gchar *url_prefix;
|
|
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
g_return_val_if_fail (segmentURL != NULL, NULL);
|
|
|
|
url_prefix = segmentURL->media ? segmentURL->media : stream->baseURL;
|
|
g_return_val_if_fail (url_prefix != NULL, NULL);
|
|
|
|
return segmentURL->media;
|
|
}
|
|
|
|
/* navigation functions */
|
|
GstStreamMimeType
|
|
gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set,
|
|
GstRepresentationNode * rep)
|
|
{
|
|
gchar *mime = NULL;
|
|
if (rep->RepresentationBase)
|
|
mime = rep->RepresentationBase->mimeType;
|
|
if (mime == NULL && adapt_set->RepresentationBase) {
|
|
mime = adapt_set->RepresentationBase->mimeType;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Helper methods */
|
|
gchar *
|
|
gst_mpdparser_build_URL_from_template (const gchar * url_template,
|
|
const gchar * id, guint number, guint bandwidth, guint64 time)
|
|
{
|
|
static const gchar default_format[] = "%01d";
|
|
gchar **tokens, *token, *ret;
|
|
const gchar *format;
|
|
gint i, num_tokens;
|
|
|
|
g_return_val_if_fail (url_template != NULL, NULL);
|
|
tokens = g_strsplit_set (url_template, "$", -1);
|
|
if (!tokens) {
|
|
GST_WARNING ("Scan of URL template failed!");
|
|
return NULL;
|
|
}
|
|
num_tokens = g_strv_length (tokens);
|
|
|
|
/*
|
|
* each identifier is guarded by 2 $, which means that we must have an odd number of tokens
|
|
* An even number of tokens means the string is not valid.
|
|
*/
|
|
if ((num_tokens & 1) == 0) {
|
|
GST_ERROR ("Invalid number of tokens (%d). url_template is '%s'",
|
|
num_tokens, url_template);
|
|
g_strfreev (tokens);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < num_tokens; i++) {
|
|
token = tokens[i];
|
|
format = default_format;
|
|
|
|
/* the tokens to replace must be provided between $ characters, eg $token$
|
|
* For a string like token0$token1$token2$token3$token4, only the odd number
|
|
* tokens (1,3,...) must be parsed.
|
|
*
|
|
* Skip even tokens
|
|
*/
|
|
if ((i & 1) == 0)
|
|
continue;
|
|
|
|
if (!g_strcmp0 (token, "RepresentationID")) {
|
|
if (!gst_mpdparser_validate_rfc1738_url (id))
|
|
goto invalid_representation_id;
|
|
|
|
tokens[i] = g_strdup_printf ("%s", id);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Number", 6)) {
|
|
if (strlen (token) > 6) {
|
|
format = token + 6; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
tokens[i] = g_strdup_printf (format, number);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Bandwidth", 9)) {
|
|
if (strlen (token) > 9) {
|
|
format = token + 9; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
tokens[i] = g_strdup_printf (format, bandwidth);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Time", 4)) {
|
|
gchar *promoted_format;
|
|
|
|
if (strlen (token) > 4) {
|
|
format = token + 4; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
promoted_format = promote_format_to_uint64 (format);
|
|
tokens[i] = g_strdup_printf (promoted_format, time);
|
|
g_free (promoted_format);
|
|
g_free (token);
|
|
} else if (!g_strcmp0 (token, "")) {
|
|
tokens[i] = g_strdup_printf ("%s", "$");
|
|
g_free (token);
|
|
} else {
|
|
/* unexpected identifier found between $ signs
|
|
*
|
|
* "If the URL contains unescaped $ symbols which do not enclose a valid
|
|
* identifier then the result of URL formation is undefined"
|
|
*/
|
|
goto invalid_format;
|
|
}
|
|
}
|
|
|
|
ret = g_strjoinv (NULL, tokens);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return ret;
|
|
|
|
invalid_format:
|
|
{
|
|
GST_ERROR ("Invalid format '%s' in '%s'", format, token);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return NULL;
|
|
}
|
|
invalid_representation_id:
|
|
{
|
|
GST_ERROR
|
|
("Representation ID string '%s' has characters invalid in an RFC 1738 URL",
|
|
id);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const gchar *
|
|
gst_mpdparser_mimetype_to_caps (const gchar * mimeType)
|
|
{
|
|
if (mimeType == NULL)
|
|
return NULL;
|
|
if (strcmp (mimeType, "video/mp2t") == 0) {
|
|
return "video/mpegts, systemstream=(bool) true";
|
|
} else if (strcmp (mimeType, "video/mp4") == 0) {
|
|
return "video/quicktime";
|
|
} else if (strcmp (mimeType, "audio/mp4") == 0) {
|
|
return "audio/x-m4a";
|
|
} else if (strcmp (mimeType, "text/vtt") == 0) {
|
|
return "application/x-subtitle-vtt";
|
|
} else
|
|
return mimeType;
|
|
}
|
|
|
|
/*
|
|
* Combine a base url with the current stream base url from the list of
|
|
* baseURLs. Takes ownership of base and returns a new base.
|
|
*/
|
|
GstUri *
|
|
combine_urls (GstUri * base, GList * list, gchar ** query, guint idx)
|
|
{
|
|
GstBaseURL *baseURL;
|
|
GstUri *ret = base;
|
|
|
|
if (list != NULL) {
|
|
baseURL = g_list_nth_data (list, idx);
|
|
if (!baseURL) {
|
|
baseURL = list->data;
|
|
}
|
|
|
|
ret = gst_uri_from_string_with_base (base, baseURL->baseURL);
|
|
gst_uri_unref (base);
|
|
|
|
if (ret && query) {
|
|
g_free (*query);
|
|
*query = gst_uri_get_query_string (ret);
|
|
if (*query) {
|
|
ret = gst_uri_make_writable (ret);
|
|
gst_uri_set_query_table (ret, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* comparison functions */
|
|
int
|
|
strncmp_ext (const char *s1, const char *s2)
|
|
{
|
|
if (s1 == NULL && s2 == NULL)
|
|
return 0;
|
|
if (s1 == NULL && s2 != NULL)
|
|
return 1;
|
|
if (s2 == NULL && s1 != NULL)
|
|
return 1;
|
|
return strncmp (s1, s2, strlen (s2));
|
|
}
|