mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 10:41:04 +00:00
a11f7ed924
The start of each segment is relative to the Period start, minus the presentation time offset. As specified in section 5.3.9.6 of the MPEG DASH specification: The value of the @t attribute minus the value of the @presentationTimeOffset specifies the MPD start time of the first Segment in the series. dashdemux was not taking account of presentationTimeOffset and in some methods was not taking into account the Period start time. This commit modifies the segment->start value to always be relative to the MPD start time (zero for VOD, availabilityStartTime for live streams). This makes all uses of the segment list consistent. Fixes #841
6165 lines
196 KiB
C
6165 lines
196 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 <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
#include "gstmpdparser.h"
|
|
#include "gstdash_debug.h"
|
|
|
|
#define GST_CAT_DEFAULT gst_dash_demux_debug
|
|
|
|
/* Property parsing */
|
|
static gboolean gst_mpdparser_get_xml_prop_validated_string (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value,
|
|
gboolean (*validator) (const char *));
|
|
static gboolean gst_mpdparser_get_xml_prop_string (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_string_stripped (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_ns_prop_string (xmlNode * a_node,
|
|
const gchar * ns_name, const gchar * property_name,
|
|
gchar ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node,
|
|
const gchar * property_name, gchar *** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_signed_integer (xmlNode * a_node,
|
|
const gchar * property_name, gint default_val, gint * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node,
|
|
const gchar * property_name, guint default_val, guint * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_unsigned_integer_64 (xmlNode *
|
|
a_node, const gchar * property_name, guint64 default_val,
|
|
guint64 * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node,
|
|
const gchar * property_name, guint ** property_value, guint * value_size);
|
|
static gboolean gst_mpdparser_get_xml_prop_double (xmlNode * a_node,
|
|
const gchar * property_name, gdouble * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node,
|
|
const gchar * property_name, gboolean default_val,
|
|
gboolean * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_type (xmlNode * a_node,
|
|
const gchar * property_name, GstMPDFileType * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node,
|
|
const gchar * property_name, GstSAPType * property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_range (xmlNode * a_node,
|
|
const gchar * property_name, GstRange ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node,
|
|
const gchar * property_name, GstRatio ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node,
|
|
const gchar * property_name, GstFrameRate ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node,
|
|
const gchar * property_name, GstConditionalUintType ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node,
|
|
const gchar * property_name, GstDateTime ** property_value);
|
|
static gboolean gst_mpdparser_get_xml_prop_duration (xmlNode * a_node,
|
|
const gchar * property_name, guint64 default_value,
|
|
guint64 * property_value);
|
|
static gboolean gst_mpdparser_get_xml_node_content (xmlNode * a_node,
|
|
gchar ** content);
|
|
static gchar *gst_mpdparser_get_xml_node_namespace (xmlNode * a_node,
|
|
const gchar * prefix);
|
|
static gboolean gst_mpdparser_get_xml_node_as_string (xmlNode * a_node,
|
|
gchar ** content);
|
|
|
|
/* XML node parsing */
|
|
static void gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node);
|
|
static void gst_mpdparser_parse_descriptor_type_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_content_component_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node);
|
|
static void gst_mpdparser_parse_subrepresentation_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_segment_url_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_url_type_node (GstURLType ** pointer,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType **
|
|
pointer, xmlNode * a_node, GstSegmentBaseType * parent);
|
|
static void gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node);
|
|
static void gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode **
|
|
pointer, xmlNode * a_node);
|
|
static gboolean
|
|
gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer,
|
|
xmlNode * a_node, GstMultSegmentBaseType * parent);
|
|
static gboolean gst_mpdparser_parse_segment_list_node (GstSegmentListNode **
|
|
pointer, xmlNode * a_node, GstSegmentListNode * parent);
|
|
static void
|
|
gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType **
|
|
pointer, xmlNode * a_node);
|
|
static gboolean gst_mpdparser_parse_representation_node (GList ** list,
|
|
xmlNode * a_node, GstAdaptationSetNode * parent,
|
|
GstPeriodNode * period_node);
|
|
static gboolean gst_mpdparser_parse_adaptation_set_node (GList ** list,
|
|
xmlNode * a_node, GstPeriodNode * parent);
|
|
static void gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node);
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer,
|
|
xmlNode * a_node, GstSegmentTemplateNode * parent);
|
|
static gboolean gst_mpdparser_parse_period_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_program_info_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_metrics_range_node (GList ** list,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node);
|
|
static gboolean gst_mpdparser_parse_root_node (GstMPDNode ** pointer,
|
|
xmlNode * a_node);
|
|
static void gst_mpdparser_parse_utctiming_node (GList ** list,
|
|
xmlNode * a_node);
|
|
|
|
/* Helper functions */
|
|
static guint convert_to_millisecs (guint decimals, gint pos);
|
|
static int strncmp_ext (const char *s1, const char *s2);
|
|
static GstStreamPeriod *gst_mpdparser_get_stream_period (GstMpdClient * client);
|
|
static GstSNode *gst_mpdparser_clone_s_node (GstSNode * pointer);
|
|
static GstSegmentTimelineNode
|
|
* gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer);
|
|
static GstRange *gst_mpdparser_clone_range (GstRange * range);
|
|
static GstURLType *gst_mpdparser_clone_URL (GstURLType * url);
|
|
static gchar *gst_mpdparser_parse_baseURL (GstMpdClient * client,
|
|
GstActiveStream * stream, gchar ** query);
|
|
static GstSegmentURLNode *gst_mpdparser_clone_segment_url (GstSegmentURLNode *
|
|
seg_url);
|
|
static gchar *gst_mpdparser_get_mediaURL (GstActiveStream * stream,
|
|
GstSegmentURLNode * segmentURL);
|
|
static const gchar *gst_mpdparser_get_initializationURL (GstActiveStream *
|
|
stream, GstURLType * InitializationURL);
|
|
static gchar *gst_mpdparser_build_URL_from_template (const gchar * url_template,
|
|
const gchar * id, guint number, guint bandwidth, guint64 time);
|
|
static gboolean gst_mpd_client_add_media_segment (GstActiveStream * stream,
|
|
GstSegmentURLNode * url_node, guint number, gint repeat,
|
|
guint64 scale_start, guint64 scale_duration, GstClockTime start,
|
|
GstClockTime duration);
|
|
static const gchar *gst_mpdparser_mimetype_to_caps (const gchar * mimeType);
|
|
static GstClockTime gst_mpd_client_get_segment_duration (GstMpdClient * client,
|
|
GstActiveStream * stream, guint64 * scale_duration);
|
|
static GstDateTime *gst_mpd_client_get_availability_start_time (GstMpdClient *
|
|
client);
|
|
|
|
/* Representation */
|
|
static GstRepresentationNode *gst_mpdparser_get_lowest_representation (GList *
|
|
Representations);
|
|
#if 0
|
|
static GstRepresentationNode *gst_mpdparser_get_highest_representation (GList *
|
|
Representations);
|
|
static GstRepresentationNode
|
|
* gst_mpdparser_get_representation_with_max_bandwidth (GList *
|
|
Representations, gint max_bandwidth);
|
|
#endif
|
|
static GstSegmentBaseType *gst_mpdparser_get_segment_base (GstPeriodNode *
|
|
Period, GstAdaptationSetNode * AdaptationSet,
|
|
GstRepresentationNode * Representation);
|
|
static GstSegmentListNode *gst_mpdparser_get_segment_list (GstMpdClient *
|
|
client, GstPeriodNode * Period, GstAdaptationSetNode * AdaptationSet,
|
|
GstRepresentationNode * Representation);
|
|
|
|
/* Segments */
|
|
static guint gst_mpd_client_get_segments_counts (GstMpdClient * client,
|
|
GstActiveStream * stream);
|
|
|
|
/* Memory management */
|
|
static GstSegmentTimelineNode *gst_mpdparser_segment_timeline_node_new (void);
|
|
static void gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node);
|
|
static void gst_mpdparser_free_prog_info_node (GstProgramInformationNode *
|
|
prog_info_node);
|
|
static void gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node);
|
|
static void gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode *
|
|
metrics_range_node);
|
|
static void gst_mpdparser_free_period_node (GstPeriodNode * period_node);
|
|
static void gst_mpdparser_free_subset_node (GstSubsetNode * subset_node);
|
|
static void gst_mpdparser_free_segment_template_node (GstSegmentTemplateNode *
|
|
segment_template_node);
|
|
static void
|
|
gst_mpdparser_free_representation_base_type (GstRepresentationBaseType *
|
|
representation_base);
|
|
static void gst_mpdparser_free_adaptation_set_node (GstAdaptationSetNode *
|
|
adaptation_set_node);
|
|
static void gst_mpdparser_free_representation_node (GstRepresentationNode *
|
|
representation_node);
|
|
static void gst_mpdparser_free_subrepresentation_node (GstSubRepresentationNode
|
|
* subrep_node);
|
|
static void gst_mpdparser_free_s_node (GstSNode * s_node);
|
|
static void gst_mpdparser_free_segment_timeline_node (GstSegmentTimelineNode *
|
|
seg_timeline);
|
|
static void gst_mpdparser_free_url_type_node (GstURLType * url_type_node);
|
|
static void gst_mpdparser_free_seg_base_type_ext (GstSegmentBaseType *
|
|
seg_base_type);
|
|
static void gst_mpdparser_free_mult_seg_base_type_ext (GstMultSegmentBaseType *
|
|
mult_seg_base_type);
|
|
static void gst_mpdparser_free_segment_list_node (GstSegmentListNode *
|
|
segment_list_node);
|
|
static void gst_mpdparser_free_segment_url_node (GstSegmentURLNode *
|
|
segment_url);
|
|
static void gst_mpdparser_free_base_url_node (GstBaseURL * base_url_node);
|
|
static void gst_mpdparser_free_descriptor_type_node (GstDescriptorType *
|
|
descriptor_type);
|
|
static void gst_mpdparser_free_content_component_node (GstContentComponentNode *
|
|
content_component_node);
|
|
static void gst_mpdparser_free_utctiming_node (GstUTCTimingNode * timing_type);
|
|
static void gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period);
|
|
static void gst_mpdparser_free_media_segment (GstMediaSegment * media_segment);
|
|
static void gst_mpdparser_free_active_stream (GstActiveStream * active_stream);
|
|
|
|
static GstUri *combine_urls (GstUri * base, GList * list, gchar ** query,
|
|
guint idx);
|
|
|
|
static GList *gst_mpd_client_fetch_external_period (GstMpdClient * client,
|
|
GstPeriodNode * period_node);
|
|
static GList *gst_mpd_client_fetch_external_adaptation_set (GstMpdClient *
|
|
client, GstPeriodNode * period, GstAdaptationSetNode * adapt_set);
|
|
|
|
struct GstMpdParserUtcTimingMethod
|
|
{
|
|
const gchar *name;
|
|
GstMPDUTCTimingType method;
|
|
};
|
|
|
|
static const struct GstMpdParserUtcTimingMethod
|
|
gst_mpdparser_utc_timing_methods[] = {
|
|
{"urn:mpeg:dash:utc:ntp:2014", GST_MPD_UTCTIMING_TYPE_NTP},
|
|
{"urn:mpeg:dash:utc:sntp:2014", GST_MPD_UTCTIMING_TYPE_SNTP},
|
|
{"urn:mpeg:dash:utc:http-head:2014", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
|
|
{"urn:mpeg:dash:utc:http-xsdate:2014", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
|
|
{"urn:mpeg:dash:utc:http-iso:2014", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
|
|
{"urn:mpeg:dash:utc:http-ntp:2014", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
|
|
{"urn:mpeg:dash:utc:direct:2014", GST_MPD_UTCTIMING_TYPE_DIRECT},
|
|
/*
|
|
* Early working drafts used the :2012 namespace and this namespace is
|
|
* used by some DASH packagers. To work-around these packagers, we also
|
|
* accept the early draft scheme names.
|
|
*/
|
|
{"urn:mpeg:dash:utc:ntp:2012", GST_MPD_UTCTIMING_TYPE_NTP},
|
|
{"urn:mpeg:dash:utc:sntp:2012", GST_MPD_UTCTIMING_TYPE_SNTP},
|
|
{"urn:mpeg:dash:utc:http-head:2012", GST_MPD_UTCTIMING_TYPE_HTTP_HEAD},
|
|
{"urn:mpeg:dash:utc:http-xsdate:2012", GST_MPD_UTCTIMING_TYPE_HTTP_XSDATE},
|
|
{"urn:mpeg:dash:utc:http-iso:2012", GST_MPD_UTCTIMING_TYPE_HTTP_ISO},
|
|
{"urn:mpeg:dash:utc:http-ntp:2012", GST_MPD_UTCTIMING_TYPE_HTTP_NTP},
|
|
{"urn:mpeg:dash:utc:direct:2012", GST_MPD_UTCTIMING_TYPE_DIRECT},
|
|
{NULL, 0}
|
|
};
|
|
|
|
/* functions to parse node namespaces, content and properties */
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_validated_string (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value,
|
|
gboolean (*validate) (const char *))
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (validate && !(*validate) ((const char *) prop_string)) {
|
|
GST_WARNING ("Validation failure: %s", prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
*property_value = (gchar *) prop_string;
|
|
exists = TRUE;
|
|
GST_LOG (" - %s: %s", property_name, prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_ns_prop_string (xmlNode * a_node,
|
|
const gchar * ns_name, const gchar * property_name, gchar ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string =
|
|
xmlGetNsProp (a_node, (const xmlChar *) property_name,
|
|
(const xmlChar *) ns_name);
|
|
if (prop_string) {
|
|
*property_value = (gchar *) prop_string;
|
|
exists = TRUE;
|
|
GST_LOG (" - %s:%s: %s", ns_name, property_name, prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_string (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value)
|
|
{
|
|
return gst_mpdparser_get_xml_prop_validated_string (a_node, property_name,
|
|
property_value, NULL);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_string_stripped (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value)
|
|
{
|
|
gboolean ret;
|
|
ret =
|
|
gst_mpdparser_get_xml_prop_string (a_node, property_name, property_value);
|
|
if (ret)
|
|
*property_value = g_strstrip (*property_value);
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_validate_no_whitespace (const char *s)
|
|
{
|
|
return !strpbrk (s, "\r\n\t ");
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_string_no_whitespace (xmlNode * a_node,
|
|
const gchar * property_name, gchar ** property_value)
|
|
{
|
|
return gst_mpdparser_get_xml_prop_validated_string (a_node, property_name,
|
|
property_value, gst_mpdparser_validate_no_whitespace);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_string_vector_type (xmlNode * a_node,
|
|
const gchar * property_name, gchar *** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gchar **prop_string_vector = NULL;
|
|
guint i = 0;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
prop_string_vector = g_strsplit ((gchar *) prop_string, " ", -1);
|
|
if (prop_string_vector) {
|
|
exists = TRUE;
|
|
*property_value = prop_string_vector;
|
|
GST_LOG (" - %s:", property_name);
|
|
while (prop_string_vector[i]) {
|
|
GST_LOG (" %s", prop_string_vector[i]);
|
|
i++;
|
|
}
|
|
} else {
|
|
GST_WARNING ("Scan of string vector property failed!");
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_signed_integer (xmlNode * a_node,
|
|
const gchar * property_name, gint default_val, gint * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = default_val;
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (sscanf ((const gchar *) prop_string, "%d", property_value) == 1) {
|
|
exists = TRUE;
|
|
GST_LOG (" - %s: %d", property_name, *property_value);
|
|
} else {
|
|
GST_WARNING
|
|
("failed to parse signed integer property %s from xml string %s",
|
|
property_name, prop_string);
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (xmlNode * a_node,
|
|
const gchar * property_name, guint default_val, guint * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = default_val;
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (sscanf ((gchar *) prop_string, "%u", property_value) == 1 &&
|
|
strstr ((gchar *) prop_string, "-") == NULL) {
|
|
exists = TRUE;
|
|
GST_LOG (" - %s: %u", property_name, *property_value);
|
|
} else {
|
|
GST_WARNING
|
|
("failed to parse unsigned integer property %s from xml string %s",
|
|
property_name, prop_string);
|
|
/* sscanf might have written to *property_value. Restore to default */
|
|
*property_value = default_val;
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_unsigned_integer_64 (xmlNode * a_node,
|
|
const gchar * property_name, guint64 default_val, guint64 * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = default_val;
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (sscanf ((gchar *) prop_string, "%" G_GUINT64_FORMAT,
|
|
property_value) == 1 &&
|
|
strstr ((gchar *) prop_string, "-") == NULL) {
|
|
exists = TRUE;
|
|
GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value);
|
|
} else {
|
|
GST_WARNING
|
|
("failed to parse unsigned integer property %s from xml string %s",
|
|
property_name, prop_string);
|
|
/* sscanf might have written to *property_value. Restore to default */
|
|
*property_value = default_val;
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_uint_vector_type (xmlNode * a_node,
|
|
const gchar * property_name, guint ** property_value, guint * value_size)
|
|
{
|
|
xmlChar *prop_string;
|
|
gchar **str_vector;
|
|
guint *prop_uint_vector = NULL, i;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
str_vector = g_strsplit ((gchar *) prop_string, " ", -1);
|
|
if (str_vector) {
|
|
*value_size = g_strv_length (str_vector);
|
|
prop_uint_vector = g_malloc (*value_size * sizeof (guint));
|
|
if (prop_uint_vector) {
|
|
exists = TRUE;
|
|
GST_LOG (" - %s:", property_name);
|
|
for (i = 0; i < *value_size; i++) {
|
|
if (sscanf ((gchar *) str_vector[i], "%u", &prop_uint_vector[i]) == 1
|
|
&& strstr (str_vector[i], "-") == NULL) {
|
|
GST_LOG (" %u", prop_uint_vector[i]);
|
|
} else {
|
|
GST_WARNING
|
|
("failed to parse uint vector type property %s from xml string %s",
|
|
property_name, str_vector[i]);
|
|
/* there is no special value to put in prop_uint_vector[i] to
|
|
* signal it is invalid, so we just clean everything and return
|
|
* FALSE
|
|
*/
|
|
g_free (prop_uint_vector);
|
|
prop_uint_vector = NULL;
|
|
exists = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
*property_value = prop_uint_vector;
|
|
} else {
|
|
GST_WARNING ("Array allocation failed!");
|
|
}
|
|
} else {
|
|
GST_WARNING ("Scan of uint vector property failed!");
|
|
}
|
|
xmlFree (prop_string);
|
|
g_strfreev (str_vector);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_double (xmlNode * a_node,
|
|
const gchar * property_name, gdouble * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (sscanf ((gchar *) prop_string, "%lf", property_value) == 1) {
|
|
exists = TRUE;
|
|
GST_LOG (" - %s: %lf", property_name, *property_value);
|
|
} else {
|
|
GST_WARNING ("failed to parse double property %s from xml string %s",
|
|
property_name, prop_string);
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_boolean (xmlNode * a_node,
|
|
const gchar * property_name, gboolean default_val,
|
|
gboolean * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = default_val;
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (xmlStrcmp (prop_string, (xmlChar *) "false") == 0) {
|
|
exists = TRUE;
|
|
*property_value = FALSE;
|
|
GST_LOG (" - %s: false", property_name);
|
|
} else if (xmlStrcmp (prop_string, (xmlChar *) "true") == 0) {
|
|
exists = TRUE;
|
|
*property_value = TRUE;
|
|
GST_LOG (" - %s: true", property_name);
|
|
} else {
|
|
GST_WARNING ("failed to parse boolean property %s from xml string %s",
|
|
property_name, prop_string);
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_type (xmlNode * a_node,
|
|
const gchar * property_name, GstMPDFileType * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = GST_MPD_FILE_TYPE_STATIC; /* default */
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (xmlStrcmp (prop_string, (xmlChar *) "OnDemand") == 0
|
|
|| xmlStrcmp (prop_string, (xmlChar *) "static") == 0) {
|
|
exists = TRUE;
|
|
*property_value = GST_MPD_FILE_TYPE_STATIC;
|
|
GST_LOG (" - %s: static", property_name);
|
|
} else if (xmlStrcmp (prop_string, (xmlChar *) "Live") == 0
|
|
|| xmlStrcmp (prop_string, (xmlChar *) "dynamic") == 0) {
|
|
exists = TRUE;
|
|
*property_value = GST_MPD_FILE_TYPE_DYNAMIC;
|
|
GST_LOG (" - %s: dynamic", property_name);
|
|
} else {
|
|
GST_WARNING ("failed to parse MPD type property %s from xml string %s",
|
|
property_name, prop_string);
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_SAP_type (xmlNode * a_node,
|
|
const gchar * property_name, GstSAPType * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
guint prop_SAP_type = 0;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
if (sscanf ((gchar *) prop_string, "%u", &prop_SAP_type) == 1
|
|
&& prop_SAP_type <= 6) {
|
|
exists = TRUE;
|
|
*property_value = (GstSAPType) prop_SAP_type;
|
|
GST_LOG (" - %s: %u", property_name, prop_SAP_type);
|
|
} else {
|
|
GST_WARNING
|
|
("failed to parse unsigned integer property %s from xml string %s",
|
|
property_name, prop_string);
|
|
}
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_range (xmlNode * a_node, const gchar * property_name,
|
|
GstRange ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
guint64 first_byte_pos = 0, last_byte_pos = -1;
|
|
guint len, pos;
|
|
gchar *str;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
len = xmlStrlen (prop_string);
|
|
str = (gchar *) prop_string;
|
|
GST_TRACE ("range: %s, len %d", str, len);
|
|
|
|
/* read "-" */
|
|
pos = strcspn (str, "-");
|
|
if (pos >= len) {
|
|
GST_TRACE ("pos %d >= len %d", pos, len);
|
|
goto error;
|
|
}
|
|
/* read first_byte_pos */
|
|
if (pos != 0) {
|
|
/* replace str[pos] with '\0' to allow sscanf to not be confused by
|
|
* the minus sign (eg " -1" (observe the space before -) would otherwise
|
|
* be interpreted as range -1 to 1)
|
|
*/
|
|
str[pos] = 0;
|
|
if (sscanf (str, "%" G_GUINT64_FORMAT, &first_byte_pos) != 1 ||
|
|
strstr (str, "-") != NULL) {
|
|
/* sscanf failed or it found a negative number */
|
|
/* restore the '-' sign */
|
|
str[pos] = '-';
|
|
goto error;
|
|
}
|
|
/* restore the '-' sign */
|
|
str[pos] = '-';
|
|
}
|
|
/* read last_byte_pos */
|
|
if (pos < (len - 1)) {
|
|
if (sscanf (str + pos + 1, "%" G_GUINT64_FORMAT, &last_byte_pos) != 1 ||
|
|
strstr (str + pos + 1, "-") != NULL) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* malloc return data structure */
|
|
*property_value = g_slice_new0 (GstRange);
|
|
exists = TRUE;
|
|
(*property_value)->first_byte_pos = first_byte_pos;
|
|
(*property_value)->last_byte_pos = last_byte_pos;
|
|
xmlFree (prop_string);
|
|
GST_LOG (" - %s: %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT,
|
|
property_name, first_byte_pos, last_byte_pos);
|
|
}
|
|
|
|
return exists;
|
|
|
|
error:
|
|
GST_WARNING ("failed to parse property %s from xml string %s", property_name,
|
|
prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_ratio (xmlNode * a_node,
|
|
const gchar * property_name, GstRatio ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
guint num = 0, den = 1;
|
|
guint len, pos;
|
|
gchar *str;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
len = xmlStrlen (prop_string);
|
|
str = (gchar *) prop_string;
|
|
GST_TRACE ("ratio: %s, len %d", str, len);
|
|
|
|
/* read ":" */
|
|
pos = strcspn (str, ":");
|
|
if (pos >= len) {
|
|
GST_TRACE ("pos %d >= len %d", pos, len);
|
|
goto error;
|
|
}
|
|
/* search for negative sign */
|
|
if (strstr (str, "-") != NULL) {
|
|
goto error;
|
|
}
|
|
/* read num */
|
|
if (pos != 0) {
|
|
if (sscanf (str, "%u", &num) != 1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* read den */
|
|
if (pos < (len - 1)) {
|
|
if (sscanf (str + pos + 1, "%u", &den) != 1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* malloc return data structure */
|
|
*property_value = g_slice_new0 (GstRatio);
|
|
exists = TRUE;
|
|
(*property_value)->num = num;
|
|
(*property_value)->den = den;
|
|
xmlFree (prop_string);
|
|
GST_LOG (" - %s: %u:%u", property_name, num, den);
|
|
}
|
|
|
|
return exists;
|
|
|
|
error:
|
|
GST_WARNING ("failed to parse property %s from xml string %s", property_name,
|
|
prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_framerate (xmlNode * a_node,
|
|
const gchar * property_name, GstFrameRate ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
guint num = 0, den = 1;
|
|
guint len, pos;
|
|
gchar *str;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
len = xmlStrlen (prop_string);
|
|
str = (gchar *) prop_string;
|
|
GST_TRACE ("framerate: %s, len %d", str, len);
|
|
|
|
/* search for negative sign */
|
|
if (strstr (str, "-") != NULL) {
|
|
goto error;
|
|
}
|
|
|
|
/* read "/" if available */
|
|
pos = strcspn (str, "/");
|
|
/* read num */
|
|
if (pos != 0) {
|
|
if (sscanf (str, "%u", &num) != 1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* read den (if available) */
|
|
if (pos < (len - 1)) {
|
|
if (sscanf (str + pos + 1, "%u", &den) != 1) {
|
|
goto error;
|
|
}
|
|
}
|
|
/* alloc return data structure */
|
|
*property_value = g_slice_new0 (GstFrameRate);
|
|
exists = TRUE;
|
|
(*property_value)->num = num;
|
|
(*property_value)->den = den;
|
|
xmlFree (prop_string);
|
|
if (den == 1)
|
|
GST_LOG (" - %s: %u", property_name, num);
|
|
else
|
|
GST_LOG (" - %s: %u/%u", property_name, num, den);
|
|
}
|
|
|
|
return exists;
|
|
|
|
error:
|
|
GST_WARNING ("failed to parse property %s from xml string %s", property_name,
|
|
prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_cond_uint (xmlNode * a_node,
|
|
const gchar * property_name, GstConditionalUintType ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gchar *str;
|
|
gboolean flag;
|
|
guint val;
|
|
gboolean exists = FALSE;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
str = (gchar *) prop_string;
|
|
GST_TRACE ("conditional uint: %s", str);
|
|
|
|
if (strcmp (str, "false") == 0) {
|
|
flag = FALSE;
|
|
val = 0;
|
|
} else if (strcmp (str, "true") == 0) {
|
|
flag = TRUE;
|
|
val = 0;
|
|
} else {
|
|
flag = TRUE;
|
|
if (sscanf (str, "%u", &val) != 1 || strstr (str, "-") != NULL)
|
|
goto error;
|
|
}
|
|
|
|
/* alloc return data structure */
|
|
*property_value = g_slice_new0 (GstConditionalUintType);
|
|
exists = TRUE;
|
|
(*property_value)->flag = flag;
|
|
(*property_value)->value = val;
|
|
xmlFree (prop_string);
|
|
GST_LOG (" - %s: flag=%s val=%u", property_name, flag ? "true" : "false",
|
|
val);
|
|
}
|
|
|
|
return exists;
|
|
|
|
error:
|
|
GST_WARNING ("failed to parse property %s from xml string %s", property_name,
|
|
prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
DateTime Data Type
|
|
|
|
The dateTime data type is used to specify a date and a time.
|
|
|
|
The lexical form of xs:dateTime is YYYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
|
|
|
|
* YYYY indicates the year
|
|
* MM indicates the month
|
|
* DD indicates the day
|
|
* T indicates the start of the required time section
|
|
* hh indicates the hour
|
|
* mm indicates the minute
|
|
* ss indicates the second
|
|
|
|
The time zone may be specified as Z (UTC) or (+|-)hh:mm
|
|
*/
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_dateTime (xmlNode * a_node,
|
|
const gchar * property_name, GstDateTime ** property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gchar *str;
|
|
gint ret, pos;
|
|
gint year, month, day, hour, minute;
|
|
gdouble second;
|
|
gboolean exists = FALSE;
|
|
gfloat tzoffset = 0.0;
|
|
gint gmt_offset_hour = -99, gmt_offset_min = -99;
|
|
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
str = (gchar *) prop_string;
|
|
GST_TRACE ("dateTime: %s, len %d", str, xmlStrlen (prop_string));
|
|
/* parse year */
|
|
ret = sscanf (str, "%d", &year);
|
|
if (ret != 1 || year <= 0)
|
|
goto error;
|
|
pos = strcspn (str, "-");
|
|
str += (pos + 1);
|
|
GST_TRACE (" - year %d", year);
|
|
/* parse month */
|
|
ret = sscanf (str, "%d", &month);
|
|
if (ret != 1 || month <= 0)
|
|
goto error;
|
|
pos = strcspn (str, "-");
|
|
str += (pos + 1);
|
|
GST_TRACE (" - month %d", month);
|
|
/* parse day */
|
|
ret = sscanf (str, "%d", &day);
|
|
if (ret != 1 || day <= 0)
|
|
goto error;
|
|
pos = strcspn (str, "T");
|
|
str += (pos + 1);
|
|
GST_TRACE (" - day %d", day);
|
|
/* parse hour */
|
|
ret = sscanf (str, "%d", &hour);
|
|
if (ret != 1 || hour < 0)
|
|
goto error;
|
|
pos = strcspn (str, ":");
|
|
str += (pos + 1);
|
|
GST_TRACE (" - hour %d", hour);
|
|
/* parse minute */
|
|
ret = sscanf (str, "%d", &minute);
|
|
if (ret != 1 || minute < 0)
|
|
goto error;
|
|
pos = strcspn (str, ":");
|
|
str += (pos + 1);
|
|
GST_TRACE (" - minute %d", minute);
|
|
/* parse second */
|
|
ret = sscanf (str, "%lf", &second);
|
|
if (ret != 1 || second < 0)
|
|
goto error;
|
|
GST_TRACE (" - second %lf", second);
|
|
|
|
GST_LOG (" - %s: %4d/%02d/%02d %02d:%02d:%09.6lf", property_name,
|
|
year, month, day, hour, minute, second);
|
|
|
|
if (strrchr (str, '+') || strrchr (str, '-')) {
|
|
/* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */
|
|
gint gmt_offset = -1;
|
|
gchar *plus_pos = NULL;
|
|
gchar *neg_pos = NULL;
|
|
gchar *pos = NULL;
|
|
|
|
GST_LOG ("Checking for timezone information");
|
|
|
|
/* check if there is timezone info */
|
|
plus_pos = strrchr (str, '+');
|
|
neg_pos = strrchr (str, '-');
|
|
if (plus_pos)
|
|
pos = plus_pos + 1;
|
|
else if (neg_pos)
|
|
pos = neg_pos + 1;
|
|
|
|
if (pos && strlen (pos) >= 3) {
|
|
gint ret_tz;
|
|
if (pos[2] == ':')
|
|
ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min);
|
|
else
|
|
ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min);
|
|
|
|
GST_DEBUG ("Parsing timezone: %s", pos);
|
|
|
|
if (ret_tz == 2) {
|
|
if (neg_pos != NULL && neg_pos + 1 == pos) {
|
|
gmt_offset_hour *= -1;
|
|
gmt_offset_min *= -1;
|
|
}
|
|
gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
|
|
|
|
tzoffset = gmt_offset / 60.0;
|
|
|
|
GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset);
|
|
} else
|
|
GST_WARNING ("Failed to parse timezone information");
|
|
}
|
|
}
|
|
|
|
exists = TRUE;
|
|
*property_value =
|
|
gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
|
|
xmlFree (prop_string);
|
|
}
|
|
|
|
return exists;
|
|
|
|
error:
|
|
GST_WARNING ("failed to parse property %s from xml string %s", property_name,
|
|
prop_string);
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
Duration Data Type
|
|
|
|
The duration data type is used to specify a time interval.
|
|
|
|
The time interval is specified in the following form "-PnYnMnDTnHnMnS" where:
|
|
|
|
* - indicates the negative sign (optional)
|
|
* P indicates the period (required)
|
|
* nY indicates the number of years
|
|
* nM indicates the number of months
|
|
* nD indicates the number of days
|
|
* T indicates the start of a time section (required if you are going to specify hours, minutes, or seconds)
|
|
* nH indicates the number of hours
|
|
* nM indicates the number of minutes
|
|
* nS indicates the number of seconds
|
|
*/
|
|
|
|
/* this function computes decimals * 10 ^ (3 - pos) */
|
|
static guint
|
|
convert_to_millisecs (guint decimals, gint pos)
|
|
{
|
|
guint num = 1, den = 1;
|
|
gint i = 3 - pos;
|
|
|
|
while (i < 0) {
|
|
den *= 10;
|
|
i++;
|
|
}
|
|
while (i > 0) {
|
|
num *= 10;
|
|
i--;
|
|
}
|
|
/* if i == 0 we have exactly 3 decimals and nothing to do */
|
|
return decimals * num / den;
|
|
}
|
|
|
|
static gboolean
|
|
accumulate (guint64 * v, guint64 mul, guint64 add)
|
|
{
|
|
guint64 tmp;
|
|
|
|
if (*v > G_MAXUINT64 / mul)
|
|
return FALSE;
|
|
tmp = *v * mul;
|
|
if (tmp > G_MAXUINT64 - add)
|
|
return FALSE;
|
|
*v = tmp + add;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_duration (const char *str, guint64 * value)
|
|
{
|
|
gint ret, len, pos, posT;
|
|
gint years = -1, months = -1, days = -1, hours = -1, minutes = -1, seconds =
|
|
-1, decimals = -1, read;
|
|
gboolean have_ms = FALSE;
|
|
guint64 tmp_value;
|
|
|
|
len = strlen (str);
|
|
GST_TRACE ("duration: %s, len %d", str, len);
|
|
if (strspn (str, "PT0123456789., \tHMDSY") < len) {
|
|
GST_WARNING ("Invalid character found: '%s'", str);
|
|
goto error;
|
|
}
|
|
/* skip leading/trailing whitespace */
|
|
while (g_ascii_isspace (str[0])) {
|
|
str++;
|
|
len--;
|
|
}
|
|
while (len > 0 && g_ascii_isspace (str[len - 1]))
|
|
--len;
|
|
|
|
/* read "P" for period */
|
|
if (str[0] != 'P') {
|
|
GST_WARNING ("P not found at the beginning of the string!");
|
|
goto error;
|
|
}
|
|
str++;
|
|
len--;
|
|
|
|
/* read "T" for time (if present) */
|
|
posT = strcspn (str, "T");
|
|
len -= posT;
|
|
if (posT > 0) {
|
|
/* there is some room between P and T, so there must be a period section */
|
|
/* read years, months, days */
|
|
do {
|
|
GST_TRACE ("parsing substring %s", str);
|
|
pos = strcspn (str, "YMD");
|
|
ret = sscanf (str, "%u", &read);
|
|
if (ret != 1) {
|
|
GST_WARNING ("can not read integer value from string %s!", str);
|
|
goto error;
|
|
}
|
|
switch (str[pos]) {
|
|
case 'Y':
|
|
if (years != -1 || months != -1 || days != -1) {
|
|
GST_WARNING ("year, month or day was already set");
|
|
goto error;
|
|
}
|
|
years = read;
|
|
break;
|
|
case 'M':
|
|
if (months != -1 || days != -1) {
|
|
GST_WARNING ("month or day was already set");
|
|
goto error;
|
|
}
|
|
months = read;
|
|
if (months >= 12) {
|
|
GST_WARNING ("Month out of range");
|
|
goto error;
|
|
}
|
|
break;
|
|
case 'D':
|
|
if (days != -1) {
|
|
GST_WARNING ("day was already set");
|
|
goto error;
|
|
}
|
|
days = read;
|
|
if (days >= 31) {
|
|
GST_WARNING ("Day out of range");
|
|
goto error;
|
|
}
|
|
break;
|
|
default:
|
|
GST_WARNING ("unexpected char %c!", str[pos]);
|
|
goto error;
|
|
break;
|
|
}
|
|
GST_TRACE ("read number %u type %c", read, str[pos]);
|
|
str += (pos + 1);
|
|
posT -= (pos + 1);
|
|
} while (posT > 0);
|
|
}
|
|
|
|
if (years == -1)
|
|
years = 0;
|
|
if (months == -1)
|
|
months = 0;
|
|
if (days == -1)
|
|
days = 0;
|
|
|
|
GST_TRACE ("Y:M:D=%d:%d:%d", years, months, days);
|
|
|
|
/* read "T" for time (if present) */
|
|
/* here T is at pos == 0 */
|
|
str++;
|
|
len--;
|
|
pos = 0;
|
|
if (pos < len) {
|
|
/* T found, there is a time section */
|
|
/* read hours, minutes, seconds, hundredths of second */
|
|
do {
|
|
GST_TRACE ("parsing substring %s", str);
|
|
pos = strcspn (str, "HMS,.");
|
|
ret = sscanf (str, "%u", &read);
|
|
if (ret != 1) {
|
|
GST_WARNING ("can not read integer value from string %s!", str);
|
|
goto error;
|
|
}
|
|
switch (str[pos]) {
|
|
case 'H':
|
|
if (hours != -1 || minutes != -1 || seconds != -1) {
|
|
GST_WARNING ("hour, minute or second was already set");
|
|
goto error;
|
|
}
|
|
hours = read;
|
|
if (hours >= 24) {
|
|
GST_WARNING ("Hour out of range");
|
|
goto error;
|
|
}
|
|
break;
|
|
case 'M':
|
|
if (minutes != -1 || seconds != -1) {
|
|
GST_WARNING ("minute or second was already set");
|
|
goto error;
|
|
}
|
|
minutes = read;
|
|
if (minutes >= 60) {
|
|
GST_WARNING ("Minute out of range");
|
|
goto error;
|
|
}
|
|
break;
|
|
case 'S':
|
|
if (have_ms) {
|
|
/* we have read the decimal part of the seconds */
|
|
decimals = convert_to_millisecs (read, pos);
|
|
GST_TRACE ("decimal number %u (%d digits) -> %d ms", read, pos,
|
|
decimals);
|
|
} else {
|
|
if (seconds != -1) {
|
|
GST_WARNING ("second was already set");
|
|
goto error;
|
|
}
|
|
/* no decimals */
|
|
seconds = read;
|
|
}
|
|
break;
|
|
case '.':
|
|
case ',':
|
|
/* we have read the integer part of a decimal number in seconds */
|
|
if (seconds != -1) {
|
|
GST_WARNING ("second was already set");
|
|
goto error;
|
|
}
|
|
seconds = read;
|
|
have_ms = TRUE;
|
|
break;
|
|
default:
|
|
GST_WARNING ("unexpected char %c!", str[pos]);
|
|
goto error;
|
|
break;
|
|
}
|
|
GST_TRACE ("read number %u type %c", read, str[pos]);
|
|
str += pos + 1;
|
|
len -= (pos + 1);
|
|
} while (len > 0);
|
|
}
|
|
|
|
if (hours == -1)
|
|
hours = 0;
|
|
if (minutes == -1)
|
|
minutes = 0;
|
|
if (seconds == -1)
|
|
seconds = 0;
|
|
if (decimals == -1)
|
|
decimals = 0;
|
|
GST_TRACE ("H:M:S.MS=%d:%d:%d.%03d", hours, minutes, seconds, decimals);
|
|
|
|
tmp_value = 0;
|
|
if (!accumulate (&tmp_value, 1, years)
|
|
|| !accumulate (&tmp_value, 365, months * 30)
|
|
|| !accumulate (&tmp_value, 1, days)
|
|
|| !accumulate (&tmp_value, 24, hours)
|
|
|| !accumulate (&tmp_value, 60, minutes)
|
|
|| !accumulate (&tmp_value, 60, seconds)
|
|
|| !accumulate (&tmp_value, 1000, decimals))
|
|
goto error;
|
|
|
|
/* ensure it can be converted from milliseconds to nanoseconds */
|
|
if (tmp_value > G_MAXUINT64 / 1000000)
|
|
goto error;
|
|
|
|
*value = tmp_value;
|
|
return TRUE;
|
|
|
|
error:
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_prop_duration (xmlNode * a_node,
|
|
const gchar * property_name, guint64 default_value,
|
|
guint64 * property_value)
|
|
{
|
|
xmlChar *prop_string;
|
|
gchar *str;
|
|
gboolean exists = FALSE;
|
|
|
|
*property_value = default_value;
|
|
prop_string = xmlGetProp (a_node, (const xmlChar *) property_name);
|
|
if (prop_string) {
|
|
str = (gchar *) prop_string;
|
|
if (!gst_mpdparser_parse_duration (str, property_value))
|
|
goto error;
|
|
GST_LOG (" - %s: %" G_GUINT64_FORMAT, property_name, *property_value);
|
|
xmlFree (prop_string);
|
|
exists = TRUE;
|
|
}
|
|
return exists;
|
|
|
|
error:
|
|
xmlFree (prop_string);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_node_content (xmlNode * a_node, gchar ** content)
|
|
{
|
|
xmlChar *node_content = NULL;
|
|
gboolean exists = FALSE;
|
|
|
|
node_content = xmlNodeGetContent (a_node);
|
|
if (node_content) {
|
|
exists = TRUE;
|
|
*content = (gchar *) node_content;
|
|
GST_LOG (" - %s: %s", a_node->name, *content);
|
|
}
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_get_xml_node_as_string (xmlNode * a_node, gchar ** content)
|
|
{
|
|
gboolean exists = FALSE;
|
|
const char *txt_encoding;
|
|
xmlOutputBufferPtr out_buf;
|
|
|
|
txt_encoding = (const char *) a_node->doc->encoding;
|
|
out_buf = xmlAllocOutputBuffer (NULL);
|
|
g_assert (out_buf != NULL);
|
|
xmlNodeDumpOutput (out_buf, a_node->doc, a_node, 0, 0, txt_encoding);
|
|
xmlOutputBufferFlush (out_buf);
|
|
#ifdef LIBXML2_NEW_BUFFER
|
|
if (xmlOutputBufferGetSize (out_buf) > 0) {
|
|
*content =
|
|
(gchar *) xmlStrndup (xmlOutputBufferGetContent (out_buf),
|
|
xmlOutputBufferGetSize (out_buf));
|
|
exists = TRUE;
|
|
}
|
|
#else
|
|
if (out_buf->conv && out_buf->conv->use > 0) {
|
|
*content =
|
|
(gchar *) xmlStrndup (out_buf->conv->content, out_buf->conv->use);
|
|
exists = TRUE;
|
|
} else if (out_buf->buffer && out_buf->buffer->use > 0) {
|
|
*content =
|
|
(gchar *) xmlStrndup (out_buf->buffer->content, out_buf->buffer->use);
|
|
exists = TRUE;
|
|
}
|
|
#endif // LIBXML2_NEW_BUFFER
|
|
(void) xmlOutputBufferClose (out_buf);
|
|
|
|
if (exists) {
|
|
GST_LOG (" - %s: %s", a_node->name, *content);
|
|
}
|
|
return exists;
|
|
}
|
|
|
|
static gchar *
|
|
gst_mpdparser_get_xml_node_namespace (xmlNode * a_node, const gchar * prefix)
|
|
{
|
|
xmlNs *curr_ns;
|
|
gchar *namespace = NULL;
|
|
|
|
if (prefix == NULL) {
|
|
/* return the default namespace */
|
|
if (a_node->ns) {
|
|
namespace = xmlMemStrdup ((const gchar *) a_node->ns->href);
|
|
if (namespace) {
|
|
GST_LOG (" - default namespace: %s", namespace);
|
|
}
|
|
}
|
|
} else {
|
|
/* look for the specified prefix in the namespace list */
|
|
for (curr_ns = a_node->ns; curr_ns; curr_ns = curr_ns->next) {
|
|
if (xmlStrcmp (curr_ns->prefix, (xmlChar *) prefix) == 0) {
|
|
namespace = xmlMemStrdup ((const gchar *) curr_ns->href);
|
|
if (namespace) {
|
|
GST_LOG (" - %s namespace: %s", curr_ns->prefix, curr_ns->href);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return namespace;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_baseURL_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstBaseURL *new_base_url;
|
|
|
|
new_base_url = g_slice_new0 (GstBaseURL);
|
|
*list = g_list_append (*list, new_base_url);
|
|
|
|
GST_LOG ("content of BaseURL node:");
|
|
gst_mpdparser_get_xml_node_content (a_node, &new_base_url->baseURL);
|
|
|
|
GST_LOG ("attributes of BaseURL node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "serviceLocation",
|
|
&new_base_url->serviceLocation);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "byteRange",
|
|
&new_base_url->byteRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_descriptor_type_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstDescriptorType *new_descriptor;
|
|
|
|
new_descriptor = g_slice_new0 (GstDescriptorType);
|
|
*list = g_list_append (*list, new_descriptor);
|
|
|
|
GST_LOG ("attributes of %s node:", a_node->name);
|
|
gst_mpdparser_get_xml_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
if (!gst_mpdparser_get_xml_prop_string (a_node, "value",
|
|
&new_descriptor->value)) {
|
|
/* if no value attribute, use XML string representation of the node */
|
|
gst_mpdparser_get_xml_node_as_string (a_node, &new_descriptor->value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_content_component_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstContentComponentNode *new_content_component;
|
|
|
|
new_content_component = g_slice_new0 (GstContentComponentNode);
|
|
*list = g_list_append (*list, new_content_component);
|
|
|
|
GST_LOG ("attributes of ContentComponent node:");
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0,
|
|
&new_content_component->id);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "lang",
|
|
&new_content_component->lang);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "contentType",
|
|
&new_content_component->contentType);
|
|
gst_mpdparser_get_xml_prop_ratio (a_node, "par", &new_content_component->par);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node
|
|
(&new_content_component->Accessibility, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node (&new_content_component->Role,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node
|
|
(&new_content_component->Rating, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node
|
|
(&new_content_component->Viewpoint, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_location_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
gchar *location = NULL;
|
|
|
|
GST_LOG ("content of Location node:");
|
|
if (gst_mpdparser_get_xml_node_content (a_node, &location))
|
|
*list = g_list_append (*list, location);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_subrepresentation_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstSubRepresentationNode *new_subrep;
|
|
|
|
new_subrep = g_slice_new0 (GstSubRepresentationNode);
|
|
*list = g_list_append (*list, new_subrep);
|
|
|
|
GST_LOG ("attributes of SubRepresentation node:");
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "level", 0,
|
|
&new_subrep->level);
|
|
gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "dependencyLevel",
|
|
&new_subrep->dependencyLevel, &new_subrep->size);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0,
|
|
&new_subrep->bandwidth);
|
|
gst_mpdparser_get_xml_prop_string_vector_type (a_node,
|
|
"contentComponent", &new_subrep->contentComponent);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base_type (&new_subrep->RepresentationBase,
|
|
a_node);
|
|
}
|
|
|
|
static GstSegmentURLNode *
|
|
gst_mpdparser_clone_segment_url (GstSegmentURLNode * seg_url)
|
|
{
|
|
GstSegmentURLNode *clone = NULL;
|
|
|
|
if (seg_url) {
|
|
clone = g_slice_new0 (GstSegmentURLNode);
|
|
clone->media = xmlMemStrdup (seg_url->media);
|
|
clone->mediaRange = gst_mpdparser_clone_range (seg_url->mediaRange);
|
|
clone->index = xmlMemStrdup (seg_url->index);
|
|
clone->indexRange = gst_mpdparser_clone_range (seg_url->indexRange);
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_segment_url_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstSegmentURLNode *new_segment_url;
|
|
|
|
new_segment_url = g_slice_new0 (GstSegmentURLNode);
|
|
*list = g_list_append (*list, new_segment_url);
|
|
|
|
GST_LOG ("attributes of SegmentURL node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "media", &new_segment_url->media);
|
|
gst_mpdparser_get_xml_prop_range (a_node, "mediaRange",
|
|
&new_segment_url->mediaRange);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "index", &new_segment_url->index);
|
|
gst_mpdparser_get_xml_prop_range (a_node, "indexRange",
|
|
&new_segment_url->indexRange);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_url_type_node (GstURLType ** pointer, xmlNode * a_node)
|
|
{
|
|
GstURLType *new_url_type;
|
|
|
|
gst_mpdparser_free_url_type_node (*pointer);
|
|
*pointer = new_url_type = g_slice_new0 (GstURLType);
|
|
|
|
GST_LOG ("attributes of URLType node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "sourceURL",
|
|
&new_url_type->sourceURL);
|
|
gst_mpdparser_get_xml_prop_range (a_node, "range", &new_url_type->range);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_seg_base_type_ext (GstSegmentBaseType ** pointer,
|
|
xmlNode * a_node, GstSegmentBaseType * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstSegmentBaseType *seg_base_type;
|
|
guint intval;
|
|
guint64 int64val;
|
|
gboolean boolval;
|
|
GstRange *rangeval;
|
|
|
|
gst_mpdparser_free_seg_base_type_ext (*pointer);
|
|
*pointer = seg_base_type = g_slice_new0 (GstSegmentBaseType);
|
|
|
|
/* Initialize values that have defaults */
|
|
seg_base_type->indexRangeExact = FALSE;
|
|
seg_base_type->timescale = 1;
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
seg_base_type->timescale = parent->timescale;
|
|
seg_base_type->presentationTimeOffset = parent->presentationTimeOffset;
|
|
seg_base_type->indexRange = gst_mpdparser_clone_range (parent->indexRange);
|
|
seg_base_type->indexRangeExact = parent->indexRangeExact;
|
|
seg_base_type->Initialization =
|
|
gst_mpdparser_clone_URL (parent->Initialization);
|
|
seg_base_type->RepresentationIndex =
|
|
gst_mpdparser_clone_URL (parent->RepresentationIndex);
|
|
}
|
|
|
|
/* We must retrieve each value first to see if it exists. If it does not
|
|
* exist, we do not want to overwrite an inherited value */
|
|
GST_LOG ("attributes of SegmentBaseType extension:");
|
|
if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "timescale", 1,
|
|
&intval)) {
|
|
seg_base_type->timescale = intval;
|
|
}
|
|
if (gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node,
|
|
"presentationTimeOffset", 0, &int64val)) {
|
|
seg_base_type->presentationTimeOffset = int64val;
|
|
}
|
|
if (gst_mpdparser_get_xml_prop_range (a_node, "indexRange", &rangeval)) {
|
|
if (seg_base_type->indexRange) {
|
|
g_slice_free (GstRange, seg_base_type->indexRange);
|
|
}
|
|
seg_base_type->indexRange = rangeval;
|
|
}
|
|
if (gst_mpdparser_get_xml_prop_boolean (a_node, "indexRangeExact",
|
|
FALSE, &boolval)) {
|
|
seg_base_type->indexRangeExact = boolval;
|
|
}
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Initialization") == 0 ||
|
|
xmlStrcmp (cur_node->name, (xmlChar *) "Initialisation") == 0) {
|
|
/* parse will free the previous pointer to create a new one */
|
|
gst_mpdparser_parse_url_type_node (&seg_base_type->Initialization,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "RepresentationIndex") == 0) {
|
|
/* parse will free the previous pointer to create a new one */
|
|
gst_mpdparser_parse_url_type_node (&seg_base_type->RepresentationIndex,
|
|
cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstSNode *
|
|
gst_mpdparser_clone_s_node (GstSNode * pointer)
|
|
{
|
|
GstSNode *clone = NULL;
|
|
|
|
if (pointer) {
|
|
clone = g_slice_new0 (GstSNode);
|
|
clone->t = pointer->t;
|
|
clone->d = pointer->d;
|
|
clone->r = pointer->r;
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_s_node (GQueue * queue, xmlNode * a_node)
|
|
{
|
|
GstSNode *new_s_node;
|
|
|
|
new_s_node = g_slice_new0 (GstSNode);
|
|
g_queue_push_tail (queue, new_s_node);
|
|
|
|
GST_LOG ("attributes of S node:");
|
|
gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node, "t", 0,
|
|
&new_s_node->t);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer_64 (a_node, "d", 0,
|
|
&new_s_node->d);
|
|
gst_mpdparser_get_xml_prop_signed_integer (a_node, "r", 0, &new_s_node->r);
|
|
}
|
|
|
|
static GstSegmentTimelineNode *
|
|
gst_mpdparser_clone_segment_timeline (GstSegmentTimelineNode * pointer)
|
|
{
|
|
GstSegmentTimelineNode *clone = NULL;
|
|
|
|
if (pointer) {
|
|
clone = gst_mpdparser_segment_timeline_node_new ();
|
|
if (clone) {
|
|
GList *list;
|
|
for (list = g_queue_peek_head_link (&pointer->S); list;
|
|
list = g_list_next (list)) {
|
|
GstSNode *s_node;
|
|
s_node = (GstSNode *) list->data;
|
|
if (s_node) {
|
|
g_queue_push_tail (&clone->S, gst_mpdparser_clone_s_node (s_node));
|
|
}
|
|
}
|
|
} else {
|
|
GST_WARNING ("Allocation of SegmentTimeline node failed!");
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_segment_timeline_node (GstSegmentTimelineNode ** pointer,
|
|
xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstSegmentTimelineNode *new_seg_timeline;
|
|
|
|
gst_mpdparser_free_segment_timeline_node (*pointer);
|
|
*pointer = new_seg_timeline = gst_mpdparser_segment_timeline_node_new ();
|
|
if (new_seg_timeline == NULL) {
|
|
GST_WARNING ("Allocation of SegmentTimeline node failed!");
|
|
return;
|
|
}
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "S") == 0) {
|
|
gst_mpdparser_parse_s_node (&new_seg_timeline->S, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_mult_seg_base_type_ext (GstMultSegmentBaseType ** pointer,
|
|
xmlNode * a_node, GstMultSegmentBaseType * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMultSegmentBaseType *mult_seg_base_type;
|
|
guint intval;
|
|
gboolean has_timeline = FALSE, has_duration = FALSE;
|
|
|
|
gst_mpdparser_free_mult_seg_base_type_ext (*pointer);
|
|
mult_seg_base_type = g_slice_new0 (GstMultSegmentBaseType);
|
|
|
|
mult_seg_base_type->duration = 0;
|
|
mult_seg_base_type->startNumber = 1;
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
mult_seg_base_type->duration = parent->duration;
|
|
mult_seg_base_type->startNumber = parent->startNumber;
|
|
mult_seg_base_type->SegmentTimeline =
|
|
gst_mpdparser_clone_segment_timeline (parent->SegmentTimeline);
|
|
mult_seg_base_type->BitstreamSwitching =
|
|
gst_mpdparser_clone_URL (parent->BitstreamSwitching);
|
|
}
|
|
|
|
GST_LOG ("attributes of MultipleSegmentBaseType extension:");
|
|
if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "duration", 0,
|
|
&intval)) {
|
|
mult_seg_base_type->duration = intval;
|
|
}
|
|
|
|
/* duration might be specified from parent */
|
|
if (mult_seg_base_type->duration)
|
|
has_duration = TRUE;
|
|
|
|
if (gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "startNumber", 1,
|
|
&intval)) {
|
|
mult_seg_base_type->startNumber = intval;
|
|
}
|
|
|
|
GST_LOG ("extension of MultipleSegmentBaseType extension:");
|
|
gst_mpdparser_parse_seg_base_type_ext (&mult_seg_base_type->SegBaseType,
|
|
a_node, (parent ? parent->SegBaseType : NULL));
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTimeline") == 0) {
|
|
/* parse frees the segmenttimeline if any */
|
|
gst_mpdparser_parse_segment_timeline_node
|
|
(&mult_seg_base_type->SegmentTimeline, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "BitstreamSwitching") == 0) {
|
|
/* parse frees the old url before setting the new one */
|
|
gst_mpdparser_parse_url_type_node
|
|
(&mult_seg_base_type->BitstreamSwitching, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
has_timeline = mult_seg_base_type->SegmentTimeline != NULL;
|
|
|
|
/* Checking duration and timeline only at Representation's child level */
|
|
if (xmlStrcmp (a_node->parent->name, (xmlChar *) "Representation") == 0
|
|
&& !has_duration && !has_timeline) {
|
|
GST_ERROR ("segment has neither duration nor timeline");
|
|
goto error;
|
|
}
|
|
|
|
*pointer = mult_seg_base_type;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_mult_seg_base_type_ext (mult_seg_base_type);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_list_node (GstSegmentListNode ** pointer,
|
|
xmlNode * a_node, GstSegmentListNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstSegmentListNode *new_segment_list;
|
|
gchar *actuate;
|
|
gboolean segment_urls_inherited_from_parent = FALSE;
|
|
|
|
gst_mpdparser_free_segment_list_node (*pointer);
|
|
new_segment_list = g_slice_new0 (GstSegmentListNode);
|
|
|
|
/* Inherit attribute values from parent */
|
|
if (parent) {
|
|
GList *list;
|
|
GstSegmentURLNode *seg_url;
|
|
for (list = g_list_first (parent->SegmentURL); list;
|
|
list = g_list_next (list)) {
|
|
seg_url = (GstSegmentURLNode *) list->data;
|
|
new_segment_list->SegmentURL =
|
|
g_list_append (new_segment_list->SegmentURL,
|
|
gst_mpdparser_clone_segment_url (seg_url));
|
|
segment_urls_inherited_from_parent = TRUE;
|
|
}
|
|
}
|
|
|
|
new_segment_list->actuate = GST_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_segment_list->xlink_href)
|
|
&& gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_segment_list->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
GST_LOG ("extension of SegmentList node:");
|
|
if (!gst_mpdparser_parse_mult_seg_base_type_ext
|
|
(&new_segment_list->MultSegBaseType, a_node,
|
|
(parent ? parent->MultSegBaseType : NULL)))
|
|
goto error;
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentURL") == 0) {
|
|
if (segment_urls_inherited_from_parent) {
|
|
/*
|
|
* SegmentBase, SegmentTemplate and SegmentList shall inherit
|
|
* attributes and elements from the same element on a higher level.
|
|
* If the same attribute or element is present on both levels,
|
|
* the one on the lower level shall take precedence over the one
|
|
* on the higher level.
|
|
*/
|
|
|
|
/* Clear the list of inherited segment URLs */
|
|
g_list_free_full (new_segment_list->SegmentURL,
|
|
(GDestroyNotify) gst_mpdparser_free_segment_url_node);
|
|
new_segment_list->SegmentURL = NULL;
|
|
|
|
/* mark the fact that we cleared the list, so that it is not tried again */
|
|
segment_urls_inherited_from_parent = FALSE;
|
|
}
|
|
gst_mpdparser_parse_segment_url_node (&new_segment_list->SegmentURL,
|
|
cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pointer = new_segment_list;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_segment_list_node (new_segment_list);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_content_protection_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
gchar *value = NULL;
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) {
|
|
if (!g_strcmp0 (value, "MSPR 2.0")) {
|
|
xmlNode *cur_node;
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "pro") == 0) {
|
|
GstDescriptorType *new_descriptor;
|
|
new_descriptor = g_slice_new0 (GstDescriptorType);
|
|
*list = g_list_append (*list, new_descriptor);
|
|
|
|
gst_mpdparser_get_xml_prop_string_stripped (a_node, "schemeIdUri",
|
|
&new_descriptor->schemeIdUri);
|
|
|
|
gst_mpdparser_get_xml_node_content (cur_node,
|
|
&new_descriptor->value);
|
|
goto beach;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
gst_mpdparser_parse_descriptor_type_node (list, a_node);
|
|
}
|
|
} else {
|
|
gst_mpdparser_parse_descriptor_type_node (list, a_node);
|
|
}
|
|
beach:
|
|
if (value)
|
|
g_free (value);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_representation_base_type (GstRepresentationBaseType **
|
|
pointer, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstRepresentationBaseType *representation_base;
|
|
|
|
gst_mpdparser_free_representation_base_type (*pointer);
|
|
*pointer = representation_base = g_slice_new0 (GstRepresentationBaseType);
|
|
|
|
GST_LOG ("attributes of RepresentationBaseType extension:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "profiles",
|
|
&representation_base->profiles);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "width", 0,
|
|
&representation_base->width);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "height", 0,
|
|
&representation_base->height);
|
|
gst_mpdparser_get_xml_prop_ratio (a_node, "sar", &representation_base->sar);
|
|
gst_mpdparser_get_xml_prop_framerate (a_node, "frameRate",
|
|
&representation_base->frameRate);
|
|
gst_mpdparser_get_xml_prop_framerate (a_node, "minFrameRate",
|
|
&representation_base->minFrameRate);
|
|
gst_mpdparser_get_xml_prop_framerate (a_node, "maxFrameRate",
|
|
&representation_base->maxFrameRate);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "audioSamplingRate",
|
|
&representation_base->audioSamplingRate);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "mimeType",
|
|
&representation_base->mimeType);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "segmentProfiles",
|
|
&representation_base->segmentProfiles);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "codecs",
|
|
&representation_base->codecs);
|
|
gst_mpdparser_get_xml_prop_double (a_node, "maximumSAPPeriod",
|
|
&representation_base->maximumSAPPeriod);
|
|
gst_mpdparser_get_xml_prop_SAP_type (a_node, "startWithSAP",
|
|
&representation_base->startWithSAP);
|
|
gst_mpdparser_get_xml_prop_double (a_node, "maxPlayoutRate",
|
|
&representation_base->maxPlayoutRate);
|
|
gst_mpdparser_get_xml_prop_boolean (a_node, "codingDependency",
|
|
FALSE, &representation_base->codingDependency);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "scanType",
|
|
&representation_base->scanType);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "FramePacking") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node
|
|
(&representation_base->FramePacking, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "AudioChannelConfiguration") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node
|
|
(&representation_base->AudioChannelConfiguration, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ContentProtection") == 0) {
|
|
gst_mpdparser_parse_content_protection_node
|
|
(&representation_base->ContentProtection, cur_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_representation_node (GList ** list, xmlNode * a_node,
|
|
GstAdaptationSetNode * parent, GstPeriodNode * period_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstRepresentationNode *new_representation;
|
|
|
|
new_representation = g_slice_new0 (GstRepresentationNode);
|
|
|
|
GST_LOG ("attributes of Representation node:");
|
|
if (!gst_mpdparser_get_xml_prop_string_no_whitespace (a_node, "id",
|
|
&new_representation->id)) {
|
|
GST_ERROR ("Cannot parse Representation id, invalid manifest");
|
|
goto error;
|
|
}
|
|
if (!gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "bandwidth", 0,
|
|
&new_representation->bandwidth)) {
|
|
GST_ERROR ("Cannot parse Representation bandwidth, invalid manifest");
|
|
goto error;
|
|
}
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "qualityRanking", 0,
|
|
&new_representation->qualityRanking);
|
|
gst_mpdparser_get_xml_prop_string_vector_type (a_node, "dependencyId",
|
|
&new_representation->dependencyId);
|
|
gst_mpdparser_get_xml_prop_string_vector_type (a_node,
|
|
"mediaStreamStructureId", &new_representation->mediaStreamStructureId);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base_type
|
|
(&new_representation->RepresentationBase, a_node);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_representation->SegmentBase,
|
|
cur_node, parent->SegmentBase ?
|
|
parent->SegmentBase : period_node->SegmentBase);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_representation->SegmentTemplate, cur_node,
|
|
parent->SegmentTemplate ?
|
|
parent->SegmentTemplate : period_node->SegmentTemplate))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node
|
|
(&new_representation->SegmentList, cur_node,
|
|
parent->SegmentList ? parent->
|
|
SegmentList : period_node->SegmentList))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_representation->BaseURLs,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "SubRepresentation") == 0) {
|
|
gst_mpdparser_parse_subrepresentation_node
|
|
(&new_representation->SubRepresentations, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* some sanity checking */
|
|
|
|
*list = g_list_append (*list, new_representation);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_representation_node (new_representation);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_adaptation_set_node (GList ** list, xmlNode * a_node,
|
|
GstPeriodNode * parent)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstAdaptationSetNode *new_adap_set;
|
|
gchar *actuate;
|
|
|
|
new_adap_set = g_slice_new0 (GstAdaptationSetNode);
|
|
|
|
GST_LOG ("attributes of AdaptationSet node:");
|
|
|
|
new_adap_set->actuate = GST_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_adap_set->xlink_href)
|
|
&& gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_adap_set->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "id", 0,
|
|
&new_adap_set->id);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "group", 0,
|
|
&new_adap_set->group);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "lang", &new_adap_set->lang);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "contentType",
|
|
&new_adap_set->contentType);
|
|
gst_mpdparser_get_xml_prop_ratio (a_node, "par", &new_adap_set->par);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minBandwidth", 0,
|
|
&new_adap_set->minBandwidth);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxBandwidth", 0,
|
|
&new_adap_set->maxBandwidth);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minWidth", 0,
|
|
&new_adap_set->minWidth);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxWidth", 0,
|
|
&new_adap_set->maxWidth);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "minHeight", 0,
|
|
&new_adap_set->minHeight);
|
|
gst_mpdparser_get_xml_prop_unsigned_integer (a_node, "maxHeight", 0,
|
|
&new_adap_set->maxHeight);
|
|
gst_mpdparser_get_xml_prop_cond_uint (a_node, "segmentAlignment",
|
|
&new_adap_set->segmentAlignment);
|
|
gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching",
|
|
parent->bitstreamSwitching, &new_adap_set->bitstreamSwitching);
|
|
if (parent->bitstreamSwitching && !new_adap_set->bitstreamSwitching) {
|
|
/* according to the standard, if the Period's bitstreamSwitching attribute
|
|
* is true, the AdaptationSet should not have the bitstreamSwitching
|
|
* attribute set to false.
|
|
* We should return a parsing error, but we are generous and ignore the
|
|
* standard violation.
|
|
*/
|
|
new_adap_set->bitstreamSwitching = parent->bitstreamSwitching;
|
|
}
|
|
gst_mpdparser_get_xml_prop_cond_uint (a_node, "subsegmentAlignment",
|
|
&new_adap_set->subsegmentAlignment);
|
|
gst_mpdparser_get_xml_prop_SAP_type (a_node, "subsegmentStartsWithSAP",
|
|
&new_adap_set->subsegmentStartsWithSAP);
|
|
|
|
/* RepresentationBase extension */
|
|
gst_mpdparser_parse_representation_base_type
|
|
(&new_adap_set->RepresentationBase, a_node);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Accessibility") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Accessibility,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Role") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Role,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Rating") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Rating,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Viewpoint") == 0) {
|
|
gst_mpdparser_parse_descriptor_type_node (&new_adap_set->Viewpoint,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_adap_set->BaseURLs, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_adap_set->SegmentBase,
|
|
cur_node, parent->SegmentBase);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node (&new_adap_set->SegmentList,
|
|
cur_node, parent->SegmentList))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ContentComponent") == 0) {
|
|
gst_mpdparser_parse_content_component_node
|
|
(&new_adap_set->ContentComponents, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_adap_set->SegmentTemplate, cur_node, parent->SegmentTemplate))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must parse Representation after everything else in the AdaptationSet
|
|
* has been parsed because certain Representation child elements can inherit
|
|
* attributes specified by the same element in the AdaptationSet
|
|
*/
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Representation") == 0) {
|
|
if (!gst_mpdparser_parse_representation_node
|
|
(&new_adap_set->Representations, cur_node, new_adap_set, parent))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
*list = g_list_append (*list, new_adap_set);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_adaptation_set_node (new_adap_set);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_subset_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstSubsetNode *new_subset;
|
|
|
|
new_subset = g_slice_new0 (GstSubsetNode);
|
|
*list = g_list_append (*list, new_subset);
|
|
|
|
GST_LOG ("attributes of Subset node:");
|
|
gst_mpdparser_get_xml_prop_uint_vector_type (a_node, "contains",
|
|
&new_subset->contains, &new_subset->size);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_segment_template_node (GstSegmentTemplateNode ** pointer,
|
|
xmlNode * a_node, GstSegmentTemplateNode * parent)
|
|
{
|
|
GstSegmentTemplateNode *new_segment_template;
|
|
gchar *strval;
|
|
|
|
gst_mpdparser_free_segment_template_node (*pointer);
|
|
new_segment_template = g_slice_new0 (GstSegmentTemplateNode);
|
|
|
|
GST_LOG ("extension of SegmentTemplate node:");
|
|
if (!gst_mpdparser_parse_mult_seg_base_type_ext
|
|
(&new_segment_template->MultSegBaseType, a_node,
|
|
(parent ? parent->MultSegBaseType : NULL)))
|
|
goto error;
|
|
|
|
/* Inherit attribute values from parent when the value isn't found */
|
|
GST_LOG ("attributes of SegmentTemplate node:");
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "media", &strval)) {
|
|
new_segment_template->media = strval;
|
|
} else if (parent) {
|
|
new_segment_template->media = xmlMemStrdup (parent->media);
|
|
}
|
|
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "index", &strval)) {
|
|
new_segment_template->index = strval;
|
|
} else if (parent) {
|
|
new_segment_template->index = xmlMemStrdup (parent->index);
|
|
}
|
|
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "initialization", &strval)) {
|
|
new_segment_template->initialization = strval;
|
|
} else if (parent) {
|
|
new_segment_template->initialization =
|
|
xmlMemStrdup (parent->initialization);
|
|
}
|
|
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "bitstreamSwitching", &strval)) {
|
|
new_segment_template->bitstreamSwitching = strval;
|
|
} else if (parent) {
|
|
new_segment_template->bitstreamSwitching =
|
|
xmlMemStrdup (parent->bitstreamSwitching);
|
|
}
|
|
|
|
*pointer = new_segment_template;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_segment_template_node (new_segment_template);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_period_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstPeriodNode *new_period;
|
|
gchar *actuate;
|
|
|
|
new_period = g_slice_new0 (GstPeriodNode);
|
|
|
|
GST_LOG ("attributes of Period node:");
|
|
|
|
new_period->actuate = GST_XLINK_ACTUATE_ON_REQUEST;
|
|
if (gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "href", &new_period->xlink_href)
|
|
&& gst_mpdparser_get_xml_ns_prop_string (a_node,
|
|
"http://www.w3.org/1999/xlink", "actuate", &actuate)) {
|
|
if (strcmp (actuate, "onLoad") == 0)
|
|
new_period->actuate = GST_XLINK_ACTUATE_ON_LOAD;
|
|
xmlFree (actuate);
|
|
}
|
|
|
|
gst_mpdparser_get_xml_prop_string (a_node, "id", &new_period->id);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "start", GST_MPD_DURATION_NONE,
|
|
&new_period->start);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_period->duration);
|
|
gst_mpdparser_get_xml_prop_boolean (a_node, "bitstreamSwitching", FALSE,
|
|
&new_period->bitstreamSwitching);
|
|
|
|
/* explore children nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentBase") == 0) {
|
|
gst_mpdparser_parse_seg_base_type_ext (&new_period->SegmentBase,
|
|
cur_node, NULL);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentList") == 0) {
|
|
if (!gst_mpdparser_parse_segment_list_node (&new_period->SegmentList,
|
|
cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "SegmentTemplate") == 0) {
|
|
if (!gst_mpdparser_parse_segment_template_node
|
|
(&new_period->SegmentTemplate, cur_node, NULL))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Subset") == 0) {
|
|
gst_mpdparser_parse_subset_node (&new_period->Subsets, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_period->BaseURLs, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We must parse AdaptationSet after everything else in the Period has been
|
|
* parsed because certain AdaptationSet child elements can inherit attributes
|
|
* specified by the same element in the Period
|
|
*/
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "AdaptationSet") == 0) {
|
|
if (!gst_mpdparser_parse_adaptation_set_node
|
|
(&new_period->AdaptationSets, cur_node, new_period))
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
*list = g_list_append (*list, new_period);
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_period_node (new_period);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_program_info_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstProgramInformationNode *new_prog_info;
|
|
|
|
new_prog_info = g_slice_new0 (GstProgramInformationNode);
|
|
*list = g_list_append (*list, new_prog_info);
|
|
|
|
GST_LOG ("attributes of ProgramInformation node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "lang", &new_prog_info->lang);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "moreInformationURL",
|
|
&new_prog_info->moreInformationURL);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of ProgramInformation node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Title") == 0) {
|
|
gst_mpdparser_get_xml_node_content (cur_node, &new_prog_info->Title);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Source") == 0) {
|
|
gst_mpdparser_get_xml_node_content (cur_node, &new_prog_info->Source);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Copyright") == 0) {
|
|
gst_mpdparser_get_xml_node_content (cur_node,
|
|
&new_prog_info->Copyright);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_range_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstMetricsRangeNode *new_metrics_range;
|
|
|
|
new_metrics_range = g_slice_new0 (GstMetricsRangeNode);
|
|
*list = g_list_append (*list, new_metrics_range);
|
|
|
|
GST_LOG ("attributes of Metrics Range node:");
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "starttime",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->starttime);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "duration",
|
|
GST_MPD_DURATION_NONE, &new_metrics_range->duration);
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_parse_metrics_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMetricsNode *new_metrics;
|
|
|
|
new_metrics = g_slice_new0 (GstMetricsNode);
|
|
*list = g_list_append (*list, new_metrics);
|
|
|
|
GST_LOG ("attributes of Metrics node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "metrics", &new_metrics->metrics);
|
|
|
|
/* explore children nodes */
|
|
GST_LOG ("children of Metrics node:");
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Range") == 0) {
|
|
gst_mpdparser_parse_metrics_range_node (&new_metrics->MetricsRanges,
|
|
cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Reporting") == 0) {
|
|
/* No reporting scheme is specified in this part of ISO/IEC 23009.
|
|
* It is expected that external specifications may define formats
|
|
* and delivery for the reporting data. */
|
|
GST_LOG (" - Reporting node found (unknown structure)");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The UTCTiming element is defined in
|
|
* ISO/IEC 23009-1:2014/PDAM 1 "Information technology — Dynamic adaptive streaming over HTTP (DASH) — Part 1: Media presentation description and segment formats / Amendment 1: High Profile and Availability Time Synchronization"
|
|
*/
|
|
static void
|
|
gst_mpdparser_parse_utctiming_node (GList ** list, xmlNode * a_node)
|
|
{
|
|
GstUTCTimingNode *new_timing;
|
|
gchar *method = NULL;
|
|
gchar *value = NULL;
|
|
|
|
new_timing = g_slice_new0 (GstUTCTimingNode);
|
|
|
|
GST_LOG ("attributes of UTCTiming node:");
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "schemeIdUri", &method)) {
|
|
int i;
|
|
|
|
for (i = 0; gst_mpdparser_utc_timing_methods[i].name; ++i) {
|
|
if (g_ascii_strncasecmp (gst_mpdparser_utc_timing_methods[i].name,
|
|
method, strlen (gst_mpdparser_utc_timing_methods[i].name)) == 0) {
|
|
new_timing->method = gst_mpdparser_utc_timing_methods[i].method;
|
|
break;
|
|
}
|
|
}
|
|
xmlFree (method);
|
|
}
|
|
|
|
if (gst_mpdparser_get_xml_prop_string (a_node, "value", &value)) {
|
|
int max_tokens = 0;
|
|
if (GST_MPD_UTCTIMING_TYPE_DIRECT == new_timing->method) {
|
|
/* The GST_MPD_UTCTIMING_TYPE_DIRECT method is a special case
|
|
* that is not a space separated list.
|
|
*/
|
|
max_tokens = 1;
|
|
}
|
|
new_timing->urls = g_strsplit (value, " ", max_tokens);
|
|
xmlFree (value);
|
|
}
|
|
|
|
/* append to list only if both method and urls were set */
|
|
if (new_timing->method != 0 && new_timing->urls != NULL &&
|
|
g_strv_length (new_timing->urls) != 0) {
|
|
*list = g_list_append (*list, new_timing);
|
|
} else {
|
|
gst_mpdparser_free_utctiming_node (new_timing);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpdparser_parse_root_node (GstMPDNode ** pointer, xmlNode * a_node)
|
|
{
|
|
xmlNode *cur_node;
|
|
GstMPDNode *new_mpd;
|
|
|
|
gst_mpdparser_free_mpd_node (*pointer);
|
|
*pointer = NULL;
|
|
new_mpd = g_slice_new0 (GstMPDNode);
|
|
|
|
GST_LOG ("namespaces of root MPD node:");
|
|
new_mpd->default_namespace =
|
|
gst_mpdparser_get_xml_node_namespace (a_node, NULL);
|
|
new_mpd->namespace_xsi = gst_mpdparser_get_xml_node_namespace (a_node, "xsi");
|
|
new_mpd->namespace_ext = gst_mpdparser_get_xml_node_namespace (a_node, "ext");
|
|
|
|
GST_LOG ("attributes of root MPD node:");
|
|
gst_mpdparser_get_xml_prop_string (a_node, "schemaLocation",
|
|
&new_mpd->schemaLocation);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "id", &new_mpd->id);
|
|
gst_mpdparser_get_xml_prop_string (a_node, "profiles", &new_mpd->profiles);
|
|
gst_mpdparser_get_xml_prop_type (a_node, "type", &new_mpd->type);
|
|
gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityStartTime",
|
|
&new_mpd->availabilityStartTime);
|
|
gst_mpdparser_get_xml_prop_dateTime (a_node, "availabilityEndTime",
|
|
&new_mpd->availabilityEndTime);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "mediaPresentationDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd->mediaPresentationDuration);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "minimumUpdatePeriod",
|
|
GST_MPD_DURATION_NONE, &new_mpd->minimumUpdatePeriod);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "minBufferTime",
|
|
GST_MPD_DURATION_NONE, &new_mpd->minBufferTime);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "timeShiftBufferDepth",
|
|
GST_MPD_DURATION_NONE, &new_mpd->timeShiftBufferDepth);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "suggestedPresentationDelay",
|
|
GST_MPD_DURATION_NONE, &new_mpd->suggestedPresentationDelay);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "maxSegmentDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd->maxSegmentDuration);
|
|
gst_mpdparser_get_xml_prop_duration (a_node, "maxSubsegmentDuration",
|
|
GST_MPD_DURATION_NONE, &new_mpd->maxSubsegmentDuration);
|
|
|
|
/* explore children Period nodes */
|
|
for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
|
|
if (cur_node->type == XML_ELEMENT_NODE) {
|
|
if (xmlStrcmp (cur_node->name, (xmlChar *) "Period") == 0) {
|
|
if (!gst_mpdparser_parse_period_node (&new_mpd->Periods, cur_node))
|
|
goto error;
|
|
} else if (xmlStrcmp (cur_node->name,
|
|
(xmlChar *) "ProgramInformation") == 0) {
|
|
gst_mpdparser_parse_program_info_node (&new_mpd->ProgramInfo, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "BaseURL") == 0) {
|
|
gst_mpdparser_parse_baseURL_node (&new_mpd->BaseURLs, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Location") == 0) {
|
|
gst_mpdparser_parse_location_node (&new_mpd->Locations, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "Metrics") == 0) {
|
|
gst_mpdparser_parse_metrics_node (&new_mpd->Metrics, cur_node);
|
|
} else if (xmlStrcmp (cur_node->name, (xmlChar *) "UTCTiming") == 0) {
|
|
gst_mpdparser_parse_utctiming_node (&new_mpd->UTCTiming, cur_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
*pointer = new_mpd;
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_mpdparser_free_mpd_node (new_mpd);
|
|
return FALSE;
|
|
}
|
|
|
|
/* comparison functions */
|
|
static int
|
|
strncmp_ext (const char *s1, const char *s2)
|
|
{
|
|
if (s1 == NULL && s2 == NULL)
|
|
return 0;
|
|
if (s1 == NULL && s2 != NULL)
|
|
return 1;
|
|
if (s2 == NULL && s1 != NULL)
|
|
return 1;
|
|
return strncmp (s1, s2, strlen (s2));
|
|
}
|
|
|
|
/* navigation functions */
|
|
static GstStreamMimeType
|
|
gst_mpdparser_representation_get_mimetype (GstAdaptationSetNode * adapt_set,
|
|
GstRepresentationNode * rep)
|
|
{
|
|
gchar *mime = NULL;
|
|
if (rep->RepresentationBase)
|
|
mime = rep->RepresentationBase->mimeType;
|
|
if (mime == NULL && adapt_set->RepresentationBase) {
|
|
mime = adapt_set->RepresentationBase->mimeType;
|
|
}
|
|
|
|
if (strncmp_ext (mime, "audio") == 0)
|
|
return GST_STREAM_AUDIO;
|
|
if (strncmp_ext (mime, "video") == 0)
|
|
return GST_STREAM_VIDEO;
|
|
if (strncmp_ext (mime, "application") == 0 || strncmp_ext (mime, "text") == 0)
|
|
return GST_STREAM_APPLICATION;
|
|
|
|
return GST_STREAM_UNKNOWN;
|
|
}
|
|
|
|
static GstRepresentationNode *
|
|
gst_mpdparser_get_lowest_representation (GList * Representations)
|
|
{
|
|
GList *list = NULL;
|
|
GstRepresentationNode *rep = NULL;
|
|
GstRepresentationNode *lowest = NULL;
|
|
|
|
if (Representations == NULL)
|
|
return NULL;
|
|
|
|
for (list = g_list_first (Representations); list; list = g_list_next (list)) {
|
|
rep = (GstRepresentationNode *) list->data;
|
|
if (rep && (!lowest || rep->bandwidth < lowest->bandwidth)) {
|
|
lowest = rep;
|
|
}
|
|
}
|
|
|
|
return lowest;
|
|
}
|
|
|
|
#if 0
|
|
static GstRepresentationNode *
|
|
gst_mpdparser_get_highest_representation (GList * Representations)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
if (Representations == NULL)
|
|
return NULL;
|
|
|
|
list = g_list_last (Representations);
|
|
|
|
return list ? (GstRepresentationNode *) list->data : NULL;
|
|
}
|
|
|
|
static GstRepresentationNode *
|
|
gst_mpdparser_get_representation_with_max_bandwidth (GList * Representations,
|
|
gint max_bandwidth)
|
|
{
|
|
GList *list = NULL;
|
|
GstRepresentationNode *representation, *best_rep = NULL;
|
|
|
|
if (Representations == NULL)
|
|
return NULL;
|
|
|
|
if (max_bandwidth <= 0) /* 0 => get highest representation available */
|
|
return gst_mpdparser_get_highest_representation (Representations);
|
|
|
|
for (list = g_list_first (Representations); list; list = g_list_next (list)) {
|
|
representation = (GstRepresentationNode *) list->data;
|
|
if (representation && representation->bandwidth <= max_bandwidth) {
|
|
best_rep = representation;
|
|
}
|
|
}
|
|
|
|
return best_rep;
|
|
}
|
|
#endif
|
|
|
|
static GstSegmentBaseType *
|
|
gst_mpdparser_get_segment_base (GstPeriodNode * Period,
|
|
GstAdaptationSetNode * AdaptationSet,
|
|
GstRepresentationNode * Representation)
|
|
{
|
|
GstSegmentBaseType *SegmentBase = NULL;
|
|
|
|
if (Representation && Representation->SegmentBase) {
|
|
SegmentBase = Representation->SegmentBase;
|
|
} else if (AdaptationSet && AdaptationSet->SegmentBase) {
|
|
SegmentBase = AdaptationSet->SegmentBase;
|
|
} else if (Period && Period->SegmentBase) {
|
|
SegmentBase = Period->SegmentBase;
|
|
}
|
|
/* the SegmentBase element could be encoded also inside a SegmentList element */
|
|
if (SegmentBase == NULL) {
|
|
if (Representation && Representation->SegmentList
|
|
&& Representation->SegmentList->MultSegBaseType
|
|
&& Representation->SegmentList->MultSegBaseType->SegBaseType) {
|
|
SegmentBase = Representation->SegmentList->MultSegBaseType->SegBaseType;
|
|
} else if (AdaptationSet && AdaptationSet->SegmentList
|
|
&& AdaptationSet->SegmentList->MultSegBaseType
|
|
&& AdaptationSet->SegmentList->MultSegBaseType->SegBaseType) {
|
|
SegmentBase = AdaptationSet->SegmentList->MultSegBaseType->SegBaseType;
|
|
} else if (Period && Period->SegmentList
|
|
&& Period->SegmentList->MultSegBaseType
|
|
&& Period->SegmentList->MultSegBaseType->SegBaseType) {
|
|
SegmentBase = Period->SegmentList->MultSegBaseType->SegBaseType;
|
|
}
|
|
}
|
|
|
|
return SegmentBase;
|
|
}
|
|
|
|
gint
|
|
gst_mpdparser_get_rep_idx_with_min_bandwidth (GList * Representations)
|
|
{
|
|
GList *list = NULL, *lowest = NULL;
|
|
GstRepresentationNode *rep = NULL;
|
|
gint lowest_bandwidth = -1;
|
|
|
|
if (Representations == NULL)
|
|
return -1;
|
|
|
|
for (list = g_list_first (Representations); list; list = g_list_next (list)) {
|
|
rep = (GstRepresentationNode *) list->data;
|
|
if (rep && (!lowest || rep->bandwidth < lowest_bandwidth)) {
|
|
lowest = list;
|
|
lowest_bandwidth = rep->bandwidth;
|
|
}
|
|
}
|
|
|
|
return lowest ? g_list_position (Representations, lowest) : -1;
|
|
}
|
|
|
|
gint
|
|
gst_mpdparser_get_rep_idx_with_max_bandwidth (GList * Representations,
|
|
gint64 max_bandwidth, gint max_video_width, gint max_video_height, gint
|
|
max_video_framerate_n, gint max_video_framerate_d)
|
|
{
|
|
GList *list = NULL, *best = NULL;
|
|
GstRepresentationNode *representation;
|
|
gint best_bandwidth = 0;
|
|
|
|
GST_DEBUG ("max_bandwidth = %" G_GINT64_FORMAT, max_bandwidth);
|
|
|
|
if (Representations == NULL)
|
|
return -1;
|
|
|
|
if (max_bandwidth <= 0) /* 0 => get lowest representation available */
|
|
return gst_mpdparser_get_rep_idx_with_min_bandwidth (Representations);
|
|
|
|
for (list = g_list_first (Representations); list; list = g_list_next (list)) {
|
|
GstFrameRate *framerate = NULL;
|
|
|
|
representation = (GstRepresentationNode *) list->data;
|
|
|
|
/* FIXME: Really? */
|
|
if (!representation)
|
|
continue;
|
|
|
|
framerate = representation->RepresentationBase->frameRate;
|
|
if (!framerate)
|
|
framerate = representation->RepresentationBase->maxFrameRate;
|
|
|
|
if (framerate && max_video_framerate_n > 0) {
|
|
if (gst_util_fraction_compare (framerate->num, framerate->den,
|
|
max_video_framerate_n, max_video_framerate_d) > 0)
|
|
continue;
|
|
}
|
|
|
|
if (max_video_width > 0
|
|
&& representation->RepresentationBase->width > max_video_width)
|
|
continue;
|
|
if (max_video_height > 0
|
|
&& representation->RepresentationBase->height > max_video_height)
|
|
continue;
|
|
|
|
if (representation->bandwidth <= max_bandwidth &&
|
|
representation->bandwidth > best_bandwidth) {
|
|
best = list;
|
|
best_bandwidth = representation->bandwidth;
|
|
}
|
|
}
|
|
|
|
return best ? g_list_position (Representations, best) : -1;
|
|
}
|
|
|
|
static GstSegmentListNode *
|
|
gst_mpd_client_fetch_external_segment_list (GstMpdClient * client,
|
|
GstPeriodNode * Period,
|
|
GstAdaptationSetNode * AdaptationSet,
|
|
GstRepresentationNode * Representation,
|
|
GstSegmentListNode * parent, GstSegmentListNode * segment_list)
|
|
{
|
|
GstFragment *download;
|
|
GstBuffer *segment_list_buffer;
|
|
GstMapInfo map;
|
|
GError *err = NULL;
|
|
xmlDocPtr doc = NULL;
|
|
GstUri *base_uri, *uri;
|
|
gchar *query = NULL;
|
|
gchar *uri_string;
|
|
GstSegmentListNode *new_segment_list = NULL;
|
|
|
|
/* ISO/IEC 23009-1:2014 5.5.3 4)
|
|
* Remove nodes that resolve to nothing when resolving
|
|
*/
|
|
if (strcmp (segment_list->xlink_href,
|
|
"urn:mpeg:dash:resolve-to-zero:2013") == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!client->downloader) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Build absolute URI */
|
|
|
|
/* Get base URI at the MPD level */
|
|
base_uri =
|
|
gst_uri_from_string (client->
|
|
mpd_base_uri ? client->mpd_base_uri : client->mpd_uri);
|
|
|
|
/* combine a BaseURL at the MPD level with the current base url */
|
|
base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0);
|
|
|
|
/* combine a BaseURL at the Period level with the current base url */
|
|
base_uri = combine_urls (base_uri, Period->BaseURLs, &query, 0);
|
|
|
|
if (AdaptationSet) {
|
|
/* combine a BaseURL at the AdaptationSet level with the current base url */
|
|
base_uri = combine_urls (base_uri, AdaptationSet->BaseURLs, &query, 0);
|
|
|
|
if (Representation) {
|
|
/* combine a BaseURL at the Representation level with the current base url */
|
|
base_uri = combine_urls (base_uri, Representation->BaseURLs, &query, 0);
|
|
}
|
|
}
|
|
|
|
uri = gst_uri_from_string_with_base (base_uri, segment_list->xlink_href);
|
|
if (query)
|
|
gst_uri_set_query_string (uri, query);
|
|
g_free (query);
|
|
uri_string = gst_uri_to_string (uri);
|
|
gst_uri_unref (base_uri);
|
|
gst_uri_unref (uri);
|
|
|
|
download =
|
|
gst_uri_downloader_fetch_uri (client->downloader,
|
|
uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
|
|
g_free (uri_string);
|
|
|
|
if (!download) {
|
|
GST_ERROR ("Failed to download external SegmentList node at '%s': %s",
|
|
segment_list->xlink_href, err->message);
|
|
g_clear_error (&err);
|
|
return NULL;
|
|
}
|
|
|
|
segment_list_buffer = gst_fragment_get_buffer (download);
|
|
g_object_unref (download);
|
|
|
|
gst_buffer_map (segment_list_buffer, &map, GST_MAP_READ);
|
|
|
|
doc =
|
|
xmlReadMemory ((const gchar *) map.data, map.size, "noname.xml", NULL,
|
|
XML_PARSE_NONET);
|
|
|
|
gst_buffer_unmap (segment_list_buffer, &map);
|
|
gst_buffer_unref (segment_list_buffer);
|
|
|
|
/* NOTE: ISO/IEC 23009-1:2014 5.3.9.3.2 is saying that one or multiple SegmentList
|
|
* in external xml is allowed, however, multiple SegmentList does not make sense
|
|
* because Period/AdaptationSet/Representation allow only one SegmentList */
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
|
|
if (root_element->type != XML_ELEMENT_NODE ||
|
|
xmlStrcmp (root_element->name, (xmlChar *) "SegmentList") != 0) {
|
|
goto error;
|
|
}
|
|
|
|
gst_mpdparser_parse_segment_list_node (&new_segment_list, root_element,
|
|
parent);
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
done:
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_segment_list;
|
|
|
|
error:
|
|
GST_ERROR ("Failed to parse segment list node XML");
|
|
goto done;
|
|
}
|
|
|
|
static GstSegmentListNode *
|
|
gst_mpdparser_get_segment_list (GstMpdClient * client, GstPeriodNode * Period,
|
|
GstAdaptationSetNode * AdaptationSet,
|
|
GstRepresentationNode * Representation)
|
|
{
|
|
GstSegmentListNode **SegmentList;
|
|
GstSegmentListNode *ParentSegmentList = NULL;
|
|
|
|
if (Representation && Representation->SegmentList) {
|
|
SegmentList = &Representation->SegmentList;
|
|
ParentSegmentList = AdaptationSet->SegmentList;
|
|
} else if (AdaptationSet && AdaptationSet->SegmentList) {
|
|
SegmentList = &AdaptationSet->SegmentList;
|
|
ParentSegmentList = Period->SegmentList;
|
|
Representation = NULL;
|
|
} else {
|
|
Representation = NULL;
|
|
AdaptationSet = NULL;
|
|
SegmentList = &Period->SegmentList;
|
|
}
|
|
|
|
/* Resolve external segment list here. */
|
|
if (*SegmentList && (*SegmentList)->xlink_href) {
|
|
GstSegmentListNode *new_segment_list;
|
|
|
|
/* TODO: Use SegmentList of parent if
|
|
* - Parent has its own SegmentList
|
|
* - Fail to get SegmentList from external xml
|
|
*/
|
|
new_segment_list =
|
|
gst_mpd_client_fetch_external_segment_list (client, Period,
|
|
AdaptationSet, Representation, ParentSegmentList, *SegmentList);
|
|
|
|
gst_mpdparser_free_segment_list_node (*SegmentList);
|
|
*SegmentList = new_segment_list;
|
|
}
|
|
|
|
return *SegmentList;
|
|
}
|
|
|
|
/* memory management functions */
|
|
static void
|
|
gst_mpdparser_free_mpd_node (GstMPDNode * mpd_node)
|
|
{
|
|
if (mpd_node) {
|
|
if (mpd_node->default_namespace)
|
|
xmlFree (mpd_node->default_namespace);
|
|
if (mpd_node->namespace_xsi)
|
|
xmlFree (mpd_node->namespace_xsi);
|
|
if (mpd_node->namespace_ext)
|
|
xmlFree (mpd_node->namespace_ext);
|
|
if (mpd_node->schemaLocation)
|
|
xmlFree (mpd_node->schemaLocation);
|
|
if (mpd_node->id)
|
|
xmlFree (mpd_node->id);
|
|
if (mpd_node->profiles)
|
|
xmlFree (mpd_node->profiles);
|
|
if (mpd_node->availabilityStartTime)
|
|
gst_date_time_unref (mpd_node->availabilityStartTime);
|
|
if (mpd_node->availabilityEndTime)
|
|
gst_date_time_unref (mpd_node->availabilityEndTime);
|
|
g_list_free_full (mpd_node->ProgramInfo,
|
|
(GDestroyNotify) gst_mpdparser_free_prog_info_node);
|
|
g_list_free_full (mpd_node->BaseURLs,
|
|
(GDestroyNotify) gst_mpdparser_free_base_url_node);
|
|
g_list_free_full (mpd_node->Locations, (GDestroyNotify) xmlFree);
|
|
g_list_free_full (mpd_node->Periods,
|
|
(GDestroyNotify) gst_mpdparser_free_period_node);
|
|
g_list_free_full (mpd_node->Metrics,
|
|
(GDestroyNotify) gst_mpdparser_free_metrics_node);
|
|
g_list_free_full (mpd_node->UTCTiming,
|
|
(GDestroyNotify) gst_mpdparser_free_utctiming_node);
|
|
g_slice_free (GstMPDNode, mpd_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_prog_info_node (GstProgramInformationNode * prog_info_node)
|
|
{
|
|
if (prog_info_node) {
|
|
if (prog_info_node->lang)
|
|
xmlFree (prog_info_node->lang);
|
|
if (prog_info_node->moreInformationURL)
|
|
xmlFree (prog_info_node->moreInformationURL);
|
|
if (prog_info_node->Title)
|
|
xmlFree (prog_info_node->Title);
|
|
if (prog_info_node->Source)
|
|
xmlFree (prog_info_node->Source);
|
|
if (prog_info_node->Copyright)
|
|
xmlFree (prog_info_node->Copyright);
|
|
g_slice_free (GstProgramInformationNode, prog_info_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_metrics_node (GstMetricsNode * metrics_node)
|
|
{
|
|
if (metrics_node) {
|
|
if (metrics_node->metrics)
|
|
xmlFree (metrics_node->metrics);
|
|
g_list_free_full (metrics_node->MetricsRanges,
|
|
(GDestroyNotify) gst_mpdparser_free_metrics_range_node);
|
|
g_slice_free (GstMetricsNode, metrics_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_metrics_range_node (GstMetricsRangeNode * metrics_range_node)
|
|
{
|
|
if (metrics_range_node) {
|
|
g_slice_free (GstMetricsRangeNode, metrics_range_node);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_period_node (GstPeriodNode * period_node)
|
|
{
|
|
if (period_node) {
|
|
if (period_node->id)
|
|
xmlFree (period_node->id);
|
|
gst_mpdparser_free_seg_base_type_ext (period_node->SegmentBase);
|
|
gst_mpdparser_free_segment_list_node (period_node->SegmentList);
|
|
gst_mpdparser_free_segment_template_node (period_node->SegmentTemplate);
|
|
g_list_free_full (period_node->AdaptationSets,
|
|
(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);
|
|
}
|
|
}
|
|
|
|
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 (GstRatio, representation_base->sar);
|
|
g_slice_free (GstFrameRate, representation_base->frameRate);
|
|
g_slice_free (GstFrameRate, representation_base->minFrameRate);
|
|
g_slice_free (GstFrameRate, 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_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 (GstRatio, adaptation_set_node->par);
|
|
g_slice_free (GstConditionalUintType,
|
|
adaptation_set_node->segmentAlignment);
|
|
g_slice_free (GstConditionalUintType,
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 (GstRange, 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 (GstRange, 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_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);
|
|
}
|
|
}
|
|
|
|
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 (GstRange, segment_url->mediaRange);
|
|
if (segment_url->index)
|
|
xmlFree (segment_url->index);
|
|
g_slice_free (GstRange, segment_url->indexRange);
|
|
g_slice_free (GstSegmentURLNode, segment_url);
|
|
}
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
}
|
|
|
|
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 (GstRatio, 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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_stream_period (GstStreamPeriod * stream_period)
|
|
{
|
|
if (stream_period) {
|
|
g_slice_free (GstStreamPeriod, stream_period);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpdparser_free_media_segment (GstMediaSegment * media_segment)
|
|
{
|
|
if (media_segment) {
|
|
g_slice_free (GstMediaSegment, media_segment);
|
|
}
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
/* 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 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;
|
|
}
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_period_index_at_time (GstMpdClient * client,
|
|
GstDateTime * time)
|
|
{
|
|
GList *iter;
|
|
guint period_idx = G_MAXUINT;
|
|
guint idx;
|
|
gint64 time_offset;
|
|
GstDateTime *avail_start =
|
|
gst_mpd_client_get_availability_start_time (client);
|
|
GstStreamPeriod *stream_period;
|
|
|
|
if (avail_start == NULL)
|
|
return 0;
|
|
|
|
time_offset = gst_mpd_client_calculate_time_difference (avail_start, time);
|
|
gst_date_time_unref (avail_start);
|
|
|
|
if (time_offset < 0)
|
|
return 0;
|
|
|
|
if (!gst_mpd_client_setup_media_presentation (client, time_offset, -1, NULL))
|
|
return 0;
|
|
|
|
for (idx = 0, iter = client->periods; iter; idx++, iter = g_list_next (iter)) {
|
|
stream_period = iter->data;
|
|
if (stream_period->start <= time_offset
|
|
&& (!GST_CLOCK_TIME_IS_VALID (stream_period->duration)
|
|
|| stream_period->start + stream_period->duration > time_offset)) {
|
|
period_idx = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return period_idx;
|
|
}
|
|
|
|
static GstStreamPeriod *
|
|
gst_mpdparser_get_stream_period (GstMpdClient * client)
|
|
{
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (client->periods != NULL, NULL);
|
|
|
|
return g_list_nth_data (client->periods, client->period_idx);
|
|
}
|
|
|
|
static GstRange *
|
|
gst_mpdparser_clone_range (GstRange * range)
|
|
{
|
|
GstRange *clone = NULL;
|
|
|
|
if (range) {
|
|
clone = g_slice_new0 (GstRange);
|
|
clone->first_byte_pos = range->first_byte_pos;
|
|
clone->last_byte_pos = range->last_byte_pos;
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
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_mpdparser_clone_range (url->range);
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
/*
|
|
* Combine a base url with the current stream base url from the list of
|
|
* baseURLs. Takes ownership of base and returns a new base.
|
|
*/
|
|
static 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;
|
|
}
|
|
|
|
/* select a stream and extract the baseURL (if present) */
|
|
static gchar *
|
|
gst_mpdparser_parse_baseURL (GstMpdClient * client, GstActiveStream * stream,
|
|
gchar ** query)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
static const gchar empty[] = "";
|
|
gchar *ret = NULL;
|
|
GstUri *abs_url;
|
|
|
|
g_return_val_if_fail (stream != NULL, g_strdup (empty));
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, g_strdup (empty));
|
|
g_return_val_if_fail (stream_period->period != NULL, g_strdup (empty));
|
|
|
|
/* NULLify query return before we start */
|
|
if (query)
|
|
*query = NULL;
|
|
|
|
/* initialise base url */
|
|
abs_url =
|
|
gst_uri_from_string (client->
|
|
mpd_base_uri ? client->mpd_base_uri : client->mpd_uri);
|
|
|
|
/* combine a BaseURL at the MPD level with the current base url */
|
|
abs_url =
|
|
combine_urls (abs_url, client->mpd_node->BaseURLs, query,
|
|
stream->baseURL_idx);
|
|
|
|
/* combine a BaseURL at the Period level with the current base url */
|
|
abs_url =
|
|
combine_urls (abs_url, stream_period->period->BaseURLs, query,
|
|
stream->baseURL_idx);
|
|
|
|
GST_DEBUG ("Current adaptation set id %i (%s)", stream->cur_adapt_set->id,
|
|
stream->cur_adapt_set->contentType);
|
|
/* combine a BaseURL at the AdaptationSet level with the current base url */
|
|
abs_url =
|
|
combine_urls (abs_url, stream->cur_adapt_set->BaseURLs, query,
|
|
stream->baseURL_idx);
|
|
|
|
/* combine a BaseURL at the Representation level with the current base url */
|
|
abs_url =
|
|
combine_urls (abs_url, stream->cur_representation->BaseURLs, query,
|
|
stream->baseURL_idx);
|
|
|
|
ret = gst_uri_to_string (abs_url);
|
|
gst_uri_unref (abs_url);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstClockTime
|
|
gst_mpd_client_get_segment_duration (GstMpdClient * client,
|
|
GstActiveStream * stream, guint64 * scale_dur)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
GstMultSegmentBaseType *base = NULL;
|
|
GstClockTime duration = 0;
|
|
|
|
g_return_val_if_fail (stream != NULL, GST_CLOCK_TIME_NONE);
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, GST_CLOCK_TIME_NONE);
|
|
|
|
if (stream->cur_segment_list) {
|
|
base = stream->cur_segment_list->MultSegBaseType;
|
|
} else if (stream->cur_seg_template) {
|
|
base = stream->cur_seg_template->MultSegBaseType;
|
|
}
|
|
|
|
if (base == NULL || base->SegBaseType == NULL) {
|
|
/* this may happen when we have a single segment */
|
|
duration = stream_period->duration;
|
|
if (scale_dur)
|
|
*scale_dur = duration;
|
|
} else {
|
|
/* duration is guint so this cannot overflow */
|
|
duration = base->duration * GST_SECOND;
|
|
if (scale_dur)
|
|
*scale_dur = duration;
|
|
duration /= base->SegBaseType->timescale;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
/*****************************/
|
|
/******* API functions *******/
|
|
/*****************************/
|
|
|
|
GstMpdClient *
|
|
gst_mpd_client_new (void)
|
|
{
|
|
GstMpdClient *client;
|
|
|
|
client = g_new0 (GstMpdClient, 1);
|
|
|
|
return client;
|
|
}
|
|
|
|
void
|
|
gst_active_streams_free (GstMpdClient * client)
|
|
{
|
|
if (client->active_streams) {
|
|
g_list_foreach (client->active_streams,
|
|
(GFunc) gst_mpdparser_free_active_stream, NULL);
|
|
g_list_free (client->active_streams);
|
|
client->active_streams = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
gst_mpd_client_free (GstMpdClient * client)
|
|
{
|
|
g_return_if_fail (client != NULL);
|
|
|
|
if (client->mpd_node)
|
|
gst_mpdparser_free_mpd_node (client->mpd_node);
|
|
|
|
if (client->periods) {
|
|
g_list_free_full (client->periods,
|
|
(GDestroyNotify) gst_mpdparser_free_stream_period);
|
|
}
|
|
|
|
gst_active_streams_free (client);
|
|
|
|
g_free (client->mpd_uri);
|
|
client->mpd_uri = NULL;
|
|
g_free (client->mpd_base_uri);
|
|
client->mpd_base_uri = NULL;
|
|
|
|
if (client->downloader)
|
|
gst_object_unref (client->downloader);
|
|
client->downloader = NULL;
|
|
|
|
g_free (client);
|
|
}
|
|
|
|
void
|
|
gst_mpd_client_set_uri_downloader (GstMpdClient * client,
|
|
GstUriDownloader * downloader)
|
|
{
|
|
if (client->downloader)
|
|
gst_object_unref (client->downloader);
|
|
client->downloader = gst_object_ref (downloader);
|
|
}
|
|
|
|
static void
|
|
gst_mpd_client_check_profiles (GstMpdClient * client)
|
|
{
|
|
GST_DEBUG ("Profiles: %s",
|
|
client->mpd_node->profiles ? client->mpd_node->profiles : "<none>");
|
|
|
|
if (!client->mpd_node->profiles)
|
|
return;
|
|
|
|
if (g_strstr_len (client->mpd_node->profiles, -1,
|
|
"urn:mpeg:dash:profile:isoff-on-demand:2011")) {
|
|
client->profile_isoff_ondemand = TRUE;
|
|
GST_DEBUG ("Found ISOFF on demand profile (2011)");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mpd_client_fetch_on_load_external_resources (GstMpdClient * client)
|
|
{
|
|
GList *l;
|
|
|
|
for (l = client->mpd_node->Periods; l; /* explicitly advanced below */ ) {
|
|
GstPeriodNode *period = l->data;
|
|
GList *m;
|
|
|
|
if (period->xlink_href && period->actuate == GST_XLINK_ACTUATE_ON_LOAD) {
|
|
GList *new_periods, *prev, *next;
|
|
|
|
new_periods = gst_mpd_client_fetch_external_period (client, period);
|
|
|
|
prev = l->prev;
|
|
client->mpd_node->Periods =
|
|
g_list_delete_link (client->mpd_node->Periods, l);
|
|
gst_mpdparser_free_period_node (period);
|
|
period = NULL;
|
|
|
|
/* Get new next node, we will insert before this */
|
|
if (prev)
|
|
next = prev->next;
|
|
else
|
|
next = client->mpd_node->Periods;
|
|
|
|
while (new_periods) {
|
|
client->mpd_node->Periods =
|
|
g_list_insert_before (client->mpd_node->Periods, next,
|
|
new_periods->data);
|
|
new_periods = g_list_delete_link (new_periods, new_periods);
|
|
}
|
|
next = NULL;
|
|
|
|
/* Update our iterator to the first new period if any, or the next */
|
|
if (prev)
|
|
l = prev->next;
|
|
else
|
|
l = client->mpd_node->Periods;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (period->SegmentList && period->SegmentList->xlink_href
|
|
&& period->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) {
|
|
GstSegmentListNode *new_segment_list;
|
|
|
|
new_segment_list =
|
|
gst_mpd_client_fetch_external_segment_list (client, period, NULL,
|
|
NULL, NULL, period->SegmentList);
|
|
|
|
gst_mpdparser_free_segment_list_node (period->SegmentList);
|
|
period->SegmentList = new_segment_list;
|
|
}
|
|
|
|
for (m = period->AdaptationSets; m; /* explicitly advanced below */ ) {
|
|
GstAdaptationSetNode *adapt_set = m->data;
|
|
GList *n;
|
|
|
|
if (adapt_set->xlink_href
|
|
&& adapt_set->actuate == GST_XLINK_ACTUATE_ON_LOAD) {
|
|
GList *new_adapt_sets, *prev, *next;
|
|
|
|
new_adapt_sets =
|
|
gst_mpd_client_fetch_external_adaptation_set (client, period,
|
|
adapt_set);
|
|
|
|
prev = m->prev;
|
|
period->AdaptationSets = g_list_delete_link (period->AdaptationSets, m);
|
|
gst_mpdparser_free_adaptation_set_node (adapt_set);
|
|
adapt_set = NULL;
|
|
|
|
/* Get new next node, we will insert before this */
|
|
if (prev)
|
|
next = prev->next;
|
|
else
|
|
next = period->AdaptationSets;
|
|
|
|
while (new_adapt_sets) {
|
|
period->AdaptationSets =
|
|
g_list_insert_before (period->AdaptationSets, next,
|
|
new_adapt_sets->data);
|
|
new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets);
|
|
}
|
|
next = NULL;
|
|
|
|
/* Update our iterator to the first new adapt_set if any, or the next */
|
|
if (prev)
|
|
m = prev->next;
|
|
else
|
|
m = period->AdaptationSets;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (adapt_set->SegmentList && adapt_set->SegmentList->xlink_href
|
|
&& adapt_set->SegmentList->actuate == GST_XLINK_ACTUATE_ON_LOAD) {
|
|
GstSegmentListNode *new_segment_list;
|
|
|
|
new_segment_list =
|
|
gst_mpd_client_fetch_external_segment_list (client, period,
|
|
adapt_set, NULL, period->SegmentList, adapt_set->SegmentList);
|
|
|
|
gst_mpdparser_free_segment_list_node (adapt_set->SegmentList);
|
|
adapt_set->SegmentList = new_segment_list;
|
|
}
|
|
|
|
for (n = adapt_set->Representations; n; n = n->next) {
|
|
GstRepresentationNode *representation = n->data;
|
|
|
|
if (representation->SegmentList
|
|
&& representation->SegmentList->xlink_href
|
|
&& representation->SegmentList->actuate ==
|
|
GST_XLINK_ACTUATE_ON_LOAD) {
|
|
|
|
GstSegmentListNode *new_segment_list;
|
|
|
|
new_segment_list =
|
|
gst_mpd_client_fetch_external_segment_list (client, period,
|
|
adapt_set, representation, adapt_set->SegmentList,
|
|
representation->SegmentList);
|
|
|
|
gst_mpdparser_free_segment_list_node (representation->SegmentList);
|
|
representation->SegmentList = new_segment_list;
|
|
|
|
}
|
|
}
|
|
|
|
m = m->next;
|
|
}
|
|
|
|
l = l->next;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_parse (GstMpdClient * client, 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 (&client->mpd_node, root_element);
|
|
}
|
|
/* free the document */
|
|
xmlFreeDoc (doc);
|
|
}
|
|
|
|
if (ret) {
|
|
gst_mpd_client_check_profiles (client);
|
|
gst_mpd_client_fetch_on_load_external_resources (client);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const gchar *
|
|
gst_mpdparser_get_baseURL (GstMpdClient * client, guint indexStream)
|
|
{
|
|
GstActiveStream *stream;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (client->active_streams != NULL, NULL);
|
|
stream = g_list_nth_data (client->active_streams, indexStream);
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
|
|
return stream->baseURL;
|
|
}
|
|
|
|
static GstClockTime
|
|
gst_mpdparser_get_segment_end_time (GstMpdClient * client, GPtrArray * segments,
|
|
const GstMediaSegment * segment, gint index)
|
|
{
|
|
const GstStreamPeriod *stream_period;
|
|
GstClockTime end;
|
|
|
|
if (segment->repeat >= 0)
|
|
return segment->start + (segment->repeat + 1) * segment->duration;
|
|
|
|
if (index < segments->len - 1) {
|
|
const GstMediaSegment *next_segment =
|
|
g_ptr_array_index (segments, index + 1);
|
|
end = next_segment->start;
|
|
} else {
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
end = stream_period->start + stream_period->duration;
|
|
}
|
|
return end;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mpd_client_add_media_segment (GstActiveStream * stream,
|
|
GstSegmentURLNode * url_node, guint number, gint repeat,
|
|
guint64 scale_start, guint64 scale_duration,
|
|
GstClockTime start, GstClockTime duration)
|
|
{
|
|
GstMediaSegment *media_segment;
|
|
|
|
g_return_val_if_fail (stream->segments != NULL, FALSE);
|
|
|
|
media_segment = g_slice_new0 (GstMediaSegment);
|
|
|
|
media_segment->SegmentURL = url_node;
|
|
media_segment->number = number;
|
|
media_segment->scale_start = scale_start;
|
|
media_segment->scale_duration = scale_duration;
|
|
media_segment->start = start;
|
|
media_segment->duration = duration;
|
|
media_segment->repeat = repeat;
|
|
|
|
g_ptr_array_add (stream->segments, media_segment);
|
|
GST_LOG ("Added new segment: number %d, repeat %d, "
|
|
"ts: %" GST_TIME_FORMAT ", dur: %"
|
|
GST_TIME_FORMAT, number, repeat,
|
|
GST_TIME_ARGS (start), GST_TIME_ARGS (duration));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_mpd_client_stream_update_presentation_time_offset (GstMpdClient * client,
|
|
GstActiveStream * stream)
|
|
{
|
|
GstSegmentBaseType *segbase = NULL;
|
|
|
|
/* Find the used segbase */
|
|
if (stream->cur_segment_list) {
|
|
segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType;
|
|
} else if (stream->cur_seg_template) {
|
|
segbase = stream->cur_seg_template->MultSegBaseType->SegBaseType;
|
|
} else if (stream->cur_segment_base) {
|
|
segbase = stream->cur_segment_base;
|
|
}
|
|
|
|
if (segbase) {
|
|
/* Avoid overflows */
|
|
stream->presentationTimeOffset =
|
|
gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND,
|
|
segbase->timescale);
|
|
} else {
|
|
stream->presentationTimeOffset = 0;
|
|
}
|
|
|
|
GST_LOG ("Setting stream's presentation time offset to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (stream->presentationTimeOffset));
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_setup_representation (GstMpdClient * client,
|
|
GstActiveStream * stream, GstRepresentationNode * representation)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
GList *rep_list;
|
|
GstClockTime PeriodStart, PeriodEnd, start_time, duration;
|
|
guint i;
|
|
guint64 start;
|
|
|
|
if (stream->cur_adapt_set == NULL) {
|
|
GST_WARNING ("No valid AdaptationSet node in the MPD file, aborting...");
|
|
return FALSE;
|
|
}
|
|
|
|
rep_list = stream->cur_adapt_set->Representations;
|
|
stream->cur_representation = representation;
|
|
stream->representation_idx = g_list_index (rep_list, representation);
|
|
|
|
/* clean the old segment list, if any */
|
|
if (stream->segments) {
|
|
g_ptr_array_unref (stream->segments);
|
|
stream->segments = NULL;
|
|
}
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, FALSE);
|
|
g_return_val_if_fail (stream_period->period != NULL, FALSE);
|
|
|
|
PeriodStart = stream_period->start;
|
|
if (GST_CLOCK_TIME_IS_VALID (stream_period->duration))
|
|
PeriodEnd = stream_period->start + stream_period->duration;
|
|
else
|
|
PeriodEnd = GST_CLOCK_TIME_NONE;
|
|
|
|
GST_LOG ("Building segment list for Period from %" GST_TIME_FORMAT " to %"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (PeriodStart), GST_TIME_ARGS (PeriodEnd));
|
|
|
|
if (representation->SegmentBase != NULL
|
|
|| representation->SegmentList != NULL) {
|
|
GList *SegmentURL;
|
|
|
|
/* We have a fixed list of segments for any of the cases here,
|
|
* init the segments list */
|
|
gst_mpdparser_init_active_stream_segments (stream);
|
|
|
|
/* get the first segment_base of the selected representation */
|
|
if ((stream->cur_segment_base =
|
|
gst_mpdparser_get_segment_base (stream_period->period,
|
|
stream->cur_adapt_set, representation)) == NULL) {
|
|
GST_DEBUG ("No useful SegmentBase node for the current Representation");
|
|
}
|
|
|
|
/* get the first segment_list of the selected representation */
|
|
if ((stream->cur_segment_list =
|
|
gst_mpdparser_get_segment_list (client, stream_period->period,
|
|
stream->cur_adapt_set, representation)) == NULL) {
|
|
GST_DEBUG ("No useful SegmentList node for the current Representation");
|
|
/* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */
|
|
if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0,
|
|
PeriodEnd - PeriodStart, PeriodStart, PeriodEnd - PeriodStart)) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
/* build the list of GstMediaSegment nodes from the SegmentList node */
|
|
SegmentURL = stream->cur_segment_list->SegmentURL;
|
|
if (SegmentURL == NULL) {
|
|
GST_WARNING
|
|
("No valid list of SegmentURL nodes in the MPD file, aborting...");
|
|
return FALSE;
|
|
}
|
|
|
|
/* build segment list */
|
|
i = stream->cur_segment_list->MultSegBaseType->startNumber;
|
|
start = 0;
|
|
start_time = PeriodStart;
|
|
|
|
GST_LOG ("Building media segment list using a SegmentList node");
|
|
if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) {
|
|
GstSegmentTimelineNode *timeline;
|
|
GstSNode *S;
|
|
GList *list;
|
|
GstClockTime presentationTimeOffset;
|
|
GstSegmentBaseType *segbase;
|
|
|
|
segbase = stream->cur_segment_list->MultSegBaseType->SegBaseType;
|
|
presentationTimeOffset =
|
|
gst_util_uint64_scale (segbase->presentationTimeOffset, GST_SECOND,
|
|
segbase->timescale);
|
|
GST_LOG ("presentationTimeOffset = %" G_GUINT64_FORMAT,
|
|
presentationTimeOffset);
|
|
timeline = stream->cur_segment_list->MultSegBaseType->SegmentTimeline;
|
|
for (list = g_queue_peek_head_link (&timeline->S); list;
|
|
list = g_list_next (list)) {
|
|
guint timescale;
|
|
|
|
S = (GstSNode *) list->data;
|
|
GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%d t=%"
|
|
G_GUINT64_FORMAT, S->d, S->r, S->t);
|
|
timescale =
|
|
stream->cur_segment_list->MultSegBaseType->SegBaseType->timescale;
|
|
duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale);
|
|
|
|
if (S->t > 0) {
|
|
start = S->t;
|
|
start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
|
|
+ PeriodStart - presentationTimeOffset;
|
|
}
|
|
|
|
if (!SegmentURL) {
|
|
GST_WARNING
|
|
("SegmentTimeline does not have a matching SegmentURL, aborting...");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i,
|
|
S->r, start, S->d, start_time, duration)) {
|
|
return FALSE;
|
|
}
|
|
i += S->r + 1;
|
|
start_time += duration * (S->r + 1);
|
|
start += S->d * (S->r + 1);
|
|
SegmentURL = g_list_next (SegmentURL);
|
|
}
|
|
} else {
|
|
guint64 scale_dur;
|
|
|
|
duration =
|
|
gst_mpd_client_get_segment_duration (client, stream, &scale_dur);
|
|
if (!GST_CLOCK_TIME_IS_VALID (duration))
|
|
return FALSE;
|
|
|
|
while (SegmentURL) {
|
|
if (!gst_mpd_client_add_media_segment (stream, SegmentURL->data, i,
|
|
0, start, scale_dur, start_time, duration)) {
|
|
return FALSE;
|
|
}
|
|
i++;
|
|
start += scale_dur;
|
|
start_time += duration;
|
|
SegmentURL = g_list_next (SegmentURL);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (representation->SegmentTemplate != NULL) {
|
|
stream->cur_seg_template = representation->SegmentTemplate;
|
|
} else if (stream->cur_adapt_set->SegmentTemplate != NULL) {
|
|
stream->cur_seg_template = stream->cur_adapt_set->SegmentTemplate;
|
|
} else if (stream_period->period->SegmentTemplate != NULL) {
|
|
stream->cur_seg_template = stream_period->period->SegmentTemplate;
|
|
}
|
|
|
|
if (stream->cur_seg_template == NULL
|
|
|| stream->cur_seg_template->MultSegBaseType == NULL) {
|
|
|
|
gst_mpdparser_init_active_stream_segments (stream);
|
|
/* here we should have a single segment for each representation, whose URL is encoded in the baseURL element */
|
|
if (!gst_mpd_client_add_media_segment (stream, NULL, 1, 0, 0,
|
|
PeriodEnd - PeriodStart, 0, PeriodEnd - PeriodStart)) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
GstClockTime presentationTimeOffset;
|
|
GstMultSegmentBaseType *mult_seg =
|
|
stream->cur_seg_template->MultSegBaseType;
|
|
|
|
presentationTimeOffset =
|
|
gst_util_uint64_scale (mult_seg->SegBaseType->presentationTimeOffset,
|
|
GST_SECOND, mult_seg->SegBaseType->timescale);
|
|
GST_LOG ("presentationTimeOffset = %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (presentationTimeOffset));
|
|
/* build segment list */
|
|
i = mult_seg->startNumber;
|
|
start = 0;
|
|
start_time = 0;
|
|
|
|
GST_LOG ("Building media segment list using this template: %s",
|
|
stream->cur_seg_template->media);
|
|
|
|
if (mult_seg->SegmentTimeline) {
|
|
GstSegmentTimelineNode *timeline;
|
|
GstSNode *S;
|
|
GList *list;
|
|
|
|
timeline = mult_seg->SegmentTimeline;
|
|
gst_mpdparser_init_active_stream_segments (stream);
|
|
for (list = g_queue_peek_head_link (&timeline->S); list;
|
|
list = g_list_next (list)) {
|
|
guint timescale;
|
|
|
|
S = (GstSNode *) list->data;
|
|
GST_LOG ("Processing S node: d=%" G_GUINT64_FORMAT " r=%u t=%"
|
|
G_GUINT64_FORMAT, S->d, S->r, S->t);
|
|
timescale = mult_seg->SegBaseType->timescale;
|
|
duration = gst_util_uint64_scale (S->d, GST_SECOND, timescale);
|
|
if (S->t > 0) {
|
|
start = S->t;
|
|
start_time = gst_util_uint64_scale (S->t, GST_SECOND, timescale)
|
|
+ PeriodStart - presentationTimeOffset;
|
|
}
|
|
|
|
if (!gst_mpd_client_add_media_segment (stream, NULL, i, S->r, start,
|
|
S->d, start_time, duration)) {
|
|
return FALSE;
|
|
}
|
|
i += S->r + 1;
|
|
start += S->d * (S->r + 1);
|
|
start_time += duration * (S->r + 1);
|
|
}
|
|
} else {
|
|
/* NOP - The segment is created on demand with the template, no need
|
|
* to build a list */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* clip duration of segments to stop at period end */
|
|
if (stream->segments && stream->segments->len) {
|
|
if (GST_CLOCK_TIME_IS_VALID (PeriodEnd)) {
|
|
guint n;
|
|
|
|
for (n = 0; n < stream->segments->len; ++n) {
|
|
GstMediaSegment *media_segment =
|
|
g_ptr_array_index (stream->segments, n);
|
|
if (media_segment) {
|
|
if (media_segment->start + media_segment->duration > PeriodEnd) {
|
|
GstClockTime stop = PeriodEnd;
|
|
if (n < stream->segments->len - 1) {
|
|
GstMediaSegment *next_segment =
|
|
g_ptr_array_index (stream->segments, n + 1);
|
|
if (next_segment && next_segment->start < PeriodEnd)
|
|
stop = next_segment->start;
|
|
}
|
|
media_segment->duration =
|
|
media_segment->start > stop ? 0 : stop - media_segment->start;
|
|
GST_LOG ("Fixed duration of segment %u: %" GST_TIME_FORMAT, n,
|
|
GST_TIME_ARGS (media_segment->duration));
|
|
|
|
/* If the segment was clipped entirely, we discard it and all
|
|
* subsequent ones */
|
|
if (media_segment->duration == 0) {
|
|
GST_WARNING ("Discarding %u segments outside period",
|
|
stream->segments->len - n);
|
|
/* _set_size should properly unref elements */
|
|
g_ptr_array_set_size (stream->segments, n);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
if (stream->segments->len > 0) {
|
|
GstMediaSegment *last_media_segment =
|
|
g_ptr_array_index (stream->segments, stream->segments->len - 1);
|
|
GST_LOG ("Built a list of %d segments", last_media_segment->number);
|
|
} else {
|
|
GST_LOG ("All media segments were clipped");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
g_free (stream->baseURL);
|
|
g_free (stream->queryURL);
|
|
stream->baseURL =
|
|
gst_mpdparser_parse_baseURL (client, stream, &stream->queryURL);
|
|
|
|
gst_mpd_client_stream_update_presentation_time_offset (client, stream);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define CUSTOM_WRAPPER_START "<custom_wrapper>"
|
|
#define CUSTOM_WRAPPER_END "</custom_wrapper>"
|
|
|
|
static GList *
|
|
gst_mpd_client_fetch_external_period (GstMpdClient * client,
|
|
GstPeriodNode * period_node)
|
|
{
|
|
GstFragment *download;
|
|
GstAdapter *adapter;
|
|
GstBuffer *period_buffer;
|
|
GError *err = NULL;
|
|
xmlDocPtr doc = NULL;
|
|
GstUri *base_uri, *uri;
|
|
gchar *query = NULL;
|
|
gchar *uri_string, *wrapper;
|
|
GList *new_periods = NULL;
|
|
const gchar *data;
|
|
|
|
/* ISO/IEC 23009-1:2014 5.5.3 4)
|
|
* Remove nodes that resolve to nothing when resolving
|
|
*/
|
|
if (strcmp (period_node->xlink_href,
|
|
"urn:mpeg:dash:resolve-to-zero:2013") == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!client->downloader) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Build absolute URI */
|
|
|
|
/* Get base URI at the MPD level */
|
|
base_uri =
|
|
gst_uri_from_string (client->
|
|
mpd_base_uri ? client->mpd_base_uri : client->mpd_uri);
|
|
|
|
/* combine a BaseURL at the MPD level with the current base url */
|
|
base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0);
|
|
uri = gst_uri_from_string_with_base (base_uri, period_node->xlink_href);
|
|
if (query)
|
|
gst_uri_set_query_string (uri, query);
|
|
g_free (query);
|
|
uri_string = gst_uri_to_string (uri);
|
|
gst_uri_unref (base_uri);
|
|
gst_uri_unref (uri);
|
|
|
|
download =
|
|
gst_uri_downloader_fetch_uri (client->downloader,
|
|
uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
|
|
g_free (uri_string);
|
|
|
|
if (!download) {
|
|
GST_ERROR ("Failed to download external Period node at '%s': %s",
|
|
period_node->xlink_href, err->message);
|
|
g_clear_error (&err);
|
|
return NULL;
|
|
}
|
|
|
|
period_buffer = gst_fragment_get_buffer (download);
|
|
g_object_unref (download);
|
|
|
|
/* external xml could have multiple period without root xmlNode.
|
|
* To avoid xml parsing error caused by no root node, wrapping it with
|
|
* custom root node */
|
|
adapter = gst_adapter_new ();
|
|
|
|
wrapper = g_new (gchar, strlen (CUSTOM_WRAPPER_START));
|
|
memcpy (wrapper, CUSTOM_WRAPPER_START, strlen (CUSTOM_WRAPPER_START));
|
|
gst_adapter_push (adapter,
|
|
gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_START)));
|
|
|
|
gst_adapter_push (adapter, period_buffer);
|
|
|
|
wrapper = g_strdup (CUSTOM_WRAPPER_END);
|
|
gst_adapter_push (adapter,
|
|
gst_buffer_new_wrapped (wrapper, strlen (CUSTOM_WRAPPER_END) + 1));
|
|
|
|
data = gst_adapter_map (adapter, gst_adapter_available (adapter));
|
|
|
|
doc =
|
|
xmlReadMemory (data, gst_adapter_available (adapter), "noname.xml", NULL,
|
|
XML_PARSE_NONET);
|
|
|
|
gst_adapter_unmap (adapter);
|
|
gst_adapter_clear (adapter);
|
|
gst_object_unref (adapter);
|
|
|
|
if (doc) {
|
|
xmlNode *root_element = xmlDocGetRootElement (doc);
|
|
xmlNode *iter;
|
|
|
|
if (root_element->type != XML_ELEMENT_NODE)
|
|
goto error;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_setup_media_presentation (GstMpdClient * client,
|
|
GstClockTime time, gint period_idx, const gchar * period_id)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
GstClockTime start, duration;
|
|
GList *list, *next;
|
|
guint idx;
|
|
gboolean ret = FALSE;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->mpd_node != NULL, FALSE);
|
|
|
|
/* Check if we set up the media presentation far enough already */
|
|
for (list = client->periods; list; list = list->next) {
|
|
GstStreamPeriod *stream_period = list->data;
|
|
|
|
if ((time != GST_CLOCK_TIME_NONE
|
|
&& stream_period->duration != GST_CLOCK_TIME_NONE
|
|
&& stream_period->start + stream_period->duration >= time)
|
|
|| (time != GST_CLOCK_TIME_NONE && stream_period->start >= time))
|
|
return TRUE;
|
|
|
|
if (period_idx != -1 && stream_period->number >= period_idx)
|
|
return TRUE;
|
|
|
|
if (period_id != NULL && stream_period->period->id != NULL
|
|
&& strcmp (stream_period->period->id, period_id) == 0)
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
GST_DEBUG ("Building the list of Periods in the Media Presentation");
|
|
/* clean the old period list, if any */
|
|
/* TODO: In theory we could reuse the ones we have so far but that
|
|
* seems more complicated than the overhead caused here
|
|
*/
|
|
if (client->periods) {
|
|
g_list_foreach (client->periods,
|
|
(GFunc) gst_mpdparser_free_stream_period, NULL);
|
|
g_list_free (client->periods);
|
|
client->periods = NULL;
|
|
}
|
|
|
|
idx = 0;
|
|
start = 0;
|
|
duration = GST_CLOCK_TIME_NONE;
|
|
|
|
if (client->mpd_node->mediaPresentationDuration <= 0 &&
|
|
client->mpd_node->mediaPresentationDuration != -1) {
|
|
/* Invalid MPD file: MPD duration is negative or zero */
|
|
goto syntax_error;
|
|
}
|
|
|
|
for (list = client->mpd_node->Periods; list; /* explicitly advanced below */ ) {
|
|
GstPeriodNode *period_node = list->data;
|
|
GstPeriodNode *next_period_node = NULL;
|
|
|
|
/* Download external period */
|
|
if (period_node->xlink_href) {
|
|
GList *new_periods;
|
|
GList *prev;
|
|
|
|
new_periods = gst_mpd_client_fetch_external_period (client, period_node);
|
|
|
|
prev = list->prev;
|
|
client->mpd_node->Periods =
|
|
g_list_delete_link (client->mpd_node->Periods, list);
|
|
gst_mpdparser_free_period_node (period_node);
|
|
period_node = NULL;
|
|
|
|
/* Get new next node, we will insert before this */
|
|
if (prev)
|
|
next = prev->next;
|
|
else
|
|
next = client->mpd_node->Periods;
|
|
|
|
while (new_periods) {
|
|
client->mpd_node->Periods =
|
|
g_list_insert_before (client->mpd_node->Periods, next,
|
|
new_periods->data);
|
|
new_periods = g_list_delete_link (new_periods, new_periods);
|
|
}
|
|
next = NULL;
|
|
|
|
/* Update our iterator to the first new period if any, or the next */
|
|
if (prev)
|
|
list = prev->next;
|
|
else
|
|
list = client->mpd_node->Periods;
|
|
|
|
/* And try again */
|
|
continue;
|
|
}
|
|
|
|
if (period_node->start != -1) {
|
|
/* we have a regular period */
|
|
/* start cannot be smaller than previous start */
|
|
if (list != g_list_first (client->mpd_node->Periods)
|
|
&& start >= period_node->start * GST_MSECOND) {
|
|
/* Invalid MPD file: duration would be negative or zero */
|
|
goto syntax_error;
|
|
}
|
|
start = period_node->start * GST_MSECOND;
|
|
} else if (duration != GST_CLOCK_TIME_NONE) {
|
|
/* start time inferred from previous period, this is still a regular period */
|
|
start += duration;
|
|
} else if (idx == 0 && client->mpd_node->type == GST_MPD_FILE_TYPE_STATIC) {
|
|
/* first period of a static MPD file, start time is 0 */
|
|
start = 0;
|
|
} else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
|
|
/* this should be a live stream, let this pass */
|
|
} else {
|
|
/* this is an 'Early Available Period' */
|
|
goto early;
|
|
}
|
|
|
|
/* compute duration.
|
|
If there is a start time for the next period, or this is the last period
|
|
and mediaPresentationDuration was set, those values will take precedence
|
|
over a configured period duration in computing this period's duration
|
|
|
|
ISO/IEC 23009-1:2014(E), chapter 5.3.2.1
|
|
"The Period extends until the PeriodStart of the next Period, or until
|
|
the end of the Media Presentation in the case of the last Period."
|
|
*/
|
|
|
|
while ((next = g_list_next (list)) != NULL) {
|
|
/* try to infer this period duration from the start time of the next period */
|
|
next_period_node = next->data;
|
|
|
|
if (next_period_node->xlink_href) {
|
|
GList *new_periods;
|
|
|
|
new_periods =
|
|
gst_mpd_client_fetch_external_period (client, next_period_node);
|
|
|
|
client->mpd_node->Periods =
|
|
g_list_delete_link (client->mpd_node->Periods, next);
|
|
gst_mpdparser_free_period_node (next_period_node);
|
|
next_period_node = NULL;
|
|
/* Get new next node, we will insert before this */
|
|
next = g_list_next (list);
|
|
while (new_periods) {
|
|
client->mpd_node->Periods =
|
|
g_list_insert_before (client->mpd_node->Periods, next,
|
|
new_periods->data);
|
|
new_periods = g_list_delete_link (new_periods, new_periods);
|
|
}
|
|
|
|
/* And try again, getting the next list element which is now our newly
|
|
* inserted nodes. If any */
|
|
} else {
|
|
/* Got the next period and it doesn't have to be downloaded first */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (next_period_node) {
|
|
if (next_period_node->start != -1) {
|
|
if (start >= next_period_node->start * GST_MSECOND) {
|
|
/* Invalid MPD file: duration would be negative or zero */
|
|
goto syntax_error;
|
|
}
|
|
duration = next_period_node->start * GST_MSECOND - start;
|
|
} else if (period_node->duration != -1) {
|
|
if (period_node->duration <= 0) {
|
|
/* Invalid MPD file: duration would be negative or zero */
|
|
goto syntax_error;
|
|
}
|
|
duration = period_node->duration * GST_MSECOND;
|
|
} else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
|
|
/* might be a live file, ignore unspecified duration */
|
|
} else {
|
|
/* Invalid MPD file! */
|
|
goto syntax_error;
|
|
}
|
|
} else if (client->mpd_node->mediaPresentationDuration != -1) {
|
|
/* last Period of the Media Presentation */
|
|
if (client->mpd_node->mediaPresentationDuration * GST_MSECOND <= start) {
|
|
/* Invalid MPD file: duration would be negative or zero */
|
|
goto syntax_error;
|
|
}
|
|
duration =
|
|
client->mpd_node->mediaPresentationDuration * GST_MSECOND - start;
|
|
} else if (period_node->duration != -1) {
|
|
duration = period_node->duration * GST_MSECOND;
|
|
} else if (client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC) {
|
|
/* might be a live file, ignore unspecified duration */
|
|
} else {
|
|
/* Invalid MPD file! */
|
|
goto syntax_error;
|
|
}
|
|
|
|
stream_period = g_slice_new0 (GstStreamPeriod);
|
|
client->periods = g_list_append (client->periods, stream_period);
|
|
stream_period->period = period_node;
|
|
stream_period->number = idx++;
|
|
stream_period->start = start;
|
|
stream_period->duration = duration;
|
|
ret = TRUE;
|
|
GST_LOG (" - added Period %d start=%" GST_TIME_FORMAT " duration=%"
|
|
GST_TIME_FORMAT, idx, GST_TIME_ARGS (start), GST_TIME_ARGS (duration));
|
|
|
|
if ((time != GST_CLOCK_TIME_NONE
|
|
&& stream_period->duration != GST_CLOCK_TIME_NONE
|
|
&& stream_period->start + stream_period->duration >= time)
|
|
|| (time != GST_CLOCK_TIME_NONE && stream_period->start >= time))
|
|
break;
|
|
|
|
if (period_idx != -1 && stream_period->number >= period_idx)
|
|
break;
|
|
|
|
if (period_id != NULL && stream_period->period->id != NULL
|
|
&& strcmp (stream_period->period->id, period_id) == 0)
|
|
break;
|
|
|
|
list = list->next;
|
|
}
|
|
|
|
GST_DEBUG
|
|
("Found a total of %d valid Periods in the Media Presentation up to this point",
|
|
idx);
|
|
return ret;
|
|
|
|
early:
|
|
GST_WARNING
|
|
("Found an Early Available Period, skipping the rest of the Media Presentation");
|
|
return ret;
|
|
|
|
syntax_error:
|
|
GST_WARNING
|
|
("Cannot get the duration of the Period %d, skipping the rest of the Media Presentation",
|
|
idx);
|
|
return ret;
|
|
}
|
|
|
|
static GList *
|
|
gst_mpd_client_fetch_external_adaptation_set (GstMpdClient * client,
|
|
GstPeriodNode * period, GstAdaptationSetNode * adapt_set)
|
|
{
|
|
GstFragment *download;
|
|
GstBuffer *adapt_set_buffer;
|
|
GstMapInfo map;
|
|
GError *err = NULL;
|
|
xmlDocPtr doc = NULL;
|
|
GstUri *base_uri, *uri;
|
|
gchar *query = NULL;
|
|
gchar *uri_string;
|
|
GList *new_adapt_sets = NULL;
|
|
|
|
/* ISO/IEC 23009-1:2014 5.5.3 4)
|
|
* Remove nodes that resolve to nothing when resolving
|
|
*/
|
|
if (strcmp (adapt_set->xlink_href, "urn:mpeg:dash:resolve-to-zero:2013") == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!client->downloader) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Build absolute URI */
|
|
|
|
/* Get base URI at the MPD level */
|
|
base_uri =
|
|
gst_uri_from_string (client->
|
|
mpd_base_uri ? client->mpd_base_uri : client->mpd_uri);
|
|
|
|
/* combine a BaseURL at the MPD level with the current base url */
|
|
base_uri = combine_urls (base_uri, client->mpd_node->BaseURLs, &query, 0);
|
|
|
|
/* combine a BaseURL at the Period level with the current base url */
|
|
base_uri = combine_urls (base_uri, period->BaseURLs, &query, 0);
|
|
|
|
uri = gst_uri_from_string_with_base (base_uri, adapt_set->xlink_href);
|
|
if (query)
|
|
gst_uri_set_query_string (uri, query);
|
|
g_free (query);
|
|
uri_string = gst_uri_to_string (uri);
|
|
gst_uri_unref (base_uri);
|
|
gst_uri_unref (uri);
|
|
|
|
download =
|
|
gst_uri_downloader_fetch_uri (client->downloader,
|
|
uri_string, client->mpd_uri, TRUE, FALSE, TRUE, &err);
|
|
g_free (uri_string);
|
|
|
|
if (!download) {
|
|
GST_ERROR ("Failed to download external AdaptationSet node at '%s': %s",
|
|
adapt_set->xlink_href, err->message);
|
|
g_clear_error (&err);
|
|
return NULL;
|
|
}
|
|
|
|
adapt_set_buffer = gst_fragment_get_buffer (download);
|
|
g_object_unref (download);
|
|
|
|
gst_buffer_map (adapt_set_buffer, &map, GST_MAP_READ);
|
|
|
|
doc =
|
|
xmlReadMemory ((const gchar *) map.data, map.size, "noname.xml", NULL,
|
|
XML_PARSE_NONET);
|
|
|
|
gst_buffer_unmap (adapt_set_buffer, &map);
|
|
gst_buffer_unref (adapt_set_buffer);
|
|
|
|
/* 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) {
|
|
goto error;
|
|
}
|
|
|
|
gst_mpdparser_parse_adaptation_set_node (&new_adapt_sets, root_element,
|
|
period);
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
done:
|
|
if (doc)
|
|
xmlFreeDoc (doc);
|
|
|
|
return new_adapt_sets;
|
|
|
|
error:
|
|
GST_ERROR ("Failed to parse adaptation set node XML");
|
|
goto done;
|
|
}
|
|
|
|
static GList *
|
|
gst_mpd_client_get_adaptation_sets_for_period (GstMpdClient * client,
|
|
GstStreamPeriod * period)
|
|
{
|
|
GList *list;
|
|
|
|
g_return_val_if_fail (period != NULL, NULL);
|
|
|
|
/* Resolve all external adaptation sets of this period. Every user of
|
|
* the adaptation sets would need to know the content of all adaptation sets
|
|
* to decide which one to use, so we have to resolve them all here
|
|
*/
|
|
for (list = period->period->AdaptationSets; list;
|
|
/* advanced explicitely below */ ) {
|
|
GstAdaptationSetNode *adapt_set = (GstAdaptationSetNode *) list->data;
|
|
GList *new_adapt_sets = NULL, *prev, *next;
|
|
|
|
if (!adapt_set->xlink_href) {
|
|
list = list->next;
|
|
continue;
|
|
}
|
|
|
|
new_adapt_sets =
|
|
gst_mpd_client_fetch_external_adaptation_set (client, period->period,
|
|
adapt_set);
|
|
|
|
prev = list->prev;
|
|
period->period->AdaptationSets =
|
|
g_list_delete_link (period->period->AdaptationSets, list);
|
|
gst_mpdparser_free_adaptation_set_node (adapt_set);
|
|
adapt_set = NULL;
|
|
|
|
/* Get new next node, we will insert before this */
|
|
if (prev)
|
|
next = prev->next;
|
|
else
|
|
next = period->period->AdaptationSets;
|
|
|
|
while (new_adapt_sets) {
|
|
period->period->AdaptationSets =
|
|
g_list_insert_before (period->period->AdaptationSets, next,
|
|
new_adapt_sets->data);
|
|
new_adapt_sets = g_list_delete_link (new_adapt_sets, new_adapt_sets);
|
|
}
|
|
|
|
/* Update our iterator to the first new adaptation set if any, or the next */
|
|
if (prev)
|
|
list = prev->next;
|
|
else
|
|
list = period->period->AdaptationSets;
|
|
}
|
|
|
|
return period->period->AdaptationSets;
|
|
}
|
|
|
|
GList *
|
|
gst_mpd_client_get_adaptation_sets (GstMpdClient * client)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
if (stream_period == NULL || stream_period->period == NULL) {
|
|
GST_DEBUG ("No more Period nodes in the MPD file, terminating...");
|
|
return NULL;
|
|
}
|
|
|
|
return gst_mpd_client_get_adaptation_sets_for_period (client, stream_period);
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_setup_streaming (GstMpdClient * client,
|
|
GstAdaptationSetNode * adapt_set)
|
|
{
|
|
GstRepresentationNode *representation;
|
|
GList *rep_list = NULL;
|
|
GstActiveStream *stream;
|
|
|
|
rep_list = adapt_set->Representations;
|
|
if (!rep_list) {
|
|
GST_WARNING ("Can not retrieve any representation, aborting...");
|
|
return FALSE;
|
|
}
|
|
|
|
stream = g_slice_new0 (GstActiveStream);
|
|
gst_mpdparser_init_active_stream_segments (stream);
|
|
|
|
stream->baseURL_idx = 0;
|
|
stream->cur_adapt_set = adapt_set;
|
|
|
|
GST_DEBUG ("0. Current stream %p", stream);
|
|
|
|
#if 0
|
|
/* fast start */
|
|
representation =
|
|
gst_mpdparser_get_representation_with_max_bandwidth (rep_list,
|
|
stream->max_bandwidth);
|
|
|
|
if (!representation) {
|
|
GST_WARNING
|
|
("Can not retrieve a representation with the requested bandwidth");
|
|
representation = gst_mpdparser_get_lowest_representation (rep_list);
|
|
}
|
|
#else
|
|
/* slow start */
|
|
representation = gst_mpdparser_get_lowest_representation (rep_list);
|
|
#endif
|
|
|
|
if (!representation) {
|
|
GST_WARNING ("No valid representation in the MPD file, aborting...");
|
|
gst_mpdparser_free_active_stream (stream);
|
|
return FALSE;
|
|
}
|
|
stream->mimeType =
|
|
gst_mpdparser_representation_get_mimetype (adapt_set, representation);
|
|
if (stream->mimeType == GST_STREAM_UNKNOWN) {
|
|
GST_WARNING ("Unknown mime type in the representation, aborting...");
|
|
gst_mpdparser_free_active_stream (stream);
|
|
return FALSE;
|
|
}
|
|
|
|
client->active_streams = g_list_append (client->active_streams, stream);
|
|
if (!gst_mpd_client_setup_representation (client, stream, representation)) {
|
|
GST_WARNING ("Failed to setup the representation, aborting...");
|
|
return FALSE;
|
|
}
|
|
|
|
GST_INFO ("Successfully setup the download pipeline for mimeType %d",
|
|
stream->mimeType);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_stream_seek (GstMpdClient * client, GstActiveStream * stream,
|
|
gboolean forward, GstSeekFlags flags, GstClockTime ts,
|
|
GstClockTime * final_ts)
|
|
{
|
|
gint index = 0;
|
|
gint repeat_index = 0;
|
|
GstMediaSegment *selectedChunk = NULL;
|
|
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
if (stream->segments) {
|
|
for (index = 0; index < stream->segments->len; index++) {
|
|
gboolean in_segment = FALSE;
|
|
GstMediaSegment *segment = g_ptr_array_index (stream->segments, index);
|
|
GstClockTime end_time;
|
|
|
|
GST_DEBUG ("Looking at fragment sequence chunk %d / %d", index,
|
|
stream->segments->len);
|
|
|
|
end_time =
|
|
gst_mpdparser_get_segment_end_time (client, stream->segments,
|
|
segment, index);
|
|
|
|
/* avoid downloading another fragment just for 1ns in reverse mode */
|
|
if (forward)
|
|
in_segment = ts < end_time;
|
|
else
|
|
in_segment = ts <= end_time;
|
|
|
|
if (in_segment) {
|
|
GstClockTime chunk_time;
|
|
|
|
selectedChunk = segment;
|
|
repeat_index = (ts - segment->start) / segment->duration;
|
|
|
|
chunk_time = segment->start + segment->duration * repeat_index;
|
|
|
|
/* At the end of a segment in reverse mode, start from the previous fragment */
|
|
if (!forward && repeat_index > 0
|
|
&& ((ts - segment->start) % segment->duration == 0))
|
|
repeat_index--;
|
|
|
|
if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) {
|
|
if (repeat_index + 1 < segment->repeat) {
|
|
if (ts - chunk_time > chunk_time + segment->duration - ts)
|
|
repeat_index++;
|
|
} else if (index + 1 < stream->segments->len) {
|
|
GstMediaSegment *next_segment =
|
|
g_ptr_array_index (stream->segments, index + 1);
|
|
|
|
if (ts - chunk_time > next_segment->start - ts) {
|
|
repeat_index = 0;
|
|
selectedChunk = next_segment;
|
|
index++;
|
|
}
|
|
}
|
|
} else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) ||
|
|
(!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE)) &&
|
|
ts != chunk_time) {
|
|
|
|
if (repeat_index + 1 < segment->repeat) {
|
|
repeat_index++;
|
|
} else {
|
|
repeat_index = 0;
|
|
if (index + 1 >= stream->segments->len) {
|
|
selectedChunk = NULL;
|
|
} else {
|
|
selectedChunk = g_ptr_array_index (stream->segments, ++index);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (selectedChunk == NULL) {
|
|
stream->segment_index = stream->segments->len;
|
|
stream->segment_repeat_index = 0;
|
|
GST_DEBUG ("Seek to after last segment");
|
|
return FALSE;
|
|
}
|
|
|
|
if (final_ts)
|
|
*final_ts = selectedChunk->start + selectedChunk->duration * repeat_index;
|
|
} else {
|
|
GstClockTime duration =
|
|
gst_mpd_client_get_segment_duration (client, stream, NULL);
|
|
GstStreamPeriod *stream_period = gst_mpdparser_get_stream_period (client);
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
GstClockTime index_time;
|
|
|
|
g_return_val_if_fail (stream->cur_seg_template->
|
|
MultSegBaseType->SegmentTimeline == NULL, FALSE);
|
|
if (!GST_CLOCK_TIME_IS_VALID (duration)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (ts > stream_period->start)
|
|
ts -= stream_period->start;
|
|
else
|
|
ts = 0;
|
|
|
|
index = ts / duration;
|
|
|
|
/* At the end of a segment in reverse mode, start from the previous fragment */
|
|
if (!forward && index > 0 && ts % duration == 0)
|
|
index--;
|
|
|
|
index_time = index * duration;
|
|
|
|
if ((flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST) {
|
|
if (ts - index_time > index_time + duration - ts)
|
|
index++;
|
|
} else if (((forward && flags & GST_SEEK_FLAG_SNAP_AFTER) ||
|
|
(!forward && flags & GST_SEEK_FLAG_SNAP_BEFORE))
|
|
&& ts != index_time) {
|
|
index++;
|
|
}
|
|
|
|
if (segments_count > 0 && index >= segments_count) {
|
|
stream->segment_index = segments_count;
|
|
stream->segment_repeat_index = 0;
|
|
GST_DEBUG ("Seek to after last segment");
|
|
return FALSE;
|
|
}
|
|
if (final_ts)
|
|
*final_ts = index * duration;
|
|
}
|
|
|
|
stream->segment_repeat_index = repeat_index;
|
|
stream->segment_index = index;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gint64
|
|
gst_mpd_client_calculate_time_difference (const GstDateTime * t1,
|
|
const GstDateTime * t2)
|
|
{
|
|
GDateTime *gdt1, *gdt2;
|
|
GTimeSpan diff;
|
|
|
|
g_assert (t1 != NULL && t2 != NULL);
|
|
gdt1 = gst_date_time_to_g_date_time ((GstDateTime *) t1);
|
|
gdt2 = gst_date_time_to_g_date_time ((GstDateTime *) t2);
|
|
diff = g_date_time_difference (gdt2, gdt1);
|
|
g_date_time_unref (gdt1);
|
|
g_date_time_unref (gdt2);
|
|
return diff * GST_USECOND;
|
|
}
|
|
|
|
GstDateTime *
|
|
gst_mpd_client_add_time_difference (GstDateTime * t1, gint64 usecs)
|
|
{
|
|
GDateTime *gdt;
|
|
GDateTime *gdt2;
|
|
GstDateTime *rv;
|
|
|
|
g_assert (t1 != NULL);
|
|
gdt = gst_date_time_to_g_date_time (t1);
|
|
g_assert (gdt != NULL);
|
|
gdt2 = g_date_time_add (gdt, usecs);
|
|
g_assert (gdt2 != NULL);
|
|
g_date_time_unref (gdt);
|
|
rv = gst_date_time_new_from_g_date_time (gdt2);
|
|
|
|
/* Don't g_date_time_unref(gdt2) because gst_date_time_new_from_g_date_time takes
|
|
* ownership of the GDateTime pointer.
|
|
*/
|
|
|
|
return rv;
|
|
}
|
|
|
|
static GstDateTime *
|
|
gst_mpd_client_get_availability_start_time (GstMpdClient * client)
|
|
{
|
|
GstDateTime *start_time;
|
|
|
|
if (client == NULL)
|
|
return (GstDateTime *) NULL;
|
|
|
|
start_time = client->mpd_node->availabilityStartTime;
|
|
if (start_time)
|
|
gst_date_time_ref (start_time);
|
|
return start_time;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_last_fragment_timestamp_end (GstMpdClient * client,
|
|
guint stream_idx, GstClockTime * ts)
|
|
{
|
|
GstActiveStream *stream;
|
|
gint segment_idx;
|
|
GstMediaSegment *currentChunk;
|
|
GstStreamPeriod *stream_period;
|
|
|
|
GST_DEBUG ("Stream index: %i", stream_idx);
|
|
stream = g_list_nth_data (client->active_streams, stream_idx);
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
if (!stream->segments) {
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
*ts = stream_period->start + stream_period->duration;
|
|
} else {
|
|
segment_idx = gst_mpd_client_get_segments_counts (client, stream) - 1;
|
|
if (segment_idx >= stream->segments->len) {
|
|
GST_WARNING ("Segment index %d is outside of segment list of length %d",
|
|
segment_idx, stream->segments->len);
|
|
return FALSE;
|
|
}
|
|
currentChunk = g_ptr_array_index (stream->segments, segment_idx);
|
|
|
|
if (currentChunk->repeat >= 0) {
|
|
*ts =
|
|
currentChunk->start + (currentChunk->duration * (1 +
|
|
currentChunk->repeat));
|
|
} else {
|
|
/* 5.3.9.6.1: negative repeat means repeat till the end of the
|
|
* period, or the next update of the MPD (which I think is
|
|
* implicit, as this will all get deleted/recreated), or the
|
|
* start of the next segment, if any. */
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
*ts = stream_period->start + stream_period->duration;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_next_fragment_timestamp (GstMpdClient * client,
|
|
guint stream_idx, GstClockTime * ts)
|
|
{
|
|
GstActiveStream *stream;
|
|
GstMediaSegment *currentChunk;
|
|
|
|
GST_DEBUG ("Stream index: %i", stream_idx);
|
|
stream = g_list_nth_data (client->active_streams, stream_idx);
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
if (stream->segments) {
|
|
GST_DEBUG ("Looking for fragment sequence chunk %d / %d",
|
|
stream->segment_index, stream->segments->len);
|
|
if (stream->segment_index >= stream->segments->len)
|
|
return FALSE;
|
|
currentChunk = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
|
|
*ts =
|
|
currentChunk->start +
|
|
(currentChunk->duration * stream->segment_repeat_index);
|
|
} else {
|
|
GstClockTime duration =
|
|
gst_mpd_client_get_segment_duration (client, stream, NULL);
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
|
|
g_return_val_if_fail (stream->cur_seg_template->
|
|
MultSegBaseType->SegmentTimeline == NULL, FALSE);
|
|
if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
|
|
&& stream->segment_index >= segments_count)) {
|
|
return FALSE;
|
|
}
|
|
*ts = stream->segment_index * duration;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_mpd_parser_get_stream_presentation_offset (GstMpdClient * client,
|
|
guint stream_idx)
|
|
{
|
|
GstActiveStream *stream = NULL;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
g_return_val_if_fail (client->active_streams != NULL, 0);
|
|
stream = g_list_nth_data (client->active_streams, stream_idx);
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
return stream->presentationTimeOffset;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_mpd_parser_get_period_start_time (GstMpdClient * client)
|
|
{
|
|
GstStreamPeriod *stream_period = NULL;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, 0);
|
|
|
|
return stream_period->start;
|
|
}
|
|
|
|
/**
|
|
* gst_mpd_client_get_utc_timing_sources:
|
|
* @client: #GstMpdClient to check for UTCTiming elements
|
|
* @methods: A bit mask of #GstMPDUTCTimingType that specifies the methods
|
|
* to search for.
|
|
* @selected_method: (nullable): The selected method
|
|
* Returns: (transfer none): A NULL terminated array of URLs of servers
|
|
* that use @selected_method to provide a realtime clock.
|
|
*
|
|
* Searches the UTCTiming elements found in the manifest for an element
|
|
* that uses one of the UTC timing methods specified in @selected_method.
|
|
* If multiple UTCTiming elements are present that support one of the
|
|
* methods specified in @selected_method, the first one is returned.
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
gchar **
|
|
gst_mpd_client_get_utc_timing_sources (GstMpdClient * client,
|
|
guint methods, GstMPDUTCTimingType * selected_method)
|
|
{
|
|
GList *list;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (client->mpd_node != NULL, NULL);
|
|
for (list = g_list_first (client->mpd_node->UTCTiming); list;
|
|
list = g_list_next (list)) {
|
|
const GstUTCTimingNode *node = (const GstUTCTimingNode *) list->data;
|
|
if (node->method & methods) {
|
|
if (selected_method) {
|
|
*selected_method = node->method;
|
|
}
|
|
return node->urls;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_next_fragment (GstMpdClient * client,
|
|
guint indexStream, GstMediaFragmentInfo * fragment)
|
|
{
|
|
GstActiveStream *stream = NULL;
|
|
GstMediaSegment *currentChunk;
|
|
gchar *mediaURL = NULL;
|
|
gchar *indexURL = NULL;
|
|
GstUri *base_url, *frag_url;
|
|
|
|
/* select stream */
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->active_streams != NULL, FALSE);
|
|
stream = g_list_nth_data (client->active_streams, indexStream);
|
|
g_return_val_if_fail (stream != NULL, FALSE);
|
|
g_return_val_if_fail (stream->cur_representation != NULL, FALSE);
|
|
|
|
if (stream->segments) {
|
|
GST_DEBUG ("Looking for fragment sequence chunk %d / %d",
|
|
stream->segment_index, stream->segments->len);
|
|
if (stream->segment_index >= stream->segments->len)
|
|
return FALSE;
|
|
} else {
|
|
GstClockTime duration = gst_mpd_client_get_segment_duration (client,
|
|
stream, NULL);
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
|
|
g_return_val_if_fail (stream->cur_seg_template->
|
|
MultSegBaseType->SegmentTimeline == NULL, FALSE);
|
|
if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
|
|
&& stream->segment_index >= segments_count)) {
|
|
return FALSE;
|
|
}
|
|
fragment->duration = duration;
|
|
}
|
|
|
|
/* FIXME rework discont checking */
|
|
/* fragment->discontinuity = segment_idx != currentChunk.number; */
|
|
fragment->range_start = 0;
|
|
fragment->range_end = -1;
|
|
fragment->index_uri = NULL;
|
|
fragment->index_range_start = 0;
|
|
fragment->index_range_end = -1;
|
|
|
|
if (stream->segments) {
|
|
currentChunk = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
|
|
GST_DEBUG ("currentChunk->SegmentURL = %p", currentChunk->SegmentURL);
|
|
if (currentChunk->SegmentURL != NULL) {
|
|
mediaURL =
|
|
g_strdup (gst_mpdparser_get_mediaURL (stream,
|
|
currentChunk->SegmentURL));
|
|
indexURL = g_strdup (currentChunk->SegmentURL->index);
|
|
} else if (stream->cur_seg_template != NULL) {
|
|
mediaURL =
|
|
gst_mpdparser_build_URL_from_template (stream->
|
|
cur_seg_template->media, stream->cur_representation->id,
|
|
currentChunk->number + stream->segment_repeat_index,
|
|
stream->cur_representation->bandwidth,
|
|
currentChunk->scale_start +
|
|
stream->segment_repeat_index * currentChunk->scale_duration);
|
|
if (stream->cur_seg_template->index) {
|
|
indexURL =
|
|
gst_mpdparser_build_URL_from_template (stream->
|
|
cur_seg_template->index, stream->cur_representation->id,
|
|
currentChunk->number + stream->segment_repeat_index,
|
|
stream->cur_representation->bandwidth,
|
|
currentChunk->scale_start +
|
|
stream->segment_repeat_index * currentChunk->scale_duration);
|
|
}
|
|
}
|
|
GST_DEBUG ("mediaURL = %s", mediaURL);
|
|
GST_DEBUG ("indexURL = %s", indexURL);
|
|
|
|
fragment->timestamp =
|
|
currentChunk->start +
|
|
stream->segment_repeat_index * currentChunk->duration;
|
|
fragment->duration = currentChunk->duration;
|
|
if (currentChunk->SegmentURL) {
|
|
if (currentChunk->SegmentURL->mediaRange) {
|
|
fragment->range_start =
|
|
currentChunk->SegmentURL->mediaRange->first_byte_pos;
|
|
fragment->range_end =
|
|
currentChunk->SegmentURL->mediaRange->last_byte_pos;
|
|
}
|
|
if (currentChunk->SegmentURL->indexRange) {
|
|
fragment->index_range_start =
|
|
currentChunk->SegmentURL->indexRange->first_byte_pos;
|
|
fragment->index_range_end =
|
|
currentChunk->SegmentURL->indexRange->last_byte_pos;
|
|
}
|
|
}
|
|
} else {
|
|
if (stream->cur_seg_template != NULL) {
|
|
mediaURL =
|
|
gst_mpdparser_build_URL_from_template (stream->
|
|
cur_seg_template->media, stream->cur_representation->id,
|
|
stream->segment_index +
|
|
stream->cur_seg_template->MultSegBaseType->startNumber,
|
|
stream->cur_representation->bandwidth,
|
|
stream->segment_index * fragment->duration);
|
|
if (stream->cur_seg_template->index) {
|
|
indexURL =
|
|
gst_mpdparser_build_URL_from_template (stream->
|
|
cur_seg_template->index, stream->cur_representation->id,
|
|
stream->segment_index +
|
|
stream->cur_seg_template->MultSegBaseType->startNumber,
|
|
stream->cur_representation->bandwidth,
|
|
stream->segment_index * fragment->duration);
|
|
}
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG ("mediaURL = %s", mediaURL);
|
|
GST_DEBUG ("indexURL = %s", indexURL);
|
|
|
|
fragment->timestamp = stream->segment_index * fragment->duration;
|
|
}
|
|
|
|
base_url = gst_uri_from_string (stream->baseURL);
|
|
frag_url = gst_uri_from_string_with_base (base_url, mediaURL);
|
|
g_free (mediaURL);
|
|
if (stream->queryURL) {
|
|
frag_url = gst_uri_make_writable (frag_url);
|
|
gst_uri_set_query_string (frag_url, stream->queryURL);
|
|
}
|
|
fragment->uri = gst_uri_to_string (frag_url);
|
|
gst_uri_unref (frag_url);
|
|
|
|
if (indexURL != NULL) {
|
|
frag_url = gst_uri_make_writable (gst_uri_from_string_with_base (base_url,
|
|
indexURL));
|
|
gst_uri_set_query_string (frag_url, stream->queryURL);
|
|
fragment->index_uri = gst_uri_to_string (frag_url);
|
|
gst_uri_unref (frag_url);
|
|
g_free (indexURL);
|
|
} else if (indexURL == NULL && (fragment->index_range_start
|
|
|| fragment->index_range_end != -1)) {
|
|
/* index has no specific URL but has a range, we should only use this if
|
|
* the media also has a range, otherwise we are serving some data twice
|
|
* (in the media fragment and again in the index) */
|
|
if (!(fragment->range_start || fragment->range_end != -1)) {
|
|
GST_WARNING ("Ignoring index ranges because there isn't a media range "
|
|
"and URIs would be the same");
|
|
/* removing index information */
|
|
fragment->index_range_start = 0;
|
|
fragment->index_range_end = -1;
|
|
}
|
|
}
|
|
|
|
gst_uri_unref (base_url);
|
|
|
|
GST_DEBUG ("Loading chunk with URL %s", fragment->uri);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_has_next_segment (GstMpdClient * client,
|
|
GstActiveStream * stream, gboolean forward)
|
|
{
|
|
if (forward) {
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
|
|
if (segments_count > 0 && stream->segments
|
|
&& stream->segment_index + 1 == segments_count) {
|
|
GstMediaSegment *segment;
|
|
|
|
segment = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
if (segment->repeat >= 0
|
|
&& stream->segment_repeat_index >= segment->repeat)
|
|
return FALSE;
|
|
} else if (segments_count > 0
|
|
&& stream->segment_index + 1 >= segments_count) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
if (stream->segment_index < 0)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstFlowReturn
|
|
gst_mpd_client_advance_segment (GstMpdClient * client, GstActiveStream * stream,
|
|
gboolean forward)
|
|
{
|
|
GstMediaSegment *segment;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
|
|
GST_DEBUG ("Advancing segment. Current: %d / %d r:%d", stream->segment_index,
|
|
segments_count, stream->segment_repeat_index);
|
|
|
|
/* handle special cases first */
|
|
if (forward) {
|
|
if (segments_count > 0 && stream->segment_index >= segments_count) {
|
|
ret = GST_FLOW_EOS;
|
|
goto done;
|
|
}
|
|
|
|
if (stream->segments == NULL) {
|
|
if (stream->segment_index < 0) {
|
|
stream->segment_index = 0;
|
|
} else {
|
|
stream->segment_index++;
|
|
if (segments_count > 0 && stream->segment_index >= segments_count) {
|
|
ret = GST_FLOW_EOS;
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* special case for when playback direction is reverted right at *
|
|
* the end of the segment list */
|
|
if (stream->segment_index < 0) {
|
|
stream->segment_index = 0;
|
|
goto done;
|
|
}
|
|
} else {
|
|
if (stream->segments == NULL)
|
|
stream->segment_index--;
|
|
if (stream->segment_index < 0) {
|
|
stream->segment_index = -1;
|
|
ret = GST_FLOW_EOS;
|
|
goto done;
|
|
}
|
|
if (stream->segments == NULL)
|
|
goto done;
|
|
|
|
/* special case for when playback direction is reverted right at *
|
|
* the end of the segment list */
|
|
if (stream->segment_index >= segments_count) {
|
|
stream->segment_index = segments_count - 1;
|
|
segment = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
if (segment->repeat >= 0) {
|
|
stream->segment_repeat_index = segment->repeat;
|
|
} else {
|
|
GstClockTime start = segment->start;
|
|
GstClockTime end =
|
|
gst_mpdparser_get_segment_end_time (client, stream->segments,
|
|
segment,
|
|
stream->segment_index);
|
|
stream->segment_repeat_index =
|
|
(guint) (end - start) / segment->duration;
|
|
}
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* for the normal cases we can get the segment safely here */
|
|
segment = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
if (forward) {
|
|
if (segment->repeat >= 0 && stream->segment_repeat_index >= segment->repeat) {
|
|
stream->segment_repeat_index = 0;
|
|
stream->segment_index++;
|
|
if (segments_count > 0 && stream->segment_index >= segments_count) {
|
|
ret = GST_FLOW_EOS;
|
|
goto done;
|
|
}
|
|
} else {
|
|
stream->segment_repeat_index++;
|
|
}
|
|
} else {
|
|
if (stream->segment_repeat_index == 0) {
|
|
stream->segment_index--;
|
|
if (stream->segment_index < 0) {
|
|
ret = GST_FLOW_EOS;
|
|
goto done;
|
|
}
|
|
|
|
segment = g_ptr_array_index (stream->segments, stream->segment_index);
|
|
/* negative repeats only seem to make sense at the end of a list,
|
|
* so this one will probably not be. Needs some sanity checking
|
|
* when loading the XML data. */
|
|
if (segment->repeat >= 0) {
|
|
stream->segment_repeat_index = segment->repeat;
|
|
} else {
|
|
GstClockTime start = segment->start;
|
|
GstClockTime end =
|
|
gst_mpdparser_get_segment_end_time (client, stream->segments,
|
|
segment,
|
|
stream->segment_index);
|
|
stream->segment_repeat_index =
|
|
(guint) (end - start) / segment->duration;
|
|
}
|
|
} else {
|
|
stream->segment_repeat_index--;
|
|
}
|
|
}
|
|
|
|
done:
|
|
GST_DEBUG ("Advanced to segment: %d / %d r:%d (ret: %s)",
|
|
stream->segment_index, segments_count,
|
|
stream->segment_repeat_index, gst_flow_get_name (ret));
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_next_header (GstMpdClient * client, gchar ** uri,
|
|
guint stream_idx, gint64 * range_start, gint64 * range_end)
|
|
{
|
|
GstActiveStream *stream;
|
|
GstStreamPeriod *stream_period;
|
|
|
|
stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx);
|
|
g_return_val_if_fail (stream != NULL, FALSE);
|
|
g_return_val_if_fail (stream->cur_representation != NULL, FALSE);
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, FALSE);
|
|
g_return_val_if_fail (stream_period->period != NULL, FALSE);
|
|
|
|
*range_start = 0;
|
|
*range_end = -1;
|
|
|
|
GST_DEBUG ("Looking for current representation header");
|
|
*uri = NULL;
|
|
if (stream->cur_segment_base) {
|
|
if (stream->cur_segment_base->Initialization) {
|
|
*uri =
|
|
g_strdup (gst_mpdparser_get_initializationURL (stream,
|
|
stream->cur_segment_base->Initialization));
|
|
if (stream->cur_segment_base->Initialization->range) {
|
|
*range_start =
|
|
stream->cur_segment_base->Initialization->range->first_byte_pos;
|
|
*range_end =
|
|
stream->cur_segment_base->Initialization->range->last_byte_pos;
|
|
}
|
|
} else if (stream->cur_segment_base->indexRange) {
|
|
*uri =
|
|
g_strdup (gst_mpdparser_get_initializationURL (stream,
|
|
stream->cur_segment_base->Initialization));
|
|
*range_start = 0;
|
|
*range_end = stream->cur_segment_base->indexRange->first_byte_pos - 1;
|
|
}
|
|
} else if (stream->cur_seg_template
|
|
&& stream->cur_seg_template->initialization) {
|
|
*uri =
|
|
gst_mpdparser_build_URL_from_template (stream->
|
|
cur_seg_template->initialization, stream->cur_representation->id, 0,
|
|
stream->cur_representation->bandwidth, 0);
|
|
}
|
|
|
|
return *uri == NULL ? FALSE : TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_next_header_index (GstMpdClient * client, gchar ** uri,
|
|
guint stream_idx, gint64 * range_start, gint64 * range_end)
|
|
{
|
|
GstActiveStream *stream;
|
|
GstStreamPeriod *stream_period;
|
|
|
|
stream = gst_mpdparser_get_active_stream_by_index (client, stream_idx);
|
|
g_return_val_if_fail (stream != NULL, FALSE);
|
|
g_return_val_if_fail (stream->cur_representation != NULL, FALSE);
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, FALSE);
|
|
g_return_val_if_fail (stream_period->period != NULL, FALSE);
|
|
|
|
*range_start = 0;
|
|
*range_end = -1;
|
|
|
|
GST_DEBUG ("Looking for current representation index");
|
|
*uri = NULL;
|
|
if (stream->cur_segment_base && stream->cur_segment_base->indexRange) {
|
|
*uri =
|
|
g_strdup (gst_mpdparser_get_initializationURL (stream,
|
|
stream->cur_segment_base->RepresentationIndex));
|
|
*range_start = stream->cur_segment_base->indexRange->first_byte_pos;
|
|
*range_end = stream->cur_segment_base->indexRange->last_byte_pos;
|
|
} else if (stream->cur_seg_template && stream->cur_seg_template->index) {
|
|
*uri =
|
|
gst_mpdparser_build_URL_from_template (stream->cur_seg_template->index,
|
|
stream->cur_representation->id, 0,
|
|
stream->cur_representation->bandwidth, 0);
|
|
}
|
|
|
|
return *uri == NULL ? FALSE : TRUE;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_mpd_client_get_next_fragment_duration (GstMpdClient * client,
|
|
GstActiveStream * stream)
|
|
{
|
|
GstMediaSegment *media_segment = NULL;
|
|
gint seg_idx;
|
|
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
seg_idx = stream->segment_index;
|
|
|
|
if (stream->segments) {
|
|
if (seg_idx < stream->segments->len && seg_idx >= 0)
|
|
media_segment = g_ptr_array_index (stream->segments, seg_idx);
|
|
|
|
return media_segment == NULL ? 0 : media_segment->duration;
|
|
} else {
|
|
GstClockTime duration =
|
|
gst_mpd_client_get_segment_duration (client, stream, NULL);
|
|
guint segments_count = gst_mpd_client_get_segments_counts (client, stream);
|
|
|
|
g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType->
|
|
SegmentTimeline == NULL, 0);
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (duration) || (segments_count > 0
|
|
&& seg_idx >= segments_count)) {
|
|
return 0;
|
|
}
|
|
return duration;
|
|
}
|
|
}
|
|
|
|
GstClockTime
|
|
gst_mpd_client_get_media_presentation_duration (GstMpdClient * client)
|
|
{
|
|
GstClockTime duration;
|
|
|
|
g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
|
|
|
|
if (client->mpd_node->mediaPresentationDuration != -1) {
|
|
duration = client->mpd_node->mediaPresentationDuration * GST_MSECOND;
|
|
} else {
|
|
/* We can only get the duration for on-demand streams */
|
|
duration = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_set_period_id (GstMpdClient * client, const gchar * period_id)
|
|
{
|
|
GstStreamPeriod *next_stream_period;
|
|
gboolean ret = FALSE;
|
|
GList *iter;
|
|
guint period_idx;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->periods != NULL, FALSE);
|
|
g_return_val_if_fail (period_id != NULL, FALSE);
|
|
|
|
if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE, -1,
|
|
period_id))
|
|
return FALSE;
|
|
|
|
for (period_idx = 0, iter = client->periods; iter;
|
|
period_idx++, iter = g_list_next (iter)) {
|
|
next_stream_period = iter->data;
|
|
|
|
if (next_stream_period->period->id
|
|
&& strcmp (next_stream_period->period->id, period_id) == 0) {
|
|
ret = TRUE;
|
|
client->period_idx = period_idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_set_period_index (GstMpdClient * client, guint period_idx)
|
|
{
|
|
GstStreamPeriod *next_stream_period;
|
|
gboolean ret = FALSE;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->periods != NULL, FALSE);
|
|
|
|
if (!gst_mpd_client_setup_media_presentation (client, -1, period_idx, NULL))
|
|
return FALSE;
|
|
|
|
next_stream_period = g_list_nth_data (client->periods, period_idx);
|
|
if (next_stream_period != NULL) {
|
|
client->period_idx = period_idx;
|
|
ret = TRUE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_period_index (GstMpdClient * client)
|
|
{
|
|
guint period_idx;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
period_idx = client->period_idx;
|
|
|
|
return period_idx;
|
|
}
|
|
|
|
const gchar *
|
|
gst_mpd_client_get_period_id (GstMpdClient * client)
|
|
{
|
|
GstStreamPeriod *period;
|
|
gchar *period_id = NULL;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
period = g_list_nth_data (client->periods, client->period_idx);
|
|
if (period && period->period)
|
|
period_id = period->period->id;
|
|
|
|
return period_id;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_has_previous_period (GstMpdClient * client)
|
|
{
|
|
GList *next_stream_period;
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->periods != NULL, FALSE);
|
|
|
|
if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE,
|
|
client->period_idx - 1, NULL))
|
|
return FALSE;
|
|
|
|
next_stream_period =
|
|
g_list_nth_data (client->periods, client->period_idx - 1);
|
|
|
|
return next_stream_period != NULL;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_has_next_period (GstMpdClient * client)
|
|
{
|
|
GList *next_stream_period;
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->periods != NULL, FALSE);
|
|
|
|
if (!gst_mpd_client_setup_media_presentation (client, GST_CLOCK_TIME_NONE,
|
|
client->period_idx + 1, NULL))
|
|
return FALSE;
|
|
|
|
next_stream_period =
|
|
g_list_nth_data (client->periods, client->period_idx + 1);
|
|
return next_stream_period != NULL;
|
|
}
|
|
|
|
void
|
|
gst_mpd_client_seek_to_first_segment (GstMpdClient * client)
|
|
{
|
|
GList *list;
|
|
|
|
g_return_if_fail (client != NULL);
|
|
g_return_if_fail (client->active_streams != NULL);
|
|
|
|
for (list = g_list_first (client->active_streams); list;
|
|
list = g_list_next (list)) {
|
|
GstActiveStream *stream = (GstActiveStream *) list->data;
|
|
if (stream) {
|
|
stream->segment_index = 0;
|
|
stream->segment_repeat_index = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static guint
|
|
gst_mpd_client_get_segments_counts (GstMpdClient * client,
|
|
GstActiveStream * stream)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
|
|
g_return_val_if_fail (stream != NULL, 0);
|
|
|
|
if (stream->segments)
|
|
return stream->segments->len;
|
|
g_return_val_if_fail (stream->cur_seg_template->MultSegBaseType->
|
|
SegmentTimeline == NULL, 0);
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
if (stream_period->duration != -1)
|
|
return gst_util_uint64_scale_ceil (stream_period->duration, 1,
|
|
gst_mpd_client_get_segment_duration (client, stream, NULL));
|
|
|
|
return 0;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_is_live (GstMpdClient * client)
|
|
{
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->mpd_node != NULL, FALSE);
|
|
|
|
return client->mpd_node->type == GST_MPD_FILE_TYPE_DYNAMIC;
|
|
}
|
|
|
|
guint
|
|
gst_mpdparser_get_nb_active_stream (GstMpdClient * client)
|
|
{
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
|
|
return g_list_length (client->active_streams);
|
|
}
|
|
|
|
guint
|
|
gst_mpdparser_get_nb_adaptationSet (GstMpdClient * client)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, 0);
|
|
g_return_val_if_fail (stream_period->period != NULL, 0);
|
|
|
|
return g_list_length (stream_period->period->AdaptationSets);
|
|
}
|
|
|
|
GstActiveStream *
|
|
gst_mpdparser_get_active_stream_by_index (GstMpdClient * client,
|
|
guint stream_idx)
|
|
{
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (client->active_streams != NULL, NULL);
|
|
|
|
return g_list_nth_data (client->active_streams, stream_idx);
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_active_stream_contains_subtitles (GstActiveStream * stream)
|
|
{
|
|
const gchar *mimeType;
|
|
const gchar *adapt_set_codecs;
|
|
const gchar *rep_codecs;
|
|
|
|
mimeType = stream->cur_representation->RepresentationBase->mimeType;
|
|
if (!mimeType)
|
|
mimeType = stream->cur_adapt_set->RepresentationBase->mimeType;
|
|
|
|
if (g_strcmp0 (mimeType, "application/ttml+xml") == 0 ||
|
|
g_strcmp0 (mimeType, "text/vtt") == 0)
|
|
return TRUE;
|
|
|
|
adapt_set_codecs = stream->cur_adapt_set->RepresentationBase->codecs;
|
|
rep_codecs = stream->cur_representation->RepresentationBase->codecs;
|
|
|
|
return (adapt_set_codecs && g_str_has_prefix (adapt_set_codecs, "stpp"))
|
|
|| (rep_codecs && g_str_has_prefix (rep_codecs, "stpp"));
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
GstCaps *
|
|
gst_mpd_client_get_stream_caps (GstActiveStream * stream)
|
|
{
|
|
const gchar *mimeType, *caps_string;
|
|
GstCaps *ret = NULL;
|
|
|
|
if (stream == NULL || stream->cur_adapt_set == NULL
|
|
|| stream->cur_representation == NULL)
|
|
return NULL;
|
|
|
|
mimeType = stream->cur_representation->RepresentationBase->mimeType;
|
|
if (mimeType == NULL) {
|
|
mimeType = stream->cur_adapt_set->RepresentationBase->mimeType;
|
|
}
|
|
|
|
caps_string = gst_mpdparser_mimetype_to_caps (mimeType);
|
|
|
|
if ((g_strcmp0 (caps_string, "application/mp4") == 0)
|
|
&& gst_mpd_client_active_stream_contains_subtitles (stream))
|
|
caps_string = "video/quicktime";
|
|
|
|
if (caps_string)
|
|
ret = gst_caps_from_string (caps_string);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_bitstream_switching_flag (GstActiveStream * stream)
|
|
{
|
|
if (stream == NULL || stream->cur_adapt_set == NULL)
|
|
return FALSE;
|
|
|
|
return stream->cur_adapt_set->bitstreamSwitching;
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_video_stream_width (GstActiveStream * stream)
|
|
{
|
|
guint width;
|
|
|
|
if (stream == NULL || stream->cur_adapt_set == NULL
|
|
|| stream->cur_representation == NULL)
|
|
return 0;
|
|
|
|
width = stream->cur_representation->RepresentationBase->width;
|
|
if (width == 0) {
|
|
width = stream->cur_adapt_set->RepresentationBase->width;
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_video_stream_height (GstActiveStream * stream)
|
|
{
|
|
guint height;
|
|
|
|
if (stream == NULL || stream->cur_adapt_set == NULL
|
|
|| stream->cur_representation == NULL)
|
|
return 0;
|
|
|
|
height = stream->cur_representation->RepresentationBase->height;
|
|
if (height == 0) {
|
|
height = stream->cur_adapt_set->RepresentationBase->height;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_get_video_stream_framerate (GstActiveStream * stream,
|
|
gint * fps_num, gint * fps_den)
|
|
{
|
|
if (stream == NULL)
|
|
return FALSE;
|
|
|
|
if (stream->cur_adapt_set &&
|
|
stream->cur_adapt_set->RepresentationBase->frameRate != NULL) {
|
|
*fps_num = stream->cur_adapt_set->RepresentationBase->frameRate->num;
|
|
*fps_den = stream->cur_adapt_set->RepresentationBase->frameRate->den;
|
|
return TRUE;
|
|
}
|
|
|
|
if (stream->cur_adapt_set &&
|
|
stream->cur_adapt_set->RepresentationBase->maxFrameRate != NULL) {
|
|
*fps_num = stream->cur_adapt_set->RepresentationBase->maxFrameRate->num;
|
|
*fps_den = stream->cur_adapt_set->RepresentationBase->maxFrameRate->den;
|
|
return TRUE;
|
|
}
|
|
|
|
if (stream->cur_representation &&
|
|
stream->cur_representation->RepresentationBase->frameRate != NULL) {
|
|
*fps_num = stream->cur_representation->RepresentationBase->frameRate->num;
|
|
*fps_den = stream->cur_representation->RepresentationBase->frameRate->den;
|
|
return TRUE;
|
|
}
|
|
|
|
if (stream->cur_representation &&
|
|
stream->cur_representation->RepresentationBase->maxFrameRate != NULL) {
|
|
*fps_num =
|
|
stream->cur_representation->RepresentationBase->maxFrameRate->num;
|
|
*fps_den =
|
|
stream->cur_representation->RepresentationBase->maxFrameRate->den;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_audio_stream_rate (GstActiveStream * stream)
|
|
{
|
|
const gchar *rate;
|
|
|
|
if (stream == NULL || stream->cur_adapt_set == NULL
|
|
|| stream->cur_representation == NULL)
|
|
return 0;
|
|
|
|
rate = stream->cur_representation->RepresentationBase->audioSamplingRate;
|
|
if (rate == NULL) {
|
|
rate = stream->cur_adapt_set->RepresentationBase->audioSamplingRate;
|
|
}
|
|
|
|
return rate ? atoi (rate) : 0;
|
|
}
|
|
|
|
guint
|
|
gst_mpd_client_get_audio_stream_num_channels (GstActiveStream * stream)
|
|
{
|
|
if (stream == NULL || stream->cur_adapt_set == NULL
|
|
|| stream->cur_representation == NULL)
|
|
return 0;
|
|
/* TODO: here we have to parse the AudioChannelConfiguration descriptors */
|
|
return 0;
|
|
}
|
|
|
|
guint
|
|
gst_mpdparser_get_list_and_nb_of_audio_language (GstMpdClient * client,
|
|
GList ** lang)
|
|
{
|
|
GstStreamPeriod *stream_period;
|
|
GstAdaptationSetNode *adapt_set;
|
|
GList *adaptation_sets, *list;
|
|
const gchar *this_mimeType = "audio";
|
|
gchar *mimeType = NULL;
|
|
guint nb_adaptation_set = 0;
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
g_return_val_if_fail (stream_period != NULL, 0);
|
|
g_return_val_if_fail (stream_period->period != NULL, 0);
|
|
|
|
adaptation_sets =
|
|
gst_mpd_client_get_adaptation_sets_for_period (client, stream_period);
|
|
for (list = adaptation_sets; list; list = g_list_next (list)) {
|
|
adapt_set = (GstAdaptationSetNode *) list->data;
|
|
if (adapt_set && adapt_set->lang) {
|
|
gchar *this_lang = adapt_set->lang;
|
|
GstRepresentationNode *rep;
|
|
rep =
|
|
gst_mpdparser_get_lowest_representation (adapt_set->Representations);
|
|
mimeType = NULL;
|
|
if (rep->RepresentationBase)
|
|
mimeType = rep->RepresentationBase->mimeType;
|
|
if (!mimeType && adapt_set->RepresentationBase) {
|
|
mimeType = adapt_set->RepresentationBase->mimeType;
|
|
}
|
|
|
|
if (strncmp_ext (mimeType, this_mimeType) == 0) {
|
|
nb_adaptation_set++;
|
|
*lang = g_list_append (*lang, this_lang);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nb_adaptation_set;
|
|
}
|
|
|
|
|
|
GstDateTime *
|
|
gst_mpd_client_get_next_segment_availability_start_time (GstMpdClient * client,
|
|
GstActiveStream * stream)
|
|
{
|
|
GstDateTime *availability_start_time, *rv;
|
|
gint seg_idx;
|
|
GstMediaSegment *segment;
|
|
GstClockTime segmentEndTime;
|
|
const GstStreamPeriod *stream_period;
|
|
GstClockTime period_start = 0;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
if (stream_period && stream_period->period) {
|
|
period_start = stream_period->start;
|
|
}
|
|
|
|
seg_idx = stream->segment_index;
|
|
|
|
if (stream->segments) {
|
|
segment = g_ptr_array_index (stream->segments, seg_idx);
|
|
|
|
if (segment->repeat >= 0) {
|
|
segmentEndTime = segment->start + (stream->segment_repeat_index + 1) *
|
|
segment->duration;
|
|
} else if (seg_idx < stream->segments->len - 1) {
|
|
const GstMediaSegment *next_segment =
|
|
g_ptr_array_index (stream->segments, seg_idx + 1);
|
|
segmentEndTime = next_segment->start;
|
|
} else {
|
|
g_return_val_if_fail (stream_period != NULL, NULL);
|
|
segmentEndTime = period_start + stream_period->duration;
|
|
}
|
|
} else {
|
|
GstClockTime seg_duration;
|
|
seg_duration = gst_mpd_client_get_segment_duration (client, stream, NULL);
|
|
if (seg_duration == 0)
|
|
return NULL;
|
|
segmentEndTime = period_start + (1 + seg_idx) * seg_duration;
|
|
}
|
|
|
|
availability_start_time = gst_mpd_client_get_availability_start_time (client);
|
|
if (availability_start_time == NULL) {
|
|
GST_WARNING_OBJECT (client, "Failed to get availability_start_time");
|
|
return NULL;
|
|
}
|
|
|
|
rv = gst_mpd_client_add_time_difference (availability_start_time,
|
|
segmentEndTime / GST_USECOND);
|
|
gst_date_time_unref (availability_start_time);
|
|
if (rv == NULL) {
|
|
GST_WARNING_OBJECT (client, "Failed to offset availability_start_time");
|
|
return NULL;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_seek_to_time (GstMpdClient * client, GDateTime * time)
|
|
{
|
|
GDateTime *start;
|
|
GTimeSpan ts_microseconds;
|
|
GstClockTime ts;
|
|
gboolean ret = TRUE;
|
|
GList *stream;
|
|
|
|
g_return_val_if_fail (gst_mpd_client_is_live (client), FALSE);
|
|
g_return_val_if_fail (client->mpd_node->availabilityStartTime != NULL, FALSE);
|
|
|
|
start =
|
|
gst_date_time_to_g_date_time (client->mpd_node->availabilityStartTime);
|
|
|
|
ts_microseconds = g_date_time_difference (time, start);
|
|
g_date_time_unref (start);
|
|
|
|
/* Clamp to availability start time, otherwise calculations wrap around */
|
|
if (ts_microseconds < 0)
|
|
ts_microseconds = 0;
|
|
|
|
ts = ts_microseconds * GST_USECOND;
|
|
for (stream = client->active_streams; stream; stream = g_list_next (stream)) {
|
|
ret =
|
|
ret & gst_mpd_client_stream_seek (client, stream->data, TRUE, 0, ts,
|
|
NULL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
gst_media_fragment_info_clear (GstMediaFragmentInfo * fragment)
|
|
{
|
|
g_free (fragment->uri);
|
|
g_free (fragment->index_uri);
|
|
}
|
|
|
|
gboolean
|
|
gst_mpd_client_has_isoff_ondemand_profile (GstMpdClient * client)
|
|
{
|
|
return client->profile_isoff_ondemand;
|
|
}
|
|
|
|
/**
|
|
* gst_mpd_client_parse_default_presentation_delay:
|
|
* @client: #GstMpdClient that has a parsed manifest
|
|
* @default_presentation_delay: A string that specifies a time period
|
|
* in fragments (e.g. "5 f"), seconds ("12 s") or milliseconds
|
|
* ("12000 ms")
|
|
* Returns: the parsed string in milliseconds
|
|
*
|
|
* Since: 1.6
|
|
*/
|
|
gint64
|
|
gst_mpd_client_parse_default_presentation_delay (GstMpdClient * client,
|
|
const gchar * default_presentation_delay)
|
|
{
|
|
gint64 value;
|
|
char *endptr = NULL;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
g_return_val_if_fail (default_presentation_delay != NULL, 0);
|
|
value = strtol (default_presentation_delay, &endptr, 10);
|
|
if (endptr == default_presentation_delay || value == 0) {
|
|
return 0;
|
|
}
|
|
while (*endptr == ' ')
|
|
endptr++;
|
|
if (*endptr == 's' || *endptr == 'S') {
|
|
value *= 1000; /* convert to ms */
|
|
} else if (*endptr == 'f' || *endptr == 'F') {
|
|
gint64 segment_duration;
|
|
g_assert (client->mpd_node != NULL);
|
|
segment_duration = client->mpd_node->maxSegmentDuration;
|
|
value *= segment_duration;
|
|
} else if (*endptr != 'm' && *endptr != 'M') {
|
|
GST_ERROR ("Unable to parse default presentation delay: %s",
|
|
default_presentation_delay);
|
|
value = 0;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_mpd_client_get_maximum_segment_duration (GstMpdClient * client)
|
|
{
|
|
GstClockTime ret = GST_CLOCK_TIME_NONE, dur;
|
|
GList *stream;
|
|
|
|
g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
|
|
g_return_val_if_fail (client->mpd_node != NULL, GST_CLOCK_TIME_NONE);
|
|
|
|
if (client->mpd_node->maxSegmentDuration != GST_MPD_DURATION_NONE) {
|
|
return client->mpd_node->maxSegmentDuration * GST_MSECOND;
|
|
}
|
|
|
|
/* According to the DASH specification, if maxSegmentDuration is not present:
|
|
"If not present, then the maximum Segment duration shall be the maximum
|
|
duration of any Segment documented in this MPD"
|
|
*/
|
|
for (stream = client->active_streams; stream; stream = g_list_next (stream)) {
|
|
dur = gst_mpd_client_get_segment_duration (client, stream->data, NULL);
|
|
if (dur != GST_CLOCK_TIME_NONE && (dur > ret || ret == GST_CLOCK_TIME_NONE)) {
|
|
ret = dur;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|