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 ×tamp, &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