dashsink: Add new sink to produce DASH content
authorStéphane Cerveau <scerveau@collabora.com>
Thu, 16 May 2019 17:42:37 +0000 (19:42 +0200)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Fri, 3 Jan 2020 20:50:27 +0000 (20:50 +0000)
Add static or dynamic mpd with:
- baseURL
- period
- adaptation_set
- representaton
- SegmentList
- SegmentURL
- SegmentTemplate

Support multiple audio and video streams.
Pass conformance test with DashIF.org

ext/dash/gstdashsink.c [new file with mode: 0644]
ext/dash/gstdashsink.h [new file with mode: 0644]
ext/dash/gstmpdclient.c
ext/dash/gstmpdclient.h
ext/dash/gstmpdhelper.c
ext/dash/gstmpdhelper.h
ext/dash/gstplugin.c
ext/dash/meson.build
tests/check/elements/dash_mpd.c

diff --git a/ext/dash/gstdashsink.c b/ext/dash/gstdashsink.c
new file mode 100644 (file)
index 0000000..8109238
--- /dev/null
@@ -0,0 +1,988 @@
+/* GStreamer
+  * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.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 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; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-dashsink
+ * @title: dashsink
+ *
+ * Dynamic Adaptive Streaming over HTTP sink/server
+ *
+ * ## Example launch line
+ * |[
+  * gst-launch-1.0 dashsink name=dashsink max-files=5  audiotestsrc is-live=true ! avenc_aac ! dashsink.audio_0 videotestsrc is-live=true ! x264enc ! dashsink.video_0
+ * ]|
+ *
+ */
+
+/* Implementation notes:
+ *
+ * The following section describes how dashsink works internally.
+ *
+ * Introduction:
+ *
+ * This element aims to generate the Media Pressentation Description XML based file
+ * used as DASH content in addition to the necessary media frgaments.
+ * Based on splitmuxsink branches to generate the media fragments,
+ * the element will generate a new adaptation set for each media type (video/audio/test)
+ * and a new representation for each additional stream for a media type.
+ *                                    ,----------------dashsink------------------,
+ *                                    ;  ,----------splitmuxsink--------------,  ;
+ *    ,-videotestsrc-,  ,-x264enc-,   ;  ; ,-Queue-, ,-tsdemux-, ,-filesink-, ;  ;
+ *    ;              o--o         o---o--o ;       o-o         o-o          , ;  ;
+ *    '--------------'  '---------'   ;  ; '-------' '---------' '----------' ;  ;
+ *                                    ;  '------------------------------------'  ;
+ *                                    ;                                          ;
+ *                                    ;  ,----------splitmuxsink--------------,  ;
+ *    ,-audiotestsrc-,  ,-avenc_aac-, ;  ; ,-Queue-, ,-tsdemux-, ,-filesink-, ;  ;
+ *    ;              o--o           o-o--o         o-o         o-o          ; ;  ;
+ *    '--------------'  '-----------' ;  ; '-------' '---------' '----------' ;  ;
+ *                                    ;  '------------------------------------'  ;
+ *                                    ' -----------------------------------------'
+ * * "DASH Sink"
+ * |_ Period 1
+ * |   |_ Video Adaptation Set
+ * |   |   |_ Representation 1 - Container/Codec - bitrate X
+ * |       |_ Representation 2 - Container/Codec - bitrate Y
+ * |   |_ Audio Adaptation Set
+ * |       |_ Representation 1 - Container/Codec - bitrate X
+ * |       |_ Representation 2 - Container/Codec - bitrate Y
+ *
+ * This element is able to generate static or dynamic MPD with multiple adaptation sets,
+ * multiple representations and multiple periods for three kind of .
+ *
+ * It supports any kind of stream input codec
+ * which can be encapsulated in Transport Stream or ISO media format.
+ * The current implementation is generating compliant MPDs for both static and dynamic
+ * prfiles with  https://conformance.dashif.org/
+ *
+ * Limitations:
+ *
+ * The fragments during the DASH generation does not look reliable enough to be used as
+ * a production solution. Some additional or fine tuning work needs to be performed to address
+ * these issues, especially for MP4 fragments.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstdashsink.h"
+#include "gstmpdparser.h"
+#include <gst/pbutils/pbutils.h>
+#include <gst/video/video.h>
+#include <glib/gstdio.h>
+#include <memory.h>
+
+
+GST_DEBUG_CATEGORY_STATIC (gst_dash_sink_debug);
+#define GST_CAT_DEFAULT gst_dash_sink_debug
+
+/**
+ * GstDashSinkMuxerType:
+ * @GST_DASH_SINK_MUXER_TS: Use mpegtsmux
+ * @GST_DASH_SINK_MUXER_MP4: Use mp4mux
+ *
+ * Muxer type
+ */
+typedef enum
+{
+  GST_DASH_SINK_MUXER_TS = 0,
+  GST_DASH_SINK_MUXER_MP4 = 1,
+} GstDashSinkMuxerType;
+
+typedef struct _DashSinkMuxer
+{
+  GstDashSinkMuxerType type;
+  const gchar *element_name;
+  const gchar *mimetype;
+  const gchar *file_ext;
+} DashSinkMuxer;
+
+#define GST_TYPE_DASH_SINK_MUXER (gst_dash_sink_muxer_get_type())
+static GType
+gst_dash_sink_muxer_get_type (void)
+{
+  static GType dash_sink_muxer_type = 0;
+  static const GEnumValue muxer_type[] = {
+    {GST_DASH_SINK_MUXER_TS, "Use mpegtsmux", "ts"},
+    {GST_DASH_SINK_MUXER_MP4, "Use mp4mux", "mp4"},
+    {0, NULL, NULL},
+  };
+
+  if (!dash_sink_muxer_type) {
+    dash_sink_muxer_type =
+        g_enum_register_static ("GstDashSinkMuxerType", muxer_type);
+  }
+  return dash_sink_muxer_type;
+}
+
+static const DashSinkMuxer dash_muxer_list[] = {
+  {
+        GST_DASH_SINK_MUXER_TS,
+        "mpegtsmux",
+        "video/mp2t",
+      "ts"},
+  {
+        GST_DASH_SINK_MUXER_MP4,
+        "mp4mux",
+        "video/mp4",
+      "mp4"},
+};
+
+#define DEFAULT_SEGMENT_LIST_TPL "_%05d"
+#define DEFAULT_SEGMENT_TEMPLATE_TPL "_%d"
+#define DEFAULT_MPD_FILENAME "dash.mpd"
+#define DEFAULT_MPD_ROOT_PATH NULL
+#define DEFAULT_TARGET_DURATION 15
+#define DEFAULT_SEND_KEYFRAME_REQUESTS TRUE
+#define DEFAULT_MPD_NAMESPACE "urn:mpeg:dash:schema:mpd:2011"
+#define DEFAULT_MPD_PROFILES "urn:mpeg:dash:profile:isoff-main:2011"
+#define DEFAULT_MPD_SCHEMA_LOCATION "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
+#define DEFAULT_MPD_USE_SEGMENT_LIST FALSE
+#define DEFAULT_MPD_MIN_BUFFER_TIME 2000
+#define DEFAULT_MPD_PERIOD_DURATION GST_CLOCK_TIME_NONE
+
+#define DEFAULT_DASH_SINK_MUXER GST_DASH_SINK_MUXER_TS
+
+enum
+{
+  ADAPTATION_SET_ID_VIDEO = 1,
+  ADAPTATION_SET_ID_AUDIO,
+  ADAPTATION_SET_ID_SUBTITLE,
+};
+
+enum
+{
+  PROP_0,
+  PROP_MPD_FILENAME,
+  PROP_MPD_ROOT_PATH,
+  PROP_MAX_FILES,
+  PROP_TARGET_DURATION,
+  PROP_SEND_KEYFRAME_REQUESTS,
+  PROP_USE_SEGMENT_LIST,
+  PROP_MPD_DYNAMIC,
+  PROP_MUXER,
+  PROP_MPD_MINIMUM_UPDATE_PERIOD,
+  PROP_MPD_MIN_BUFFER_TIME,
+  PROP_MPD_BASEURL,
+  PROP_MPD_PERIOD_DURATION,
+};
+
+typedef enum
+{
+  DASH_SINK_STREAM_TYPE_VIDEO = 0,
+  DASH_SINK_STREAM_TYPE_AUDIO,
+  DASH_SINK_STREAM_TYPE_SUBTITLE,
+  DASH_SINK_STREAM_TYPE_UNKNOWN,
+} GstDashSinkStreamType;
+
+typedef struct _GstDashSinkStreamVideoInfo
+{
+  gint width;
+  gint height;
+} GstDashSinkStreamVideoInfo;
+
+typedef struct _GstDashSinkStreamAudioInfo
+{
+  gint channels;
+  gint rate;
+} GstDashSinkStreamAudioInfo;
+
+typedef struct GstDashSinkStreamSubtitleInfo
+{
+  gchar *codec;
+} GstDashSinkStreamSubtitleInfo;
+
+typedef union _GstDashSinkStreamInfo
+{
+  GstDashSinkStreamVideoInfo video;
+  GstDashSinkStreamAudioInfo audio;
+  GstDashSinkStreamSubtitleInfo subtitle;
+} GstDashSinkStreamInfo;
+
+typedef struct _GstDashSinkStream
+{
+  GstDashSinkStreamType type;
+  GstPad *pad;
+  gint buffer_probe;
+  GstElement *splitmuxsink;
+  gint adaptation_set_id;
+  gchar *representation_id;
+  gchar *current_segment_location;
+  gchar *mimetype;
+  gint bitrate;
+  gchar *codec;
+  GstClockTime current_running_time_start;
+  GstDashSinkStreamInfo info;
+} GstDashSinkStream;
+
+struct _GstDashSink
+{
+  GstBin bin;
+  GMutex mpd_lock;
+  gchar *location;
+  gchar *mpd_filename;
+  gchar *mpd_root_path;
+  gchar *mpd_profiles;
+  gchar *mpd_baseurl;
+  GstDashSinkMuxerType muxer;
+  GstMPDClient *mpd_client;
+  gchar *current_period_id;
+  gint target_duration;
+  GstClockTime running_time;
+  gboolean send_keyframe_requests;
+  gboolean use_segment_list;
+  gboolean is_dynamic;
+  gchar *segment_file_tpl;
+  guint index;
+  GList *streams;
+  guint64 minimum_update_period;
+  guint64 min_buffer_time;
+  gint64 period_duration;
+};
+
+static GstStaticPadTemplate video_sink_template =
+GST_STATIC_PAD_TEMPLATE ("video_%u",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+
+static GstStaticPadTemplate audio_sink_template =
+GST_STATIC_PAD_TEMPLATE ("audio_%u",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+static GstStaticPadTemplate subtitle_sink_template =
+GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+    GST_STATIC_CAPS_ANY);
+
+#define gst_dash_sink_parent_class parent_class
+G_DEFINE_TYPE (GstDashSink, gst_dash_sink, GST_TYPE_BIN);
+
+static void gst_dash_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * spec);
+static void gst_dash_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * spec);
+static void gst_dash_sink_handle_message (GstBin * bin, GstMessage * message);
+static void gst_dash_sink_reset (GstDashSink * sink);
+static GstStateChangeReturn
+gst_dash_sink_change_state (GstElement * element, GstStateChange trans);
+static GstPad *gst_dash_sink_request_new_pad (GstElement * element,
+    GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
+static void gst_dash_sink_release_pad (GstElement * element, GstPad * pad);
+
+
+static GstDashSinkStream *
+gst_dash_sink_stream_from_pad (GList * streams, GstPad * pad)
+{
+  GList *l;
+  GstDashSinkStream *stream = NULL;
+  for (l = streams; l != NULL; l = l->next) {
+    stream = l->data;
+    if (stream->pad == pad)
+      return stream;
+  }
+  return NULL;
+}
+
+static GstDashSinkStream *
+gst_dash_sink_stream_from_splitmuxsink (GList * streams, GstElement * element)
+{
+  GList *l;
+  GstDashSinkStream *stream = NULL;
+  for (l = streams; l != NULL; l = l->next) {
+    stream = l->data;
+    if (stream->splitmuxsink == element)
+      return stream;
+  }
+  return NULL;
+}
+
+static void
+gst_dash_sink_stream_dispose (gpointer s)
+{
+  GstDashSinkStream *stream = (GstDashSinkStream *) s;
+  g_free (stream->current_segment_location);
+  g_free (stream->representation_id);
+  g_free (stream->mimetype);
+  g_free (stream->codec);
+
+  g_free (stream);
+}
+
+static void
+gst_dash_sink_dispose (GObject * object)
+{
+  GstDashSink *sink = GST_DASH_SINK (object);
+
+  G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
+}
+
+static void
+gst_dash_sink_finalize (GObject * object)
+{
+  GstDashSink *sink = GST_DASH_SINK (object);
+
+  g_free (sink->mpd_filename);
+  g_free (sink->mpd_root_path);
+  g_free (sink->mpd_profiles);
+  if (sink->mpd_client)
+    gst_mpd_client_free (sink->mpd_client);
+  g_mutex_clear (&sink->mpd_lock);
+
+  g_list_free_full (sink->streams, gst_dash_sink_stream_dispose);
+
+  G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
+}
+
+static void
+gst_dash_sink_class_init (GstDashSinkClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *element_class;
+  GstBinClass *bin_class;
+
+  gobject_class = (GObjectClass *) klass;
+  element_class = GST_ELEMENT_CLASS (klass);
+  bin_class = GST_BIN_CLASS (klass);
+
+  gst_element_class_add_static_pad_template (element_class,
+      &video_sink_template);
+  gst_element_class_add_static_pad_template (element_class,
+      &audio_sink_template);
+  gst_element_class_add_static_pad_template (element_class,
+      &subtitle_sink_template);
+
+  gst_element_class_set_static_metadata (element_class,
+      "DASH Sink", "Sink",
+      "Dynamic Adaptive Streaming over HTTP sink",
+      "Stéphane Cerveau <scerveau@collabora.com>");
+
+  element_class->change_state = GST_DEBUG_FUNCPTR (gst_dash_sink_change_state);
+  element_class->request_new_pad =
+      GST_DEBUG_FUNCPTR (gst_dash_sink_request_new_pad);
+  element_class->release_pad = GST_DEBUG_FUNCPTR (gst_dash_sink_release_pad);
+
+  bin_class->handle_message = gst_dash_sink_handle_message;
+
+  gobject_class->dispose = gst_dash_sink_dispose;
+  gobject_class->finalize = gst_dash_sink_finalize;
+  gobject_class->set_property = gst_dash_sink_set_property;
+  gobject_class->get_property = gst_dash_sink_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_MPD_FILENAME,
+      g_param_spec_string ("mpd-filename", "MPD filename",
+          "filename of the mpd to write", DEFAULT_MPD_FILENAME,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MPD_ROOT_PATH,
+      g_param_spec_string ("mpd-root-path", "MPD Root Path",
+          "Path where the MPD and its fragents will be written",
+          DEFAULT_MPD_ROOT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MPD_BASEURL,
+      g_param_spec_string ("mpd-baseurl", "MPD BaseURL",
+          "BaseURL to set in the MPD", DEFAULT_MPD_ROOT_PATH,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
+      g_param_spec_uint ("target-duration", "Target duration",
+          "The target duration in seconds of a segment/file. "
+          "(0 - disabled, useful for management of segment duration by the "
+          "streaming server)", 0, G_MAXUINT, DEFAULT_TARGET_DURATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
+      g_param_spec_boolean ("send-keyframe-requests", "Send Keyframe Requests",
+          "Send keyframe requests to ensure correct fragmentation. If this is disabled "
+          "then the input must have keyframes in regular intervals",
+          DEFAULT_SEND_KEYFRAME_REQUESTS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_USE_SEGMENT_LIST,
+      g_param_spec_boolean ("use-segment-list", "Use segment list",
+          "Use segment list instead of segment template to create the segments",
+          DEFAULT_MPD_USE_SEGMENT_LIST,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MPD_DYNAMIC,
+      g_param_spec_boolean ("dynamic", "dynamic", "Provides a dynamic mpd",
+          FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_MUXER,
+      g_param_spec_enum ("muxer", "Muxer",
+          "Muxer type to be used by dashsink to generate the fragment",
+          GST_TYPE_DASH_SINK_MUXER, DEFAULT_DASH_SINK_MUXER,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class,
+      PROP_MPD_MINIMUM_UPDATE_PERIOD,
+      g_param_spec_uint64 ("minimum-update-period", "Minimum update period",
+          "Provides to the manifest a minimum update period in milliseconds", 0,
+          G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class,
+      PROP_MPD_MIN_BUFFER_TIME,
+      g_param_spec_uint64 ("min-buffer-time", "Mininim buffer time",
+          "Provides to the manifest a minimum buffer time in milliseconds", 0,
+          G_MAXUINT64, DEFAULT_MPD_MIN_BUFFER_TIME,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class,
+      PROP_MPD_PERIOD_DURATION,
+      g_param_spec_uint64 ("period-duration", "period duration",
+          "Provides the explicit duration of a period in milliseconds", 0,
+          G_MAXUINT64, DEFAULT_MPD_PERIOD_DURATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static gboolean
+gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
+{
+  GstElement *mux = NULL;
+  gchar *segment_tpl;
+  gchar *segment_tpl_path;
+  guint start_index = 0;
+  mux =
+      gst_element_factory_make (dash_muxer_list[sink->muxer].element_name,
+      NULL);
+
+  if (sink->muxer == GST_DASH_SINK_MUXER_MP4)
+    g_object_set (mux, "fragment-duration", sink->target_duration * GST_MSECOND,
+        NULL);
+
+  g_return_val_if_fail (mux != NULL, FALSE);
+
+  stream->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
+  if (stream->splitmuxsink == NULL) {
+    gst_object_unref (mux);
+    return FALSE;
+  }
+
+  gst_bin_add (GST_BIN (sink), stream->splitmuxsink);
+  if (sink->use_segment_list)
+    segment_tpl =
+        g_strconcat (stream->representation_id, DEFAULT_SEGMENT_LIST_TPL,
+        ".", dash_muxer_list[sink->muxer].file_ext, NULL);
+  else {
+    segment_tpl =
+        g_strconcat (stream->representation_id, DEFAULT_SEGMENT_TEMPLATE_TPL,
+        ".", dash_muxer_list[sink->muxer].file_ext, NULL);
+    start_index = 1;
+  }
+  if (sink->mpd_root_path)
+    segment_tpl_path =
+        g_build_path ("/", sink->mpd_root_path, segment_tpl, NULL);
+  else
+    segment_tpl_path = g_strdup (segment_tpl);
+
+  g_object_set (stream->splitmuxsink, "location", segment_tpl_path,
+      "max-size-time", ((GstClockTime) sink->target_duration * GST_SECOND),
+      "send-keyframe-requests", TRUE, "muxer", mux, "reset-muxer", FALSE,
+      "send-keyframe-requests", sink->send_keyframe_requests,
+      "start-index", start_index, NULL);
+  g_free (segment_tpl);
+  g_free (segment_tpl_path);
+
+  return TRUE;
+}
+
+static void
+gst_dash_sink_init (GstDashSink * sink)
+{
+  sink->mpd_filename = g_strdup (DEFAULT_MPD_FILENAME);
+  sink->mpd_root_path = g_strdup (DEFAULT_MPD_ROOT_PATH);
+  sink->mpd_client = NULL;
+
+  sink->target_duration = DEFAULT_TARGET_DURATION;
+  sink->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
+  sink->mpd_profiles = g_strdup (DEFAULT_MPD_PROFILES);
+  sink->use_segment_list = DEFAULT_MPD_USE_SEGMENT_LIST;
+
+  sink->min_buffer_time = DEFAULT_MPD_MIN_BUFFER_TIME;
+  sink->period_duration = DEFAULT_MPD_PERIOD_DURATION;
+
+  g_mutex_init (&sink->mpd_lock);
+
+  GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
+
+  gst_dash_sink_reset (sink);
+}
+
+static void
+gst_dash_sink_reset (GstDashSink * sink)
+{
+  sink->index = 0;
+}
+
+static void
+gst_dash_sink_get_stream_metadata (GstDashSink * sink,
+    GstDashSinkStream * stream)
+{
+  GstStructure *s;
+  GstCaps *caps = gst_pad_get_current_caps (stream->pad);
+
+  GST_DEBUG_OBJECT (sink, "stream caps %s", gst_caps_to_string (caps));
+  s = gst_caps_get_structure (caps, 0);
+
+  switch (stream->type) {
+    case DASH_SINK_STREAM_TYPE_VIDEO:
+    {
+      gst_structure_get_int (s, "width", &stream->info.video.width);
+      gst_structure_get_int (s, "height", &stream->info.video.height);
+      g_free (stream->codec);
+      stream->codec =
+          g_strdup (gst_mpd_helper_get_video_codec_from_mime (caps));
+      break;
+    }
+    case DASH_SINK_STREAM_TYPE_AUDIO:
+    {
+      gst_structure_get_int (s, "channels", &stream->info.audio.channels);
+      gst_structure_get_int (s, "rate", &stream->info.audio.rate);
+      g_free (stream->codec);
+      stream->codec =
+          g_strdup (gst_mpd_helper_get_audio_codec_from_mime (caps));
+      break;
+    }
+    case DASH_SINK_STREAM_TYPE_SUBTITLE:
+    {
+      break;
+    }
+    default:
+      break;
+  }
+
+  gst_caps_unref (caps);
+}
+
+static void
+gst_dash_sink_generate_mpd_content (GstDashSink * sink,
+    GstDashSinkStream * stream)
+{
+  if (!sink->mpd_client) {
+    GList *l;
+    sink->mpd_client = gst_mpd_client_new ();
+    /* Add or set root node with stream ids */
+    gst_mpd_client_set_root_node (sink->mpd_client,
+        "profiles", sink->mpd_profiles,
+        "default-namespace", DEFAULT_MPD_NAMESPACE,
+        "min-buffer-time", sink->min_buffer_time, NULL);
+    if (sink->is_dynamic) {
+      GstDateTime *now = gst_date_time_new_now_utc ();
+      gst_mpd_client_set_root_node (sink->mpd_client,
+          "type", GST_MPD_FILE_TYPE_DYNAMIC,
+          "availability-start-time", now, "publish-time", now, NULL);
+    }
+    if (sink->minimum_update_period)
+      gst_mpd_client_set_root_node (sink->mpd_client,
+          "minimum-update-period", sink->minimum_update_period, NULL);
+    if (sink->mpd_baseurl)
+      gst_mpd_client_add_baseurl_node (sink->mpd_client, "url",
+          sink->mpd_baseurl, NULL);
+    /* Add or set period node with stream ids
+     * TODO support multiple period
+     * */
+    sink->current_period_id =
+        gst_mpd_client_set_period_node (sink->mpd_client,
+        sink->current_period_id, NULL);
+    for (l = sink->streams; l != NULL; l = l->next) {
+      GstDashSinkStream *stream = (GstDashSinkStream *) l->data;
+      /* Add or set adaptation_set node with stream ids
+       * AdaptationSet per stream type
+       * */
+      gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
+          sink->current_period_id, stream->adaptation_set_id, NULL);
+      /* Add or set representation node with stream ids */
+      gst_mpd_client_set_representation_node (sink->mpd_client,
+          sink->current_period_id, stream->adaptation_set_id,
+          stream->representation_id, "bandwidth", stream->bitrate, "mime-type",
+          stream->mimetype, "codecs", stream->codec, NULL);
+      /* Set specific to stream type */
+      if (stream->type == DASH_SINK_STREAM_TYPE_VIDEO) {
+        gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id, "content-type",
+            "video", NULL);
+        gst_mpd_client_set_representation_node (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id,
+            stream->representation_id, "width", stream->info.video.width,
+            "height", stream->info.video.height, NULL);
+      } else if (stream->type == DASH_SINK_STREAM_TYPE_AUDIO) {
+        gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id, "content-type",
+            "audio", NULL);
+        gst_mpd_client_set_representation_node (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id,
+            stream->representation_id, "audio-sampling-rate",
+            stream->info.audio.rate, NULL);
+      }
+      if (sink->use_segment_list) {
+        /* Add a default segment list */
+        gst_mpd_client_set_segment_list (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id,
+            stream->representation_id, "duration", sink->target_duration, NULL);
+      } else {
+        gchar *media_segment_template =
+            g_strconcat (stream->representation_id, "_$Number$",
+            ".", dash_muxer_list[sink->muxer].file_ext, NULL);
+        gst_mpd_client_set_segment_template (sink->mpd_client,
+            sink->current_period_id, stream->adaptation_set_id,
+            stream->representation_id, "media", media_segment_template,
+            "duration", sink->target_duration, NULL);
+        g_free (media_segment_template);
+      }
+    }
+  }
+  /* MPD updates */
+  if (sink->use_segment_list) {
+    GST_INFO_OBJECT (sink, "Add segment URL: %s",
+        stream->current_segment_location);
+    gst_mpd_client_add_segment_url (sink->mpd_client, sink->current_period_id,
+        stream->adaptation_set_id, stream->representation_id, "media",
+        stream->current_segment_location, NULL);
+  } else {
+    if (!sink->is_dynamic) {
+      if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
+        gst_mpd_client_set_period_node (sink->mpd_client,
+            sink->current_period_id, "duration", sink->period_duration, NULL);
+      else
+        gst_mpd_client_set_period_node (sink->mpd_client,
+            sink->current_period_id, "duration",
+            gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
+    }
+    if (!sink->minimum_update_period) {
+      if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
+        gst_mpd_client_set_root_node (sink->mpd_client,
+            "media-presentation-duration", sink->period_duration, NULL);
+      else
+        gst_mpd_client_set_root_node (sink->mpd_client,
+            "media-presentation-duration",
+            gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
+    }
+  }
+}
+
+static void
+gst_dash_sink_write_mpd_file (GstDashSink * sink,
+    GstDashSinkStream * current_stream)
+{
+  char *mpd_content = NULL;
+  gint size;
+  GError *error = NULL;
+  gchar *mpd_filepath = NULL;
+  g_mutex_lock (&sink->mpd_lock);
+  gst_dash_sink_generate_mpd_content (sink, current_stream);
+  if (!gst_mpd_client_get_xml_content (sink->mpd_client, &mpd_content, &size))
+    return;
+  g_mutex_unlock (&sink->mpd_lock);
+  if (sink->mpd_root_path)
+    mpd_filepath =
+        g_build_path ("/", sink->mpd_root_path, sink->mpd_filename, NULL);
+  else
+    mpd_filepath = g_strdup (sink->mpd_filename);
+  GST_DEBUG_OBJECT (sink, "a new mpd content is available: %s", mpd_content);
+  GST_DEBUG_OBJECT (sink, "write mpd to %s", mpd_filepath);
+
+  if (!mpd_content
+      || !g_file_set_contents (mpd_filepath, mpd_content, -1, &error)) {
+    GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
+        (("Failed to write mpd '%s'."), error->message), (NULL));
+    g_error_free (error);
+    error = NULL;
+  }
+  g_free (mpd_content);
+  g_free (mpd_filepath);
+}
+
+static void
+gst_dash_sink_handle_message (GstBin * bin, GstMessage * message)
+{
+  GstDashSink *sink = GST_DASH_SINK (bin);
+  GstDashSinkStream *stream = NULL;
+  switch (message->type) {
+    case GST_MESSAGE_ELEMENT:
+    {
+      const GstStructure *s = gst_message_get_structure (message);
+      GST_DEBUG_OBJECT (sink, "Received message with name %s",
+          gst_structure_get_name (s));
+      stream =
+          gst_dash_sink_stream_from_splitmuxsink (sink->streams,
+          GST_ELEMENT (message->src));
+      if (stream) {
+        if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
+          gst_dash_sink_get_stream_metadata (sink, stream);
+          g_free (stream->current_segment_location);
+          stream->current_segment_location =
+              g_strdup (gst_structure_get_string (s, "location"));
+          gst_structure_get_clock_time (s, "running-time",
+              &stream->current_running_time_start);
+        } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
+          GstClockTime running_time;
+          g_assert (strcmp (stream->current_segment_location,
+                  gst_structure_get_string (s, "location")) == 0);
+          gst_structure_get_clock_time (s, "running-time", &running_time);
+          if (sink->running_time < running_time)
+            sink->running_time = running_time;
+          gst_dash_sink_write_mpd_file (sink, stream);
+        }
+      }
+      break;
+    }
+    case GST_MESSAGE_EOS:{
+      gst_dash_sink_write_mpd_file (sink, NULL);
+      break;
+    }
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (parent_class)->handle_message (bin, message);
+}
+
+static GstPadProbeReturn
+_dash_sink_buffers_probe (GstPad * pad, GstPadProbeInfo * probe_info,
+    gpointer user_data)
+{
+  GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (probe_info);
+  GstDashSinkStream *stream = (GstDashSinkStream *) user_data;
+
+  if (GST_BUFFER_DURATION (buffer))
+    stream->bitrate =
+        gst_buffer_get_size (buffer) * GST_SECOND /
+        GST_BUFFER_DURATION (buffer);
+
+  return GST_PAD_PROBE_OK;
+}
+
+static GstPad *
+gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
+    const gchar * pad_name, const GstCaps * caps)
+{
+  GstDashSink *sink = GST_DASH_SINK (element);
+  GstDashSinkStream *stream = NULL;
+  GstPad *pad = NULL;
+  GstPad *peer = NULL;
+  const gchar *split_pad_name = pad_name;
+
+  stream = g_new0 (GstDashSinkStream, 1);
+  if (g_str_has_prefix (templ->name_template, "video")) {
+    stream->type = DASH_SINK_STREAM_TYPE_VIDEO;
+    stream->adaptation_set_id = ADAPTATION_SET_ID_VIDEO;
+    split_pad_name = "video";
+  } else if (g_str_has_prefix (templ->name_template, "audio")) {
+    stream->type = DASH_SINK_STREAM_TYPE_AUDIO;
+    stream->adaptation_set_id = ADAPTATION_SET_ID_AUDIO;
+  } else if (g_str_has_prefix (templ->name_template, "subtitle")) {
+    stream->type = DASH_SINK_STREAM_TYPE_SUBTITLE;
+    stream->adaptation_set_id = ADAPTATION_SET_ID_SUBTITLE;
+  }
+
+  stream->representation_id = g_strdup (pad_name);
+  stream->mimetype = g_strdup (dash_muxer_list[sink->muxer].mimetype);
+
+
+  if (!gst_dash_sink_add_splitmuxsink (sink, stream)) {
+    GST_ERROR_OBJECT (sink,
+        "Unable to create splitmuxsink element for pad template name %s",
+        templ->name_template);
+    gst_dash_sink_stream_dispose (stream);
+    goto done;
+  }
+
+  peer = gst_element_get_request_pad (stream->splitmuxsink, split_pad_name);
+  if (!peer) {
+    GST_ERROR_OBJECT (sink, "Unable to request pad name %s", split_pad_name);
+    return NULL;
+  }
+
+  pad = gst_ghost_pad_new_from_template (pad_name, peer, templ);
+  gst_pad_set_active (pad, TRUE);
+  gst_element_add_pad (element, pad);
+  gst_object_unref (peer);
+
+  stream->pad = pad;
+
+  stream->buffer_probe = gst_pad_add_probe (stream->pad,
+      GST_PAD_PROBE_TYPE_BUFFER, _dash_sink_buffers_probe, stream, NULL);
+
+  sink->streams = g_list_append (sink->streams, stream);
+  GST_DEBUG_OBJECT (sink, "Adding a new stream with id %s",
+      stream->representation_id);
+
+done:
+  return pad;
+}
+
+static void
+gst_dash_sink_release_pad (GstElement * element, GstPad * pad)
+{
+  GstDashSink *sink = GST_DASH_SINK (element);
+  GstPad *peer;
+  GstDashSinkStream *stream =
+      gst_dash_sink_stream_from_pad (sink->streams, pad);
+
+  g_return_if_fail (stream != NULL);
+
+  peer = gst_pad_get_peer (pad);
+  if (peer) {
+    gst_element_release_request_pad (stream->splitmuxsink, pad);
+    gst_object_unref (peer);
+  }
+
+  if (stream->buffer_probe > 0) {
+    gst_pad_remove_probe (pad, stream->buffer_probe);
+    stream->buffer_probe = 0;
+  }
+
+  gst_object_ref (pad);
+  gst_element_remove_pad (element, pad);
+  gst_pad_set_active (pad, FALSE);
+
+  stream->pad = NULL;
+
+  gst_object_unref (pad);
+}
+
+static GstStateChangeReturn
+gst_dash_sink_change_state (GstElement * element, GstStateChange trans)
+{
+  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+  GstDashSink *sink = GST_DASH_SINK (element);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!g_list_length (sink->streams)) {
+        return GST_STATE_CHANGE_FAILURE;
+      }
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
+
+  switch (trans) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gst_dash_sink_reset (sink);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_dash_sink_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstDashSink *sink = GST_DASH_SINK (object);
+
+  switch (prop_id) {
+    case PROP_MPD_FILENAME:
+      g_free (sink->mpd_filename);
+      sink->mpd_filename = g_value_dup_string (value);
+      break;
+    case PROP_MPD_ROOT_PATH:
+      g_free (sink->mpd_root_path);
+      sink->mpd_root_path = g_value_dup_string (value);
+      break;
+    case PROP_MPD_BASEURL:
+      g_free (sink->mpd_baseurl);
+      sink->mpd_baseurl = g_value_dup_string (value);
+      break;
+    case PROP_TARGET_DURATION:
+      sink->target_duration = g_value_get_uint (value);
+      break;
+    case PROP_SEND_KEYFRAME_REQUESTS:
+      sink->send_keyframe_requests = g_value_get_boolean (value);
+      break;
+    case PROP_USE_SEGMENT_LIST:
+      sink->use_segment_list = g_value_get_boolean (value);
+      break;
+    case PROP_MPD_DYNAMIC:
+      sink->is_dynamic = g_value_get_boolean (value);
+      break;
+    case PROP_MUXER:
+      sink->muxer = g_value_get_enum (value);
+      break;
+    case PROP_MPD_MINIMUM_UPDATE_PERIOD:
+      sink->minimum_update_period = g_value_get_uint64 (value);
+      break;
+    case PROP_MPD_MIN_BUFFER_TIME:
+      sink->min_buffer_time = g_value_get_uint64 (value);
+      break;
+    case PROP_MPD_PERIOD_DURATION:
+      sink->period_duration = g_value_get_uint64 (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_dash_sink_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstDashSink *sink = GST_DASH_SINK (object);
+
+  switch (prop_id) {
+    case PROP_MPD_FILENAME:
+      g_value_set_string (value, sink->mpd_filename);
+      break;
+    case PROP_MPD_ROOT_PATH:
+      g_value_set_string (value, sink->mpd_root_path);
+      break;
+    case PROP_MPD_BASEURL:
+      g_value_set_string (value, sink->mpd_baseurl);
+      break;
+    case PROP_TARGET_DURATION:
+      g_value_set_uint (value, sink->target_duration);
+      break;
+    case PROP_SEND_KEYFRAME_REQUESTS:
+      g_value_set_boolean (value, sink->send_keyframe_requests);
+      break;
+    case PROP_USE_SEGMENT_LIST:
+      g_value_set_boolean (value, sink->use_segment_list);
+      break;
+    case PROP_MPD_DYNAMIC:
+      g_value_set_boolean (value, sink->is_dynamic);
+      break;
+    case PROP_MUXER:
+      g_value_set_enum (value, sink->muxer);
+      break;
+    case PROP_MPD_MINIMUM_UPDATE_PERIOD:
+      g_value_set_uint64 (value, sink->minimum_update_period);
+      break;
+    case PROP_MPD_MIN_BUFFER_TIME:
+      g_value_set_uint64 (value, sink->min_buffer_time);
+      break;
+    case PROP_MPD_PERIOD_DURATION:
+      g_value_set_uint64 (value, sink->period_duration);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+gboolean
+gst_dash_sink_plugin_init (GstPlugin * plugin)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_dash_sink_debug, "dashsink", 0, "DashSink");
+  return gst_element_register (plugin, "dashsink", GST_RANK_NONE,
+      gst_dash_sink_get_type ());
+}
diff --git a/ext/dash/gstdashsink.h b/ext/dash/gstdashsink.h
new file mode 100644 (file)
index 0000000..0f30574
--- /dev/null
@@ -0,0 +1,34 @@
+/* GStreamer
+ * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.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 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; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef _GST_DASH_SINK_H_
+#define _GST_DASH_SINK_H_
+
+#include <gst/gst.h>
+#include "gstmpdclient.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_DASH_SINK  gst_dash_sink_get_type ()
+G_DECLARE_FINAL_TYPE (GstDashSink, gst_dash_sink, GST, DASH_SINK, GstBin)
+
+gboolean gst_dash_sink_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif
index 99878d3..4ebf966 100644 (file)
@@ -48,6 +48,82 @@ static GstMPDRepresentationNode *gst_mpd_client_get_lowest_representation (GList
 static GstStreamPeriod *gst_mpd_client_get_stream_period (GstMPDClient *
     client);
 
+typedef GstMPDNode *(*MpdClientStringIDFilter) (GList * list, gchar * data);
+typedef GstMPDNode *(*MpdClientIDFilter) (GList * list, guint data);
+
+static GstMPDNode *
+gst_mpd_client_get_period_with_id (GList * periods, gchar * period_id)
+{
+  GstMPDPeriodNode *period;
+  GList *list = NULL;
+
+  for (list = g_list_first (periods); list; list = g_list_next (list)) {
+    period = (GstMPDPeriodNode *) list->data;
+    if (!g_strcmp0 (period->id, period_id))
+      return GST_MPD_NODE (period);
+  }
+  return NULL;
+}
+
+static GstMPDNode *
+gst_mpd_client_get_adaptation_set_with_id (GList * adaptation_sets, guint id)
+{
+  GstMPDAdaptationSetNode *adaptation_set;
+  GList *list = NULL;
+
+  for (list = g_list_first (adaptation_sets); list; list = g_list_next (list)) {
+    adaptation_set = (GstMPDAdaptationSetNode *) list->data;
+    if (adaptation_set->id == id)
+      return GST_MPD_NODE (adaptation_set);
+  }
+  return NULL;
+}
+
+static GstMPDNode *
+gst_mpd_client_get_representation_with_id (GList * representations,
+    gchar * rep_id)
+{
+  GstMPDRepresentationNode *representation;
+  GList *list = NULL;
+
+  for (list = g_list_first (representations); list; list = g_list_next (list)) {
+    representation = (GstMPDRepresentationNode *) list->data;
+    if (!g_strcmp0 (representation->id, rep_id))
+      return GST_MPD_NODE (representation);
+  }
+  return NULL;
+}
+
+static gchar *
+_generate_new_string_id (GList * list, const gchar * tuple,
+    MpdClientStringIDFilter filter)
+{
+  guint i = 0;
+  gchar *id = NULL;
+  GstMPDNode *node;
+  do {
+    g_free (id);
+    id = g_strdup_printf (tuple, i);
+    node = filter (list, id);
+    i++;
+  } while (node);
+
+  return id;
+}
+
+static guint
+_generate_new_id (GList * list, MpdClientIDFilter filter)
+{
+  guint id = 0;
+  GstMPDNode *node;
+  do {
+    node = filter (list, id);
+    id++;
+  } while (node);
+
+  return id;
+}
+
 static GstMPDRepresentationNode *
 gst_mpd_client_get_lowest_representation (GList * Representations)
 {
@@ -375,6 +451,22 @@ gst_mpd_client_new (void)
   return g_object_new (GST_TYPE_MPD_CLIENT, NULL);
 }
 
+GstMPDClient *
+gst_mpd_client_new_static (void)
+{
+  GstMPDClient *client = gst_mpd_client_new ();
+
+  client->mpd_root_node = gst_mpd_root_node_new ();
+  client->mpd_root_node->default_namespace =
+      g_strdup ("urn:mpeg:dash:schema:mpd:2011");
+  client->mpd_root_node->profiles =
+      g_strdup ("urn:mpeg:dash:profile:isoff-main:2011");
+  client->mpd_root_node->type = GST_MPD_FILE_TYPE_STATIC;
+  client->mpd_root_node->minBufferTime = 1500;
+
+  return client;
+}
+
 void
 gst_mpd_client_free (GstMPDClient * client)
 {
@@ -1311,6 +1403,8 @@ gst_mpd_client_setup_media_presentation (GstMPDClient * client,
       /* might be a live file, ignore unspecified duration */
     } else {
       /* Invalid MPD file! */
+      GST_ERROR
+          ("Invalid MPD file. The MPD is static without a valid duration");
       goto syntax_error;
     }
 
@@ -3012,3 +3106,292 @@ gst_mpd_client_get_period_index_at_time (GstMPDClient * client,
 
   return period_idx;
 }
+
+/* add or set node methods */
+
+gboolean
+gst_mpd_client_set_root_node (GstMPDClient * client,
+    const gchar * property_name, ...)
+{
+  va_list myargs;
+  g_return_val_if_fail (client != NULL, FALSE);
+
+  va_start (myargs, property_name);
+
+  if (!client->mpd_root_node)
+    client->mpd_root_node = gst_mpd_root_node_new ();
+  g_object_set_valist (G_OBJECT (client->mpd_root_node), property_name, myargs);
+
+  va_end (myargs);
+
+  return TRUE;
+}
+
+gboolean
+gst_mpd_client_add_baseurl_node (GstMPDClient * client,
+    const gchar * property_name, ...)
+{
+  GstMPDBaseURLNode *baseurl_node = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, FALSE);
+  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);
+
+  va_start (myargs, property_name);
+
+  baseurl_node = gst_mpd_baseurl_node_new ();
+  g_object_set_valist (G_OBJECT (baseurl_node), property_name, myargs);
+  client->mpd_root_node->BaseURLs =
+      g_list_append (client->mpd_root_node->BaseURLs, baseurl_node);
+
+  va_end (myargs);
+  return TRUE;
+}
+
+/* returns a period id */
+gchar *
+gst_mpd_client_set_period_node (GstMPDClient * client,
+    gchar * period_id, const gchar * property_name, ...)
+{
+  GstMPDPeriodNode *period_node = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, NULL);
+  g_return_val_if_fail (client->mpd_root_node != NULL, NULL);
+
+  va_start (myargs, property_name);
+
+  period_node =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  if (!period_node) {
+    period_node = gst_mpd_period_node_new ();
+    if (period_id)
+      period_node->id = g_strdup (period_id);
+    else
+      period_node->id =
+          _generate_new_string_id (client->mpd_root_node->Periods,
+          "period_%.2d", gst_mpd_client_get_period_with_id);
+    client->mpd_root_node->Periods =
+        g_list_append (client->mpd_root_node->Periods, period_node);
+  }
+
+  g_object_set_valist (G_OBJECT (period_node), property_name, myargs);
+
+  va_end (myargs);
+  return period_node->id;
+}
+
+/* returns an adaptation set id */
+guint
+gst_mpd_client_set_adaptation_set_node (GstMPDClient * client,
+    gchar * period_id, guint adaptation_set_id, const gchar * property_name,
+    ...)
+{
+  GstMPDAdaptationSetNode *adap_node = NULL;
+  GstMPDPeriodNode *period_node = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, 0);
+  g_return_val_if_fail (client->mpd_root_node != NULL, 0);
+
+  va_start (myargs, property_name);
+
+  period_node =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  g_return_val_if_fail (period_node != NULL, 0);
+  adap_node =
+      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
+      (period_node->AdaptationSets, adaptation_set_id));
+  if (!adap_node) {
+    adap_node = gst_mpd_adaptation_set_node_new ();
+    if (adaptation_set_id)
+      adap_node->id = adaptation_set_id;
+    else
+      adap_node->id =
+          _generate_new_id (period_node->AdaptationSets,
+          gst_mpd_client_get_adaptation_set_with_id);
+    GST_DEBUG_OBJECT (client, "Add a new adaptation set with id %d",
+        adap_node->id);
+    period_node->AdaptationSets =
+        g_list_append (period_node->AdaptationSets, adap_node);
+  }
+  g_object_set_valist (G_OBJECT (adap_node), property_name, myargs);
+
+  va_end (myargs);
+  return adap_node->id;
+}
+
+/* returns a representation id */
+gchar *
+gst_mpd_client_set_representation_node (GstMPDClient * client,
+    gchar * period_id, guint adaptation_set_id, gchar * representation_id,
+    const gchar * property_name, ...)
+{
+  GstMPDRepresentationNode *rep_node = NULL;
+  GstMPDAdaptationSetNode *adap_set_node = NULL;
+  GstMPDPeriodNode *period_node = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, NULL);
+  g_return_val_if_fail (client->mpd_root_node != NULL, NULL);
+
+  va_start (myargs, property_name);
+
+  period_node =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  adap_set_node =
+      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
+      (period_node->AdaptationSets, adaptation_set_id));
+  g_return_val_if_fail (adap_set_node != NULL, NULL);
+  rep_node =
+      GST_MPD_REPRESENTATION_NODE (gst_mpd_client_get_representation_with_id
+      (adap_set_node->Representations, representation_id));
+  if (!rep_node) {
+    rep_node = gst_mpd_representation_node_new ();
+    if (representation_id)
+      rep_node->id = g_strdup (representation_id);
+    else
+      rep_node->id =
+          _generate_new_string_id (adap_set_node->Representations,
+          "representation_%.2d", gst_mpd_client_get_representation_with_id);
+    GST_DEBUG_OBJECT (client, "Add a new representation with id %s",
+        rep_node->id);
+    adap_set_node->Representations =
+        g_list_append (adap_set_node->Representations, rep_node);
+  }
+  g_object_set_valist (G_OBJECT (rep_node), property_name, myargs);
+
+
+  va_end (myargs);
+  return rep_node->id;
+}
+
+/* add/set a segment list node */
+gboolean
+gst_mpd_client_set_segment_list (GstMPDClient * client,
+    gchar * period_id, guint adap_set_id, gchar * rep_id,
+    const gchar * property_name, ...)
+{
+  GstMPDRepresentationNode *representation = NULL;
+  GstMPDAdaptationSetNode *adaptation_set = NULL;
+  GstMPDPeriodNode *period = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, FALSE);
+  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);
+
+  va_start (myargs, property_name);
+
+  period =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  adaptation_set =
+      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
+      (period->AdaptationSets, adap_set_id));
+  g_return_val_if_fail (adaptation_set != NULL, FALSE);
+
+  representation =
+      GST_MPD_REPRESENTATION_NODE (gst_mpd_client_get_representation_with_id
+      (adaptation_set->Representations, rep_id));
+  if (!representation->SegmentList) {
+    representation->SegmentList = gst_mpd_segment_list_node_new ();
+  }
+
+  g_object_set_valist (G_OBJECT (representation->SegmentList), property_name,
+      myargs);
+
+  va_end (myargs);
+  return TRUE;
+}
+
+/* add/set a segment template node */
+gboolean
+gst_mpd_client_set_segment_template (GstMPDClient * client,
+    gchar * period_id, guint adap_set_id, gchar * rep_id,
+    const gchar * property_name, ...)
+{
+  GstMPDRepresentationNode *representation = NULL;
+  GstMPDAdaptationSetNode *adaptation_set = NULL;
+  GstMPDPeriodNode *period = NULL;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, FALSE);
+  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);
+
+  va_start (myargs, property_name);
+
+  period =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  adaptation_set =
+      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
+      (period->AdaptationSets, adap_set_id));
+  g_return_val_if_fail (adaptation_set != NULL, FALSE);
+
+  representation =
+      GST_MPD_REPRESENTATION_NODE (gst_mpd_client_get_representation_with_id
+      (adaptation_set->Representations, rep_id));
+  if (!representation->SegmentTemplate) {
+    representation->SegmentTemplate = gst_mpd_segment_template_node_new ();
+  }
+
+  g_object_set_valist (G_OBJECT (representation->SegmentTemplate),
+      property_name, myargs);
+
+  va_end (myargs);
+  return TRUE;
+}
+
+/* add a segmentURL node with to a SegmentList node */
+gboolean
+gst_mpd_client_add_segment_url (GstMPDClient * client,
+    gchar * period_id, guint adap_set_id, gchar * rep_id,
+    const gchar * property_name, ...)
+{
+  GstMPDRepresentationNode *representation = NULL;
+  GstMPDAdaptationSetNode *adaptation_set = NULL;
+  GstMPDPeriodNode *period = NULL;
+  GstMPDSegmentURLNode *segment_url = NULL;
+  guint64 media_presentation_duration = 0;
+  va_list myargs;
+
+  g_return_val_if_fail (client != NULL, FALSE);
+  g_return_val_if_fail (client->mpd_root_node != NULL, FALSE);
+
+  va_start (myargs, property_name);
+
+  period =
+      GST_MPD_PERIOD_NODE (gst_mpd_client_get_period_with_id
+      (client->mpd_root_node->Periods, period_id));
+  adaptation_set =
+      GST_MPD_ADAPTATION_SET_NODE (gst_mpd_client_get_adaptation_set_with_id
+      (period->AdaptationSets, adap_set_id));
+  g_return_val_if_fail (adaptation_set != NULL, FALSE);
+
+  representation =
+      GST_MPD_REPRESENTATION_NODE (gst_mpd_client_get_representation_with_id
+      (adaptation_set->Representations, rep_id));
+
+  if (!representation->SegmentList) {
+    representation->SegmentList = gst_mpd_segment_list_node_new ();
+  }
+
+  segment_url = gst_mpd_segment_url_node_new ();
+  g_object_set_valist (G_OBJECT (segment_url), property_name, myargs);
+  gst_mpd_segment_list_node_add_segment (representation->SegmentList,
+      segment_url);
+
+  /* Set the media presentation time according to the new segment duration added */
+  g_object_get (client->mpd_root_node, "media-presentation-duration",
+      &media_presentation_duration, NULL);
+  media_presentation_duration +=
+      GST_MPD_MULT_SEGMENT_BASE_NODE (representation->SegmentList)->duration;
+  g_object_set (client->mpd_root_node, "media-presentation-duration",
+      media_presentation_duration, NULL);
+
+  va_end (myargs);
+  return TRUE;
+}
index ccf9923..bc422fc 100644 (file)
@@ -50,7 +50,10 @@ struct _GstMPDClient
 };
 
 /* Basic initialization/deinitialization functions */
+
 GstMPDClient *gst_mpd_client_new (void);
+GstMPDClient *gst_mpd_client_new_static (void);
+
 void gst_mpd_client_active_streams_free (GstMPDClient * client);
 void gst_mpd_client_free (GstMPDClient * client);
 
@@ -138,7 +141,48 @@ gint64 gst_mpd_client_parse_default_presentation_delay(GstMPDClient * client, co
 /* profiles */
 gboolean gst_mpd_client_has_isoff_ondemand_profile (GstMPDClient *client);
 
-
+/* add/set node methods */
+gboolean gst_mpd_client_set_root_node (GstMPDClient * client,
+                                       const gchar * property_name,
+                                       ...);
+gchar * gst_mpd_client_set_period_node (GstMPDClient * client,
+                                        gchar * period_id,
+                                        const gchar * property_name,
+                                        ...);
+guint gst_mpd_client_set_adaptation_set_node (GstMPDClient * client,
+                                              gchar * period_id,
+                                              guint adap_set_id,
+                                              const gchar * property_name,
+                                              ...);
+gchar * gst_mpd_client_set_representation_node (GstMPDClient * client,
+                                                gchar * period_id,
+                                                guint adap_set_id,
+                                                gchar * rep_id,
+                                                const gchar * property_name,
+                                                ...);
+gboolean gst_mpd_client_set_segment_list (GstMPDClient * client,
+                                          gchar * period_id,
+                                          guint adap_set_id,
+                                          gchar * rep_id,
+                                          const gchar * property_name,
+                                          ...);
+gboolean gst_mpd_client_set_segment_template (GstMPDClient * client,
+                                              gchar * period_id,
+                                              guint adap_set_id,
+                                              gchar * rep_id,
+                                              const gchar * property_name,
+                                              ...);
+
+/* create a new node */
+gboolean gst_mpd_client_add_baseurl_node (GstMPDClient * client,
+                                          const gchar * property_name,
+                                          ...);
+gboolean gst_mpd_client_add_segment_url (GstMPDClient * client,
+                                         gchar * period_id,
+                                         guint adap_set_id,
+                                         gchar * rep_id,
+                                         const gchar * property_name,
+                                         ...);
 G_END_DECLS
 
 #endif /* __GST_MPDCLIENT_H__ */
index a66de89..d2f665c 100644 (file)
@@ -79,6 +79,61 @@ gst_mpd_helper_get_SAP_type (xmlNode * a_node,
 }
 
 const gchar *
+gst_mpd_helper_get_audio_codec_from_mime (GstCaps * caps)
+{
+  GstStructure *s;
+  const gchar *name = "";
+  const gchar *codec_name = NULL;
+
+  if (!caps)
+    return NULL;
+  s = gst_caps_get_structure (caps, 0);
+  if (!s)
+    goto done;
+  name = gst_structure_get_name (s);
+  if (!g_strcmp0 (name, "audio/mpeg")) {
+    gint mpeg_version;
+    if (gst_structure_get_int (s, "mpegversion", &mpeg_version)) {
+      if (mpeg_version == 4)
+        return "mp4a";
+    }
+
+  } else {
+    GST_DEBUG ("No codecs for this caps name %s", name);
+  }
+
+done:
+  gst_caps_unref (caps);
+  return codec_name;
+}
+
+const gchar *
+gst_mpd_helper_get_video_codec_from_mime (GstCaps * caps)
+{
+  GstStructure *s;
+  const gchar *name = "";
+  const gchar *codec_name = NULL;
+
+  if (!caps)
+    return NULL;
+
+  s = gst_caps_get_structure (caps, 0);
+  if (!s)
+    goto done;
+  name = gst_structure_get_name (s);
+  if (!g_strcmp0 (name, "video/x-h264")) {
+    return "avc1";
+  } else {
+    GST_DEBUG ("No codecs for this caps name %s", name);
+  }
+
+done:
+  gst_caps_unref (caps);
+  return codec_name;
+}
+
+
+const gchar *
 gst_mpd_helper_mimetype_to_caps (const gchar * mimeType)
 {
   if (mimeType == NULL)
index 95eaec9..84ae0e7 100644 (file)
@@ -61,6 +61,8 @@ gboolean gst_mpd_helper_get_mpd_type (xmlNode * a_node, const gchar * property_n
 gboolean gst_mpd_helper_get_SAP_type (xmlNode * a_node, const gchar * property_name, GstMPDSAPType * property_value);
 
 const gchar * gst_mpd_helper_mimetype_to_caps (const gchar * mimeType);
+const gchar * gst_mpd_helper_get_video_codec_from_mime (GstCaps * caps);
+const gchar * gst_mpd_helper_get_audio_codec_from_mime (GstCaps * caps);
 GstUri *gst_mpd_helper_combine_urls (GstUri * base, GList * list, gchar ** query, guint idx);
 int gst_mpd_helper_strncmp_ext (const char *s1, const char *s2);
 
index 29f2817..028d138 100644 (file)
@@ -7,16 +7,26 @@
 #include <gst/gst.h>
 
 #include "gstdashdemux.h"
+#include "gstdashsink.h"
+
+GST_DEBUG_CATEGORY (dash_debug);
 
 static gboolean
-dashdemux_init (GstPlugin * plugin)
+dash_init (GstPlugin * plugin)
 {
-  return gst_element_register (plugin, "dashdemux", GST_RANK_PRIMARY,
-      GST_TYPE_DASH_DEMUX);
+  GST_DEBUG_CATEGORY_INIT (dash_debug, "DASH", 0, "HTTP Live Streaming (HLS)");
+
+  if (!gst_element_register (plugin, "dashdemux", GST_RANK_PRIMARY,
+          GST_TYPE_DASH_DEMUX) || FALSE)
+    return FALSE;
+
+  if (!gst_dash_sink_plugin_init (plugin))
+    return FALSE;
+
+  return TRUE;
 }
 
 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
     GST_VERSION_MINOR,
-    dashdemux,
-    "DASH demuxer plugin",
-    dashdemux_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)
+    dash,
+    "DASH plugin", dash_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN)
index 56be535..bfb3b67 100644 (file)
@@ -30,6 +30,7 @@ dash_sources = [
   'gstmpdparser.c',
   'gstmpdclient.c',
   'gstplugin.c',
+  'gstdashsink.c',
 ]
 
 xml2_dep = dependency('libxml-2.0',
@@ -39,7 +40,7 @@ xml2_dep = dependency('libxml-2.0',
 )
 
 if xml2_dep.found()
-  gstdashdemux = library('gstdashdemux',
+  gstdashdemux = library('gstdash',
     dash_sources,
     c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
     link_args : noseh_link_args,
index e651054..09d9b81 100644 (file)
@@ -6113,6 +6113,202 @@ GST_START_TEST (dash_mpdparser_check_mpd_xml_generator)
 GST_END_TEST;
 
 /*
+ * Test add mpd content with mpd_client set methods
+ *
+ */
+GST_START_TEST (dash_mpdparser_check_mpd_client_set_methods)
+{
+  const gchar *xml =
+      "<?xml version=\"1.0\"?>"
+      "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\""
+      "     profiles=\"urn:mpeg:dash:profile:isoff-main:2011\""
+      "     schemaLocation=\"TestSchemaLocation\""
+      "     xmlns:xsi=\"TestNamespaceXSI\""
+      "     xmlns:ext=\"TestNamespaceEXT\""
+      "     id=\"testId\""
+      "     type=\"static\""
+      "     availabilityStartTime=\"2015-03-24T1:10:50+08:00\""
+      "     availabilityEndTime=\"2015-03-24T1:10:50.123456-04:30\""
+      "     mediaPresentationDuration=\"P0Y1M2DT12H10M20.5S\""
+      "     minimumUpdatePeriod=\"P0Y1M2DT12H10M20.5S\""
+      "     minBufferTime=\"P0Y1M2DT12H10M20.5S\""
+      "     timeShiftBufferDepth=\"P0Y1M2DT12H10M20.5S\""
+      "     suggestedPresentationDelay=\"P0Y1M2DT12H10M20.5S\""
+      "     maxSegmentDuration=\"P0Y1M2DT12H10M20.5S\""
+      "     maxSubsegmentDuration=\"P0Y1M2DT12H10M20.5S\">"
+      "     <BaseURL serviceLocation=\"TestServiceLocation\""
+      "     byteRange=\"TestByteRange\">TestBaseURL</BaseURL>"
+      "     <Location>TestLocation</Location>"
+      "     <ProgramInformation lang=\"en\""
+      "     moreInformationURL=\"TestMoreInformationUrl\">"
+      "     <Title>TestTitle</Title>"
+      "     <Source>TestSource</Source>"
+      "     <Copyright>TestCopyright</Copyright>"
+      "     </ProgramInformation>"
+      "     <Metrics metrics=\"TestMetric\"><Range starttime=\"P0Y1M2DT12H10M20.5S\""
+      "           duration=\"P0Y1M2DT12H10M20.1234567S\">"
+      "    </Range></Metrics>"
+      "  <Period id=\"TestId\" start=\"PT1M\" duration=\"PT40S\""
+      "          bitstreamSwitching=\"true\">"
+      "    <AdaptationSet id=\"9\" contentType=\"video\" mimeType=\"video\">"
+      "      <Representation id=\"audio_1\" "
+      "                      bandwidth=\"100\""
+      "                      qualityRanking=\"200\""
+      "                      width=\"640\""
+      "                      height=\"480\""
+      "                      codecs=\"avc1\""
+      "                      audioSamplingRate=\"44100\""
+      "                      mimeType=\"audio/mp4\">"
+      "        <SegmentList duration=\"15\" startNumber=\"11\">"
+      "          <SegmentURL media=\"segment001.ts\"></SegmentURL>"
+      "          <SegmentURL media=\"segment002.ts\"></SegmentURL>"
+      "        </SegmentList>"
+      "      </Representation></AdaptationSet></Period>" "     </MPD>";
+  gboolean ret;
+  gchar *period_id;
+  guint adaptation_set_id;
+  gchar *representation_id;
+  GstMPDClient *first_mpdclient = NULL;
+  GstMPDClient *second_mpdclient = NULL;
+  GstMPDBaseURLNode *first_baseURL, *second_baseURL;
+  GstMPDPeriodNode *first_period, *second_period;
+  GstMPDAdaptationSetNode *first_adap_set, *second_adap_set;
+  GstMPDRepresentationNode *first_rep, *second_rep;
+  GstMPDSegmentListNode *first_seg_list, *second_seg_list;
+  GstMPDSegmentURLNode *first_seg_url, *second_seg_url;
+
+  first_mpdclient = gst_mpd_client_new ();
+
+  ret = gst_mpd_client_parse (first_mpdclient, xml, (gint) strlen (xml));
+  assert_equals_int (ret, TRUE);
+
+  second_mpdclient = gst_mpd_client_new ();
+  gst_mpd_client_set_root_node (second_mpdclient,
+      "default-namespace", "urn:mpeg:dash:schema:mpd:2011",
+      "profiles", "urn:mpeg:dash:profile:isoff-main:2011",
+      "schema-location", "TestSchemaLocation",
+      "namespace-xsi", "TestNamespaceXSI",
+      "namespace-ext", "TestNamespaceEXT", "id", "testId", NULL);
+  gst_mpd_client_add_baseurl_node (second_mpdclient,
+      "url", "TestBaseURL",
+      "service location", "TestServiceLocation",
+      "byte-range", "TestByteRange", NULL);
+  period_id = gst_mpd_client_set_period_node (second_mpdclient, (gchar *) "TestId", "start", 60000,     // ms
+      "duration", 40000, "bitstream-switching", 1, NULL);
+  adaptation_set_id =
+      gst_mpd_client_set_adaptation_set_node (second_mpdclient, period_id, 9,
+      "content-type", "video", "mime-type", "video", NULL);
+
+  representation_id =
+      gst_mpd_client_set_representation_node (second_mpdclient, period_id,
+      adaptation_set_id, (gchar *) "audio_1", "bandwidth", 100,
+      "quality-ranking", 200, "mime-type", "audio/mp4", "width", 640, "height",
+      480, "codecs", "avc1", "audio-sampling-rate", 44100, NULL);
+
+  gst_mpd_client_set_segment_list (second_mpdclient, period_id,
+      adaptation_set_id, representation_id, "duration", 15, "start-number", 11,
+      NULL);
+  gst_mpd_client_add_segment_url (second_mpdclient, period_id,
+      adaptation_set_id, representation_id, "media", "segment001.ts", NULL);
+  gst_mpd_client_add_segment_url (second_mpdclient, period_id,
+      adaptation_set_id, representation_id, "media", "segment002.ts", NULL);
+
+  /* assert that parameters are equal */
+  assert_equals_string (first_mpdclient->mpd_root_node->default_namespace,
+      second_mpdclient->mpd_root_node->default_namespace);
+  assert_equals_string (first_mpdclient->mpd_root_node->namespace_xsi,
+      second_mpdclient->mpd_root_node->namespace_xsi);
+  assert_equals_string (first_mpdclient->mpd_root_node->namespace_ext,
+      second_mpdclient->mpd_root_node->namespace_ext);
+  assert_equals_string (first_mpdclient->mpd_root_node->schemaLocation,
+      second_mpdclient->mpd_root_node->schemaLocation);
+  assert_equals_string (first_mpdclient->mpd_root_node->id,
+      second_mpdclient->mpd_root_node->id);
+  assert_equals_string (first_mpdclient->mpd_root_node->profiles,
+      second_mpdclient->mpd_root_node->profiles);
+
+
+  /* baseURLs */
+  first_baseURL =
+      (GstMPDBaseURLNode *) first_mpdclient->mpd_root_node->BaseURLs->data;
+  second_baseURL =
+      (GstMPDBaseURLNode *) second_mpdclient->mpd_root_node->BaseURLs->data;
+  assert_equals_string (first_baseURL->baseURL, second_baseURL->baseURL);
+  assert_equals_string (first_baseURL->serviceLocation,
+      second_baseURL->serviceLocation);
+  assert_equals_string (first_baseURL->byteRange, second_baseURL->byteRange);
+
+  /* Period */
+  first_period =
+      (GstMPDPeriodNode *) first_mpdclient->mpd_root_node->Periods->data;
+  second_period =
+      (GstMPDPeriodNode *) second_mpdclient->mpd_root_node->Periods->data;
+
+  assert_equals_string (first_period->id, second_period->id);
+  assert_equals_int64 (first_period->start, second_period->start);
+  assert_equals_int64 (first_period->duration, second_period->duration);
+  assert_equals_int (first_period->bitstreamSwitching,
+      second_period->bitstreamSwitching);
+
+  /* Adaptation set */
+  first_adap_set =
+      (GstMPDAdaptationSetNode *) first_period->AdaptationSets->data;
+  second_adap_set =
+      (GstMPDAdaptationSetNode *) second_period->AdaptationSets->data;
+
+  assert_equals_int (first_adap_set->id, second_adap_set->id);
+  assert_equals_string (first_adap_set->contentType,
+      second_adap_set->contentType);
+  assert_equals_string (GST_MPD_REPRESENTATION_BASE_NODE
+      (first_adap_set)->mimeType,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_adap_set)->mimeType);
+
+  /* Representation */
+  first_rep =
+      (GstMPDRepresentationNode *) first_adap_set->Representations->data;
+  second_rep =
+      (GstMPDRepresentationNode *) second_adap_set->Representations->data;
+  assert_equals_string (first_rep->id, second_rep->id);
+  assert_equals_int (first_rep->bandwidth, second_rep->bandwidth);
+  assert_equals_int (first_rep->qualityRanking, second_rep->qualityRanking);
+  assert_equals_string (GST_MPD_REPRESENTATION_BASE_NODE (first_rep)->mimeType,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_rep)->mimeType);
+
+  assert_equals_int (GST_MPD_REPRESENTATION_BASE_NODE (first_rep)->width,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_rep)->width);
+
+  assert_equals_int (GST_MPD_REPRESENTATION_BASE_NODE (first_rep)->height,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_rep)->height);
+
+  assert_equals_string (GST_MPD_REPRESENTATION_BASE_NODE (first_rep)->codecs,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_rep)->codecs);
+
+  assert_equals_string (GST_MPD_REPRESENTATION_BASE_NODE
+      (first_rep)->audioSamplingRate,
+      GST_MPD_REPRESENTATION_BASE_NODE (second_rep)->audioSamplingRate);
+
+  /*SegmentList */
+  first_seg_list = (GstMPDSegmentListNode *) first_rep->SegmentList;
+  second_seg_list = (GstMPDSegmentListNode *) second_rep->SegmentList;
+  assert_equals_int (GST_MPD_MULT_SEGMENT_BASE_NODE (first_seg_list)->duration,
+      GST_MPD_MULT_SEGMENT_BASE_NODE (second_seg_list)->duration);
+  assert_equals_int (GST_MPD_MULT_SEGMENT_BASE_NODE
+      (first_seg_list)->startNumber,
+      GST_MPD_MULT_SEGMENT_BASE_NODE (second_seg_list)->startNumber);
+
+  first_seg_url = (GstMPDSegmentURLNode *) first_seg_list->SegmentURL->data;
+  second_seg_url = (GstMPDSegmentURLNode *) second_seg_list->SegmentURL->data;
+
+  assert_equals_string (first_seg_url->media, second_seg_url->media);
+
+
+  gst_mpd_client_free (first_mpdclient);
+  gst_mpd_client_free (second_mpdclient);
+}
+
+GST_END_TEST;
+
+/*
  * create a test suite containing all dash testcases
  */
 static Suite *
@@ -6134,6 +6330,9 @@ dash_suite (void)
   /* test parsing the simplest possible mpd */
   tcase_add_test (tc_simpleMPD, dash_mpdparser_check_mpd_xml_generator);
 
+  /* test mpd client set methods */
+  tcase_add_test (tc_simpleMPD, dash_mpdparser_check_mpd_client_set_methods);
+
   /* tests parsing attributes from each element type */
   tcase_add_test (tc_simpleMPD, dash_mpdparser_mpd);
   tcase_add_test (tc_simpleMPD, dash_mpdparser_datetime_with_tz_offset);