• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2   * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:element-dashsink
22  * @title: dashsink
23  *
24  * Dynamic Adaptive Streaming over HTTP sink/server
25  *
26  * ## Example launch line
27  * |[
28   * gst-launch-1.0 dashsink name=dashsink audiotestsrc is-live=true ! avenc_aac ! dashsink.audio_0 videotestsrc is-live=true ! x264enc ! dashsink.video_0
29  * ]|
30  *
31  */
32 
33 /* Implementation notes:
34  *
35  * The following section describes how dashsink works internally.
36  *
37  * Introduction:
38  *
39  * This element aims to generate the Media Pressentation Description XML based file
40  * used as DASH content in addition to the necessary media fragments.
41  * Based on splitmuxsink branches to generate the media fragments,
42  * the element will generate a new adaptation set for each media type (video/audio/test)
43  * and a new representation for each additional stream for a media type.
44  *                                    ,----------------dashsink------------------,
45  *                                    ;  ,----------splitmuxsink--------------,  ;
46  *    ,-videotestsrc-,  ,-x264enc-,   ;  ; ,-Queue-, ,-mpegtsmux-, ,-filesink-, ;  ;
47  *    ;              o--o         o---o--o ;       o-o         o-o          , ;  ;
48  *    '--------------'  '---------'   ;  ; '-------' '---------' '----------' ;  ;
49  *                                    ;  '------------------------------------'  ;
50  *                                    ;                                          ;
51  *                                    ;  ,----------splitmuxsink--------------,  ;
52  *    ,-audiotestsrc-,  ,-avenc_aac-, ;  ; ,-Queue-, ,-mpegtsmux-, ,-filesink-, ;  ;
53  *    ;              o--o           o-o--o         o-o         o-o          ; ;  ;
54  *    '--------------'  '-----------' ;  ; '-------' '---------' '----------' ;  ;
55  *                                    ;  '------------------------------------'  ;
56  *                                    ' -----------------------------------------'
57  * * "DASH Sink"
58  * |_ Period 1
59  * |   |_ Video Adaptation Set
60  * |   |   |_ Representation 1 - Container/Codec - bitrate X
61  * |       |_ Representation 2 - Container/Codec - bitrate Y
62  * |   |_ Audio Adaptation Set
63  * |       |_ Representation 1 - Container/Codec - bitrate X
64  * |       |_ Representation 2 - Container/Codec - bitrate Y
65  *
66  * This element is able to generate static or dynamic MPD with multiple adaptation sets,
67  * multiple representations and multiple periods for three kind of
68  * media streams (Video/Audio/Text).
69  *
70  * It supports any kind of stream input codec
71  * which can be encapsulated in Transport Stream (MPEG-TS) or ISO media format (MP4).
72  * The current implementation is generating compliant MPDs for both static and dynamic
73  * profiles with  https://conformance.dashif.org/
74  *
75  * Limitations:
76  *
77  * The fragments during the DASH generation does not look reliable enough to be used as
78  * a production solution. Some additional or fine tuning work needs to be performed to address
79  * these issues, especially for MP4 fragments.
80  *
81  */
82 
83 #ifdef HAVE_CONFIG_H
84 #include "config.h"
85 #endif
86 
87 #include "gstdashsink.h"
88 #include "gstmpdparser.h"
89 #include <gst/pbutils/pbutils.h>
90 #include <gst/video/video.h>
91 #include <glib/gstdio.h>
92 #include <gio/gio.h>
93 #include <memory.h>
94 
95 
96 GST_DEBUG_CATEGORY_STATIC (gst_dash_sink_debug);
97 #define GST_CAT_DEFAULT gst_dash_sink_debug
98 
99 /**
100  * GstDashSinkMuxerType:
101  * @GST_DASH_SINK_MUXER_TS: Use mpegtsmux
102  * @GST_DASH_SINK_MUXER_MP4: Use mp4mux
103  *
104  * Muxer type
105  */
106 typedef enum
107 {
108   GST_DASH_SINK_MUXER_TS = 0,
109   GST_DASH_SINK_MUXER_MP4 = 1,
110 } GstDashSinkMuxerType;
111 
112 typedef struct _DashSinkMuxer
113 {
114   GstDashSinkMuxerType type;
115   const gchar *element_name;
116   const gchar *mimetype;
117   const gchar *file_ext;
118 } DashSinkMuxer;
119 
120 #define GST_TYPE_DASH_SINK_MUXER (gst_dash_sink_muxer_get_type())
121 static GType
gst_dash_sink_muxer_get_type(void)122 gst_dash_sink_muxer_get_type (void)
123 {
124   static GType dash_sink_muxer_type = 0;
125   static const GEnumValue muxer_type[] = {
126     {GST_DASH_SINK_MUXER_TS, "Use mpegtsmux", "ts"},
127     {GST_DASH_SINK_MUXER_MP4, "Use mp4mux", "mp4"},
128     {0, NULL, NULL},
129   };
130 
131   if (!dash_sink_muxer_type) {
132     dash_sink_muxer_type =
133         g_enum_register_static ("GstDashSinkMuxerType", muxer_type);
134   }
135   return dash_sink_muxer_type;
136 }
137 
138 static const DashSinkMuxer dash_muxer_list[] = {
139   {
140         GST_DASH_SINK_MUXER_TS,
141         "mpegtsmux",
142         "video/mp2t",
143       "ts"},
144   {
145         GST_DASH_SINK_MUXER_MP4,
146         "mp4mux",
147         "video/mp4",
148       "mp4"},
149 };
150 
151 #define DEFAULT_SEGMENT_LIST_TPL "_%05d"
152 #define DEFAULT_SEGMENT_TEMPLATE_TPL "_%d"
153 #define DEFAULT_MPD_FILENAME "dash.mpd"
154 #define DEFAULT_MPD_ROOT_PATH NULL
155 #define DEFAULT_TARGET_DURATION 15
156 #define DEFAULT_SEND_KEYFRAME_REQUESTS TRUE
157 #define DEFAULT_MPD_NAMESPACE "urn:mpeg:dash:schema:mpd:2011"
158 #define DEFAULT_MPD_PROFILES "urn:mpeg:dash:profile:isoff-main:2011"
159 #define DEFAULT_MPD_SCHEMA_LOCATION "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
160 #define DEFAULT_MPD_USE_SEGMENT_LIST FALSE
161 #define DEFAULT_MPD_MIN_BUFFER_TIME 2000
162 #define DEFAULT_MPD_PERIOD_DURATION GST_CLOCK_TIME_NONE
163 
164 #define DEFAULT_DASH_SINK_MUXER GST_DASH_SINK_MUXER_TS
165 
166 enum
167 {
168   ADAPTATION_SET_ID_VIDEO = 1,
169   ADAPTATION_SET_ID_AUDIO,
170   ADAPTATION_SET_ID_SUBTITLE,
171 };
172 
173 enum
174 {
175   PROP_0,
176   PROP_MPD_FILENAME,
177   PROP_MPD_ROOT_PATH,
178   PROP_TARGET_DURATION,
179   PROP_SEND_KEYFRAME_REQUESTS,
180   PROP_USE_SEGMENT_LIST,
181   PROP_MPD_DYNAMIC,
182   PROP_MUXER,
183   PROP_MPD_MINIMUM_UPDATE_PERIOD,
184   PROP_MPD_MIN_BUFFER_TIME,
185   PROP_MPD_BASEURL,
186   PROP_MPD_PERIOD_DURATION,
187 };
188 
189 enum
190 {
191   SIGNAL_GET_PLAYLIST_STREAM,
192   SIGNAL_GET_FRAGMENT_STREAM,
193   SIGNAL_LAST
194 };
195 
196 static guint signals[SIGNAL_LAST];
197 
198 typedef enum
199 {
200   DASH_SINK_STREAM_TYPE_VIDEO = 0,
201   DASH_SINK_STREAM_TYPE_AUDIO,
202   DASH_SINK_STREAM_TYPE_SUBTITLE,
203   DASH_SINK_STREAM_TYPE_UNKNOWN,
204 } GstDashSinkStreamType;
205 
206 typedef struct _GstDashSinkStreamVideoInfo
207 {
208   gint width;
209   gint height;
210 } GstDashSinkStreamVideoInfo;
211 
212 typedef struct _GstDashSinkStreamAudioInfo
213 {
214   gint channels;
215   gint rate;
216 } GstDashSinkStreamAudioInfo;
217 
218 typedef struct GstDashSinkStreamSubtitleInfo
219 {
220   gchar *codec;
221 } GstDashSinkStreamSubtitleInfo;
222 
223 typedef union _GstDashSinkStreamInfo
224 {
225   GstDashSinkStreamVideoInfo video;
226   GstDashSinkStreamAudioInfo audio;
227   GstDashSinkStreamSubtitleInfo subtitle;
228 } GstDashSinkStreamInfo;
229 
230 struct _GstDashSink
231 {
232   GstBin bin;
233   GMutex mpd_lock;
234   gchar *location;
235   gchar *mpd_filename;
236   gchar *mpd_root_path;
237   gchar *mpd_profiles;
238   gchar *mpd_baseurl;
239   GstDashSinkMuxerType muxer;
240   GstMPDClient *mpd_client;
241   gchar *current_period_id;
242   gint target_duration;
243   GstClockTime running_time;
244   gboolean send_keyframe_requests;
245   gboolean use_segment_list;
246   gboolean is_dynamic;
247   gchar *segment_file_tpl;
248   guint index;
249   GList *streams;
250   guint64 minimum_update_period;
251   guint64 min_buffer_time;
252   gint64 period_duration;
253 };
254 
255 typedef struct _GstDashSinkStream
256 {
257   GstDashSink *sink;
258   GstDashSinkStreamType type;
259   GstPad *pad;
260   gint buffer_probe;
261   GstElement *splitmuxsink;
262   gint adaptation_set_id;
263   gchar *representation_id;
264   gchar *current_segment_location;
265   gint current_segment_id;
266   gint next_segment_id;
267   gchar *mimetype;
268   gint bitrate;
269   gchar *codec;
270   GstClockTime current_running_time_start;
271   GstDashSinkStreamInfo info;
272   GstElement *giostreamsink;
273 } GstDashSinkStream;
274 
275 static GstStaticPadTemplate video_sink_template =
276 GST_STATIC_PAD_TEMPLATE ("video_%u",
277     GST_PAD_SINK,
278     GST_PAD_REQUEST,
279     GST_STATIC_CAPS_ANY);
280 
281 
282 static GstStaticPadTemplate audio_sink_template =
283 GST_STATIC_PAD_TEMPLATE ("audio_%u",
284     GST_PAD_SINK,
285     GST_PAD_REQUEST,
286     GST_STATIC_CAPS_ANY);
287 static GstStaticPadTemplate subtitle_sink_template =
288 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
289     GST_PAD_SINK,
290     GST_PAD_REQUEST,
291     GST_STATIC_CAPS_ANY);
292 
293 #define gst_dash_sink_parent_class parent_class
294 G_DEFINE_TYPE_WITH_CODE (GstDashSink, gst_dash_sink, GST_TYPE_BIN,
295     GST_DEBUG_CATEGORY_INIT (gst_dash_sink_debug, "dashsink", 0, "DashSink"));
296 GST_ELEMENT_REGISTER_DEFINE (dashsink, "dashsink", GST_RANK_NONE,
297     gst_dash_sink_get_type ());
298 
299 static void gst_dash_sink_set_property (GObject * object, guint prop_id,
300     const GValue * value, GParamSpec * spec);
301 static void gst_dash_sink_get_property (GObject * object, guint prop_id,
302     GValue * value, GParamSpec * spec);
303 static void gst_dash_sink_handle_message (GstBin * bin, GstMessage * message);
304 static void gst_dash_sink_reset (GstDashSink * sink);
305 static GstStateChangeReturn
306 gst_dash_sink_change_state (GstElement * element, GstStateChange trans);
307 static GstPad *gst_dash_sink_request_new_pad (GstElement * element,
308     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
309 static void gst_dash_sink_release_pad (GstElement * element, GstPad * pad);
310 
311 
312 static GstDashSinkStream *
gst_dash_sink_stream_from_pad(GList * streams,GstPad * pad)313 gst_dash_sink_stream_from_pad (GList * streams, GstPad * pad)
314 {
315   GList *l;
316   GstDashSinkStream *stream = NULL;
317   for (l = streams; l != NULL; l = l->next) {
318     stream = l->data;
319     if (stream->pad == pad)
320       return stream;
321   }
322   return NULL;
323 }
324 
325 static GstDashSinkStream *
gst_dash_sink_stream_from_splitmuxsink(GList * streams,GstElement * element)326 gst_dash_sink_stream_from_splitmuxsink (GList * streams, GstElement * element)
327 {
328   GList *l;
329   GstDashSinkStream *stream = NULL;
330   for (l = streams; l != NULL; l = l->next) {
331     stream = l->data;
332     if (stream->splitmuxsink == element)
333       return stream;
334   }
335   return NULL;
336 }
337 
338 static gchar *
gst_dash_sink_stream_get_next_name(GList * streams,GstDashSinkStreamType type)339 gst_dash_sink_stream_get_next_name (GList * streams, GstDashSinkStreamType type)
340 {
341   GList *l;
342   guint count = 0;
343   GstDashSinkStream *stream = NULL;
344   gchar *name = NULL;
345 
346   for (l = streams; l != NULL; l = l->next) {
347     stream = l->data;
348     if (stream->type == type)
349       count++;
350   }
351 
352   switch (type) {
353     case DASH_SINK_STREAM_TYPE_VIDEO:
354       name = g_strdup_printf ("video_%d", count);
355       break;
356     case DASH_SINK_STREAM_TYPE_AUDIO:
357       name = g_strdup_printf ("audio_%d", count);
358       break;
359     case DASH_SINK_STREAM_TYPE_SUBTITLE:
360       name = g_strdup_printf ("sub_%d", count);
361       break;
362     default:
363       name = g_strdup_printf ("unknown_%d", count);
364   }
365 
366   return name;
367 }
368 
369 static void
gst_dash_sink_stream_free(gpointer s)370 gst_dash_sink_stream_free (gpointer s)
371 {
372   GstDashSinkStream *stream = (GstDashSinkStream *) s;
373   g_object_unref (stream->sink);
374   g_free (stream->current_segment_location);
375   g_free (stream->representation_id);
376   g_free (stream->mimetype);
377   g_free (stream->codec);
378 
379   g_free (stream);
380 }
381 
382 static void
gst_dash_sink_dispose(GObject * object)383 gst_dash_sink_dispose (GObject * object)
384 {
385   GstDashSink *sink = GST_DASH_SINK (object);
386 
387   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
388 }
389 
390 static void
gst_dash_sink_finalize(GObject * object)391 gst_dash_sink_finalize (GObject * object)
392 {
393   GstDashSink *sink = GST_DASH_SINK (object);
394 
395   g_free (sink->mpd_filename);
396   g_free (sink->mpd_root_path);
397   g_free (sink->mpd_profiles);
398   if (sink->mpd_client)
399     gst_mpd_client_free (sink->mpd_client);
400   g_mutex_clear (&sink->mpd_lock);
401 
402   g_list_free_full (sink->streams, gst_dash_sink_stream_free);
403 
404   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
405 }
406 
407 /* Default implementations for the signal handlers */
408 static GOutputStream *
gst_dash_sink_get_playlist_stream(GstDashSink * sink,const gchar * location)409 gst_dash_sink_get_playlist_stream (GstDashSink * sink, const gchar * location)
410 {
411   GFile *file = g_file_new_for_path (location);
412   GOutputStream *ostream;
413   GError *err = NULL;
414 
415   ostream =
416       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
417           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
418   if (!ostream) {
419     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
420         (("Got no output stream for playlist '%s': %s."), location,
421             err->message), (NULL));
422     g_clear_error (&err);
423   }
424 
425   g_object_unref (file);
426 
427   return ostream;
428 }
429 
430 static GOutputStream *
gst_dash_sink_get_fragment_stream(GstDashSink * sink,const gchar * location)431 gst_dash_sink_get_fragment_stream (GstDashSink * sink, const gchar * location)
432 {
433   GFile *file = g_file_new_for_path (location);
434   GOutputStream *ostream;
435   GError *err = NULL;
436 
437   ostream =
438       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
439           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
440   if (!ostream) {
441     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
442         (("Got no output stream for fragment '%s': %s."), location,
443             err->message), (NULL));
444     g_clear_error (&err);
445   }
446 
447   g_object_unref (file);
448 
449   return ostream;
450 }
451 
452 static void
gst_dash_sink_class_init(GstDashSinkClass * klass)453 gst_dash_sink_class_init (GstDashSinkClass * klass)
454 {
455   GObjectClass *gobject_class;
456   GstElementClass *element_class;
457   GstBinClass *bin_class;
458 
459   gobject_class = (GObjectClass *) klass;
460   element_class = GST_ELEMENT_CLASS (klass);
461   bin_class = GST_BIN_CLASS (klass);
462 
463   gst_element_class_add_static_pad_template (element_class,
464       &video_sink_template);
465   gst_element_class_add_static_pad_template (element_class,
466       &audio_sink_template);
467   gst_element_class_add_static_pad_template (element_class,
468       &subtitle_sink_template);
469 
470   gst_element_class_set_static_metadata (element_class,
471       "DASH Sink", "Sink",
472       "Dynamic Adaptive Streaming over HTTP sink",
473       "Stéphane Cerveau <scerveau@collabora.com>");
474 
475   element_class->change_state = GST_DEBUG_FUNCPTR (gst_dash_sink_change_state);
476   element_class->request_new_pad =
477       GST_DEBUG_FUNCPTR (gst_dash_sink_request_new_pad);
478   element_class->release_pad = GST_DEBUG_FUNCPTR (gst_dash_sink_release_pad);
479 
480   bin_class->handle_message = gst_dash_sink_handle_message;
481 
482   gobject_class->dispose = gst_dash_sink_dispose;
483   gobject_class->finalize = gst_dash_sink_finalize;
484   gobject_class->set_property = gst_dash_sink_set_property;
485   gobject_class->get_property = gst_dash_sink_get_property;
486 
487   g_object_class_install_property (gobject_class, PROP_MPD_FILENAME,
488       g_param_spec_string ("mpd-filename", "MPD filename",
489           "filename of the mpd to write", DEFAULT_MPD_FILENAME,
490           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
491   g_object_class_install_property (gobject_class, PROP_MPD_ROOT_PATH,
492       g_param_spec_string ("mpd-root-path", "MPD Root Path",
493           "Path where the MPD and its fragents will be written",
494           DEFAULT_MPD_ROOT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
495   g_object_class_install_property (gobject_class, PROP_MPD_BASEURL,
496       g_param_spec_string ("mpd-baseurl", "MPD BaseURL",
497           "BaseURL to set in the MPD", DEFAULT_MPD_ROOT_PATH,
498           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
499   g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
500       g_param_spec_uint ("target-duration", "Target duration",
501           "The target duration in seconds of a segment/file. "
502           "(0 - disabled, useful for management of segment duration by the "
503           "streaming server)", 0, G_MAXUINT, DEFAULT_TARGET_DURATION,
504           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
505   g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
506       g_param_spec_boolean ("send-keyframe-requests", "Send Keyframe Requests",
507           "Send keyframe requests to ensure correct fragmentation. If this is disabled "
508           "then the input must have keyframes in regular intervals",
509           DEFAULT_SEND_KEYFRAME_REQUESTS,
510           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
511   g_object_class_install_property (gobject_class, PROP_USE_SEGMENT_LIST,
512       g_param_spec_boolean ("use-segment-list", "Use segment list",
513           "Use segment list instead of segment template to create the segments",
514           DEFAULT_MPD_USE_SEGMENT_LIST,
515           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
516   g_object_class_install_property (gobject_class, PROP_MPD_DYNAMIC,
517       g_param_spec_boolean ("dynamic", "dynamic", "Provides a dynamic mpd",
518           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
519   g_object_class_install_property (gobject_class, PROP_MUXER,
520       g_param_spec_enum ("muxer", "Muxer",
521           "Muxer type to be used by dashsink to generate the fragment",
522           GST_TYPE_DASH_SINK_MUXER, DEFAULT_DASH_SINK_MUXER,
523           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
524   g_object_class_install_property (gobject_class,
525       PROP_MPD_MINIMUM_UPDATE_PERIOD,
526       g_param_spec_uint64 ("minimum-update-period", "Minimum update period",
527           "Provides to the manifest a minimum update period in milliseconds", 0,
528           G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
529   g_object_class_install_property (gobject_class,
530       PROP_MPD_MIN_BUFFER_TIME,
531       g_param_spec_uint64 ("min-buffer-time", "Mininim buffer time",
532           "Provides to the manifest a minimum buffer time in milliseconds", 0,
533           G_MAXUINT64, DEFAULT_MPD_MIN_BUFFER_TIME,
534           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
535   g_object_class_install_property (gobject_class,
536       PROP_MPD_PERIOD_DURATION,
537       g_param_spec_uint64 ("period-duration", "period duration",
538           "Provides the explicit duration of a period in milliseconds", 0,
539           G_MAXUINT64, DEFAULT_MPD_PERIOD_DURATION,
540           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
541 
542   /**
543    * GstDashSink::get-playlist-stream:
544    * @sink: the #GstDashSink
545    * @location: Location for the playlist file
546    *
547    * Returns: #GOutputStream for writing the playlist file.
548    *
549    * Since: 1.20
550    */
551   signals[SIGNAL_GET_PLAYLIST_STREAM] =
552       g_signal_new_class_handler ("get-playlist-stream",
553       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
554       G_CALLBACK (gst_dash_sink_get_playlist_stream), NULL, NULL, NULL,
555       G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
556 
557   /**
558    * GstDashSink::get-fragment-stream:
559    * @sink: the #GstDashSink
560    * @location: Location for the fragment file
561    *
562    * Returns: #GOutputStream for writing the fragment file.
563    *
564    * Since: 1.20
565    */
566   signals[SIGNAL_GET_FRAGMENT_STREAM] =
567       g_signal_new_class_handler ("get-fragment-stream",
568       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
569       G_CALLBACK (gst_dash_sink_get_fragment_stream), NULL, NULL, NULL,
570       G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
571 
572   gst_type_mark_as_plugin_api (GST_TYPE_DASH_SINK_MUXER, 0);
573 }
574 
575 static gchar *
on_format_location(GstElement * splitmuxsink,guint fragment_id,GstDashSinkStream * dash_stream)576 on_format_location (GstElement * splitmuxsink, guint fragment_id,
577     GstDashSinkStream * dash_stream)
578 {
579   GOutputStream *stream = NULL;
580   GstDashSink *sink = dash_stream->sink;
581   gchar *segment_tpl_path;
582 
583   dash_stream->current_segment_id = dash_stream->next_segment_id;
584   g_free (dash_stream->current_segment_location);
585   if (sink->use_segment_list)
586     dash_stream->current_segment_location =
587         g_strdup_printf ("%s" DEFAULT_SEGMENT_LIST_TPL ".%s",
588         dash_stream->representation_id, dash_stream->current_segment_id,
589         dash_muxer_list[sink->muxer].file_ext);
590   else {
591     dash_stream->current_segment_location =
592         g_strdup_printf ("%s" DEFAULT_SEGMENT_TEMPLATE_TPL ".%s",
593         dash_stream->representation_id, dash_stream->current_segment_id,
594         dash_muxer_list[sink->muxer].file_ext);
595   }
596   dash_stream->next_segment_id++;
597 
598   if (sink->mpd_root_path)
599     segment_tpl_path =
600         g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
601         dash_stream->current_segment_location, NULL);
602   else
603     segment_tpl_path = g_strdup (dash_stream->current_segment_location);
604 
605 
606   g_signal_emit (sink, signals[SIGNAL_GET_FRAGMENT_STREAM], 0, segment_tpl_path,
607       &stream);
608 
609   if (!stream)
610     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
611         (("Got no output stream for fragment '%s'."), segment_tpl_path),
612         (NULL));
613   else
614     g_object_set (dash_stream->giostreamsink, "stream", stream, NULL);
615 
616   if (stream)
617     g_object_unref (stream);
618 
619   g_free (segment_tpl_path);
620 
621   return NULL;
622 }
623 
624 static gboolean
gst_dash_sink_add_splitmuxsink(GstDashSink * sink,GstDashSinkStream * stream)625 gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
626 {
627   GstElement *mux =
628       gst_element_factory_make (dash_muxer_list[sink->muxer].element_name,
629       NULL);
630 
631   if (sink->muxer == GST_DASH_SINK_MUXER_MP4)
632     g_object_set (mux, "fragment-duration", sink->target_duration * GST_MSECOND,
633         NULL);
634 
635   g_return_val_if_fail (mux != NULL, FALSE);
636 
637   stream->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
638   if (!stream->splitmuxsink) {
639     gst_object_unref (mux);
640     return FALSE;
641   }
642   stream->giostreamsink = gst_element_factory_make ("giostreamsink", NULL);
643   if (!stream->giostreamsink) {
644     gst_object_unref (stream->splitmuxsink);
645     gst_object_unref (mux);
646     return FALSE;
647   }
648 
649   gst_bin_add (GST_BIN (sink), stream->splitmuxsink);
650 
651   if (!sink->use_segment_list)
652     stream->current_segment_id = 1;
653   else
654     stream->current_segment_id = 0;
655   stream->next_segment_id = stream->current_segment_id;
656 
657   g_object_set (stream->splitmuxsink, "location", NULL,
658       "max-size-time", ((GstClockTime) sink->target_duration * GST_SECOND),
659       "send-keyframe-requests", TRUE, "muxer", mux, "sink",
660       stream->giostreamsink, "reset-muxer", FALSE, "send-keyframe-requests",
661       sink->send_keyframe_requests, NULL);
662 
663   g_signal_connect (stream->splitmuxsink, "format-location",
664       G_CALLBACK (on_format_location), stream);
665 
666   return TRUE;
667 }
668 
669 static void
gst_dash_sink_init(GstDashSink * sink)670 gst_dash_sink_init (GstDashSink * sink)
671 {
672   sink->mpd_filename = g_strdup (DEFAULT_MPD_FILENAME);
673   sink->mpd_root_path = g_strdup (DEFAULT_MPD_ROOT_PATH);
674   sink->mpd_client = NULL;
675 
676   sink->target_duration = DEFAULT_TARGET_DURATION;
677   sink->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
678   sink->mpd_profiles = g_strdup (DEFAULT_MPD_PROFILES);
679   sink->use_segment_list = DEFAULT_MPD_USE_SEGMENT_LIST;
680 
681   sink->min_buffer_time = DEFAULT_MPD_MIN_BUFFER_TIME;
682   sink->period_duration = DEFAULT_MPD_PERIOD_DURATION;
683 
684   g_mutex_init (&sink->mpd_lock);
685 
686   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
687 
688   gst_dash_sink_reset (sink);
689 }
690 
691 static void
gst_dash_sink_reset(GstDashSink * sink)692 gst_dash_sink_reset (GstDashSink * sink)
693 {
694   sink->index = 0;
695 }
696 
697 static void
gst_dash_sink_get_stream_metadata(GstDashSink * sink,GstDashSinkStream * stream)698 gst_dash_sink_get_stream_metadata (GstDashSink * sink,
699     GstDashSinkStream * stream)
700 {
701   GstStructure *s;
702   GstCaps *caps = gst_pad_get_current_caps (stream->pad);
703 
704   GST_DEBUG_OBJECT (sink, "stream caps %s", gst_caps_to_string (caps));
705   s = gst_caps_get_structure (caps, 0);
706 
707   switch (stream->type) {
708     case DASH_SINK_STREAM_TYPE_VIDEO:
709     {
710       gst_structure_get_int (s, "width", &stream->info.video.width);
711       gst_structure_get_int (s, "height", &stream->info.video.height);
712       g_free (stream->codec);
713       stream->codec =
714           g_strdup (gst_mpd_helper_get_video_codec_from_mime (caps));
715       break;
716     }
717     case DASH_SINK_STREAM_TYPE_AUDIO:
718     {
719       gst_structure_get_int (s, "channels", &stream->info.audio.channels);
720       gst_structure_get_int (s, "rate", &stream->info.audio.rate);
721       g_free (stream->codec);
722       stream->codec =
723           g_strdup (gst_mpd_helper_get_audio_codec_from_mime (caps));
724       break;
725     }
726     case DASH_SINK_STREAM_TYPE_SUBTITLE:
727     {
728       break;
729     }
730     default:
731       break;
732   }
733 
734   gst_caps_unref (caps);
735 }
736 
737 static void
gst_dash_sink_generate_mpd_content(GstDashSink * sink,GstDashSinkStream * stream)738 gst_dash_sink_generate_mpd_content (GstDashSink * sink,
739     GstDashSinkStream * stream)
740 {
741   if (!sink->mpd_client) {
742     GList *l;
743     sink->mpd_client = gst_mpd_client_new ();
744     /* Add or set root node with stream ids */
745     gst_mpd_client_set_root_node (sink->mpd_client,
746         "profiles", sink->mpd_profiles,
747         "default-namespace", DEFAULT_MPD_NAMESPACE,
748         "min-buffer-time", sink->min_buffer_time, NULL);
749     if (sink->is_dynamic) {
750       GstDateTime *now = gst_date_time_new_now_utc ();
751       gst_mpd_client_set_root_node (sink->mpd_client,
752           "type", GST_MPD_FILE_TYPE_DYNAMIC,
753           "availability-start-time", now, "publish-time", now, NULL);
754       gst_date_time_unref (now);
755     }
756     if (sink->minimum_update_period)
757       gst_mpd_client_set_root_node (sink->mpd_client,
758           "minimum-update-period", sink->minimum_update_period, NULL);
759     if (sink->mpd_baseurl)
760       gst_mpd_client_add_baseurl_node (sink->mpd_client, "url",
761           sink->mpd_baseurl, NULL);
762     /* Add or set period node with stream ids
763      * TODO support multiple period
764      * */
765     sink->current_period_id =
766         gst_mpd_client_set_period_node (sink->mpd_client,
767         sink->current_period_id, NULL);
768     for (l = sink->streams; l != NULL; l = l->next) {
769       GstDashSinkStream *stream = (GstDashSinkStream *) l->data;
770       /* Add or set adaptation_set node with stream ids
771        * AdaptationSet per stream type
772        * */
773       gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
774           sink->current_period_id, stream->adaptation_set_id, NULL);
775       /* Add or set representation node with stream ids */
776       gst_mpd_client_set_representation_node (sink->mpd_client,
777           sink->current_period_id, stream->adaptation_set_id,
778           stream->representation_id, "bandwidth", stream->bitrate, "mime-type",
779           stream->mimetype, "codecs", stream->codec, NULL);
780       /* Set specific to stream type */
781       if (stream->type == DASH_SINK_STREAM_TYPE_VIDEO) {
782         gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
783             sink->current_period_id, stream->adaptation_set_id, "content-type",
784             "video", NULL);
785         gst_mpd_client_set_representation_node (sink->mpd_client,
786             sink->current_period_id, stream->adaptation_set_id,
787             stream->representation_id, "width", stream->info.video.width,
788             "height", stream->info.video.height, NULL);
789       } else if (stream->type == DASH_SINK_STREAM_TYPE_AUDIO) {
790         gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
791             sink->current_period_id, stream->adaptation_set_id, "content-type",
792             "audio", NULL);
793         gst_mpd_client_set_representation_node (sink->mpd_client,
794             sink->current_period_id, stream->adaptation_set_id,
795             stream->representation_id, "audio-sampling-rate",
796             stream->info.audio.rate, NULL);
797       }
798       if (sink->use_segment_list) {
799         /* Add a default segment list */
800         gst_mpd_client_set_segment_list (sink->mpd_client,
801             sink->current_period_id, stream->adaptation_set_id,
802             stream->representation_id, "duration", sink->target_duration, NULL);
803       } else {
804         gchar *media_segment_template =
805             g_strconcat (stream->representation_id, "_$Number$",
806             ".", dash_muxer_list[sink->muxer].file_ext, NULL);
807         gst_mpd_client_set_segment_template (sink->mpd_client,
808             sink->current_period_id, stream->adaptation_set_id,
809             stream->representation_id, "media", media_segment_template,
810             "duration", sink->target_duration, NULL);
811         g_free (media_segment_template);
812       }
813     }
814   }
815   /* MPD updates */
816   if (sink->use_segment_list) {
817     GST_INFO_OBJECT (sink, "Add segment URL: %s",
818         stream->current_segment_location);
819     gst_mpd_client_add_segment_url (sink->mpd_client, sink->current_period_id,
820         stream->adaptation_set_id, stream->representation_id, "media",
821         stream->current_segment_location, NULL);
822   } else {
823     if (!sink->is_dynamic) {
824       if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
825         gst_mpd_client_set_period_node (sink->mpd_client,
826             sink->current_period_id, "duration", sink->period_duration, NULL);
827       else
828         gst_mpd_client_set_period_node (sink->mpd_client,
829             sink->current_period_id, "duration",
830             gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
831     }
832     if (!sink->minimum_update_period) {
833       if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
834         gst_mpd_client_set_root_node (sink->mpd_client,
835             "media-presentation-duration", sink->period_duration, NULL);
836       else
837         gst_mpd_client_set_root_node (sink->mpd_client,
838             "media-presentation-duration",
839             gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
840     }
841   }
842 }
843 
844 static void
gst_dash_sink_write_mpd_file(GstDashSink * sink,GstDashSinkStream * current_stream)845 gst_dash_sink_write_mpd_file (GstDashSink * sink,
846     GstDashSinkStream * current_stream)
847 {
848   char *mpd_content = NULL;
849   gint size;
850   GError *error = NULL;
851   gchar *mpd_filepath = NULL;
852   GOutputStream *file_stream = NULL;
853   gsize bytes_to_write;
854 
855   g_mutex_lock (&sink->mpd_lock);
856   gst_dash_sink_generate_mpd_content (sink, current_stream);
857   if (!gst_mpd_client_get_xml_content (sink->mpd_client, &mpd_content, &size)) {
858     g_mutex_unlock (&sink->mpd_lock);
859     return;
860   }
861   g_mutex_unlock (&sink->mpd_lock);
862 
863   if (sink->mpd_root_path)
864     mpd_filepath =
865         g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
866         sink->mpd_filename, NULL);
867   else
868     mpd_filepath = g_strdup (sink->mpd_filename);
869   GST_DEBUG_OBJECT (sink, "a new mpd content is available: %s", mpd_content);
870   GST_DEBUG_OBJECT (sink, "write mpd to %s", mpd_filepath);
871 
872   g_signal_emit (sink, signals[SIGNAL_GET_PLAYLIST_STREAM], 0, mpd_filepath,
873       &file_stream);
874   if (!file_stream) {
875     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
876         (("Got no output stream for fragment '%s'."), mpd_filepath), (NULL));
877   }
878 
879   bytes_to_write = strlen (mpd_content);
880   if (!g_output_stream_write_all (file_stream, mpd_content, bytes_to_write,
881           NULL, NULL, &error)) {
882     GST_ERROR ("Failed to write mpd content: %s", error->message);
883     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
884         (("Failed to write playlist '%s'."), error->message), (NULL));
885     g_error_free (error);
886     error = NULL;
887   }
888 
889   g_free (mpd_content);
890   g_free (mpd_filepath);
891   g_object_unref (file_stream);
892 }
893 
894 static void
gst_dash_sink_handle_message(GstBin * bin,GstMessage * message)895 gst_dash_sink_handle_message (GstBin * bin, GstMessage * message)
896 {
897   GstDashSink *sink = GST_DASH_SINK (bin);
898   GstDashSinkStream *stream = NULL;
899   switch (message->type) {
900     case GST_MESSAGE_ELEMENT:
901     {
902       const GstStructure *s = gst_message_get_structure (message);
903       GST_DEBUG_OBJECT (sink, "Received message with name %s",
904           gst_structure_get_name (s));
905       stream =
906           gst_dash_sink_stream_from_splitmuxsink (sink->streams,
907           GST_ELEMENT (message->src));
908       if (stream) {
909         if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
910           gst_dash_sink_get_stream_metadata (sink, stream);
911           gst_structure_get_clock_time (s, "running-time",
912               &stream->current_running_time_start);
913         } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
914           GstClockTime running_time;
915           gst_structure_get_clock_time (s, "running-time", &running_time);
916           if (sink->running_time < running_time)
917             sink->running_time = running_time;
918           gst_dash_sink_write_mpd_file (sink, stream);
919         }
920       }
921       break;
922     }
923     case GST_MESSAGE_EOS:{
924       gst_dash_sink_write_mpd_file (sink, NULL);
925       break;
926     }
927     default:
928       break;
929   }
930 
931   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
932 }
933 
934 static GstPadProbeReturn
_dash_sink_buffers_probe(GstPad * pad,GstPadProbeInfo * probe_info,gpointer user_data)935 _dash_sink_buffers_probe (GstPad * pad, GstPadProbeInfo * probe_info,
936     gpointer user_data)
937 {
938   GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (probe_info);
939   GstDashSinkStream *stream = (GstDashSinkStream *) user_data;
940 
941   if (GST_BUFFER_DURATION (buffer))
942     stream->bitrate =
943         gst_buffer_get_size (buffer) * GST_SECOND /
944         GST_BUFFER_DURATION (buffer);
945 
946   return GST_PAD_PROBE_OK;
947 }
948 
949 static GstPad *
gst_dash_sink_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * pad_name,const GstCaps * caps)950 gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
951     const gchar * pad_name, const GstCaps * caps)
952 {
953   GstDashSink *sink = GST_DASH_SINK (element);
954   GstDashSinkStream *stream = NULL;
955   GstPad *pad = NULL;
956   GstPad *peer = NULL;
957   const gchar *split_pad_name = pad_name;
958 
959   stream = g_new0 (GstDashSinkStream, 1);
960   stream->sink = g_object_ref (sink);
961   if (g_str_has_prefix (templ->name_template, "video")) {
962     stream->type = DASH_SINK_STREAM_TYPE_VIDEO;
963     stream->adaptation_set_id = ADAPTATION_SET_ID_VIDEO;
964     split_pad_name = "video";
965   } else if (g_str_has_prefix (templ->name_template, "audio")) {
966     stream->type = DASH_SINK_STREAM_TYPE_AUDIO;
967     stream->adaptation_set_id = ADAPTATION_SET_ID_AUDIO;
968   } else if (g_str_has_prefix (templ->name_template, "subtitle")) {
969     stream->type = DASH_SINK_STREAM_TYPE_SUBTITLE;
970     stream->adaptation_set_id = ADAPTATION_SET_ID_SUBTITLE;
971   }
972 
973   if (pad_name)
974     stream->representation_id = g_strdup (pad_name);
975   else
976     stream->representation_id =
977         gst_dash_sink_stream_get_next_name (sink->streams, stream->type);
978 
979 
980   stream->mimetype = g_strdup (dash_muxer_list[sink->muxer].mimetype);
981 
982 
983   if (!gst_dash_sink_add_splitmuxsink (sink, stream)) {
984     GST_ERROR_OBJECT (sink,
985         "Unable to create splitmuxsink element for pad template name %s",
986         templ->name_template);
987     gst_dash_sink_stream_free (stream);
988     goto done;
989   }
990 
991   peer = gst_element_request_pad_simple (stream->splitmuxsink, split_pad_name);
992   if (!peer) {
993     GST_ERROR_OBJECT (sink, "Unable to request pad name %s", split_pad_name);
994     return NULL;
995   }
996 
997   pad = gst_ghost_pad_new_from_template (pad_name, peer, templ);
998   gst_pad_set_active (pad, TRUE);
999   gst_element_add_pad (element, pad);
1000   gst_object_unref (peer);
1001 
1002   stream->pad = pad;
1003 
1004   stream->buffer_probe = gst_pad_add_probe (stream->pad,
1005       GST_PAD_PROBE_TYPE_BUFFER, _dash_sink_buffers_probe, stream, NULL);
1006 
1007   sink->streams = g_list_append (sink->streams, stream);
1008   GST_DEBUG_OBJECT (sink, "Adding a new stream with id %s",
1009       stream->representation_id);
1010 
1011 done:
1012   return pad;
1013 }
1014 
1015 static void
gst_dash_sink_release_pad(GstElement * element,GstPad * pad)1016 gst_dash_sink_release_pad (GstElement * element, GstPad * pad)
1017 {
1018   GstDashSink *sink = GST_DASH_SINK (element);
1019   GstPad *peer;
1020   GstDashSinkStream *stream =
1021       gst_dash_sink_stream_from_pad (sink->streams, pad);
1022 
1023   g_return_if_fail (stream != NULL);
1024 
1025   peer = gst_pad_get_peer (pad);
1026   if (peer) {
1027     gst_element_release_request_pad (stream->splitmuxsink, pad);
1028     gst_object_unref (peer);
1029   }
1030 
1031   if (stream->buffer_probe > 0) {
1032     gst_pad_remove_probe (pad, stream->buffer_probe);
1033     stream->buffer_probe = 0;
1034   }
1035 
1036   gst_object_ref (pad);
1037   gst_element_remove_pad (element, pad);
1038   gst_pad_set_active (pad, FALSE);
1039 
1040   stream->pad = NULL;
1041 
1042   gst_object_unref (pad);
1043 }
1044 
1045 static GstStateChangeReturn
gst_dash_sink_change_state(GstElement * element,GstStateChange trans)1046 gst_dash_sink_change_state (GstElement * element, GstStateChange trans)
1047 {
1048   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1049   GstDashSink *sink = GST_DASH_SINK (element);
1050 
1051   switch (trans) {
1052     case GST_STATE_CHANGE_NULL_TO_READY:
1053       if (!g_list_length (sink->streams)) {
1054         return GST_STATE_CHANGE_FAILURE;
1055       }
1056       break;
1057     default:
1058       break;
1059   }
1060 
1061   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
1062 
1063   switch (trans) {
1064     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1065       break;
1066     case GST_STATE_CHANGE_PAUSED_TO_READY:
1067     case GST_STATE_CHANGE_READY_TO_NULL:
1068       gst_dash_sink_reset (sink);
1069       break;
1070     default:
1071       break;
1072   }
1073 
1074   return ret;
1075 }
1076 
1077 static void
gst_dash_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1078 gst_dash_sink_set_property (GObject * object, guint prop_id,
1079     const GValue * value, GParamSpec * pspec)
1080 {
1081   GstDashSink *sink = GST_DASH_SINK (object);
1082 
1083   switch (prop_id) {
1084     case PROP_MPD_FILENAME:
1085       g_free (sink->mpd_filename);
1086       sink->mpd_filename = g_value_dup_string (value);
1087       break;
1088     case PROP_MPD_ROOT_PATH:
1089       g_free (sink->mpd_root_path);
1090       sink->mpd_root_path = g_value_dup_string (value);
1091       break;
1092     case PROP_MPD_BASEURL:
1093       g_free (sink->mpd_baseurl);
1094       sink->mpd_baseurl = g_value_dup_string (value);
1095       break;
1096     case PROP_TARGET_DURATION:
1097       sink->target_duration = g_value_get_uint (value);
1098       break;
1099     case PROP_SEND_KEYFRAME_REQUESTS:
1100       sink->send_keyframe_requests = g_value_get_boolean (value);
1101       break;
1102     case PROP_USE_SEGMENT_LIST:
1103       sink->use_segment_list = g_value_get_boolean (value);
1104       break;
1105     case PROP_MPD_DYNAMIC:
1106       sink->is_dynamic = g_value_get_boolean (value);
1107       break;
1108     case PROP_MUXER:
1109       sink->muxer = g_value_get_enum (value);
1110       break;
1111     case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1112       sink->minimum_update_period = g_value_get_uint64 (value);
1113       break;
1114     case PROP_MPD_MIN_BUFFER_TIME:
1115       sink->min_buffer_time = g_value_get_uint64 (value);
1116       break;
1117     case PROP_MPD_PERIOD_DURATION:
1118       sink->period_duration = g_value_get_uint64 (value);
1119       break;
1120     default:
1121       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1122       break;
1123   }
1124 }
1125 
1126 static void
gst_dash_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1127 gst_dash_sink_get_property (GObject * object, guint prop_id,
1128     GValue * value, GParamSpec * pspec)
1129 {
1130   GstDashSink *sink = GST_DASH_SINK (object);
1131 
1132   switch (prop_id) {
1133     case PROP_MPD_FILENAME:
1134       g_value_set_string (value, sink->mpd_filename);
1135       break;
1136     case PROP_MPD_ROOT_PATH:
1137       g_value_set_string (value, sink->mpd_root_path);
1138       break;
1139     case PROP_MPD_BASEURL:
1140       g_value_set_string (value, sink->mpd_baseurl);
1141       break;
1142     case PROP_TARGET_DURATION:
1143       g_value_set_uint (value, sink->target_duration);
1144       break;
1145     case PROP_SEND_KEYFRAME_REQUESTS:
1146       g_value_set_boolean (value, sink->send_keyframe_requests);
1147       break;
1148     case PROP_USE_SEGMENT_LIST:
1149       g_value_set_boolean (value, sink->use_segment_list);
1150       break;
1151     case PROP_MPD_DYNAMIC:
1152       g_value_set_boolean (value, sink->is_dynamic);
1153       break;
1154     case PROP_MUXER:
1155       g_value_set_enum (value, sink->muxer);
1156       break;
1157     case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1158       g_value_set_uint64 (value, sink->minimum_update_period);
1159       break;
1160     case PROP_MPD_MIN_BUFFER_TIME:
1161       g_value_set_uint64 (value, sink->min_buffer_time);
1162       break;
1163     case PROP_MPD_PERIOD_DURATION:
1164       g_value_set_uint64 (value, sink->period_duration);
1165       break;
1166     default:
1167       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1168       break;
1169   }
1170 }
1171