• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2011 Alessandro Decina <alessandro.d@gmail.com>
3  * Copyright (C) 2017 Sebastian Dröge <sebastian@centricular.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 /**
22  * SECTION:element-hlssink
23  * @title: hlssink
24  *
25  * HTTP Live Streaming sink/server
26  *
27  * ## Example launch line
28  * |[
29  * gst-launch-1.0 videotestsrc is-live=true ! x264enc ! hlssink max-files=5
30  * ]|
31  *
32  */
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 
37 #include "gsthlssink2.h"
38 #include <gst/pbutils/pbutils.h>
39 #include <gst/video/video.h>
40 #include <glib/gstdio.h>
41 #include <memory.h>
42 
43 
44 GST_DEBUG_CATEGORY_STATIC (gst_hls_sink2_debug);
45 #define GST_CAT_DEFAULT gst_hls_sink2_debug
46 
47 #define DEFAULT_LOCATION "segment%05d.ts"
48 #define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
49 #define DEFAULT_PLAYLIST_ROOT NULL
50 #define DEFAULT_MAX_FILES 10
51 #define DEFAULT_TARGET_DURATION 15
52 #define DEFAULT_PLAYLIST_LENGTH 5
53 #define DEFAULT_SEND_KEYFRAME_REQUESTS TRUE
54 
55 #define GST_M3U8_PLAYLIST_VERSION 3
56 
57 enum
58 {
59   PROP_0,
60   PROP_LOCATION,
61   PROP_PLAYLIST_LOCATION,
62   PROP_PLAYLIST_ROOT,
63   PROP_MAX_FILES,
64   PROP_TARGET_DURATION,
65   PROP_PLAYLIST_LENGTH,
66   PROP_SEND_KEYFRAME_REQUESTS,
67 };
68 
69 static GstStaticPadTemplate video_template = GST_STATIC_PAD_TEMPLATE ("video",
70     GST_PAD_SINK,
71     GST_PAD_REQUEST,
72     GST_STATIC_CAPS_ANY);
73 static GstStaticPadTemplate audio_template = GST_STATIC_PAD_TEMPLATE ("audio",
74     GST_PAD_SINK,
75     GST_PAD_REQUEST,
76     GST_STATIC_CAPS_ANY);
77 
78 #define gst_hls_sink2_parent_class parent_class
79 G_DEFINE_TYPE (GstHlsSink2, gst_hls_sink2, GST_TYPE_BIN);
80 
81 static void gst_hls_sink2_set_property (GObject * object, guint prop_id,
82     const GValue * value, GParamSpec * spec);
83 static void gst_hls_sink2_get_property (GObject * object, guint prop_id,
84     GValue * value, GParamSpec * spec);
85 static void gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message);
86 static void gst_hls_sink2_reset (GstHlsSink2 * sink);
87 static GstStateChangeReturn
88 gst_hls_sink2_change_state (GstElement * element, GstStateChange trans);
89 static GstPad *gst_hls_sink2_request_new_pad (GstElement * element,
90     GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
91 static void gst_hls_sink2_release_pad (GstElement * element, GstPad * pad);
92 
93 static void
gst_hls_sink2_dispose(GObject * object)94 gst_hls_sink2_dispose (GObject * object)
95 {
96   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
97 
98   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) sink);
99 }
100 
101 static void
gst_hls_sink2_finalize(GObject * object)102 gst_hls_sink2_finalize (GObject * object)
103 {
104   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
105 
106   g_free (sink->location);
107   g_free (sink->playlist_location);
108   g_free (sink->playlist_root);
109   g_free (sink->current_location);
110   if (sink->playlist)
111     gst_m3u8_playlist_free (sink->playlist);
112 
113   g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
114   g_queue_clear (&sink->old_locations);
115 
116   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) sink);
117 }
118 
119 static void
gst_hls_sink2_class_init(GstHlsSink2Class * klass)120 gst_hls_sink2_class_init (GstHlsSink2Class * klass)
121 {
122   GObjectClass *gobject_class;
123   GstElementClass *element_class;
124   GstBinClass *bin_class;
125 
126   gobject_class = (GObjectClass *) klass;
127   element_class = GST_ELEMENT_CLASS (klass);
128   bin_class = GST_BIN_CLASS (klass);
129 
130   gst_element_class_add_static_pad_template (element_class, &video_template);
131   gst_element_class_add_static_pad_template (element_class, &audio_template);
132 
133   gst_element_class_set_static_metadata (element_class,
134       "HTTP Live Streaming sink", "Sink", "HTTP Live Streaming sink",
135       "Alessandro Decina <alessandro.d@gmail.com>, "
136       "Sebastian Dröge <sebastian@centricular.com>");
137 
138   element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_sink2_change_state);
139   element_class->request_new_pad =
140       GST_DEBUG_FUNCPTR (gst_hls_sink2_request_new_pad);
141   element_class->release_pad = GST_DEBUG_FUNCPTR (gst_hls_sink2_release_pad);
142 
143   bin_class->handle_message = gst_hls_sink2_handle_message;
144 
145   gobject_class->dispose = gst_hls_sink2_dispose;
146   gobject_class->finalize = gst_hls_sink2_finalize;
147   gobject_class->set_property = gst_hls_sink2_set_property;
148   gobject_class->get_property = gst_hls_sink2_get_property;
149 
150   g_object_class_install_property (gobject_class, PROP_LOCATION,
151       g_param_spec_string ("location", "File Location",
152           "Location of the file to write", DEFAULT_LOCATION,
153           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LOCATION,
155       g_param_spec_string ("playlist-location", "Playlist Location",
156           "Location of the playlist to write", DEFAULT_PLAYLIST_LOCATION,
157           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
158   g_object_class_install_property (gobject_class, PROP_PLAYLIST_ROOT,
159       g_param_spec_string ("playlist-root", "Playlist Root",
160           "Location of the playlist to write", DEFAULT_PLAYLIST_ROOT,
161           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
163       g_param_spec_uint ("max-files", "Max files",
164           "Maximum number of files to keep on disk. Once the maximum is reached,"
165           "old files start to be deleted to make room for new ones.",
166           0, G_MAXUINT, DEFAULT_MAX_FILES,
167           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
168   g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
169       g_param_spec_uint ("target-duration", "Target duration",
170           "The target duration in seconds of a segment/file. "
171           "(0 - disabled, useful for management of segment duration by the "
172           "streaming server)",
173           0, G_MAXUINT, DEFAULT_TARGET_DURATION,
174           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
175   g_object_class_install_property (gobject_class, PROP_PLAYLIST_LENGTH,
176       g_param_spec_uint ("playlist-length", "Playlist length",
177           "Length of HLS playlist. To allow players to conform to section 6.3.3 "
178           "of the HLS specification, this should be at least 3. If set to 0, "
179           "the playlist will be infinite.",
180           0, G_MAXUINT, DEFAULT_PLAYLIST_LENGTH,
181           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
182   g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
183       g_param_spec_boolean ("send-keyframe-requests", "Send Keyframe Requests",
184           "Send keyframe requests to ensure correct fragmentation. If this is disabled "
185           "then the input must have keyframes in regular intervals",
186           DEFAULT_SEND_KEYFRAME_REQUESTS,
187           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
188 }
189 
190 static void
gst_hls_sink2_init(GstHlsSink2 * sink)191 gst_hls_sink2_init (GstHlsSink2 * sink)
192 {
193   GstElement *mux;
194 
195   sink->location = g_strdup (DEFAULT_LOCATION);
196   sink->playlist_location = g_strdup (DEFAULT_PLAYLIST_LOCATION);
197   sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
198   sink->playlist_length = DEFAULT_PLAYLIST_LENGTH;
199   sink->max_files = DEFAULT_MAX_FILES;
200   sink->target_duration = DEFAULT_TARGET_DURATION;
201   sink->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
202   g_queue_init (&sink->old_locations);
203 
204   sink->splitmuxsink = gst_element_factory_make ("splitmuxsink", NULL);
205   gst_bin_add (GST_BIN (sink), sink->splitmuxsink);
206 
207   mux = gst_element_factory_make ("mpegtsmux", NULL);
208   g_object_set (sink->splitmuxsink, "location", sink->location, "max-size-time",
209       ((GstClockTime) sink->target_duration * GST_SECOND),
210       "send-keyframe-requests", TRUE, "muxer", mux, "reset-muxer", FALSE, NULL);
211 
212   GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_SINK);
213 
214   gst_hls_sink2_reset (sink);
215 }
216 
217 static void
gst_hls_sink2_reset(GstHlsSink2 * sink)218 gst_hls_sink2_reset (GstHlsSink2 * sink)
219 {
220   sink->index = 0;
221 
222   if (sink->playlist)
223     gst_m3u8_playlist_free (sink->playlist);
224   sink->playlist =
225       gst_m3u8_playlist_new (GST_M3U8_PLAYLIST_VERSION, sink->playlist_length,
226       FALSE);
227 
228   g_queue_foreach (&sink->old_locations, (GFunc) g_free, NULL);
229   g_queue_clear (&sink->old_locations);
230 }
231 
232 static void
gst_hls_sink2_write_playlist(GstHlsSink2 * sink)233 gst_hls_sink2_write_playlist (GstHlsSink2 * sink)
234 {
235   char *playlist_content;
236   GError *error = NULL;
237 
238   playlist_content = gst_m3u8_playlist_render (sink->playlist);
239   if (!g_file_set_contents (sink->playlist_location,
240           playlist_content, -1, &error)) {
241     GST_ERROR ("Failed to write playlist: %s", error->message);
242     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
243         (("Failed to write playlist '%s'."), error->message), (NULL));
244     g_error_free (error);
245     error = NULL;
246   }
247   g_free (playlist_content);
248 
249 }
250 
251 static void
gst_hls_sink2_handle_message(GstBin * bin,GstMessage * message)252 gst_hls_sink2_handle_message (GstBin * bin, GstMessage * message)
253 {
254   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (bin);
255 
256   switch (message->type) {
257     case GST_MESSAGE_ELEMENT:
258     {
259       const GstStructure *s = gst_message_get_structure (message);
260       if (message->src == GST_OBJECT_CAST (sink->splitmuxsink)) {
261         if (gst_structure_has_name (s, "splitmuxsink-fragment-opened")) {
262           g_free (sink->current_location);
263           sink->current_location =
264               g_strdup (gst_structure_get_string (s, "location"));
265           gst_structure_get_clock_time (s, "running-time",
266               &sink->current_running_time_start);
267         } else if (gst_structure_has_name (s, "splitmuxsink-fragment-closed")) {
268           GstClockTime running_time;
269           gchar *entry_location;
270 
271           g_assert (strcmp (sink->current_location, gst_structure_get_string (s,
272                       "location")) == 0);
273 
274           gst_structure_get_clock_time (s, "running-time", &running_time);
275 
276           GST_INFO_OBJECT (sink, "COUNT %d", sink->index);
277           if (sink->playlist_root == NULL) {
278             entry_location = g_path_get_basename (sink->current_location);
279           } else {
280             gchar *name = g_path_get_basename (sink->current_location);
281             entry_location = g_build_filename (sink->playlist_root, name, NULL);
282             g_free (name);
283           }
284 
285           gst_m3u8_playlist_add_entry (sink->playlist, entry_location,
286               NULL, running_time - sink->current_running_time_start,
287               sink->index++, FALSE);
288           g_free (entry_location);
289 
290           gst_hls_sink2_write_playlist (sink);
291 
292           g_queue_push_tail (&sink->old_locations,
293               g_strdup (sink->current_location));
294 
295           while (g_queue_get_length (&sink->old_locations) >
296               g_queue_get_length (sink->playlist->entries)) {
297             gchar *old_location = g_queue_pop_head (&sink->old_locations);
298             g_remove (old_location);
299             g_free (old_location);
300           }
301         }
302       }
303       break;
304     }
305     case GST_MESSAGE_EOS:{
306       sink->playlist->end_list = TRUE;
307       gst_hls_sink2_write_playlist (sink);
308       break;
309     }
310     default:
311       break;
312   }
313 
314   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
315 }
316 
317 static GstPad *
gst_hls_sink2_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * name,const GstCaps * caps)318 gst_hls_sink2_request_new_pad (GstElement * element, GstPadTemplate * templ,
319     const gchar * name, const GstCaps * caps)
320 {
321   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
322   GstPad *pad, *peer;
323   gboolean is_audio;
324 
325   g_return_val_if_fail (strcmp (templ->name_template, "audio") == 0
326       || strcmp (templ->name_template, "video") == 0, NULL);
327   g_return_val_if_fail (strcmp (templ->name_template, "audio") != 0
328       || !sink->audio_sink, NULL);
329   g_return_val_if_fail (strcmp (templ->name_template, "video") != 0
330       || !sink->video_sink, NULL);
331 
332   is_audio = strcmp (templ->name_template, "audio") == 0;
333 
334   peer =
335       gst_element_get_request_pad (sink->splitmuxsink,
336       is_audio ? "audio_0" : "video");
337   if (!peer)
338     return NULL;
339 
340   pad = gst_ghost_pad_new_from_template (templ->name_template, peer, templ);
341   gst_pad_set_active (pad, TRUE);
342   gst_element_add_pad (element, pad);
343   gst_object_unref (peer);
344 
345   if (is_audio)
346     sink->audio_sink = pad;
347   else
348     sink->video_sink = pad;
349 
350   return pad;
351 }
352 
353 static void
gst_hls_sink2_release_pad(GstElement * element,GstPad * pad)354 gst_hls_sink2_release_pad (GstElement * element, GstPad * pad)
355 {
356   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
357   GstPad *peer;
358 
359   g_return_if_fail (pad == sink->audio_sink || pad == sink->video_sink);
360 
361   peer = gst_pad_get_peer (pad);
362   if (peer) {
363     gst_element_release_request_pad (sink->splitmuxsink, pad);
364     gst_object_unref (peer);
365   }
366 
367   gst_object_ref (pad);
368   gst_element_remove_pad (element, pad);
369   gst_pad_set_active (pad, FALSE);
370   if (pad == sink->audio_sink)
371     sink->audio_sink = NULL;
372   else
373     sink->video_sink = NULL;
374 
375   gst_object_unref (pad);
376 }
377 
378 static GstStateChangeReturn
gst_hls_sink2_change_state(GstElement * element,GstStateChange trans)379 gst_hls_sink2_change_state (GstElement * element, GstStateChange trans)
380 {
381   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
382   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (element);
383 
384   switch (trans) {
385     case GST_STATE_CHANGE_NULL_TO_READY:
386       if (!sink->splitmuxsink) {
387         return GST_STATE_CHANGE_FAILURE;
388       }
389       break;
390     default:
391       break;
392   }
393 
394   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
395 
396   switch (trans) {
397     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
398       break;
399     case GST_STATE_CHANGE_PAUSED_TO_READY:
400     case GST_STATE_CHANGE_READY_TO_NULL:
401       gst_hls_sink2_reset (sink);
402       break;
403     default:
404       break;
405   }
406 
407   return ret;
408 }
409 
410 static void
gst_hls_sink2_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)411 gst_hls_sink2_set_property (GObject * object, guint prop_id,
412     const GValue * value, GParamSpec * pspec)
413 {
414   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
415 
416   switch (prop_id) {
417     case PROP_LOCATION:
418       g_free (sink->location);
419       sink->location = g_value_dup_string (value);
420       if (sink->splitmuxsink)
421         g_object_set (sink->splitmuxsink, "location", sink->location, NULL);
422       break;
423     case PROP_PLAYLIST_LOCATION:
424       g_free (sink->playlist_location);
425       sink->playlist_location = g_value_dup_string (value);
426       break;
427     case PROP_PLAYLIST_ROOT:
428       g_free (sink->playlist_root);
429       sink->playlist_root = g_value_dup_string (value);
430       break;
431     case PROP_MAX_FILES:
432       sink->max_files = g_value_get_uint (value);
433       break;
434     case PROP_TARGET_DURATION:
435       sink->target_duration = g_value_get_uint (value);
436       if (sink->splitmuxsink) {
437         g_object_set (sink->splitmuxsink, "max-size-time",
438             ((GstClockTime) sink->target_duration * GST_SECOND), NULL);
439       }
440       break;
441     case PROP_PLAYLIST_LENGTH:
442       sink->playlist_length = g_value_get_uint (value);
443       sink->playlist->window_size = sink->playlist_length;
444       break;
445     case PROP_SEND_KEYFRAME_REQUESTS:
446       sink->send_keyframe_requests = g_value_get_boolean (value);
447       if (sink->splitmuxsink) {
448         g_object_set (sink->splitmuxsink, "send-keyframe-requests",
449             sink->send_keyframe_requests, NULL);
450       }
451       break;
452     default:
453       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454       break;
455   }
456 }
457 
458 static void
gst_hls_sink2_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)459 gst_hls_sink2_get_property (GObject * object, guint prop_id,
460     GValue * value, GParamSpec * pspec)
461 {
462   GstHlsSink2 *sink = GST_HLS_SINK2_CAST (object);
463 
464   switch (prop_id) {
465     case PROP_LOCATION:
466       g_value_set_string (value, sink->location);
467       break;
468     case PROP_PLAYLIST_LOCATION:
469       g_value_set_string (value, sink->playlist_location);
470       break;
471     case PROP_PLAYLIST_ROOT:
472       g_value_set_string (value, sink->playlist_root);
473       break;
474     case PROP_MAX_FILES:
475       g_value_set_uint (value, sink->max_files);
476       break;
477     case PROP_TARGET_DURATION:
478       g_value_set_uint (value, sink->target_duration);
479       break;
480     case PROP_PLAYLIST_LENGTH:
481       g_value_set_uint (value, sink->playlist_length);
482       break;
483     case PROP_SEND_KEYFRAME_REQUESTS:
484       g_value_set_boolean (value, sink->send_keyframe_requests);
485       break;
486     default:
487       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
488       break;
489   }
490 }
491 
492 gboolean
gst_hls_sink2_plugin_init(GstPlugin * plugin)493 gst_hls_sink2_plugin_init (GstPlugin * plugin)
494 {
495   GST_DEBUG_CATEGORY_INIT (gst_hls_sink2_debug, "hlssink2", 0, "HlsSink2");
496   return gst_element_register (plugin, "hlssink2", GST_RANK_NONE,
497       gst_hls_sink2_get_type ());
498 }
499