Merging gstreamer-sharp
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / dash / gstdashsink.c
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 max-files=5  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 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  *                                    ;  '------------------------------------'  ;
50  *                                    ;                                          ;
51  *                                    ;  ,----------splitmuxsink--------------,  ;
52  *    ,-audiotestsrc-,  ,-avenc_aac-, ;  ; ,-Queue-, ,-tsdemux-, ,-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  *
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/
73  *
74  * Limitations:
75  *
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.
79  *
80  */
81
82 #ifdef HAVE_CONFIG_H
83 #include "config.h"
84 #endif
85
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>
91 #include <gio/gio.h>
92 #include <memory.h>
93
94
95 GST_DEBUG_CATEGORY_STATIC (gst_dash_sink_debug);
96 #define GST_CAT_DEFAULT gst_dash_sink_debug
97
98 /**
99  * GstDashSinkMuxerType:
100  * @GST_DASH_SINK_MUXER_TS: Use mpegtsmux
101  * @GST_DASH_SINK_MUXER_MP4: Use mp4mux
102  *
103  * Muxer type
104  */
105 typedef enum
106 {
107   GST_DASH_SINK_MUXER_TS = 0,
108   GST_DASH_SINK_MUXER_MP4 = 1,
109 } GstDashSinkMuxerType;
110
111 typedef struct _DashSinkMuxer
112 {
113   GstDashSinkMuxerType type;
114   const gchar *element_name;
115   const gchar *mimetype;
116   const gchar *file_ext;
117 } DashSinkMuxer;
118
119 #define GST_TYPE_DASH_SINK_MUXER (gst_dash_sink_muxer_get_type())
120 static GType
121 gst_dash_sink_muxer_get_type (void)
122 {
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"},
127     {0, NULL, NULL},
128   };
129
130   if (!dash_sink_muxer_type) {
131     dash_sink_muxer_type =
132         g_enum_register_static ("GstDashSinkMuxerType", muxer_type);
133   }
134   return dash_sink_muxer_type;
135 }
136
137 static const DashSinkMuxer dash_muxer_list[] = {
138   {
139         GST_DASH_SINK_MUXER_TS,
140         "mpegtsmux",
141         "video/mp2t",
142       "ts"},
143   {
144         GST_DASH_SINK_MUXER_MP4,
145         "mp4mux",
146         "video/mp4",
147       "mp4"},
148 };
149
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
162
163 #define DEFAULT_DASH_SINK_MUXER GST_DASH_SINK_MUXER_TS
164
165 enum
166 {
167   ADAPTATION_SET_ID_VIDEO = 1,
168   ADAPTATION_SET_ID_AUDIO,
169   ADAPTATION_SET_ID_SUBTITLE,
170 };
171
172 enum
173 {
174   PROP_0,
175   PROP_MPD_FILENAME,
176   PROP_MPD_ROOT_PATH,
177   PROP_TARGET_DURATION,
178   PROP_SEND_KEYFRAME_REQUESTS,
179   PROP_USE_SEGMENT_LIST,
180   PROP_MPD_DYNAMIC,
181   PROP_MUXER,
182   PROP_MPD_MINIMUM_UPDATE_PERIOD,
183   PROP_MPD_MIN_BUFFER_TIME,
184   PROP_MPD_BASEURL,
185   PROP_MPD_PERIOD_DURATION,
186 };
187
188 enum
189 {
190   SIGNAL_GET_PLAYLIST_STREAM,
191   SIGNAL_GET_FRAGMENT_STREAM,
192   SIGNAL_LAST
193 };
194
195 static guint signals[SIGNAL_LAST];
196
197 typedef enum
198 {
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;
204
205 typedef struct _GstDashSinkStreamVideoInfo
206 {
207   gint width;
208   gint height;
209 } GstDashSinkStreamVideoInfo;
210
211 typedef struct _GstDashSinkStreamAudioInfo
212 {
213   gint channels;
214   gint rate;
215 } GstDashSinkStreamAudioInfo;
216
217 typedef struct GstDashSinkStreamSubtitleInfo
218 {
219   gchar *codec;
220 } GstDashSinkStreamSubtitleInfo;
221
222 typedef union _GstDashSinkStreamInfo
223 {
224   GstDashSinkStreamVideoInfo video;
225   GstDashSinkStreamAudioInfo audio;
226   GstDashSinkStreamSubtitleInfo subtitle;
227 } GstDashSinkStreamInfo;
228
229 struct _GstDashSink
230 {
231   GstBin bin;
232   GMutex mpd_lock;
233   gchar *location;
234   gchar *mpd_filename;
235   gchar *mpd_root_path;
236   gchar *mpd_profiles;
237   gchar *mpd_baseurl;
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;
245   gboolean is_dynamic;
246   gchar *segment_file_tpl;
247   guint index;
248   GList *streams;
249   guint64 minimum_update_period;
250   guint64 min_buffer_time;
251   gint64 period_duration;
252 };
253
254 typedef struct _GstDashSinkStream
255 {
256   GstDashSink *sink;
257   GstDashSinkStreamType type;
258   GstPad *pad;
259   gint buffer_probe;
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;
266   gchar *mimetype;
267   gint bitrate;
268   gchar *codec;
269   GstClockTime current_running_time_start;
270   GstDashSinkStreamInfo info;
271   GstElement *giostreamsink;
272 } GstDashSinkStream;
273
274 static GstStaticPadTemplate video_sink_template =
275 GST_STATIC_PAD_TEMPLATE ("video_%u",
276     GST_PAD_SINK,
277     GST_PAD_REQUEST,
278     GST_STATIC_CAPS_ANY);
279
280
281 static GstStaticPadTemplate audio_sink_template =
282 GST_STATIC_PAD_TEMPLATE ("audio_%u",
283     GST_PAD_SINK,
284     GST_PAD_REQUEST,
285     GST_STATIC_CAPS_ANY);
286 static GstStaticPadTemplate subtitle_sink_template =
287 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
288     GST_PAD_SINK,
289     GST_PAD_REQUEST,
290     GST_STATIC_CAPS_ANY);
291
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 ());
297
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);
309
310
311 static GstDashSinkStream *
312 gst_dash_sink_stream_from_pad (GList * streams, GstPad * pad)
313 {
314   GList *l;
315   GstDashSinkStream *stream = NULL;
316   for (l = streams; l != NULL; l = l->next) {
317     stream = l->data;
318     if (stream->pad == pad)
319       return stream;
320   }
321   return NULL;
322 }
323
324 static GstDashSinkStream *
325 gst_dash_sink_stream_from_splitmuxsink (GList * streams, GstElement * element)
326 {
327   GList *l;
328   GstDashSinkStream *stream = NULL;
329   for (l = streams; l != NULL; l = l->next) {
330     stream = l->data;
331     if (stream->splitmuxsink == element)
332       return stream;
333   }
334   return NULL;
335 }
336
337 static gchar *
338 gst_dash_sink_stream_get_next_name (GList * streams, GstDashSinkStreamType type)
339 {
340   GList *l;
341   guint count = 0;
342   GstDashSinkStream *stream = NULL;
343   gchar *name = NULL;
344
345   for (l = streams; l != NULL; l = l->next) {
346     stream = l->data;
347     if (stream->type == type)
348       count++;
349   }
350
351   switch (type) {
352     case DASH_SINK_STREAM_TYPE_VIDEO:
353       name = g_strdup_printf ("video_%d", count);
354       break;
355     case DASH_SINK_STREAM_TYPE_AUDIO:
356       name = g_strdup_printf ("audio_%d", count);
357       break;
358     case DASH_SINK_STREAM_TYPE_SUBTITLE:
359       name = g_strdup_printf ("sub_%d", count);
360       break;
361     default:
362       name = g_strdup_printf ("unknown_%d", count);
363   }
364
365   return name;
366 }
367
368 static void
369 gst_dash_sink_stream_free (gpointer s)
370 {
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);
377
378   g_free (stream);
379 }
380
381 static void
382 gst_dash_sink_dispose (GObject * object)
383 {
384   GstDashSink *sink = GST_DASH_SINK (object);
385
386   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
387 }
388
389 static void
390 gst_dash_sink_finalize (GObject * object)
391 {
392   GstDashSink *sink = GST_DASH_SINK (object);
393
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);
400
401   g_list_free_full (sink->streams, gst_dash_sink_stream_free);
402
403   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
404 }
405
406 /* Default implementations for the signal handlers */
407 static GOutputStream *
408 gst_dash_sink_get_playlist_stream (GstDashSink * sink, const gchar * location)
409 {
410   GFile *file = g_file_new_for_path (location);
411   GOutputStream *ostream;
412   GError *err = NULL;
413
414   ostream =
415       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
416           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
417   if (!ostream) {
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);
422   }
423
424   g_object_unref (file);
425
426   return ostream;
427 }
428
429 static GOutputStream *
430 gst_dash_sink_get_fragment_stream (GstDashSink * sink, const gchar * location)
431 {
432   GFile *file = g_file_new_for_path (location);
433   GOutputStream *ostream;
434   GError *err = NULL;
435
436   ostream =
437       G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
438           G_FILE_CREATE_REPLACE_DESTINATION, NULL, &err));
439   if (!ostream) {
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);
444   }
445
446   g_object_unref (file);
447
448   return ostream;
449 }
450
451 static void
452 gst_dash_sink_class_init (GstDashSinkClass * klass)
453 {
454   GObjectClass *gobject_class;
455   GstElementClass *element_class;
456   GstBinClass *bin_class;
457
458   gobject_class = (GObjectClass *) klass;
459   element_class = GST_ELEMENT_CLASS (klass);
460   bin_class = GST_BIN_CLASS (klass);
461
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);
468
469   gst_element_class_set_static_metadata (element_class,
470       "DASH Sink", "Sink",
471       "Dynamic Adaptive Streaming over HTTP sink",
472       "Stéphane Cerveau <scerveau@collabora.com>");
473
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);
478
479   bin_class->handle_message = gst_dash_sink_handle_message;
480
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;
485
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));
540
541   /**
542    * GstDashSink::get-playlist-stream:
543    * @sink: the #GstDashSink
544    * @location: Location for the playlist file
545    *
546    * Returns: #GOutputStream for writing the playlist file.
547    *
548    * Since: 1.20
549    */
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);
555
556   /**
557    * GstDashSink::get-fragment-stream:
558    * @sink: the #GstDashSink
559    * @location: Location for the fragment file
560    *
561    * Returns: #GOutputStream for writing the fragment file.
562    *
563    * Since: 1.20
564    */
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);
570
571   gst_type_mark_as_plugin_api (GST_TYPE_DASH_SINK_MUXER, 0);
572 }
573
574 static gchar *
575 on_format_location (GstElement * splitmuxsink, guint fragment_id,
576     GstDashSinkStream * dash_stream)
577 {
578   GOutputStream *stream = NULL;
579   GstDashSink *sink = dash_stream->sink;
580   gchar *segment_tpl_path;
581
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);
589   else {
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);
594   }
595   dash_stream->next_segment_id++;
596
597   if (sink->mpd_root_path)
598     segment_tpl_path =
599         g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
600         dash_stream->current_segment_location, NULL);
601   else
602     segment_tpl_path = g_strdup (dash_stream->current_segment_location);
603
604
605   g_signal_emit (sink, signals[SIGNAL_GET_FRAGMENT_STREAM], 0, segment_tpl_path,
606       &stream);
607
608   if (!stream)
609     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
610         (("Got no output stream for fragment '%s'."), segment_tpl_path),
611         (NULL));
612   else
613     g_object_set (dash_stream->giostreamsink, "stream", stream, NULL);
614
615   if (stream)
616     g_object_unref (stream);
617
618   g_free (segment_tpl_path);
619
620   return NULL;
621 }
622
623 static gboolean
624 gst_dash_sink_add_splitmuxsink (GstDashSink * sink, GstDashSinkStream * stream)
625 {
626   GstElement *mux =
627       gst_element_factory_make (dash_muxer_list[sink->muxer].element_name,
628       NULL);
629
630   if (sink->muxer == GST_DASH_SINK_MUXER_MP4)
631     g_object_set (mux, "fragment-duration", sink->target_duration * GST_MSECOND,
632         NULL);
633
634   g_return_val_if_fail (mux != NULL, FALSE);
635
636   stream->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
637   if (!stream->splitmuxsink) {
638     gst_object_unref (mux);
639     return FALSE;
640   }
641   stream->giostreamsink = gst_element_factory_make ("giostreamsink", NULL);
642   if (!stream->giostreamsink) {
643     gst_object_unref (stream->splitmuxsink);
644     gst_object_unref (mux);
645     return FALSE;
646   }
647
648   gst_bin_add (GST_BIN (sink), stream->splitmuxsink);
649
650   if (!sink->use_segment_list)
651     stream->current_segment_id = 1;
652   else
653     stream->current_segment_id = 0;
654   stream->next_segment_id = stream->current_segment_id;
655
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);
661
662   g_signal_connect (stream->splitmuxsink, "format-location",
663       G_CALLBACK (on_format_location), stream);
664
665   return TRUE;
666 }
667
668 static void
669 gst_dash_sink_init (GstDashSink * sink)
670 {
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;
674
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;
679
680   sink->min_buffer_time = DEFAULT_MPD_MIN_BUFFER_TIME;
681   sink->period_duration = DEFAULT_MPD_PERIOD_DURATION;
682
683   g_mutex_init (&sink->mpd_lock);
684
685   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
686
687   gst_dash_sink_reset (sink);
688 }
689
690 static void
691 gst_dash_sink_reset (GstDashSink * sink)
692 {
693   sink->index = 0;
694 }
695
696 static void
697 gst_dash_sink_get_stream_metadata (GstDashSink * sink,
698     GstDashSinkStream * stream)
699 {
700   GstStructure *s;
701   GstCaps *caps = gst_pad_get_current_caps (stream->pad);
702
703   GST_DEBUG_OBJECT (sink, "stream caps %s", gst_caps_to_string (caps));
704   s = gst_caps_get_structure (caps, 0);
705
706   switch (stream->type) {
707     case DASH_SINK_STREAM_TYPE_VIDEO:
708     {
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);
712       stream->codec =
713           g_strdup (gst_mpd_helper_get_video_codec_from_mime (caps));
714       break;
715     }
716     case DASH_SINK_STREAM_TYPE_AUDIO:
717     {
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);
721       stream->codec =
722           g_strdup (gst_mpd_helper_get_audio_codec_from_mime (caps));
723       break;
724     }
725     case DASH_SINK_STREAM_TYPE_SUBTITLE:
726     {
727       break;
728     }
729     default:
730       break;
731   }
732
733   gst_caps_unref (caps);
734 }
735
736 static void
737 gst_dash_sink_generate_mpd_content (GstDashSink * sink,
738     GstDashSinkStream * stream)
739 {
740   if (!sink->mpd_client) {
741     GList *l;
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);
754     }
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
763      * */
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
771        * */
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",
783             "video", NULL);
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",
791             "audio", NULL);
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);
796       }
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);
802       } else {
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);
811       }
812     }
813   }
814   /* MPD updates */
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);
821   } else {
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);
826       else
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);
830     }
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);
835       else
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);
839     }
840   }
841 }
842
843 static void
844 gst_dash_sink_write_mpd_file (GstDashSink * sink,
845     GstDashSinkStream * current_stream)
846 {
847   char *mpd_content = NULL;
848   gint size;
849   GError *error = NULL;
850   gchar *mpd_filepath = NULL;
851   GOutputStream *file_stream = NULL;
852   gsize bytes_to_write;
853
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))
857     return;
858   g_mutex_unlock (&sink->mpd_lock);
859
860   if (sink->mpd_root_path)
861     mpd_filepath =
862         g_build_path (G_DIR_SEPARATOR_S, sink->mpd_root_path,
863         sink->mpd_filename, NULL);
864   else
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);
868
869   g_signal_emit (sink, signals[SIGNAL_GET_PLAYLIST_STREAM], 0, mpd_filepath,
870       &file_stream);
871   if (!file_stream) {
872     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
873         (("Got no output stream for fragment '%s'."), mpd_filepath), (NULL));
874   }
875
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);
883     error = NULL;
884   }
885
886   g_free (mpd_content);
887   g_free (mpd_filepath);
888   g_object_unref (file_stream);
889 }
890
891 static void
892 gst_dash_sink_handle_message (GstBin * bin, GstMessage * message)
893 {
894   GstDashSink *sink = GST_DASH_SINK (bin);
895   GstDashSinkStream *stream = NULL;
896   switch (message->type) {
897     case GST_MESSAGE_ELEMENT:
898     {
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));
902       stream =
903           gst_dash_sink_stream_from_splitmuxsink (sink->streams,
904           GST_ELEMENT (message->src));
905       if (stream) {
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);
916         }
917       }
918       break;
919     }
920     case GST_MESSAGE_EOS:{
921       gst_dash_sink_write_mpd_file (sink, NULL);
922       break;
923     }
924     default:
925       break;
926   }
927
928   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
929 }
930
931 static GstPadProbeReturn
932 _dash_sink_buffers_probe (GstPad * pad, GstPadProbeInfo * probe_info,
933     gpointer user_data)
934 {
935   GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (probe_info);
936   GstDashSinkStream *stream = (GstDashSinkStream *) user_data;
937
938   if (GST_BUFFER_DURATION (buffer))
939     stream->bitrate =
940         gst_buffer_get_size (buffer) * GST_SECOND /
941         GST_BUFFER_DURATION (buffer);
942
943   return GST_PAD_PROBE_OK;
944 }
945
946 static GstPad *
947 gst_dash_sink_request_new_pad (GstElement * element, GstPadTemplate * templ,
948     const gchar * pad_name, const GstCaps * caps)
949 {
950   GstDashSink *sink = GST_DASH_SINK (element);
951   GstDashSinkStream *stream = NULL;
952   GstPad *pad = NULL;
953   GstPad *peer = NULL;
954   const gchar *split_pad_name = pad_name;
955
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;
968   }
969
970   if (pad_name)
971     stream->representation_id = g_strdup (pad_name);
972   else
973     stream->representation_id =
974         gst_dash_sink_stream_get_next_name (sink->streams, stream->type);
975
976
977   stream->mimetype = g_strdup (dash_muxer_list[sink->muxer].mimetype);
978
979
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);
985     goto done;
986   }
987
988   peer = gst_element_request_pad_simple (stream->splitmuxsink, split_pad_name);
989   if (!peer) {
990     GST_ERROR_OBJECT (sink, "Unable to request pad name %s", split_pad_name);
991     return NULL;
992   }
993
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);
998
999   stream->pad = pad;
1000
1001   stream->buffer_probe = gst_pad_add_probe (stream->pad,
1002       GST_PAD_PROBE_TYPE_BUFFER, _dash_sink_buffers_probe, stream, NULL);
1003
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);
1007
1008 done:
1009   return pad;
1010 }
1011
1012 static void
1013 gst_dash_sink_release_pad (GstElement * element, GstPad * pad)
1014 {
1015   GstDashSink *sink = GST_DASH_SINK (element);
1016   GstPad *peer;
1017   GstDashSinkStream *stream =
1018       gst_dash_sink_stream_from_pad (sink->streams, pad);
1019
1020   g_return_if_fail (stream != NULL);
1021
1022   peer = gst_pad_get_peer (pad);
1023   if (peer) {
1024     gst_element_release_request_pad (stream->splitmuxsink, pad);
1025     gst_object_unref (peer);
1026   }
1027
1028   if (stream->buffer_probe > 0) {
1029     gst_pad_remove_probe (pad, stream->buffer_probe);
1030     stream->buffer_probe = 0;
1031   }
1032
1033   gst_object_ref (pad);
1034   gst_element_remove_pad (element, pad);
1035   gst_pad_set_active (pad, FALSE);
1036
1037   stream->pad = NULL;
1038
1039   gst_object_unref (pad);
1040 }
1041
1042 static GstStateChangeReturn
1043 gst_dash_sink_change_state (GstElement * element, GstStateChange trans)
1044 {
1045   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1046   GstDashSink *sink = GST_DASH_SINK (element);
1047
1048   switch (trans) {
1049     case GST_STATE_CHANGE_NULL_TO_READY:
1050       if (!g_list_length (sink->streams)) {
1051         return GST_STATE_CHANGE_FAILURE;
1052       }
1053       break;
1054     default:
1055       break;
1056   }
1057
1058   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
1059
1060   switch (trans) {
1061     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1062       break;
1063     case GST_STATE_CHANGE_PAUSED_TO_READY:
1064     case GST_STATE_CHANGE_READY_TO_NULL:
1065       gst_dash_sink_reset (sink);
1066       break;
1067     default:
1068       break;
1069   }
1070
1071   return ret;
1072 }
1073
1074 static void
1075 gst_dash_sink_set_property (GObject * object, guint prop_id,
1076     const GValue * value, GParamSpec * pspec)
1077 {
1078   GstDashSink *sink = GST_DASH_SINK (object);
1079
1080   switch (prop_id) {
1081     case PROP_MPD_FILENAME:
1082       g_free (sink->mpd_filename);
1083       sink->mpd_filename = g_value_dup_string (value);
1084       break;
1085     case PROP_MPD_ROOT_PATH:
1086       g_free (sink->mpd_root_path);
1087       sink->mpd_root_path = g_value_dup_string (value);
1088       break;
1089     case PROP_MPD_BASEURL:
1090       g_free (sink->mpd_baseurl);
1091       sink->mpd_baseurl = g_value_dup_string (value);
1092       break;
1093     case PROP_TARGET_DURATION:
1094       sink->target_duration = g_value_get_uint (value);
1095       break;
1096     case PROP_SEND_KEYFRAME_REQUESTS:
1097       sink->send_keyframe_requests = g_value_get_boolean (value);
1098       break;
1099     case PROP_USE_SEGMENT_LIST:
1100       sink->use_segment_list = g_value_get_boolean (value);
1101       break;
1102     case PROP_MPD_DYNAMIC:
1103       sink->is_dynamic = g_value_get_boolean (value);
1104       break;
1105     case PROP_MUXER:
1106       sink->muxer = g_value_get_enum (value);
1107       break;
1108     case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1109       sink->minimum_update_period = g_value_get_uint64 (value);
1110       break;
1111     case PROP_MPD_MIN_BUFFER_TIME:
1112       sink->min_buffer_time = g_value_get_uint64 (value);
1113       break;
1114     case PROP_MPD_PERIOD_DURATION:
1115       sink->period_duration = g_value_get_uint64 (value);
1116       break;
1117     default:
1118       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1119       break;
1120   }
1121 }
1122
1123 static void
1124 gst_dash_sink_get_property (GObject * object, guint prop_id,
1125     GValue * value, GParamSpec * pspec)
1126 {
1127   GstDashSink *sink = GST_DASH_SINK (object);
1128
1129   switch (prop_id) {
1130     case PROP_MPD_FILENAME:
1131       g_value_set_string (value, sink->mpd_filename);
1132       break;
1133     case PROP_MPD_ROOT_PATH:
1134       g_value_set_string (value, sink->mpd_root_path);
1135       break;
1136     case PROP_MPD_BASEURL:
1137       g_value_set_string (value, sink->mpd_baseurl);
1138       break;
1139     case PROP_TARGET_DURATION:
1140       g_value_set_uint (value, sink->target_duration);
1141       break;
1142     case PROP_SEND_KEYFRAME_REQUESTS:
1143       g_value_set_boolean (value, sink->send_keyframe_requests);
1144       break;
1145     case PROP_USE_SEGMENT_LIST:
1146       g_value_set_boolean (value, sink->use_segment_list);
1147       break;
1148     case PROP_MPD_DYNAMIC:
1149       g_value_set_boolean (value, sink->is_dynamic);
1150       break;
1151     case PROP_MUXER:
1152       g_value_set_enum (value, sink->muxer);
1153       break;
1154     case PROP_MPD_MINIMUM_UPDATE_PERIOD:
1155       g_value_set_uint64 (value, sink->minimum_update_period);
1156       break;
1157     case PROP_MPD_MIN_BUFFER_TIME:
1158       g_value_set_uint64 (value, sink->min_buffer_time);
1159       break;
1160     case PROP_MPD_PERIOD_DURATION:
1161       g_value_set_uint64 (value, sink->period_duration);
1162       break;
1163     default:
1164       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1165       break;
1166   }
1167 }