2 * Copyright (C) 2019 Stéphane Cerveau <scerveau@collabora.com>
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.
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.
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.
21 * SECTION:element-dashsink
24 * Dynamic Adaptive Streaming over HTTP sink/server
26 * ## Example launch line
28 * 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
33 /* Implementation notes:
35 * The following section describes how dashsink works internally.
39 * This element aims to generate the Media Pressentation Description XML based file
40 * used as DASH content in addition to the necessary media frgaments.
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-, ,-tsdemux-, ,-filesink-, ; ;
47 * ; o--o o---o--o ; o-o o-o , ; ;
48 * '--------------' '---------' ; ; '-------' '---------' '----------' ; ;
49 * ; '------------------------------------' ;
51 * ; ,----------splitmuxsink--------------, ;
52 * ,-audiotestsrc-, ,-avenc_aac-, ; ; ,-Queue-, ,-tsdemux-, ,-filesink-, ; ;
53 * ; o--o o-o--o o-o o-o ; ; ;
54 * '--------------' '-----------' ; ; '-------' '---------' '----------' ; ;
55 * ; '------------------------------------' ;
56 * ' -----------------------------------------'
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
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 .
69 * It supports any kind of stream input codec
70 * which can be encapsulated in Transport Stream or ISO media format.
71 * The current implementation is generating compliant MPDs for both static and dynamic
72 * prfiles with https://conformance.dashif.org/
76 * The fragments during the DASH generation does not look reliable enough to be used as
77 * a production solution. Some additional or fine tuning work needs to be performed to address
78 * these issues, especially for MP4 fragments.
86 #include "gstdashsink.h"
87 #include "gstmpdparser.h"
88 #include <gst/pbutils/pbutils.h>
89 #include <gst/video/video.h>
90 #include <glib/gstdio.h>
95 GST_DEBUG_CATEGORY_STATIC (gst_dash_sink_debug);
96 #define GST_CAT_DEFAULT gst_dash_sink_debug
99 * GstDashSinkMuxerType:
100 * @GST_DASH_SINK_MUXER_TS: Use mpegtsmux
101 * @GST_DASH_SINK_MUXER_MP4: Use mp4mux
107 GST_DASH_SINK_MUXER_TS = 0,
108 GST_DASH_SINK_MUXER_MP4 = 1,
109 } GstDashSinkMuxerType;
111 typedef struct _DashSinkMuxer
113 GstDashSinkMuxerType type;
114 const gchar *element_name;
115 const gchar *mimetype;
116 const gchar *file_ext;
119 #define GST_TYPE_DASH_SINK_MUXER (gst_dash_sink_muxer_get_type())
121 gst_dash_sink_muxer_get_type (void)
123 static GType dash_sink_muxer_type = 0;
124 static const GEnumValue muxer_type[] = {
125 {GST_DASH_SINK_MUXER_TS, "Use mpegtsmux", "ts"},
126 {GST_DASH_SINK_MUXER_MP4, "Use mp4mux", "mp4"},
130 if (!dash_sink_muxer_type) {
131 dash_sink_muxer_type =
132 g_enum_register_static ("GstDashSinkMuxerType", muxer_type);
134 return dash_sink_muxer_type;
137 static const DashSinkMuxer dash_muxer_list[] = {
139 GST_DASH_SINK_MUXER_TS,
144 GST_DASH_SINK_MUXER_MP4,
150 #define DEFAULT_SEGMENT_LIST_TPL "_%05d"
151 #define DEFAULT_SEGMENT_TEMPLATE_TPL "_%d"
152 #define DEFAULT_MPD_FILENAME "dash.mpd"
153 #define DEFAULT_MPD_ROOT_PATH NULL
154 #define DEFAULT_TARGET_DURATION 15
155 #define DEFAULT_SEND_KEYFRAME_REQUESTS TRUE
156 #define DEFAULT_MPD_NAMESPACE "urn:mpeg:dash:schema:mpd:2011"
157 #define DEFAULT_MPD_PROFILES "urn:mpeg:dash:profile:isoff-main:2011"
158 #define DEFAULT_MPD_SCHEMA_LOCATION "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd"
159 #define DEFAULT_MPD_USE_SEGMENT_LIST FALSE
160 #define DEFAULT_MPD_MIN_BUFFER_TIME 2000
161 #define DEFAULT_MPD_PERIOD_DURATION GST_CLOCK_TIME_NONE
163 #define DEFAULT_DASH_SINK_MUXER GST_DASH_SINK_MUXER_TS
167 ADAPTATION_SET_ID_VIDEO = 1,
168 ADAPTATION_SET_ID_AUDIO,
169 ADAPTATION_SET_ID_SUBTITLE,
177 PROP_TARGET_DURATION,
178 PROP_SEND_KEYFRAME_REQUESTS,
179 PROP_USE_SEGMENT_LIST,
182 PROP_MPD_MINIMUM_UPDATE_PERIOD,
183 PROP_MPD_MIN_BUFFER_TIME,
185 PROP_MPD_PERIOD_DURATION,
190 SIGNAL_GET_PLAYLIST_STREAM,
191 SIGNAL_GET_FRAGMENT_STREAM,
195 static guint signals[SIGNAL_LAST];
199 DASH_SINK_STREAM_TYPE_VIDEO = 0,
200 DASH_SINK_STREAM_TYPE_AUDIO,
201 DASH_SINK_STREAM_TYPE_SUBTITLE,
202 DASH_SINK_STREAM_TYPE_UNKNOWN,
203 } GstDashSinkStreamType;
205 typedef struct _GstDashSinkStreamVideoInfo
209 } GstDashSinkStreamVideoInfo;
211 typedef struct _GstDashSinkStreamAudioInfo
215 } GstDashSinkStreamAudioInfo;
217 typedef struct GstDashSinkStreamSubtitleInfo
220 } GstDashSinkStreamSubtitleInfo;
222 typedef union _GstDashSinkStreamInfo
224 GstDashSinkStreamVideoInfo video;
225 GstDashSinkStreamAudioInfo audio;
226 GstDashSinkStreamSubtitleInfo subtitle;
227 } GstDashSinkStreamInfo;
235 gchar *mpd_root_path;
238 GstDashSinkMuxerType muxer;
239 GstMPDClient *mpd_client;
240 gchar *current_period_id;
241 gint target_duration;
242 GstClockTime running_time;
243 gboolean send_keyframe_requests;
244 gboolean use_segment_list;
246 gchar *segment_file_tpl;
249 guint64 minimum_update_period;
250 guint64 min_buffer_time;
251 gint64 period_duration;
254 typedef struct _GstDashSinkStream
257 GstDashSinkStreamType type;
260 GstElement *splitmuxsink;
261 gint adaptation_set_id;
262 gchar *representation_id;
263 gchar *current_segment_location;
264 gint current_segment_id;
265 gint next_segment_id;
269 GstClockTime current_running_time_start;
270 GstDashSinkStreamInfo info;
271 GstElement *giostreamsink;
274 static GstStaticPadTemplate video_sink_template =
275 GST_STATIC_PAD_TEMPLATE ("video_%u",
278 GST_STATIC_CAPS_ANY);
281 static GstStaticPadTemplate audio_sink_template =
282 GST_STATIC_PAD_TEMPLATE ("audio_%u",
285 GST_STATIC_CAPS_ANY);
286 static GstStaticPadTemplate subtitle_sink_template =
287 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
290 GST_STATIC_CAPS_ANY);
292 #define gst_dash_sink_parent_class parent_class
293 G_DEFINE_TYPE_WITH_CODE (GstDashSink, gst_dash_sink, GST_TYPE_BIN,
294 GST_DEBUG_CATEGORY_INIT (gst_dash_sink_debug, "dashsink", 0, "DashSink"));
295 GST_ELEMENT_REGISTER_DEFINE (dashsink, "dashsink", GST_RANK_NONE,
296 gst_dash_sink_get_type ());
298 static void gst_dash_sink_set_property (GObject * object, guint prop_id,
299 const GValue * value, GParamSpec * spec);
300 static void gst_dash_sink_get_property (GObject * object, guint prop_id,
301 GValue * value, GParamSpec * spec);
302 static void gst_dash_sink_handle_message (GstBin * bin, GstMessage * message);
303 static void gst_dash_sink_reset (GstDashSink * sink);
304 static GstStateChangeReturn
305 gst_dash_sink_change_state (GstElement * element, GstStateChange trans);
306 static GstPad *gst_dash_sink_request_new_pad (GstElement * element,
307 GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
308 static void gst_dash_sink_release_pad (GstElement * element, GstPad * pad);
311 static GstDashSinkStream *
312 gst_dash_sink_stream_from_pad (GList * streams, GstPad * pad)
315 GstDashSinkStream *stream = NULL;
316 for (l = streams; l != NULL; l = l->next) {
318 if (stream->pad == pad)
324 static GstDashSinkStream *
325 gst_dash_sink_stream_from_splitmuxsink (GList * streams, GstElement * element)
328 GstDashSinkStream *stream = NULL;
329 for (l = streams; l != NULL; l = l->next) {
331 if (stream->splitmuxsink == element)
338 gst_dash_sink_stream_get_next_name (GList * streams, GstDashSinkStreamType type)
342 GstDashSinkStream *stream = NULL;
345 for (l = streams; l != NULL; l = l->next) {
347 if (stream->type == type)
352 case DASH_SINK_STREAM_TYPE_VIDEO:
353 name = g_strdup_printf ("video_%d", count);
355 case DASH_SINK_STREAM_TYPE_AUDIO:
356 name = g_strdup_printf ("audio_%d", count);
358 case DASH_SINK_STREAM_TYPE_SUBTITLE:
359 name = g_strdup_printf ("sub_%d", count);
362 name = g_strdup_printf ("unknown_%d", count);
369 gst_dash_sink_stream_free (gpointer s)
371 GstDashSinkStream *stream = (GstDashSinkStream *) s;
372 g_object_unref (stream->sink);
373 g_free (stream->current_segment_location);
374 g_free (stream->representation_id);
375 g_free (stream->mimetype);
376 g_free (stream->codec);
382 gst_dash_sink_dispose (GObject * object)
384 GstDashSink *sink = GST_DASH_SINK (object);
386 G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
390 gst_dash_sink_finalize (GObject * object)
392 GstDashSink *sink = GST_DASH_SINK (object);
394 g_free (sink->mpd_filename);
395 g_free (sink->mpd_root_path);
396 g_free (sink->mpd_profiles);
397 if (sink->mpd_client)
398 gst_mpd_client_free (sink->mpd_client);
399 g_mutex_clear (&sink->mpd_lock);
401 g_list_free_full (sink->streams, gst_dash_sink_stream_free);
403 G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
406 /* Default implementations for the signal handlers */
407 static GOutputStream *
408 gst_dash_sink_get_playlist_stream (GstDashSink * sink, const gchar * location)
410 GFile *file = g_file_new_for_path (location);
411 GOutputStream *ostream;
415 G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
416 G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
418 GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
419 (("Got no output stream for playlist '%s': %s."), location,
420 err->message), (NULL));
421 g_clear_error (&err);
424 g_object_unref (file);
429 static GOutputStream *
430 gst_dash_sink_get_fragment_stream (GstDashSink * sink, const gchar * location)
432 GFile *file = g_file_new_for_path (location);
433 GOutputStream *ostream;
437 G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
438 G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
440 GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
441 (("Got no output stream for fragment '%s': %s."), location,
442 err->message), (NULL));
443 g_clear_error (&err);
446 g_object_unref (file);
452 gst_dash_sink_class_init (GstDashSinkClass * klass)
454 GObjectClass *gobject_class;
455 GstElementClass *element_class;
456 GstBinClass *bin_class;
458 gobject_class = (GObjectClass *) klass;
459 element_class = GST_ELEMENT_CLASS (klass);
460 bin_class = GST_BIN_CLASS (klass);
462 gst_element_class_add_static_pad_template (element_class,
463 &video_sink_template);
464 gst_element_class_add_static_pad_template (element_class,
465 &audio_sink_template);
466 gst_element_class_add_static_pad_template (element_class,
467 &subtitle_sink_template);
469 gst_element_class_set_static_metadata (element_class,
471 "Dynamic Adaptive Streaming over HTTP sink",
472 "Stéphane Cerveau <scerveau@collabora.com>");
474 element_class->change_state = GST_DEBUG_FUNCPTR (gst_dash_sink_change_state);
475 element_class->request_new_pad =
476 GST_DEBUG_FUNCPTR (gst_dash_sink_request_new_pad);
477 element_class->release_pad = GST_DEBUG_FUNCPTR (gst_dash_sink_release_pad);
479 bin_class->handle_message = gst_dash_sink_handle_message;
481 gobject_class->dispose = gst_dash_sink_dispose;
482 gobject_class->finalize = gst_dash_sink_finalize;
483 gobject_class->set_property = gst_dash_sink_set_property;
484 gobject_class->get_property = gst_dash_sink_get_property;
486 g_object_class_install_property (gobject_class, PROP_MPD_FILENAME,
487 g_param_spec_string ("mpd-filename", "MPD filename",
488 "filename of the mpd to write", DEFAULT_MPD_FILENAME,
489 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
490 g_object_class_install_property (gobject_class, PROP_MPD_ROOT_PATH,
491 g_param_spec_string ("mpd-root-path", "MPD Root Path",
492 "Path where the MPD and its fragents will be written",
493 DEFAULT_MPD_ROOT_PATH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
494 g_object_class_install_property (gobject_class, PROP_MPD_BASEURL,
495 g_param_spec_string ("mpd-baseurl", "MPD BaseURL",
496 "BaseURL to set in the MPD", DEFAULT_MPD_ROOT_PATH,
497 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
498 g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
499 g_param_spec_uint ("target-duration", "Target duration",
500 "The target duration in seconds of a segment/file. "
501 "(0 - disabled, useful for management of segment duration by the "
502 "streaming server)", 0, G_MAXUINT, DEFAULT_TARGET_DURATION,
503 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
504 g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
505 g_param_spec_boolean ("send-keyframe-requests", "Send Keyframe Requests",
506 "Send keyframe requests to ensure correct fragmentation. If this is disabled "
507 "then the input must have keyframes in regular intervals",
508 DEFAULT_SEND_KEYFRAME_REQUESTS,
509 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
510 g_object_class_install_property (gobject_class, PROP_USE_SEGMENT_LIST,
511 g_param_spec_boolean ("use-segment-list", "Use segment list",
512 "Use segment list instead of segment template to create the segments",
513 DEFAULT_MPD_USE_SEGMENT_LIST,
514 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
515 g_object_class_install_property (gobject_class, PROP_MPD_DYNAMIC,
516 g_param_spec_boolean ("dynamic", "dynamic", "Provides a dynamic mpd",
517 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
518 g_object_class_install_property (gobject_class, PROP_MUXER,
519 g_param_spec_enum ("muxer", "Muxer",
520 "Muxer type to be used by dashsink to generate the fragment",
521 GST_TYPE_DASH_SINK_MUXER, DEFAULT_DASH_SINK_MUXER,
522 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
523 g_object_class_install_property (gobject_class,
524 PROP_MPD_MINIMUM_UPDATE_PERIOD,
525 g_param_spec_uint64 ("minimum-update-period", "Minimum update period",
526 "Provides to the manifest a minimum update period in milliseconds", 0,
527 G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
528 g_object_class_install_property (gobject_class,
529 PROP_MPD_MIN_BUFFER_TIME,
530 g_param_spec_uint64 ("min-buffer-time", "Mininim buffer time",
531 "Provides to the manifest a minimum buffer time in milliseconds", 0,
532 G_MAXUINT64, DEFAULT_MPD_MIN_BUFFER_TIME,
533 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
534 g_object_class_install_property (gobject_class,
535 PROP_MPD_PERIOD_DURATION,
536 g_param_spec_uint64 ("period-duration", "period duration",
537 "Provides the explicit duration of a period in milliseconds", 0,
538 G_MAXUINT64, DEFAULT_MPD_PERIOD_DURATION,
539 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
542 * GstDashSink::get-playlist-stream:
543 * @sink: the #GstDashSink
544 * @location: Location for the playlist file
546 * Returns: #GOutputStream for writing the playlist file.
550 signals[SIGNAL_GET_PLAYLIST_STREAM] =
551 g_signal_new_class_handler ("get-playlist-stream",
552 G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
553 G_CALLBACK (gst_dash_sink_get_playlist_stream), NULL, NULL, NULL,
554 G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
557 * GstDashSink::get-fragment-stream:
558 * @sink: the #GstDashSink
559 * @location: Location for the fragment file
561 * Returns: #GOutputStream for writing the fragment file.
565 signals[SIGNAL_GET_FRAGMENT_STREAM] =
566 g_signal_new_class_handler ("get-fragment-stream",
567 G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
568 G_CALLBACK (gst_dash_sink_get_fragment_stream), NULL, NULL, NULL,
569 G_TYPE_OUTPUT_STREAM, 1, G_TYPE_STRING);
571 gst_type_mark_as_plugin_api (GST_TYPE_DASH_SINK_MUXER, 0);
575 on_format_location (GstElement * splitmuxsink, guint fragment_id,
576 GstDashSinkStream * dash_stream)
578 GOutputStream *stream = NULL;
579 GstDashSink *sink = dash_stream->sink;
580 gchar *segment_tpl_path;
582 dash_stream->current_segment_id = dash_stream->next_segment_id;
583 g_free (dash_stream->current_segment_location);
584 if (sink->use_segment_list)
585 dash_stream->current_segment_location =
586 g_strdup_printf ("%s" DEFAULT_SEGMENT_LIST_TPL ".%s",
587 dash_stream->representation_id, dash_stream->current_segment_id,
588 dash_muxer_list[sink->muxer].file_ext);
590 dash_stream->current_segment_location =
591 g_strdup_printf ("%s" DEFAULT_SEGMENT_TEMPLATE_TPL ".%s",
592 dash_stream->representation_id, dash_stream->current_segment_id,
593 dash_muxer_list[sink->muxer].file_ext);
595 dash_stream->next_segment_id++;
597 if (sink->mpd_root_path)
599 g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
600 dash_stream->current_segment_location, NULL);
602 segment_tpl_path = g_strdup (dash_stream->current_segment_location);
605 g_signal_emit (sink, signals[SIGNAL_GET_FRAGMENT_STREAM], 0, segment_tpl_path,
609 GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
610 (("Got no output stream for fragment '%s'."), segment_tpl_path),
613 g_object_set (dash_stream->giostreamsink, "stream", stream, NULL);
616 g_object_unref (stream);
618 g_free (segment_tpl_path);
624 gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
627 gst_element_factory_make (dash_muxer_list[sink->muxer].element_name,
630 if (sink->muxer == GST_DASH_SINK_MUXER_MP4)
631 g_object_set (mux, "fragment-duration", sink->target_duration * GST_MSECOND,
634 g_return_val_if_fail (mux != NULL, FALSE);
636 stream->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
637 if (!stream->splitmuxsink) {
638 gst_object_unref (mux);
641 stream->giostreamsink = gst_element_factory_make ("giostreamsink", NULL);
642 if (!stream->giostreamsink) {
643 gst_object_unref (stream->splitmuxsink);
644 gst_object_unref (mux);
648 gst_bin_add (GST_BIN (sink), stream->splitmuxsink);
650 if (!sink->use_segment_list)
651 stream->current_segment_id = 1;
653 stream->current_segment_id = 0;
654 stream->next_segment_id = stream->current_segment_id;
656 g_object_set (stream->splitmuxsink, "location", NULL,
657 "max-size-time", ((GstClockTime) sink->target_duration * GST_SECOND),
658 "send-keyframe-requests", TRUE, "muxer", mux, "sink",
659 stream->giostreamsink, "reset-muxer", FALSE, "send-keyframe-requests",
660 sink->send_keyframe_requests, NULL);
662 g_signal_connect (stream->splitmuxsink, "format-location",
663 G_CALLBACK (on_format_location), stream);
669 gst_dash_sink_init (GstDashSink * sink)
671 sink->mpd_filename = g_strdup (DEFAULT_MPD_FILENAME);
672 sink->mpd_root_path = g_strdup (DEFAULT_MPD_ROOT_PATH);
673 sink->mpd_client = NULL;
675 sink->target_duration = DEFAULT_TARGET_DURATION;
676 sink->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
677 sink->mpd_profiles = g_strdup (DEFAULT_MPD_PROFILES);
678 sink->use_segment_list = DEFAULT_MPD_USE_SEGMENT_LIST;
680 sink->min_buffer_time = DEFAULT_MPD_MIN_BUFFER_TIME;
681 sink->period_duration = DEFAULT_MPD_PERIOD_DURATION;
683 g_mutex_init (&sink->mpd_lock);
685 GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
687 gst_dash_sink_reset (sink);
691 gst_dash_sink_reset (GstDashSink * sink)
697 gst_dash_sink_get_stream_metadata (GstDashSink * sink,
698 GstDashSinkStream * stream)
701 GstCaps *caps = gst_pad_get_current_caps (stream->pad);
703 GST_DEBUG_OBJECT (sink, "stream caps %s", gst_caps_to_string (caps));
704 s = gst_caps_get_structure (caps, 0);
706 switch (stream->type) {
707 case DASH_SINK_STREAM_TYPE_VIDEO:
709 gst_structure_get_int (s, "width", &stream->info.video.width);
710 gst_structure_get_int (s, "height", &stream->info.video.height);
711 g_free (stream->codec);
713 g_strdup (gst_mpd_helper_get_video_codec_from_mime (caps));
716 case DASH_SINK_STREAM_TYPE_AUDIO:
718 gst_structure_get_int (s, "channels", &stream->info.audio.channels);
719 gst_structure_get_int (s, "rate", &stream->info.audio.rate);
720 g_free (stream->codec);
722 g_strdup (gst_mpd_helper_get_audio_codec_from_mime (caps));
725 case DASH_SINK_STREAM_TYPE_SUBTITLE:
733 gst_caps_unref (caps);
737 gst_dash_sink_generate_mpd_content (GstDashSink * sink,
738 GstDashSinkStream * stream)
740 if (!sink->mpd_client) {
742 sink->mpd_client = gst_mpd_client_new ();
743 /* Add or set root node with stream ids */
744 gst_mpd_client_set_root_node (sink->mpd_client,
745 "profiles", sink->mpd_profiles,
746 "default-namespace", DEFAULT_MPD_NAMESPACE,
747 "min-buffer-time", sink->min_buffer_time, NULL);
748 if (sink->is_dynamic) {
749 GstDateTime *now = gst_date_time_new_now_utc ();
750 gst_mpd_client_set_root_node (sink->mpd_client,
751 "type", GST_MPD_FILE_TYPE_DYNAMIC,
752 "availability-start-time", now, "publish-time", now, NULL);
753 gst_date_time_unref (now);
755 if (sink->minimum_update_period)
756 gst_mpd_client_set_root_node (sink->mpd_client,
757 "minimum-update-period", sink->minimum_update_period, NULL);
758 if (sink->mpd_baseurl)
759 gst_mpd_client_add_baseurl_node (sink->mpd_client, "url",
760 sink->mpd_baseurl, NULL);
761 /* Add or set period node with stream ids
762 * TODO support multiple period
764 sink->current_period_id =
765 gst_mpd_client_set_period_node (sink->mpd_client,
766 sink->current_period_id, NULL);
767 for (l = sink->streams; l != NULL; l = l->next) {
768 GstDashSinkStream *stream = (GstDashSinkStream *) l->data;
769 /* Add or set adaptation_set node with stream ids
770 * AdaptationSet per stream type
772 gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
773 sink->current_period_id, stream->adaptation_set_id, NULL);
774 /* Add or set representation node with stream ids */
775 gst_mpd_client_set_representation_node (sink->mpd_client,
776 sink->current_period_id, stream->adaptation_set_id,
777 stream->representation_id, "bandwidth", stream->bitrate, "mime-type",
778 stream->mimetype, "codecs", stream->codec, NULL);
779 /* Set specific to stream type */
780 if (stream->type == DASH_SINK_STREAM_TYPE_VIDEO) {
781 gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
782 sink->current_period_id, stream->adaptation_set_id, "content-type",
784 gst_mpd_client_set_representation_node (sink->mpd_client,
785 sink->current_period_id, stream->adaptation_set_id,
786 stream->representation_id, "width", stream->info.video.width,
787 "height", stream->info.video.height, NULL);
788 } else if (stream->type == DASH_SINK_STREAM_TYPE_AUDIO) {
789 gst_mpd_client_set_adaptation_set_node (sink->mpd_client,
790 sink->current_period_id, stream->adaptation_set_id, "content-type",
792 gst_mpd_client_set_representation_node (sink->mpd_client,
793 sink->current_period_id, stream->adaptation_set_id,
794 stream->representation_id, "audio-sampling-rate",
795 stream->info.audio.rate, NULL);
797 if (sink->use_segment_list) {
798 /* Add a default segment list */
799 gst_mpd_client_set_segment_list (sink->mpd_client,
800 sink->current_period_id, stream->adaptation_set_id,
801 stream->representation_id, "duration", sink->target_duration, NULL);
803 gchar *media_segment_template =
804 g_strconcat (stream->representation_id, "_$Number$",
805 ".", dash_muxer_list[sink->muxer].file_ext, NULL);
806 gst_mpd_client_set_segment_template (sink->mpd_client,
807 sink->current_period_id, stream->adaptation_set_id,
808 stream->representation_id, "media", media_segment_template,
809 "duration", sink->target_duration, NULL);
810 g_free (media_segment_template);
815 if (sink->use_segment_list) {
816 GST_INFO_OBJECT (sink, "Add segment URL: %s",
817 stream->current_segment_location);
818 gst_mpd_client_add_segment_url (sink->mpd_client, sink->current_period_id,
819 stream->adaptation_set_id, stream->representation_id, "media",
820 stream->current_segment_location, NULL);
822 if (!sink->is_dynamic) {
823 if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
824 gst_mpd_client_set_period_node (sink->mpd_client,
825 sink->current_period_id, "duration", sink->period_duration, NULL);
827 gst_mpd_client_set_period_node (sink->mpd_client,
828 sink->current_period_id, "duration",
829 gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
831 if (!sink->minimum_update_period) {
832 if (sink->period_duration != DEFAULT_MPD_PERIOD_DURATION)
833 gst_mpd_client_set_root_node (sink->mpd_client,
834 "media-presentation-duration", sink->period_duration, NULL);
836 gst_mpd_client_set_root_node (sink->mpd_client,
837 "media-presentation-duration",
838 gst_util_uint64_scale (sink->running_time, 1, GST_MSECOND), NULL);
844 gst_dash_sink_write_mpd_file (GstDashSink * sink,
845 GstDashSinkStream * current_stream)
847 char *mpd_content = NULL;
849 GError *error = NULL;
850 gchar *mpd_filepath = NULL;
851 GOutputStream *file_stream = NULL;
852 gsize bytes_to_write;
854 g_mutex_lock (&sink->mpd_lock);
855 gst_dash_sink_generate_mpd_content (sink, current_stream);
856 if (!gst_mpd_client_get_xml_content (sink->mpd_client, &mpd_content, &size))
858 g_mutex_unlock (&sink->mpd_lock);
860 if (sink->mpd_root_path)
862 g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
863 sink->mpd_filename, NULL);
865 mpd_filepath = g_strdup (sink->mpd_filename);
866 GST_DEBUG_OBJECT (sink, "a new mpd content is available: %s", mpd_content);
867 GST_DEBUG_OBJECT (sink, "write mpd to %s", mpd_filepath);
869 g_signal_emit (sink, signals[SIGNAL_GET_PLAYLIST_STREAM], 0, mpd_filepath,
872 GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
873 (("Got no output stream for fragment '%s'."), mpd_filepath), (NULL));
876 bytes_to_write = strlen (mpd_content);
877 if (!g_output_stream_write_all (file_stream, mpd_content, bytes_to_write,
878 NULL, NULL, &error)) {
879 GST_ERROR ("Failed to write mpd content: %s", error->message);
880 GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
881 (("Failed to write playlist '%s'."), error->message), (NULL));
882 g_error_free (error);
886 g_free (mpd_content);
887 g_free (mpd_filepath);
888 g_object_unref (file_stream);
892 gst_dash_sink_handle_message (GstBin * bin, GstMessage * message)
894 GstDashSink *sink = GST_DASH_SINK (bin);
895 GstDashSinkStream *stream = NULL;
896 switch (message->type) {
897 case GST_MESSAGE_ELEMENT:
899 const GstStructure *s = gst_message_get_structure (message);
900 GST_DEBUG_OBJECT (sink, "Received message with name %s",
901 gst_structure_get_name (s));
903 gst_dash_sink_stream_from_splitmuxsink (sink->streams,
904 GST_ELEMENT (message->src));
906 if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
907 gst_dash_sink_get_stream_metadata (sink, stream);
908 gst_structure_get_clock_time (s, "running-time",
909 &stream->current_running_time_start);
910 } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
911 GstClockTime running_time;
912 gst_structure_get_clock_time (s, "running-time", &running_time);
913 if (sink->running_time < running_time)
914 sink->running_time = running_time;
915 gst_dash_sink_write_mpd_file (sink, stream);
920 case GST_MESSAGE_EOS:{
921 gst_dash_sink_write_mpd_file (sink, NULL);
928 GST_BIN_CLASS (parent_class)->handle_message (bin, message);
931 static GstPadProbeReturn
932 _dash_sink_buffers_probe (GstPad * pad, GstPadProbeInfo * probe_info,
935 GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (probe_info);
936 GstDashSinkStream *stream = (GstDashSinkStream *) user_data;
938 if (GST_BUFFER_DURATION (buffer))
940 gst_buffer_get_size (buffer) * GST_SECOND /
941 GST_BUFFER_DURATION (buffer);
943 return GST_PAD_PROBE_OK;
947 gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
948 const gchar * pad_name, const GstCaps * caps)
950 GstDashSink *sink = GST_DASH_SINK (element);
951 GstDashSinkStream *stream = NULL;
954 const gchar *split_pad_name = pad_name;
956 stream = g_new0 (GstDashSinkStream, 1);
957 stream->sink = g_object_ref (sink);
958 if (g_str_has_prefix (templ->name_template, "video")) {
959 stream->type = DASH_SINK_STREAM_TYPE_VIDEO;
960 stream->adaptation_set_id = ADAPTATION_SET_ID_VIDEO;
961 split_pad_name = "video";
962 } else if (g_str_has_prefix (templ->name_template, "audio")) {
963 stream->type = DASH_SINK_STREAM_TYPE_AUDIO;
964 stream->adaptation_set_id = ADAPTATION_SET_ID_AUDIO;
965 } else if (g_str_has_prefix (templ->name_template, "subtitle")) {
966 stream->type = DASH_SINK_STREAM_TYPE_SUBTITLE;
967 stream->adaptation_set_id = ADAPTATION_SET_ID_SUBTITLE;
971 stream->representation_id = g_strdup (pad_name);
973 stream->representation_id =
974 gst_dash_sink_stream_get_next_name (sink->streams, stream->type);
977 stream->mimetype = g_strdup (dash_muxer_list[sink->muxer].mimetype);
980 if (!gst_dash_sink_add_splitmuxsink (sink, stream)) {
981 GST_ERROR_OBJECT (sink,
982 "Unable to create splitmuxsink element for pad template name %s",
983 templ->name_template);
984 gst_dash_sink_stream_free (stream);
988 peer = gst_element_request_pad_simple (stream->splitmuxsink, split_pad_name);
990 GST_ERROR_OBJECT (sink, "Unable to request pad name %s", split_pad_name);
994 pad = gst_ghost_pad_new_from_template (pad_name, peer, templ);
995 gst_pad_set_active (pad, TRUE);
996 gst_element_add_pad (element, pad);
997 gst_object_unref (peer);
1001 stream->buffer_probe = gst_pad_add_probe (stream->pad,
1002 GST_PAD_PROBE_TYPE_BUFFER, _dash_sink_buffers_probe, stream, NULL);
1004 sink->streams = g_list_append (sink->streams, stream);
1005 GST_DEBUG_OBJECT (sink, "Adding a new stream with id %s",
1006 stream->representation_id);
1013 gst_dash_sink_release_pad (GstElement * element, GstPad * pad)
1015 GstDashSink *sink = GST_DASH_SINK (element);
1017 GstDashSinkStream *stream =
1018 gst_dash_sink_stream_from_pad (sink->streams, pad);
1020 g_return_if_fail (stream != NULL);
1022 peer = gst_pad_get_peer (pad);
1024 gst_element_release_request_pad (stream->splitmuxsink, pad);
1025 gst_object_unref (peer);
1028 if (stream->buffer_probe > 0) {
1029 gst_pad_remove_probe (pad, stream->buffer_probe);
1030 stream->buffer_probe = 0;
1033 gst_object_ref (pad);
1034 gst_element_remove_pad (element, pad);
1035 gst_pad_set_active (pad, FALSE);
1039 gst_object_unref (pad);
1042 static GstStateChangeReturn
1043 gst_dash_sink_change_state (GstElement * element, GstStateChange trans)
1045 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1046 GstDashSink *sink = GST_DASH_SINK (element);
1049 case GST_STATE_CHANGE_NULL_TO_READY:
1050 if (!g_list_length (sink->streams)) {
1051 return GST_STATE_CHANGE_FAILURE;
1058 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
1061 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1063 case GST_STATE_CHANGE_PAUSED_TO_READY:
1064 case GST_STATE_CHANGE_READY_TO_NULL:
1065 gst_dash_sink_reset (sink);
1075 gst_dash_sink_set_property (GObject * object, guint prop_id,
1076 const GValue * value, GParamSpec * pspec)
1078 GstDashSink *sink = GST_DASH_SINK (object);
1081 case PROP_MPD_FILENAME:
1082 g_free (sink->mpd_filename);
1083 sink->mpd_filename = g_value_dup_string (value);
1085 case PROP_MPD_ROOT_PATH:
1086 g_free (sink->mpd_root_path);
1087 sink->mpd_root_path = g_value_dup_string (value);
1089 case PROP_MPD_BASEURL:
1090 g_free (sink->mpd_baseurl);
1091 sink->mpd_baseurl = g_value_dup_string (value);
1093 case PROP_TARGET_DURATION:
1094 sink->target_duration = g_value_get_uint (value);
1096 case PROP_SEND_KEYFRAME_REQUESTS:
1097 sink->send_keyframe_requests = g_value_get_boolean (value);
1099 case PROP_USE_SEGMENT_LIST:
1100 sink->use_segment_list = g_value_get_boolean (value);
1102 case PROP_MPD_DYNAMIC:
1103 sink->is_dynamic = g_value_get_boolean (value);
1106 sink->muxer = g_value_get_enum (value);
1108 case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1109 sink->minimum_update_period = g_value_get_uint64 (value);
1111 case PROP_MPD_MIN_BUFFER_TIME:
1112 sink->min_buffer_time = g_value_get_uint64 (value);
1114 case PROP_MPD_PERIOD_DURATION:
1115 sink->period_duration = g_value_get_uint64 (value);
1118 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1124 gst_dash_sink_get_property (GObject * object, guint prop_id,
1125 GValue * value, GParamSpec * pspec)
1127 GstDashSink *sink = GST_DASH_SINK (object);
1130 case PROP_MPD_FILENAME:
1131 g_value_set_string (value, sink->mpd_filename);
1133 case PROP_MPD_ROOT_PATH:
1134 g_value_set_string (value, sink->mpd_root_path);
1136 case PROP_MPD_BASEURL:
1137 g_value_set_string (value, sink->mpd_baseurl);
1139 case PROP_TARGET_DURATION:
1140 g_value_set_uint (value, sink->target_duration);
1142 case PROP_SEND_KEYFRAME_REQUESTS:
1143 g_value_set_boolean (value, sink->send_keyframe_requests);
1145 case PROP_USE_SEGMENT_LIST:
1146 g_value_set_boolean (value, sink->use_segment_list);
1148 case PROP_MPD_DYNAMIC:
1149 g_value_set_boolean (value, sink->is_dynamic);
1152 g_value_set_enum (value, sink->muxer);
1154 case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1155 g_value_set_uint64 (value, sink->minimum_update_period);
1157 case PROP_MPD_MIN_BUFFER_TIME:
1158 g_value_set_uint64 (value, sink->min_buffer_time);
1160 case PROP_MPD_PERIOD_DURATION:
1161 g_value_set_uint64 (value, sink->period_duration);
1164 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);