mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 21:51:09 +00:00
6154 lines
195 KiB
C
6154 lines
195 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, 0, 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 = 0;
|
|
|
|
GST_LOG ("Building media segment list using a SegmentList node");
|
|
if (stream->cur_segment_list->MultSegBaseType->SegmentTimeline) {
|
|
GstSegmentTimelineNode *timeline;
|
|
GstSNode *S;
|
|
GList *list;
|
|
|
|
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);
|
|
}
|
|
|
|
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 {
|
|
GstMultSegmentBaseType *mult_seg =
|
|
stream->cur_seg_template->MultSegBaseType;
|
|
/* 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);
|
|
}
|
|
|
|
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 - PeriodStart) {
|
|
GstClockTime stop = PeriodEnd - PeriodStart;
|
|
if (n < stream->segments->len - 1) {
|
|
GstMediaSegment *next_segment =
|
|
g_ptr_array_index (stream->segments, n + 1);
|
|
if (next_segment && next_segment->start < PeriodEnd - PeriodStart)
|
|
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;
|
|
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;
|
|
GstStreamPeriod *stream_period;
|
|
GstMediaSegment *segment;
|
|
GstClockTime segmentEndTime;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
g_return_val_if_fail (stream != NULL, NULL);
|
|
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
|
|
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 {
|
|
const GstStreamPeriod *stream_period;
|
|
stream_period = gst_mpdparser_get_stream_period (client);
|
|
segmentEndTime = stream_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 = (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;
|
|
}
|
|
|
|
if (stream_period && stream_period->period) {
|
|
GstDateTime *t =
|
|
gst_mpd_client_add_time_difference (availability_start_time,
|
|
stream_period->start / GST_USECOND);
|
|
gst_date_time_unref (availability_start_time);
|
|
availability_start_time = t;
|
|
|
|
if (availability_start_time == NULL) {
|
|
GST_WARNING_OBJECT (client, "Failed to offset 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;
|
|
}
|