mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 09:40:37 +00:00
bc09d8cc66
Some manifests use the ContentProtection node to store additional information such as the license server url. Our MPD parser used to process the ContentProtection node, extracting Playready PSSH boxes. However for other DRM systems, only the `value` attribute was passed down to the protection event, so for example, Widevine data was not parsed at all and "Widevine" was passed to the event, which is not very useful for decryptors that require a PSSH init data. Parsing should now be done by decryptors which will receive the entire ContentProtection XML node as a string. This gives more "freedom" to the decryptor which can then detect and parse custom nodes as well. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2400>
1538 lines
53 KiB
C
1538 lines
53 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 (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_content_component_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node);
|
|
static void gst_mpdparser_parse_subrepresentation_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_segment_url_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_url_type_node (GstMPDURLTypeNode ** pointer,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_seg_base_type_ext (GstMPDSegmentBaseNode **
|
|
pointer, xmlNode * a_node, GstMPDSegmentBaseNode * parent);
|
|
static void gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node);
|
|
static void gst_mpdparser_parse_segment_timeline_node (GstMPDSegmentTimelineNode
|
|
** pointer, xmlNode * a_node);
|
|
static gboolean
|
|
gst_mpdparser_parse_mult_seg_base_node (GstMPDMultSegmentBaseNode *
|
|
pointer, xmlNode * a_node, GstMPDMultSegmentBaseNode * parent);
|
|
static gboolean gst_mpdparser_parse_segment_list_node (GstMPDSegmentListNode **
|
|
pointer, xmlNode * a_node, GstMPDSegmentListNode * parent);
|
|
static void
|
|
gst_mpdparser_parse_representation_base (GstMPDRepresentationBaseNode *
|
|
pointer, xmlNode * a_node);
|
|
static gboolean gst_mpdparser_parse_representation_node (GList ** list,
|
|
xmlNode * a_node, GstMPDAdaptationSetNode * parent,
|
|
GstMPDPeriodNode * period_node);
|
|
static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list,
|
|
xmlNode * a_node, GstMPDPeriodNode * parent);
|
|
static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node);
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_template_node (GstMPDSegmentTemplateNode ** pointer,
|
|
xmlNode * a_node, GstMPDSegmentTemplateNode * parent);
|
|
static gboolean gst_mpdparser_parse_period_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_program_info_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_metrics_range_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node);
|
|
static gboolean gst_mpdparser_parse_root_node (GstMPDRootNode ** pointer,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_utctiming_node (GList ** list,
|
|
xmlNode * a_node);
|
|
|
|
/*
|
|
Duration Data Type
|
|
|
|
The duration data type is used to specify a time interval.
|
|
|
|
The time interval is specified in the following form "-PnYnMnDTnHnMnS" where:
|
|
|
|
* - indicates the negative sign (optional)
|
|
* P indicates the period (required)
|
|
* nY indicates the number of years
|
|
* nM indicates the number of months
|
|
* nD indicates the number of days
|
|
* T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds)
|
|
* nH indicates the number of hours
|
|
* nM indicates the number of minutes
|
|
* nS indicates the number of seconds
|
|
*/
|
|
|
|
|
|
|
|
|
|
static void
|
|
gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDBaseURLNode *new_base_url;
|
|
|
|
new_base_url = gst_mpd_baseurl_node_new ();
|
|
*list = g_list_append (*list, new_base_url);
|
|
|
|
GST_LOG ("content of BaseURL node:");
|
|
gst_xml_helper_get_node_content (a_node, &new_base_url->baseURL);
|
|
|
|
GST_LOG ("attributes of BaseURL node:");
|
|
gst_xml_helper_get_prop_string (a_node, "serviceLocation",
|
|
&new_base_url->serviceLocation);
|
|
gst_xml_helper_get_prop_string (a_node, "byteRange",
|
|
&new_base_url->byteRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_descriptor_type (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDDescriptorTypeNode *new_descriptor;
|
|
|
|
new_descriptor =
|
|
gst_mpd_descriptor_type_node_new ((const gchar *) a_node->name);
|
|
*list = g_list_append (*list, new_descriptor);
|
|
|
|
GST_LOG ("attributes of %s node:", a_node->name);
|
|
gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
if (!gst_xml_helper_get_prop_string (a_node, "value", &new_descriptor->value)) {
|
|
/* if no value attribute, use XML string representation of the node */
|
|
gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDContentComponentNode *new_content_component;
|
|
|
|
new_content_component = gst_mpd_content_component_node_new ();
|
|
*list = g_list_append (*list, new_content_component);
|
|
|
|
GST_LOG ("attributes of ContentComponent node:");
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0,
|
|
&new_content_component->id);
|
|
gst_xml_helper_get_prop_string (a_node, "lang", &new_content_component->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "contentType",
|
|
&new_content_component->contentType);
|
|
gst_xml_helper_get_prop_ratio (a_node, "par", &new_content_component->par);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) {
|
|
gst_mpdparser_parse_descriptor_type
|
|
(&new_content_component->Accessibility, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) {
|
|
gst_mpdparser_parse_descriptor_type (&new_content_component->Role,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) {
|
|
gst_mpdparser_parse_descriptor_type
|
|
(&new_content_component->Rating, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) {
|
|
gst_mpdparser_parse_descriptor_type
|
|
(&new_content_component->Viewpoint, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
gchar *location = NULL;
|
|
GstMPDLocationNode *locationNode;
|
|
|
|
GST_LOG ("content of Location node:");
|
|
if (gst_xml_helper_get_node_content (a_node, &location)) {
|
|
locationNode = gst_mpd_location_node_new ();
|
|
locationNode->location = location;
|
|
*list = g_list_append (*list, locationNode);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDSubRepresentationNode *new_subrep;
|
|
|
|
new_subrep = gst_mpd_sub_representation_node_new ();
|
|
*list = g_list_append (*list, new_subrep);
|
|
|
|
GST_LOG ("attributes of SubRepresentation node:");
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "level", 0,
|
|
&new_subrep->level);
|
|
gst_xml_helper_get_prop_uint_vector_type (a_node, "dependencyLevel",
|
|
&new_subrep->dependencyLevel, &new_subrep->dependencyLevel_size);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "bandwidth", 0,
|
|
&new_subrep->bandwidth);
|
|
gst_xml_helper_get_prop_string_vector_type (a_node,
|
|
"contentComponent", &new_subrep->contentComponent);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base (GST_MPD_REPRESENTATION_BASE_NODE
|
|
(new_subrep), a_node);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDSegmentURLNode *new_segment_url;
|
|
|
|
new_segment_url = gst_mpd_segment_url_node_new ();
|
|
*list = g_list_append (*list, new_segment_url);
|
|
|
|
GST_LOG ("attributes of SegmentURL node:");
|
|
gst_xml_helper_get_prop_string (a_node, "media", &new_segment_url->media);
|
|
gst_xml_helper_get_prop_range (a_node, "mediaRange",
|
|
&new_segment_url->mediaRange);
|
|
gst_xml_helper_get_prop_string (a_node, "index", &new_segment_url->index);
|
|
gst_xml_helper_get_prop_range (a_node, "indexRange",
|
|
&new_segment_url->indexRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_url_type_node (GstMPDURLTypeNode ** pointer,
|
|
xmlNode * a_node)
|
|
{
|
|
GstMPDURLTypeNode *new_url_type;
|
|
|
|
gst_mpd_url_type_node_free (*pointer);
|
|
*pointer = new_url_type =
|
|
gst_mpd_url_type_node_new ((const gchar *) a_node->name);
|
|
|
|
GST_LOG ("attributes of URLType node:");
|
|
gst_xml_helper_get_prop_string (a_node, "sourceURL",
|
|
&new_url_type->sourceURL);
|
|
gst_xml_helper_get_prop_range (a_node, "range", &new_url_type->range);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_seg_base_type_ext (GstMPDSegmentBaseNode ** pointer,
|
|
xmlNode * a_node, GstMPDSegmentBaseNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDSegmentBaseNode *seg_base_type;
|
|
guint intval;
|
|
guint64 int64val;
|
|
gboolean boolval;
|
|
GstXMLRange *rangeval;
|
|
|
|
gst_mpd_segment_base_node_free (*pointer);
|
|
*pointer = seg_base_type = gst_mpd_segment_base_node_new ();
|
|
|
|
/* Initialize values that have defaults */
|
|
seg_base_type->indexRangeExact = FALSE;
|
|
seg_base_type->timescale = 1;
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
seg_base_type->timescale = parent->timescale;
|
|
seg_base_type->presentationTimeOffset = parent->presentationTimeOffset;
|
|
seg_base_type->indexRange = gst_xml_helper_clone_range (parent->indexRange);
|
|
seg_base_type->indexRangeExact = parent->indexRangeExact;
|
|
seg_base_type->Initialization =
|
|
gst_mpd_url_type_node_clone (parent->Initialization);
|
|
seg_base_type->RepresentationIndex =
|
|
gst_mpd_url_type_node_clone (parent->RepresentationIndex);
|
|
}
|
|
|
|
/* We must retrieve each value first to see if it exists. If it does not
|
|
* exist, we do not want to overwrite an inherited value */
|
|
GST_LOG ("attributes of SegmentBaseType extension:");
|
|
if (gst_xml_helper_get_prop_unsigned_integer (a_node, "timescale", 1,
|
|
&intval)) {
|
|
seg_base_type->timescale = intval;
|
|
}
|
|
if (gst_xml_helper_get_prop_unsigned_integer_64 (a_node,
|
|
"presentationTimeOffset", 0, &int64val)) {
|
|
seg_base_type->presentationTimeOffset = int64val;
|
|
}
|
|
if (gst_xml_helper_get_prop_range (a_node, "indexRange", &rangeval)) {
|
|
if (seg_base_type->indexRange) {
|
|
g_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 void
|
|
gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node)
|
|
{
|
|
GstMPDSNode *new_s_node;
|
|
|
|
new_s_node = gst_mpd_s_node_new ();
|
|
g_queue_push_tail (queue, new_s_node);
|
|
|
|
GST_LOG ("attributes of S node:");
|
|
gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "t", 0, &new_s_node->t);
|
|
gst_xml_helper_get_prop_unsigned_integer_64 (a_node, "d", 0, &new_s_node->d);
|
|
gst_xml_helper_get_prop_signed_integer (a_node, "r", 0, &new_s_node->r);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
gst_mpdparser_parse_segment_timeline_node (GstMPDSegmentTimelineNode ** pointer,
|
|
xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDSegmentTimelineNode *new_seg_timeline;
|
|
|
|
gst_mpd_segment_timeline_node_free (*pointer);
|
|
*pointer = new_seg_timeline = gst_mpd_segment_timeline_node_new ();
|
|
if (new_seg_timeline == NULL) {
|
|
GST_WARNING ("Allocation of SegmentTimeline node failed!");
|
|
return;
|
|
}
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "S") == 0) {
|
|
gst_mpdparser_parse_s_node (&new_seg_timeline->S, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_mult_seg_base_node (GstMPDMultSegmentBaseNode *
|
|
mult_seg_base_node, xmlNode * a_node, GstMPDMultSegmentBaseNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
|
|
guint intval;
|
|
gboolean has_timeline = FALSE, has_duration = FALSE;
|
|
|
|
mult_seg_base_node->duration = 0;
|
|
mult_seg_base_node->startNumber = 1;
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
mult_seg_base_node->duration = parent->duration;
|
|
mult_seg_base_node->startNumber = parent->startNumber;
|
|
mult_seg_base_node->SegmentTimeline =
|
|
gst_mpd_segment_timeline_node_clone (parent->SegmentTimeline);
|
|
mult_seg_base_node->BitstreamSwitching =
|
|
gst_mpd_url_type_node_clone (parent->BitstreamSwitching);
|
|
}
|
|
GST_LOG ("attributes of MultipleSegmentBaseType extension:");
|
|
if (gst_xml_helper_get_prop_unsigned_integer (a_node, "duration", 0, &intval)) {
|
|
mult_seg_base_node->duration = intval;
|
|
}
|
|
|
|
/* duration might be specified from parent */
|
|
if (mult_seg_base_node->duration)
|
|
has_duration = TRUE;
|
|
|
|
if (gst_xml_helper_get_prop_unsigned_integer (a_node, "startNumber", 1,
|
|
&intval)) {
|
|
mult_seg_base_node->startNumber = intval;
|
|
}
|
|
|
|
GST_LOG ("extension of MultipleSegmentBaseType extension:");
|
|
gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_node->SegmentBase,
|
|
a_node, (parent ? parent->SegmentBase : NULL));
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTimeline") == 0) {
|
|
/* parse frees the segmenttimeline if any */
|
|
gst_mpdparser_parse_segment_timeline_node
|
|
(&mult_seg_base_node->SegmentTimeline, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "BitstreamSwitching") == 0) {
|
|
/* parse frees the old url before setting the new one */
|
|
gst_mpdparser_parse_url_type_node
|
|
(&mult_seg_base_node->BitstreamSwitching, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
has_timeline = mult_seg_base_node->SegmentTimeline != NULL;
|
|
|
|
/* Checking duration and timeline only at Representation's child level */
|
|
if (xmlStrcmp (a_node->parent->name, (xmlChar *) "Representation") == 0
|
|
&& !has_duration && !has_timeline) {
|
|
GST_ERROR ("segment has neither duration nor timeline");
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_list_node (GstMPDSegmentListNode ** pointer,
|
|
xmlNode * a_node, GstMPDSegmentListNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDSegmentListNode *new_segment_list;
|
|
gchar *actuate;
|
|
gboolean segment_urls_inherited_from_parent = FALSE;
|
|
|
|
gst_mpd_segment_list_node_free (*pointer);
|
|
new_segment_list = gst_mpd_segment_list_node_new ();
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
GList *list;
|
|
GstMPDSegmentURLNode *seg_url;
|
|
for (list = g_list_first (parent->SegmentURL); list;
|
|
list = g_list_next (list)) {
|
|
seg_url = (GstMPDSegmentURLNode *) list->data;
|
|
new_segment_list->SegmentURL =
|
|
g_list_append (new_segment_list->SegmentURL,
|
|
gst_mpd_segment_url_node_clone (seg_url));
|
|
segment_urls_inherited_from_parent = TRUE;
|
|
}
|
|
}
|
|
|
|
new_segment_list->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, GST_MPD_XLINK_ACTUATE_ON_LOAD_STR) == 0)
|
|
new_segment_list->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
GST_LOG ("extension of SegmentList node:");
|
|
if (!gst_mpdparser_parse_mult_seg_base_node
|
|
(GST_MPD_MULT_SEGMENT_BASE_NODE (new_segment_list), a_node,
|
|
(parent ? GST_MPD_MULT_SEGMENT_BASE_NODE (parent) : NULL)))
|
|
goto error;
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentURL") == 0) {
|
|
if (segment_urls_inherited_from_parent) {
|
|
/*
|
|
* SegmentBase, SegmentTemplate and SegmentList shall inherit
|
|
* attributes and elements from the same element on a higher level.
|
|
* If the same attribute or element is present on both levels,
|
|
* the one on the lower level shall take precedence over the one
|
|
* on the higher level.
|
|
*/
|
|
|
|
/* Clear the list of inherited segment URLs */
|
|
g_list_free_full (new_segment_list->SegmentURL,
|
|
(GDestroyNotify) gst_mpd_segment_url_node_free);
|
|
new_segment_list->SegmentURL = NULL;
|
|
|
|
/* mark the fact that we cleared the list, so that it is not tried again */
|
|
segment_urls_inherited_from_parent = FALSE;
|
|
}
|
|
gst_mpdparser_parse_segment_url_node (&new_segment_list->SegmentURL,
|
|
cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pointer = new_segment_list;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_segment_list_node_free (new_segment_list);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDDescriptorTypeNode *new_descriptor;
|
|
new_descriptor = gst_mpd_descriptor_type_node_new ((const gchar *)
|
|
a_node->name);
|
|
*list = g_list_append (*list, new_descriptor);
|
|
|
|
gst_xml_helper_get_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
gst_xml_helper_get_node_as_string (a_node, &new_descriptor->value);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_representation_base (GstMPDRepresentationBaseNode *
|
|
representation_base, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
|
|
GST_LOG ("attributes of RepresentationBaseType extension:");
|
|
gst_xml_helper_get_prop_string (a_node, "profiles",
|
|
&representation_base->profiles);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "width", 0,
|
|
&representation_base->width);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "height", 0,
|
|
&representation_base->height);
|
|
gst_xml_helper_get_prop_ratio (a_node, "sar", &representation_base->sar);
|
|
gst_xml_helper_get_prop_framerate (a_node, "frameRate",
|
|
&representation_base->frameRate);
|
|
gst_xml_helper_get_prop_framerate (a_node, "minFrameRate",
|
|
&representation_base->minFrameRate);
|
|
gst_xml_helper_get_prop_framerate (a_node, "maxFrameRate",
|
|
&representation_base->maxFrameRate);
|
|
gst_xml_helper_get_prop_string (a_node, "audioSamplingRate",
|
|
&representation_base->audioSamplingRate);
|
|
gst_xml_helper_get_prop_string (a_node, "mimeType",
|
|
&representation_base->mimeType);
|
|
gst_xml_helper_get_prop_string (a_node, "segmentProfiles",
|
|
&representation_base->segmentProfiles);
|
|
gst_xml_helper_get_prop_string (a_node, "codecs",
|
|
&representation_base->codecs);
|
|
gst_xml_helper_get_prop_double (a_node, "maximumSAPPeriod",
|
|
&representation_base->maximumSAPPeriod);
|
|
gst_mpd_helper_get_SAP_type (a_node, "startWithSAP",
|
|
&representation_base->startWithSAP);
|
|
gst_xml_helper_get_prop_double (a_node, "maxPlayoutRate",
|
|
&representation_base->maxPlayoutRate);
|
|
gst_xml_helper_get_prop_boolean (a_node, "codingDependency",
|
|
FALSE, &representation_base->codingDependency);
|
|
gst_xml_helper_get_prop_string (a_node, "scanType",
|
|
&representation_base->scanType);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) {
|
|
gst_mpdparser_parse_descriptor_type
|
|
(&representation_base->FramePacking, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "AudioChannelConfiguration") == 0) {
|
|
gst_mpdparser_parse_descriptor_type
|
|
(&representation_base->AudioChannelConfiguration, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ContentProtection") == 0) {
|
|
gst_mpdparser_parse_content_protection_node
|
|
(&representation_base->ContentProtection, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node,
|
|
GstMPDAdaptationSetNode * parent, GstMPDPeriodNode * period_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDRepresentationNode *new_representation;
|
|
|
|
new_representation = gst_mpd_representation_node_new ();
|
|
|
|
GST_LOG ("attributes of Representation node:");
|
|
if (!gst_xml_helper_get_prop_string_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
|
|
(GST_MPD_REPRESENTATION_BASE_NODE (new_representation), a_node);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_representation->SegmentBase,
|
|
cur_node, parent->SegmentBase ?
|
|
parent->SegmentBase : period_node->SegmentBase);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_representation->SegmentTemplate, cur_node,
|
|
parent->SegmentTemplate ?
|
|
parent->SegmentTemplate : period_node->SegmentTemplate))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node
|
|
(&new_representation->SegmentList, cur_node,
|
|
parent->SegmentList ? parent->SegmentList : period_node->
|
|
SegmentList))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_representation->BaseURLs,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "SubRepresentation") == 0) {
|
|
gst_mpdparser_parse_subrepresentation_node
|
|
(&new_representation->SubRepresentations, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* some sanity checking */
|
|
|
|
*list = g_list_append (*list, new_representation);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_representation_node_free (new_representation);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node,
|
|
GstMPDPeriodNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDAdaptationSetNode *new_adap_set;
|
|
gchar *actuate;
|
|
|
|
new_adap_set = gst_mpd_adaptation_set_node_new ();
|
|
|
|
GST_LOG ("attributes of AdaptationSet node:");
|
|
|
|
new_adap_set->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_adap_set->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_adap_set->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "id", 0, &new_adap_set->id);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "group", 0,
|
|
&new_adap_set->group);
|
|
gst_xml_helper_get_prop_string (a_node, "lang", &new_adap_set->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "contentType",
|
|
&new_adap_set->contentType);
|
|
gst_xml_helper_get_prop_ratio (a_node, "par", &new_adap_set->par);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minBandwidth", 0,
|
|
&new_adap_set->minBandwidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxBandwidth", 0,
|
|
&new_adap_set->maxBandwidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minWidth", 0,
|
|
&new_adap_set->minWidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxWidth", 0,
|
|
&new_adap_set->maxWidth);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "minHeight", 0,
|
|
&new_adap_set->minHeight);
|
|
gst_xml_helper_get_prop_unsigned_integer (a_node, "maxHeight", 0,
|
|
&new_adap_set->maxHeight);
|
|
gst_xml_helper_get_prop_cond_uint (a_node, "segmentAlignment",
|
|
&new_adap_set->segmentAlignment);
|
|
gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching",
|
|
parent->bitstreamSwitching, &new_adap_set->bitstreamSwitching);
|
|
if (parent->bitstreamSwitching && !new_adap_set->bitstreamSwitching) {
|
|
/* according to the standard, if the Period's bitstreamSwitching attribute
|
|
* is true, the AdaptationSet should not have the bitstreamSwitching
|
|
* attribute set to false.
|
|
* We should return a parsing error, but we are generous and ignore the
|
|
* standard violation.
|
|
*/
|
|
new_adap_set->bitstreamSwitching = parent->bitstreamSwitching;
|
|
}
|
|
gst_xml_helper_get_prop_cond_uint (a_node, "subsegmentAlignment",
|
|
&new_adap_set->subsegmentAlignment);
|
|
gst_mpd_helper_get_SAP_type (a_node, "subsegmentStartsWithSAP",
|
|
&new_adap_set->subsegmentStartsWithSAP);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base
|
|
(GST_MPD_REPRESENTATION_BASE_NODE (new_adap_set), a_node);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) {
|
|
gst_mpdparser_parse_descriptor_type (&new_adap_set->Accessibility,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) {
|
|
gst_mpdparser_parse_descriptor_type (&new_adap_set->Role, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) {
|
|
gst_mpdparser_parse_descriptor_type (&new_adap_set->Rating, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) {
|
|
gst_mpdparser_parse_descriptor_type (&new_adap_set->Viewpoint,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_adap_set->BaseURLs, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_adap_set->SegmentBase,
|
|
cur_node, parent->SegmentBase);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node (&new_adap_set->SegmentList,
|
|
cur_node, parent->SegmentList))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ContentComponent") == 0) {
|
|
gst_mpdparser_parse_content_component_node
|
|
(&new_adap_set->ContentComponents, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_adap_set->SegmentTemplate, cur_node, parent->SegmentTemplate))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must parse Representation after everything else in the AdaptationSet
|
|
* has been parsed because certain Representation child elements can inherit
|
|
* attributes specified by the same element in the AdaptationSet
|
|
*/
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Representation") == 0) {
|
|
if (!gst_mpdparser_parse_representation_node
|
|
(&new_adap_set->Representations, cur_node, new_adap_set, parent))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
*list = g_list_append (*list, new_adap_set);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_adaptation_set_node_free (new_adap_set);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDSubsetNode *new_subset;
|
|
|
|
new_subset = gst_mpd_subset_node_new ();
|
|
*list = g_list_append (*list, new_subset);
|
|
|
|
GST_LOG ("attributes of Subset node:");
|
|
gst_xml_helper_get_prop_uint_vector_type (a_node, "contains",
|
|
&new_subset->contains, &new_subset->contains_size);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_template_node (GstMPDSegmentTemplateNode ** pointer,
|
|
xmlNode * a_node, GstMPDSegmentTemplateNode * parent)
|
|
{
|
|
GstMPDSegmentTemplateNode *new_segment_template;
|
|
gchar *strval;
|
|
|
|
gst_mpd_segment_template_node_free (*pointer);
|
|
new_segment_template = gst_mpd_segment_template_node_new ();
|
|
|
|
GST_LOG ("extension of SegmentTemplate node:");
|
|
if (!gst_mpdparser_parse_mult_seg_base_node
|
|
(GST_MPD_MULT_SEGMENT_BASE_NODE (new_segment_template), a_node,
|
|
(parent ? GST_MPD_MULT_SEGMENT_BASE_NODE (parent) : NULL)))
|
|
goto error;
|
|
|
|
/* Inherit attribute values from parent when the value isn't found */
|
|
GST_LOG ("attributes of SegmentTemplate node:");
|
|
if (gst_xml_helper_get_prop_string (a_node, "media", &strval)) {
|
|
new_segment_template->media = strval;
|
|
} else if (parent) {
|
|
new_segment_template->media = xmlMemStrdup (parent->media);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "index", &strval)) {
|
|
new_segment_template->index = strval;
|
|
} else if (parent) {
|
|
new_segment_template->index = xmlMemStrdup (parent->index);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "initialization", &strval)) {
|
|
new_segment_template->initialization = strval;
|
|
} else if (parent) {
|
|
new_segment_template->initialization =
|
|
xmlMemStrdup (parent->initialization);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "bitstreamSwitching", &strval)) {
|
|
new_segment_template->bitstreamSwitching = strval;
|
|
} else if (parent) {
|
|
new_segment_template->bitstreamSwitching =
|
|
xmlMemStrdup (parent->bitstreamSwitching);
|
|
}
|
|
|
|
*pointer = new_segment_template;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_segment_template_node_free (new_segment_template);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDPeriodNode *new_period;
|
|
gchar *actuate;
|
|
|
|
new_period = gst_mpd_period_node_new ();
|
|
|
|
GST_LOG ("attributes of Period node:");
|
|
|
|
new_period->actuate = GST_MPD_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_period->xlink_href)
|
|
&& gst_xml_helper_get_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_period->actuate = GST_MPD_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_xml_helper_get_prop_string (a_node, "id", &new_period->id);
|
|
gst_xml_helper_get_prop_duration (a_node, "start", GST_MPD_DURATION_NONE,
|
|
&new_period->start);
|
|
gst_xml_helper_get_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_period->duration);
|
|
gst_xml_helper_get_prop_boolean (a_node, "bitstreamSwitching", FALSE,
|
|
&new_period->bitstreamSwitching);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_period->SegmentBase,
|
|
cur_node, NULL);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node (&new_period->SegmentList,
|
|
cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_period->SegmentTemplate, cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Subset") == 0) {
|
|
gst_mpdparser_parse_subset_node (&new_period->Subsets, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_period->BaseURLs, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must parse AdaptationSet after everything else in the Period has been
|
|
* parsed because certain AdaptationSet child elements can inherit attributes
|
|
* specified by the same element in the Period
|
|
*/
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "AdaptationSet") == 0) {
|
|
if (!gst_mpdparser_parse_adaptation_set_node
|
|
(&new_period->AdaptationSets, cur_node, new_period))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
*list = g_list_append (*list, new_period);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_period_node_free (new_period);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDProgramInformationNode *new_prog_info;
|
|
|
|
new_prog_info = gst_mpd_program_information_node_new ();
|
|
*list = g_list_append (*list, new_prog_info);
|
|
|
|
GST_LOG ("attributes of ProgramInformation node:");
|
|
gst_xml_helper_get_prop_string (a_node, "lang", &new_prog_info->lang);
|
|
gst_xml_helper_get_prop_string (a_node, "moreInformationURL",
|
|
&new_prog_info->moreInformationURL);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of ProgramInformation node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Title") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Title);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Source);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) {
|
|
gst_xml_helper_get_node_content (cur_node, &new_prog_info->Copyright);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDMetricsRangeNode *new_metrics_range;
|
|
|
|
new_metrics_range = gst_mpd_metrics_range_node_new ();
|
|
*list = g_list_append (*list, new_metrics_range);
|
|
|
|
GST_LOG ("attributes of Metrics Range node:");
|
|
gst_xml_helper_get_prop_duration (a_node, "starttime",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->starttime);
|
|
gst_xml_helper_get_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->duration);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDMetricsNode *new_metrics;
|
|
|
|
new_metrics = gst_mpd_metrics_node_new ();
|
|
*list = g_list_append (*list, new_metrics);
|
|
|
|
GST_LOG ("attributes of Metrics node:");
|
|
gst_xml_helper_get_prop_string (a_node, "metrics", &new_metrics->metrics);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of Metrics node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Range") == 0) {
|
|
gst_mpdparser_parse_metrics_range_node (&new_metrics->MetricsRanges,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Reporting") == 0) {
|
|
/* No reporting scheme is specified in this part of ISO/IEC 23009.
|
|
* It is expected that external specifications may define formats
|
|
* and delivery for the reporting data. */
|
|
GST_LOG (" - Reporting node found (unknown structure)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The UTCTiming element is defined in
|
|
* ISO/IEC 23009-1:2014/PDAM 1 "Information technology — Dynamic adaptive streaming over HTTP (DASH) — Part 1: Media presentation description and segment formats / Amendment 1: High Profile and Availability Time Synchronization"
|
|
*/
|
|
static void
|
|
gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMPDUTCTimingNode *new_timing;
|
|
gchar *method = NULL;
|
|
gchar *value = NULL;
|
|
|
|
new_timing = gst_mpd_utctiming_node_new ();
|
|
|
|
GST_LOG ("attributes of UTCTiming node:");
|
|
if (gst_xml_helper_get_prop_string (a_node, "schemeIdUri", &method)) {
|
|
new_timing->method = gst_mpd_utctiming_get_method (method);
|
|
xmlFree (method);
|
|
}
|
|
|
|
if (gst_xml_helper_get_prop_string (a_node, "value", &value)) {
|
|
int max_tokens = 0;
|
|
if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) {
|
|
/* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case
|
|
* that is not a space separated list.
|
|
*/
|
|
max_tokens = 1;
|
|
}
|
|
new_timing->urls = g_strsplit (value, " ", max_tokens);
|
|
xmlFree (value);
|
|
}
|
|
|
|
/* append to list only if both method and urls were set */
|
|
if (new_timing->method != 0 && new_timing->urls != NULL &&
|
|
g_strv_length (new_timing->urls) != 0) {
|
|
*list = g_list_append (*list, new_timing);
|
|
} else {
|
|
gst_mpd_utctiming_node_free (new_timing);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_root_node (GstMPDRootNode ** pointer, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDRootNode *new_mpd_root;
|
|
|
|
gst_mpd_root_node_free (*pointer);
|
|
*pointer = NULL;
|
|
new_mpd_root = gst_mpd_root_node_new ();
|
|
|
|
GST_LOG ("namespaces of root MPD node:");
|
|
new_mpd_root->default_namespace =
|
|
gst_xml_helper_get_node_namespace (a_node, NULL);
|
|
new_mpd_root->namespace_xsi =
|
|
gst_xml_helper_get_node_namespace (a_node, "xsi");
|
|
new_mpd_root->namespace_ext =
|
|
gst_xml_helper_get_node_namespace (a_node, "ext");
|
|
|
|
GST_LOG ("attributes of root MPD node:");
|
|
gst_xml_helper_get_prop_string (a_node, "schemaLocation",
|
|
&new_mpd_root->schemaLocation);
|
|
gst_xml_helper_get_prop_string (a_node, "id", &new_mpd_root->id);
|
|
gst_xml_helper_get_prop_string (a_node, "profiles", &new_mpd_root->profiles);
|
|
gst_mpd_helper_get_mpd_type (a_node, "type", &new_mpd_root->type);
|
|
gst_xml_helper_get_prop_dateTime (a_node, "availabilityStartTime",
|
|
&new_mpd_root->availabilityStartTime);
|
|
gst_xml_helper_get_prop_dateTime (a_node, "availabilityEndTime",
|
|
&new_mpd_root->availabilityEndTime);
|
|
gst_xml_helper_get_prop_duration (a_node, "mediaPresentationDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->mediaPresentationDuration);
|
|
gst_xml_helper_get_prop_duration (a_node, "minimumUpdatePeriod",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->minimumUpdatePeriod);
|
|
gst_xml_helper_get_prop_duration (a_node, "minBufferTime",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->minBufferTime);
|
|
gst_xml_helper_get_prop_duration (a_node, "timeShiftBufferDepth",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->timeShiftBufferDepth);
|
|
gst_xml_helper_get_prop_duration (a_node, "suggestedPresentationDelay",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->suggestedPresentationDelay);
|
|
gst_xml_helper_get_prop_duration (a_node, "maxSegmentDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->maxSegmentDuration);
|
|
gst_xml_helper_get_prop_duration (a_node, "maxSubsegmentDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd_root->maxSubsegmentDuration);
|
|
|
|
/* explore children Period nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Period") == 0) {
|
|
if (!gst_mpdparser_parse_period_node (&new_mpd_root->Periods, cur_node))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ProgramInformation") == 0) {
|
|
gst_mpdparser_parse_program_info_node (&new_mpd_root->ProgramInfos,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_mpd_root->BaseURLs, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Location") == 0) {
|
|
gst_mpdparser_parse_location_node (&new_mpd_root->Locations, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) {
|
|
gst_mpdparser_parse_metrics_node (&new_mpd_root->Metrics, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "UTCTiming") == 0) {
|
|
gst_mpdparser_parse_utctiming_node (&new_mpd_root->UTCTimings,
|
|
cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pointer = new_mpd_root;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpd_root_node_free (new_mpd_root);
|
|
return FALSE;
|
|
}
|
|
|
|
/* internal memory management functions */
|
|
|
|
/* ISO/IEC 23009-1:2004 5.3.9.4.4 */
|
|
static gboolean
|
|
validate_format (const gchar * format)
|
|
{
|
|
const gchar *p = format;
|
|
|
|
/* Check if it starts with % */
|
|
if (!p || p[0] != '%')
|
|
return FALSE;
|
|
p++;
|
|
|
|
/* the spec mandates a format like %0[width]d */
|
|
/* Following the %, we must have a 0 */
|
|
if (p[0] != '0')
|
|
return FALSE;
|
|
|
|
/* Following the % must be a number starting with 0
|
|
*/
|
|
while (g_ascii_isdigit (*p))
|
|
p++;
|
|
|
|
/* After any 0 and alphanumeric values, there must be a d.
|
|
*/
|
|
if (p[0] != 'd')
|
|
return FALSE;
|
|
p++;
|
|
|
|
/* And then potentially more characters without any
|
|
* further %, even if the spec does not mention this
|
|
*/
|
|
p = strchr (p, '%');
|
|
if (p)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
promote_format_to_uint64 (const gchar * format)
|
|
{
|
|
const gchar *p = format;
|
|
gchar *promoted_format;
|
|
|
|
/* Must be called with a validated format! */
|
|
g_return_val_if_fail (validate_format (format), NULL);
|
|
|
|
/* it starts with % */
|
|
p++;
|
|
|
|
/* Following the % must be a 0, or any of d, x or u.
|
|
* x and u are not part of the spec, but don't hurt us
|
|
*/
|
|
if (p[0] == '0') {
|
|
p++;
|
|
|
|
while (g_ascii_isdigit (*p))
|
|
p++;
|
|
}
|
|
|
|
/* After any 0 and alphanumeric values, there must be a d.
|
|
* Otherwise validation would have failed
|
|
*/
|
|
g_assert (p[0] == 'd');
|
|
|
|
promoted_format =
|
|
g_strdup_printf ("%.*s" G_GINT64_MODIFIER "%s", (gint) (p - format),
|
|
format, p);
|
|
|
|
return promoted_format;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_validate_rfc1738_url (const char *s)
|
|
{
|
|
while (*s) {
|
|
if (!strchr
|
|
(";:@&=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789$-_.+!*'(),%/",
|
|
*s))
|
|
return FALSE;
|
|
if (*s == '%') {
|
|
/* g_ascii_isdigit returns FALSE for NUL, and || is a short circuiting
|
|
operator, so this is safe for strings ending before two hex digits */
|
|
if (!g_ascii_isxdigit (s[1]) || !g_ascii_isxdigit (s[2]))
|
|
return FALSE;
|
|
s += 2;
|
|
}
|
|
s++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_media_fragment_info_clear (GstMediaFragmentInfo * fragment)
|
|
{
|
|
g_free (fragment->uri);
|
|
g_free (fragment->index_uri);
|
|
}
|
|
|
|
/* API */
|
|
gboolean
|
|
gst_mpdparser_get_mpd_root_node (GstMPDRootNode ** mpd_root_node,
|
|
const gchar * data, gint size)
|
|
{
|
|
gboolean ret = FALSE;
|
|
|
|
if (data) {
|
|
xmlDocPtr doc;
|
|
xmlNode *root_element = NULL;
|
|
|
|
GST_DEBUG ("MPD file fully buffered, start parsing...");
|
|
|
|
/* parse the complete MPD file into a tree (using the libxml2 default parser API) */
|
|
|
|
/* this initialize the library and check potential ABI mismatches
|
|
* between the version it was compiled for and the actual shared
|
|
* library used
|
|
*/
|
|
LIBXML_TEST_VERSION;
|
|
|
|
/* parse "data" into a document (which is a libxml2 tree structure xmlDoc) */
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
if (doc == NULL) {
|
|
GST_ERROR ("failed to parse the MPD file");
|
|
ret = FALSE;
|
|
} else {
|
|
/* get the root element node */
|
|
root_element = xmlDocGetRootElement (doc);
|
|
|
|
if (root_element->type != XML_ELEMENT_NODE
|
|
|| xmlStrcmp (root_element->name, (xmlChar *) "MPD") != 0) {
|
|
GST_ERROR
|
|
("can not find the root element MPD, failed to parse the MPD file");
|
|
ret = FALSE; /* used to return TRUE before, but this seems wrong */
|
|
} else {
|
|
/* now we can parse the MPD root node and all children nodes, recursively */
|
|
ret = gst_mpdparser_parse_root_node (mpd_root_node, root_element);
|
|
}
|
|
/* free the document */
|
|
xmlFreeDoc (doc);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GstMPDSegmentListNode *
|
|
gst_mpdparser_get_external_segment_list (const gchar * data, gint size,
|
|
GstMPDSegmentListNode * parent)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GstMPDSegmentListNode *new_segment_list = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
|
|
/* NOTE: ISO/IEC 23009-1:2014 5.3.9.3.2 is saying that one or multiple SegmentList
|
|
* in external xml is allowed, however, multiple SegmentList does not make sense
|
|
* because Period/AdaptationSet/Representation allow only one SegmentList */
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
|
|
|
|
if (root_element->type == XML_ELEMENT_NODE &&
|
|
xmlStrcmp (root_element->name, (xmlChar *) "SegmentList") == 0) {
|
|
gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element,
|
|
parent);
|
|
}
|
|
}
|
|
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_segment_list;
|
|
}
|
|
|
|
GList *
|
|
gst_mpdparser_get_external_periods (const gchar * data, gint size)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GList *new_periods = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
xmlNode *iter;
|
|
|
|
for (iter = root_element->children; iter; iter = iter->next) {
|
|
if (iter->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (iter->name, (xmlChar *) "Period") == 0) {
|
|
gst_mpdparser_parse_period_node (&new_periods, iter);
|
|
} else {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_periods;
|
|
|
|
error:
|
|
GST_ERROR ("Failed to parse period node XML");
|
|
|
|
if (new_periods) {
|
|
g_list_free_full (new_periods, (GDestroyNotify) gst_mpd_period_node_free);
|
|
new_periods = NULL;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
GList *
|
|
gst_mpdparser_get_external_adaptation_sets (const gchar * data, gint size,
|
|
GstMPDPeriodNode * period)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
GList *new_adaptation_sets = NULL;
|
|
|
|
doc = xmlReadMemory (data, size, "noname.xml", NULL, XML_PARSE_NONET);
|
|
|
|
/* NOTE: ISO/IEC 23009-1:2014 5.3.3.2 is saying that exactly one AdaptationSet
|
|
* in external xml is allowed */
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
if (root_element->type == XML_ELEMENT_NODE &&
|
|
xmlStrcmp (root_element->name, (xmlChar *) "AdaptationSet") == 0) {
|
|
gst_mpdparser_parse_adaptation_set_node (&new_adaptation_sets,
|
|
root_element, period);
|
|
}
|
|
}
|
|
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_adaptation_sets;
|
|
}
|
|
|
|
void
|
|
gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period)
|
|
{
|
|
if (stream_period) {
|
|
g_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,
|
|
GstMPDURLTypeNode * 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,
|
|
GstMPDSegmentURLNode * 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 (GstMPDAdaptationSetNode * adapt_set,
|
|
GstMPDRepresentationNode * rep)
|
|
{
|
|
gchar *mime = NULL;
|
|
if (rep)
|
|
mime = GST_MPD_REPRESENTATION_BASE_NODE (rep)->mimeType;
|
|
if (mime == NULL) {
|
|
mime = GST_MPD_REPRESENTATION_BASE_NODE (adapt_set)->mimeType;
|
|
}
|
|
|
|
if (gst_mpd_helper_strncmp_ext (mime, "audio") == 0)
|
|
return GST_STREAM_AUDIO;
|
|
if (gst_mpd_helper_strncmp_ext (mime, "video") == 0)
|
|
return GST_STREAM_VIDEO;
|
|
if (gst_mpd_helper_strncmp_ext (mime, "application") == 0
|
|
|| gst_mpd_helper_strncmp_ext (mime, "text") == 0)
|
|
return GST_STREAM_APPLICATION;
|
|
|
|
return GST_STREAM_UNKNOWN;
|
|
}
|
|
|
|
/* Helper methods */
|
|
gchar *
|
|
gst_mpdparser_build_URL_from_template (const gchar * url_template,
|
|
const gchar * id, guint number, guint bandwidth, guint64 time)
|
|
{
|
|
static const gchar default_format[] = "%01d";
|
|
gchar **tokens, *token, *ret;
|
|
const gchar *format;
|
|
gint i, num_tokens;
|
|
|
|
g_return_val_if_fail (url_template != NULL, NULL);
|
|
tokens = g_strsplit_set (url_template, "$", -1);
|
|
if (!tokens) {
|
|
GST_WARNING ("Scan of URL template failed!");
|
|
return NULL;
|
|
}
|
|
num_tokens = g_strv_length (tokens);
|
|
|
|
/*
|
|
* each identifier is guarded by 2 $, which means that we must have an odd number of tokens
|
|
* An even number of tokens means the string is not valid.
|
|
*/
|
|
if ((num_tokens & 1) == 0) {
|
|
GST_ERROR ("Invalid number of tokens (%d). url_template is '%s'",
|
|
num_tokens, url_template);
|
|
g_strfreev (tokens);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < num_tokens; i++) {
|
|
token = tokens[i];
|
|
format = default_format;
|
|
|
|
/* the tokens to replace must be provided between $ characters, eg $token$
|
|
* For a string like token0$token1$token2$token3$token4, only the odd number
|
|
* tokens (1,3,...) must be parsed.
|
|
*
|
|
* Skip even tokens
|
|
*/
|
|
if ((i & 1) == 0)
|
|
continue;
|
|
|
|
if (!g_strcmp0 (token, "RepresentationID")) {
|
|
if (!gst_mpdparser_validate_rfc1738_url (id))
|
|
goto invalid_representation_id;
|
|
|
|
tokens[i] = g_strdup_printf ("%s", id);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Number", 6)) {
|
|
if (strlen (token) > 6) {
|
|
format = token + 6; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
tokens[i] = g_strdup_printf (format, number);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Bandwidth", 9)) {
|
|
if (strlen (token) > 9) {
|
|
format = token + 9; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
tokens[i] = g_strdup_printf (format, bandwidth);
|
|
g_free (token);
|
|
} else if (!strncmp (token, "Time", 4)) {
|
|
gchar *promoted_format;
|
|
|
|
if (strlen (token) > 4) {
|
|
format = token + 4; /* format tag */
|
|
}
|
|
if (!validate_format (format))
|
|
goto invalid_format;
|
|
|
|
promoted_format = promote_format_to_uint64 (format);
|
|
tokens[i] = g_strdup_printf (promoted_format, time);
|
|
g_free (promoted_format);
|
|
g_free (token);
|
|
} else if (!g_strcmp0 (token, "")) {
|
|
tokens[i] = g_strdup_printf ("%s", "$");
|
|
g_free (token);
|
|
} else {
|
|
/* unexpected identifier found between $ signs
|
|
*
|
|
* "If the URL contains unescaped $ symbols which do not enclose a valid
|
|
* identifier then the result of URL formation is undefined"
|
|
*/
|
|
goto invalid_format;
|
|
}
|
|
}
|
|
|
|
ret = g_strjoinv (NULL, tokens);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return ret;
|
|
|
|
invalid_format:
|
|
{
|
|
GST_ERROR ("Invalid format '%s' in '%s'", format, token);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return NULL;
|
|
}
|
|
invalid_representation_id:
|
|
{
|
|
GST_ERROR
|
|
("Representation ID string '%s' has characters invalid in an RFC 1738 URL",
|
|
id);
|
|
|
|
g_strfreev (tokens);
|
|
|
|
return NULL;
|
|
}
|
|
}
|