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