1 /* GStreamer
2  * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.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-hlssink
22  * @title: hlssink
23  *
24  * HTTP Live Streaming sink/server
25  *
26  * ## Example launch line
27  * |[
28  * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! mpegtsmux ! hlssink max-files=5
29  * ]|
30  *
31  */
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include "gsthlssink.h"
37 #include <gst/pbutils/pbutils.h>
38 #include <gst/video/video.h>
39 #include <glib/gstdio.h>
40 #include <memory.h>
41 
42 
43 GST_DEBUG_CATEGORY_STATIC (gst_hls_sink_debug);
44 #define GST_CAT_DEFAULT gst_hls_sink_debug
45 
46 #define DEFAULT_LOCATION "segment%05d.ts"
47 #define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
48 #define DEFAULT_PLAYLIST_ROOT NULL
49 #define DEFAULT_MAX_FILES 10
50 #define DEFAULT_TARGET_DURATION 15
51 #define DEFAULT_PLAYLIST_LENGTH 5
52 
53 #define GST_M3U8_PLAYLIST_VERSION 3
54 
55 enum
56 {
57   PROP_0,
58   PROP_LOCATION,
59   PROP_PLAYLIST_LOCATION,
60   PROP_PLAYLIST_ROOT,
61   PROP_MAX_FILES,
62   PROP_TARGET_DURATION,
63   PROP_PLAYLIST_LENGTH
64 };
65 
66 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
67     GST_PAD_SINK,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS_ANY);
70 
71 #define gst_hls_sink_parent_class parent_class
72 G_DEFINE_TYPE (GstHlsSink, gst_hls_sink, GST_TYPE_BIN);
73 
74 static void gst_hls_sink_set_property (GObject * object, guint prop_id,
75     const GValue * value, GParamSpec * spec);
76 static void gst_hls_sink_get_property (GObject * object, guint prop_id,
77     GValue * value, GParamSpec * spec);
78 static void gst_hls_sink_handle_message (GstBin * bin, GstMessage * message);
79 static GstPadProbeReturn gst_hls_sink_ghost_event_probe (GstPad * pad,
80     GstPadProbeInfo * info, gpointer data);
81 static GstPadProbeReturn gst_hls_sink_ghost_buffer_probe (GstPad * pad,
82     GstPadProbeInfo * info, gpointer data);
83 static void gst_hls_sink_reset (GstHlsSink * sink);
84 static GstStateChangeReturn
85 gst_hls_sink_change_state (GstElement * element, GstStateChange trans);
86 static gboolean schedule_next_key_unit (GstHlsSink * sink);
87 static GstFlowReturn gst_hls_sink_chain_list (GstPad * pad, GstObject * parent,
88     GstBufferList * list);
89 
90 static void
gst_hls_sink_dispose(GObject * object)91 gst_hls_sink_dispose (GObject * object)
92 {
93   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
94 
95   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
96 }
97 
98 static void
gst_hls_sink_finalize(GObject * object)99 gst_hls_sink_finalize (GObject * object)
100 {
101   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
102 
103   g_free (sink->location);
104   g_free (sink->playlist_location);
105   g_free (sink->playlist_root);
106   if (sink->playlist)
107     gst_m3u8_playlist_free (sink->playlist);
108 
109   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
110 }
111 
112 static void
gst_hls_sink_class_init(GstHlsSinkClass * klass)113 gst_hls_sink_class_init (GstHlsSinkClass * klass)
114 {
115   GObjectClass *gobject_class;
116   GstElementClass *element_class;
117   GstBinClass *bin_class;
118 
119   gobject_class = (GObjectClass *) klass;
120   element_class = GST_ELEMENT_CLASS (klass);
121   bin_class = GST_BIN_CLASS (klass);
122 
123   gst_element_class_add_static_pad_template (element_class, &sink_template);
124 
125   gst_element_class_set_static_metadata (element_class,
126       "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink",
127       "Alessandro Decina <alessandro.d@gmail.com>");
128 
129   element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink_change_state);
130 
131   bin_class->handle_message = gst_hls_sink_handle_message;
132 
133   gobject_class->dispose = gst_hls_sink_dispose;
134   gobject_class->finalize = gst_hls_sink_finalize;
135   gobject_class->set_property = gst_hls_sink_set_property;
136   gobject_class->get_property = gst_hls_sink_get_property;
137 
138   g_object_class_install_property (gobject_class, PROP_LOCATION,
139       g_param_spec_string ("location", "File Location",
140           "Location of the file to write", DEFAULT_LOCATION,
141           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
142   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
143       g_param_spec_string ("playlist-location", "Playlist Location",
144           "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
145           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
146   g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
147       g_param_spec_string ("playlist-root", "Playlist Root",
148           "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
149           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
150   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
151       g_param_spec_uint ("max-files", "Max files",
152           "Maximum number of files to keep on disk. Once the maximum is reached,"
153           "old files start to be deleted to make room for new ones.",
154           0, G_MAXUINT, DEFAULT_MAX_FILES,
155           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
156   g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
157       g_param_spec_uint ("target-duration", "Target duration",
158           "The target duration in seconds of a segment/file. "
159           "(0 - disabled, useful for management of segment duration by the "
160           "streaming server)",
161           0, G_MAXUINT, DEFAULT_TARGET_DURATION,
162           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
163   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH,
164       g_param_spec_uint ("playlist-length", "Playlist length",
165           "Length of HLS playlist. To allow players to conform to section 6.3.3 "
166           "of the HLS specification, this should be at least 3. If set to 0, "
167           "the playlist will be infinite.",
168           0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH,
169           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
170 }
171 
172 static void
gst_hls_sink_init(GstHlsSink * sink)173 gst_hls_sink_init (GstHlsSink * sink)
174 {
175   GstPadTemplate *templ = gst_static_pad_template_get (&sink_template);
176   sink->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
177   gst_object_unref (templ);
178   gst_element_add_pad (GST_ELEMENT_CAST (sink), sink->ghostpad);
179   gst_pad_add_probe (sink->ghostpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
180       gst_hls_sink_ghost_event_probe, sink, NULL);
181   gst_pad_add_probe (sink->ghostpad, GST_PAD_PROBE_TYPE_BUFFER,
182       gst_hls_sink_ghost_buffer_probe, sink, NULL);
183   gst_pad_set_chain_list_function (sink->ghostpad, gst_hls_sink_chain_list);
184 
185   sink->location = g_strdup (DEFAULT_LOCATION);
186   sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
187   sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
188   sink->playlist_length = DEFAULT_PLAYLIST_LENGTH;
189   sink->max_files = DEFAULT_MAX_FILES;
190   sink->target_duration = DEFAULT_TARGET_DURATION;
191 
192   /* haven't added a sink yet, make it is detected as a sink meanwhile */
193   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
194 
195   gst_hls_sink_reset (sink);
196 }
197 
198 static void
gst_hls_sink_reset(GstHlsSink * sink)199 gst_hls_sink_reset (GstHlsSink * sink)
200 {
201   sink->index = 0;
202   sink->last_running_time = 0;
203   sink->waiting_fku = FALSE;
204   gst_event_replace (&sink->force_key_unit_event, NULL);
205   gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED);
206 
207   if (sink->playlist)
208     gst_m3u8_playlist_free (sink->playlist);
209   sink->playlist =
210       gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length,
211       FALSE);
212 }
213 
214 static gboolean
gst_hls_sink_create_elements(GstHlsSink * sink)215 gst_hls_sink_create_elements (GstHlsSink * sink)
216 {
217   GstPad *pad = NULL;
218 
219   GST_DEBUG_OBJECT (sink, "Creating internal elements");
220 
221   if (sink->elements_created)
222     return TRUE;
223 
224   sink->multifilesink = gst_element_factory_make ("multifilesink", NULL);
225   if (sink->multifilesink == NULL)
226     goto missing_element;
227 
228   g_object_set (sink->multifilesink, "location", sink->location,
229       "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
230       NULL);
231 
232   gst_bin_add (GST_BIN_CAST (sink), sink->multifilesink);
233 
234   pad = gst_element_get_static_pad (sink->multifilesink, "sink");
235   gst_ghost_pad_set_target (GST_GHOST_PAD (sink->ghostpad), pad);
236   gst_object_unref (pad);
237 
238   sink->elements_created = TRUE;
239   return TRUE;
240 
241 missing_element:
242   gst_element_post_message (GST_ELEMENT_CAST (sink),
243       gst_missing_element_message_new (GST_ELEMENT_CAST (sink),
244           "multifilesink"));
245   GST_ELEMENT_ERROR (sink, CORE, MISSING_PLUGIN,
246       (("Missing element '%s' - check your GStreamer installation."),
247           "multifilesink"), (NULL));
248   return FALSE;
249 }
250 
251 static void
gst_hls_sink_write_playlist(GstHlsSink * sink)252 gst_hls_sink_write_playlist (GstHlsSink * sink)
253 {
254   char *playlist_content;
255   GError *error = NULL;
256 
257   playlist_content = gst_m3u8_playlist_render (sink->playlist);
258   if (!g_file_set_contents (sink->playlist_location,
259           playlist_content, -1, &error)) {
260     GST_ERROR ("Failed to write playlist: %s", error->message);
261     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
262         (("Failed to write playlist '%s'."), error->message), (NULL));
263     g_error_free (error);
264     error = NULL;
265   }
266   g_free (playlist_content);
267 
268 }
269 
270 static void
gst_hls_sink_handle_message(GstBin * bin,GstMessage * message)271 gst_hls_sink_handle_message (GstBin * bin, GstMessage * message)
272 {
273   GstHlsSink *sink = GST_HLS_SINK_CAST (bin);
274 
275   switch (message->type) {
276     case GST_MESSAGE_ELEMENT:
277     {
278       const char *filename;
279       GstClockTime running_time, duration;
280       gboolean discont = FALSE;
281       gchar *entry_location;
282       const GstStructure *structure;
283 
284       structure = gst_message_get_structure (message);
285       if (strcmp (gst_structure_get_name (structure), "GstMultiFileSink"))
286         break;
287 
288       filename = gst_structure_get_string (structure, "filename");
289       gst_structure_get_clock_time (structure, "running-time", &running_time);
290       duration = running_time - sink->last_running_time;
291       sink->last_running_time = running_time;
292 
293       GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
294       if (sink->playlist_root == NULL)
295         entry_location = g_path_get_basename (filename);
296       else {
297         gchar *name = g_path_get_basename (filename);
298         entry_location = g_build_filename (sink->playlist_root, name, NULL);
299         g_free (name);
300       }
301 
302       gst_m3u8_playlist_add_entry (sink->playlist, entry_location,
303           NULL, duration, sink->index, discont);
304       g_free (entry_location);
305 
306       gst_hls_sink_write_playlist (sink);
307 
308       /* multifilesink is starting a new file. It means that upstream sent a key
309        * unit and we can schedule the next key unit now.
310        */
311       sink->waiting_fku = FALSE;
312       schedule_next_key_unit (sink);
313 
314       /* multifilesink is an internal implementation detail. If applications
315        * need a notification, we should probably do our own message */
316       GST_DEBUG_OBJECT (bin, "dropping message %" GST_PTR_FORMAT, message);
317       gst_message_unref (message);
318       message = NULL;
319       break;
320     }
321     case GST_MESSAGE_EOS:{
322       sink->playlist->end_list = TRUE;
323       gst_hls_sink_write_playlist (sink);
324       break;
325     }
326     default:
327       break;
328   }
329 
330   if (message)
331     GST_BIN_CLASS (parent_class)->handle_message (bin, message);
332 }
333 
334 static GstStateChangeReturn
gst_hls_sink_change_state(GstElement * element,GstStateChange trans)335 gst_hls_sink_change_state (GstElement * element, GstStateChange trans)
336 {
337   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
338   GstHlsSink *sink = GST_HLS_SINK_CAST (element);
339 
340   switch (trans) {
341     case GST_STATE_CHANGE_NULL_TO_READY:
342       if (!gst_hls_sink_create_elements (sink)) {
343         return GST_STATE_CHANGE_FAILURE;
344       }
345       break;
346     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
347       break;
348     default:
349       break;
350   }
351 
352   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
353 
354   switch (trans) {
355     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
356       break;
357     case GST_STATE_CHANGE_PAUSED_TO_READY:
358       gst_hls_sink_reset (sink);
359       break;
360     case GST_STATE_CHANGE_READY_TO_NULL:
361       gst_hls_sink_reset (sink);
362       break;
363     default:
364       break;
365   }
366 
367   return ret;
368 }
369 
370 static void
gst_hls_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)371 gst_hls_sink_set_property (GObject * object, guint prop_id,
372     const GValue * value, GParamSpec * pspec)
373 {
374   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
375 
376   switch (prop_id) {
377     case PROP_LOCATION:
378       g_free (sink->location);
379       sink->location = g_value_dup_string (value);
380       if (sink->multifilesink)
381         g_object_set (sink->multifilesink, "location", sink->location, NULL);
382       break;
383     case PROP_PLAYLIST_LOCATION:
384       g_free (sink->playlist_location);
385       sink->playlist_location = g_value_dup_string (value);
386       break;
387     case PROP_PLAYLIST_ROOT:
388       g_free (sink->playlist_root);
389       sink->playlist_root = g_value_dup_string (value);
390       break;
391     case PROP_MAX_FILES:
392       sink->max_files = g_value_get_uint (value);
393       if (sink->multifilesink) {
394         g_object_set (sink->multifilesink, "location", sink->location,
395             "next-file", 3, "post-messages", TRUE, "max-files", sink->max_files,
396             NULL);
397       }
398       break;
399     case PROP_TARGET_DURATION:
400       sink->target_duration = g_value_get_uint (value);
401       break;
402     case PROP_PLAYLIST_LENGTH:
403       sink->playlist_length = g_value_get_uint (value);
404       sink->playlist->window_size = sink->playlist_length;
405       break;
406     default:
407       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
408       break;
409   }
410 }
411 
412 static void
gst_hls_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)413 gst_hls_sink_get_property (GObject * object, guint prop_id,
414     GValue * value, GParamSpec * pspec)
415 {
416   GstHlsSink *sink = GST_HLS_SINK_CAST (object);
417 
418   switch (prop_id) {
419     case PROP_LOCATION:
420       g_value_set_string (value, sink->location);
421       break;
422     case PROP_PLAYLIST_LOCATION:
423       g_value_set_string (value, sink->playlist_location);
424       break;
425     case PROP_PLAYLIST_ROOT:
426       g_value_set_string (value, sink->playlist_root);
427       break;
428     case PROP_MAX_FILES:
429       g_value_set_uint (value, sink->max_files);
430       break;
431     case PROP_TARGET_DURATION:
432       g_value_set_uint (value, sink->target_duration);
433       break;
434     case PROP_PLAYLIST_LENGTH:
435       g_value_set_uint (value, sink->playlist_length);
436       break;
437     default:
438       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
439       break;
440   }
441 }
442 
443 static GstPadProbeReturn
gst_hls_sink_ghost_event_probe(GstPad * pad,GstPadProbeInfo * info,gpointer data)444 gst_hls_sink_ghost_event_probe (GstPad * pad, GstPadProbeInfo * info,
445     gpointer data)
446 {
447   GstHlsSink *sink = GST_HLS_SINK_CAST (data);
448   GstEvent *event = gst_pad_probe_info_get_event (info);
449 
450   switch (GST_EVENT_TYPE (event)) {
451     case GST_EVENT_SEGMENT:
452     {
453       gst_event_copy_segment (event, &sink->segment);
454       break;
455     }
456     case GST_EVENT_FLUSH_STOP:
457       gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED);
458       break;
459     case GST_EVENT_CUSTOM_DOWNSTREAM:
460     {
461       GstClockTime timestamp;
462       GstClockTime running_time, stream_time;
463       gboolean all_headers;
464       guint count;
465 
466       if (!gst_video_event_is_force_key_unit (event))
467         break;
468 
469       gst_event_replace (&sink->force_key_unit_event, event);
470       gst_video_event_parse_downstream_force_key_unit (event,
471           &timestamp, &stream_time, &running_time, &all_headers, &count);
472       GST_INFO_OBJECT (sink, "setting index %d", count);
473       sink->index = count;
474       break;
475     }
476     default:
477       break;
478   }
479 
480   return GST_PAD_PROBE_OK;
481 }
482 
483 static gboolean
schedule_next_key_unit(GstHlsSink * sink)484 schedule_next_key_unit (GstHlsSink * sink)
485 {
486   gboolean res = TRUE;
487   GstClockTime running_time;
488   GstPad *sinkpad = gst_element_get_static_pad (GST_ELEMENT (sink), "sink");
489 
490   if (sink->target_duration == 0)
491     /* target-duration == 0 means that the app schedules key units itself */
492     goto out;
493 
494   running_time = sink->last_running_time + sink->target_duration * GST_SECOND;
495   GST_INFO_OBJECT (sink, "sending upstream force-key-unit, index %d "
496       "now %" GST_TIME_FORMAT " target %" GST_TIME_FORMAT,
497       sink->index + 1, GST_TIME_ARGS (sink->last_running_time),
498       GST_TIME_ARGS (running_time));
499 
500   if (!(res = gst_pad_push_event (sinkpad,
501               gst_video_event_new_upstream_force_key_unit (running_time,
502                   TRUE, sink->index + 1)))) {
503     GST_ERROR_OBJECT (sink, "Failed to push upstream force key unit event");
504   }
505 
506 out:
507   /* mark as waiting for a fku event if the app schedules them or if we just
508    * successfully scheduled one
509    */
510   sink->waiting_fku = res;
511   gst_object_unref (sinkpad);
512   return res;
513 }
514 
515 static void
gst_hls_sink_check_schedule_next_key_unit(GstHlsSink * sink,GstBuffer * buf)516 gst_hls_sink_check_schedule_next_key_unit (GstHlsSink * sink, GstBuffer * buf)
517 {
518   GstClockTime timestamp;
519 
520   timestamp = GST_BUFFER_TIMESTAMP (buf);
521   if (!GST_CLOCK_TIME_IS_VALID (timestamp))
522     return;
523 
524   sink->last_running_time = gst_segment_to_running_time (&sink->segment,
525       GST_FORMAT_TIME, timestamp);
526   schedule_next_key_unit (sink);
527 }
528 
529 static GstPadProbeReturn
gst_hls_sink_ghost_buffer_probe(GstPad * pad,GstPadProbeInfo * info,gpointer data)530 gst_hls_sink_ghost_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
531     gpointer data)
532 {
533   GstHlsSink *sink = GST_HLS_SINK_CAST (data);
534   GstBuffer *buffer = gst_pad_probe_info_get_buffer (info);
535 
536   if (sink->target_duration == 0 || sink->waiting_fku)
537     return GST_PAD_PROBE_OK;
538 
539   gst_hls_sink_check_schedule_next_key_unit (sink, buffer);
540   return GST_PAD_PROBE_OK;
541 }
542 
543 static GstFlowReturn
gst_hls_sink_chain_list(GstPad * pad,GstObject * parent,GstBufferList * list)544 gst_hls_sink_chain_list (GstPad * pad, GstObject * parent, GstBufferList * list)
545 {
546   guint i, len;
547   GstBuffer *buffer;
548   GstFlowReturn ret;
549   GstHlsSink *sink = GST_HLS_SINK_CAST (parent);
550 
551   if (sink->target_duration == 0 || sink->waiting_fku)
552     return gst_proxy_pad_chain_list_default (pad, parent, list);
553 
554   GST_DEBUG_OBJECT (pad, "chaining each group in list as a merged buffer");
555 
556   len = gst_buffer_list_length (list);
557 
558   ret = GST_FLOW_OK;
559   for (i = 0; i < len; i++) {
560     buffer = gst_buffer_list_get (list, i);
561 
562     if (!sink->waiting_fku)
563       gst_hls_sink_check_schedule_next_key_unit (sink, buffer);
564 
565     ret = gst_pad_chain (pad, gst_buffer_ref (buffer));
566     if (ret != GST_FLOW_OK)
567       break;
568   }
569   gst_buffer_list_unref (list);
570 
571   return ret;
572 }
573 
574 gboolean
gst_hls_sink_plugin_init(GstPlugin * plugin)575 gst_hls_sink_plugin_init (GstPlugin * plugin)
576 {
577   GST_DEBUG_CATEGORY_INIT (gst_hls_sink_debug, "hlssink", 0, "HlsSink");
578   return gst_element_register (plugin, "hlssink", GST_RANK_NONE,
579       gst_hls_sink_get_type ());
580 }
581