1 /* GStreamer Split Demuxer bin that recombines files created by
2  * the splitmuxsink element.
3  *
4  * Copyright (C) <2014> Jan Schmidt <jan@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-splitmuxsrc
24  * @short_description: Split Demuxer bin that recombines files created by
25  * the splitmuxsink element.
26  *
27  * This element reads a set of input files created by the splitmuxsink element
28  * containing contiguous elementary streams split across multiple files.
29  *
30  * This element is similar to splitfilesrc, except that it recombines the
31  * streams in each file part at the demuxed elementary level, rather than
32  * as a single larger bytestream.
33  *
34  * <refsect2>
35  * <title>Example pipelines</title>
36  * |[
37  * gst-launch-1.0 splitmuxsrc location=video*.mov ! decodebin ! xvimagesink
38  * ]| Demux each file part and output the video stream as one continuous stream
39  * |[
40  * gst-launch-1.0 playbin uri="splitmux://path/to/foo.mp4.*"
41  * ]| Play back a set of files created by splitmuxsink
42  * </refsect2>
43  */
44 
45 #ifdef HAVE_CONFIG_H
46 #include "config.h"
47 #endif
48 
49 #include <string.h>
50 #include "gstsplitmuxsrc.h"
51 #include "gstsplitutils.h"
52 
53 #include "../../gst-libs/gst/gst-i18n-plugin.h"
54 
55 GST_DEBUG_CATEGORY (splitmux_debug);
56 #define GST_CAT_DEFAULT splitmux_debug
57 
58 enum
59 {
60   PROP_0,
61   PROP_LOCATION
62 };
63 
64 enum
65 {
66   SIGNAL_FORMAT_LOCATION,
67   SIGNAL_LAST
68 };
69 
70 static guint signals[SIGNAL_LAST];
71 
72 static GstStaticPadTemplate video_src_template =
73 GST_STATIC_PAD_TEMPLATE ("video",
74     GST_PAD_SRC,
75     GST_PAD_SOMETIMES,
76     GST_STATIC_CAPS_ANY);
77 
78 static GstStaticPadTemplate audio_src_template =
79 GST_STATIC_PAD_TEMPLATE ("audio_%u",
80     GST_PAD_SRC,
81     GST_PAD_SOMETIMES,
82     GST_STATIC_CAPS_ANY);
83 
84 static GstStaticPadTemplate subtitle_src_template =
85 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
86     GST_PAD_SRC,
87     GST_PAD_SOMETIMES,
88     GST_STATIC_CAPS_ANY);
89 
90 static GstStateChangeReturn gst_splitmux_src_change_state (GstElement *
91     element, GstStateChange transition);
92 static void gst_splitmux_src_set_property (GObject * object, guint prop_id,
93     const GValue * value, GParamSpec * pspec);
94 static void gst_splitmux_src_get_property (GObject * object, guint prop_id,
95     GValue * value, GParamSpec * pspec);
96 static void gst_splitmux_src_dispose (GObject * object);
97 static void gst_splitmux_src_finalize (GObject * object);
98 static gboolean gst_splitmux_src_start (GstSplitMuxSrc * splitmux);
99 static gboolean gst_splitmux_src_stop (GstSplitMuxSrc * splitmux);
100 static void splitmux_src_pad_constructed (GObject * pad);
101 static gboolean splitmux_src_pad_event (GstPad * pad, GstObject * parent,
102     GstEvent * event);
103 static gboolean splitmux_src_pad_query (GstPad * pad, GstObject * parent,
104     GstQuery * query);
105 static void splitmux_src_uri_handler_init (gpointer g_iface,
106     gpointer iface_data);
107 
108 
109 static GstPad *gst_splitmux_find_output_pad (GstSplitMuxPartReader * part,
110     GstPad * pad, GstSplitMuxSrc * splitmux);
111 static gboolean gst_splitmux_end_of_part (GstSplitMuxSrc * splitmux,
112     SplitMuxSrcPad * pad);
113 static gboolean gst_splitmux_check_new_caps (SplitMuxSrcPad * splitpad,
114     GstEvent * event);
115 static gboolean gst_splitmux_src_prepare_next_part (GstSplitMuxSrc * splitmux);
116 static gboolean gst_splitmux_src_activate_part (GstSplitMuxSrc * splitmux,
117     guint part, GstSeekFlags extra_flags);
118 
119 #define _do_init \
120     G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, splitmux_src_uri_handler_init);
121 #define gst_splitmux_src_parent_class parent_class
122 
123 G_DEFINE_TYPE_EXTENDED (GstSplitMuxSrc, gst_splitmux_src, GST_TYPE_BIN, 0,
124     _do_init);
125 
126 static GstURIType
splitmux_src_uri_get_type(GType type)127 splitmux_src_uri_get_type (GType type)
128 {
129   return GST_URI_SRC;
130 }
131 
132 static const gchar *const *
splitmux_src_uri_get_protocols(GType type)133 splitmux_src_uri_get_protocols (GType type)
134 {
135   static const gchar *protocols[] = { "splitmux", NULL };
136 
137   return protocols;
138 }
139 
140 static gchar *
splitmux_src_uri_get_uri(GstURIHandler * handler)141 splitmux_src_uri_get_uri (GstURIHandler * handler)
142 {
143   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (handler);
144   gchar *ret = NULL;
145 
146   GST_OBJECT_LOCK (splitmux);
147   if (splitmux->location)
148     ret = g_strdup_printf ("splitmux://%s", splitmux->location);
149   GST_OBJECT_UNLOCK (splitmux);
150   return ret;
151 }
152 
153 static gboolean
splitmux_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** err)154 splitmux_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
155     GError ** err)
156 {
157   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (handler);
158   gchar *protocol, *location;
159 
160   protocol = gst_uri_get_protocol (uri);
161   if (protocol == NULL || !g_str_equal (protocol, "splitmux"))
162     goto wrong_uri;
163   g_free (protocol);
164 
165   location = gst_uri_get_location (uri);
166   GST_OBJECT_LOCK (splitmux);
167   g_free (splitmux->location);
168   splitmux->location = location;
169   GST_OBJECT_UNLOCK (splitmux);
170 
171   return TRUE;
172 
173 wrong_uri:
174   g_free (protocol);
175   GST_ELEMENT_ERROR (splitmux, RESOURCE, READ, (NULL),
176       ("Error parsing uri %s", uri));
177   g_set_error_literal (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
178       "Could not parse splitmux URI");
179   return FALSE;
180 }
181 
182 static void
splitmux_src_uri_handler_init(gpointer g_iface,gpointer iface_data)183 splitmux_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
184 {
185   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) (g_iface);
186 
187   iface->get_type = splitmux_src_uri_get_type;
188   iface->get_protocols = splitmux_src_uri_get_protocols;
189   iface->set_uri = splitmux_src_uri_set_uri;
190   iface->get_uri = splitmux_src_uri_get_uri;
191 }
192 
193 
194 static void
gst_splitmux_src_class_init(GstSplitMuxSrcClass * klass)195 gst_splitmux_src_class_init (GstSplitMuxSrcClass * klass)
196 {
197   GObjectClass *gobject_class = (GObjectClass *) klass;
198   GstElementClass *gstelement_class = (GstElementClass *) klass;
199 
200   gobject_class->set_property = gst_splitmux_src_set_property;
201   gobject_class->get_property = gst_splitmux_src_get_property;
202   gobject_class->dispose = gst_splitmux_src_dispose;
203   gobject_class->finalize = gst_splitmux_src_finalize;
204 
205   gst_element_class_set_static_metadata (gstelement_class,
206       "Split File Demuxing Bin", "Generic/Bin/Demuxer",
207       "Source that reads a set of files created by splitmuxsink",
208       "Jan Schmidt <jan@centricular.com>");
209 
210   gst_element_class_add_static_pad_template (gstelement_class,
211       &video_src_template);
212   gst_element_class_add_static_pad_template (gstelement_class,
213       &audio_src_template);
214   gst_element_class_add_static_pad_template (gstelement_class,
215       &subtitle_src_template);
216 
217   gstelement_class->change_state =
218       GST_DEBUG_FUNCPTR (gst_splitmux_src_change_state);
219 
220   g_object_class_install_property (gobject_class, PROP_LOCATION,
221       g_param_spec_string ("location", "File Input Pattern",
222           "Glob pattern for the location of the files to read", NULL,
223           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
224 
225   /**
226    * GstSplitMuxSrc::format-location:
227    * @splitmux: the #GstSplitMuxSrc
228    *
229    * Returns: A NULL-terminated sorted array of strings containing the
230    *   filenames of the input files. The array will be freed internally
231    *   using g_strfreev()
232    *
233    * Since: 1.8
234    */
235   signals[SIGNAL_FORMAT_LOCATION] =
236       g_signal_new ("format-location", G_TYPE_FROM_CLASS (klass),
237       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_STRV, 0);
238 }
239 
240 static void
gst_splitmux_src_init(GstSplitMuxSrc * splitmux)241 gst_splitmux_src_init (GstSplitMuxSrc * splitmux)
242 {
243   g_mutex_init (&splitmux->lock);
244   g_mutex_init (&splitmux->pads_lock);
245   splitmux->total_duration = GST_CLOCK_TIME_NONE;
246   gst_segment_init (&splitmux->play_segment, GST_FORMAT_TIME);
247 }
248 
249 static void
gst_splitmux_src_dispose(GObject * object)250 gst_splitmux_src_dispose (GObject * object)
251 {
252   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
253   GList *cur;
254 
255   SPLITMUX_SRC_PADS_LOCK (splitmux);
256 
257   for (cur = g_list_first (splitmux->pads);
258       cur != NULL; cur = g_list_next (cur)) {
259     GstPad *pad = GST_PAD (cur->data);
260     gst_element_remove_pad (GST_ELEMENT (splitmux), pad);
261   }
262   g_list_free (splitmux->pads);
263   splitmux->n_pads = 0;
264   splitmux->pads = NULL;
265   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
266 
267   G_OBJECT_CLASS (parent_class)->dispose (object);
268 }
269 
270 static void
gst_splitmux_src_finalize(GObject * object)271 gst_splitmux_src_finalize (GObject * object)
272 {
273   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
274   g_mutex_clear (&splitmux->lock);
275   g_mutex_clear (&splitmux->pads_lock);
276   g_free (splitmux->location);
277 
278   G_OBJECT_CLASS (parent_class)->finalize (object);
279 }
280 
281 static void
gst_splitmux_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)282 gst_splitmux_src_set_property (GObject * object, guint prop_id,
283     const GValue * value, GParamSpec * pspec)
284 {
285   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
286 
287   switch (prop_id) {
288     case PROP_LOCATION:{
289       GST_OBJECT_LOCK (splitmux);
290       g_free (splitmux->location);
291       splitmux->location = g_value_dup_string (value);
292       GST_OBJECT_UNLOCK (splitmux);
293       break;
294     }
295     default:
296       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
297       break;
298   }
299 }
300 
301 static void
gst_splitmux_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)302 gst_splitmux_src_get_property (GObject * object, guint prop_id,
303     GValue * value, GParamSpec * pspec)
304 {
305   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
306 
307   switch (prop_id) {
308     case PROP_LOCATION:
309       GST_OBJECT_LOCK (splitmux);
310       g_value_set_string (value, splitmux->location);
311       GST_OBJECT_UNLOCK (splitmux);
312       break;
313     default:
314       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
315       break;
316   }
317 }
318 
319 static void
do_async_start(GstSplitMuxSrc * splitmux)320 do_async_start (GstSplitMuxSrc * splitmux)
321 {
322   GstMessage *message;
323 
324   GST_STATE_LOCK (splitmux);
325   splitmux->async_pending = TRUE;
326 
327   message = gst_message_new_async_start (GST_OBJECT_CAST (splitmux));
328   GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (splitmux),
329       message);
330   GST_STATE_UNLOCK (splitmux);
331 }
332 
333 static void
do_async_done(GstSplitMuxSrc * splitmux)334 do_async_done (GstSplitMuxSrc * splitmux)
335 {
336   GstMessage *message;
337 
338   GST_STATE_LOCK (splitmux);
339   if (splitmux->async_pending) {
340     message =
341         gst_message_new_async_done (GST_OBJECT_CAST (splitmux),
342         GST_CLOCK_TIME_NONE);
343     GST_BIN_CLASS (parent_class)->handle_message (GST_BIN_CAST (splitmux),
344         message);
345 
346     splitmux->async_pending = FALSE;
347   }
348   GST_STATE_UNLOCK (splitmux);
349 }
350 
351 static GstStateChangeReturn
gst_splitmux_src_change_state(GstElement * element,GstStateChange transition)352 gst_splitmux_src_change_state (GstElement * element, GstStateChange transition)
353 {
354   GstStateChangeReturn ret;
355   GstSplitMuxSrc *splitmux = (GstSplitMuxSrc *) element;
356 
357   switch (transition) {
358     case GST_STATE_CHANGE_NULL_TO_READY:{
359       break;
360     }
361     case GST_STATE_CHANGE_READY_TO_PAUSED:{
362       do_async_start (splitmux);
363 
364       if (!gst_splitmux_src_start (splitmux)) {
365         do_async_done (splitmux);
366         return GST_STATE_CHANGE_FAILURE;
367       }
368       break;
369     }
370     case GST_STATE_CHANGE_PAUSED_TO_READY:
371     case GST_STATE_CHANGE_READY_TO_NULL:
372       /* Make sure the element will shut down */
373       if (!gst_splitmux_src_stop (splitmux))
374         return GST_STATE_CHANGE_FAILURE;
375       break;
376     default:
377       break;
378   }
379 
380   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
381   if (ret == GST_STATE_CHANGE_FAILURE) {
382     do_async_done (splitmux);
383     return ret;
384   }
385 
386   switch (transition) {
387     case GST_STATE_CHANGE_READY_TO_PAUSED:
388       ret = GST_STATE_CHANGE_ASYNC;
389       break;
390     default:
391       break;
392   }
393 
394 
395   return ret;
396 }
397 
398 static void
gst_splitmux_src_activate_first_part(GstSplitMuxSrc * splitmux)399 gst_splitmux_src_activate_first_part (GstSplitMuxSrc * splitmux)
400 {
401   if (!gst_splitmux_src_activate_part (splitmux, 0, GST_SEEK_FLAG_NONE)) {
402     GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, (NULL),
403         ("Failed to activate first part for playback"));
404   }
405 }
406 
407 static GstBusSyncReply
gst_splitmux_part_bus_handler(GstBus * bus,GstMessage * msg,gpointer user_data)408 gst_splitmux_part_bus_handler (GstBus * bus, GstMessage * msg,
409     gpointer user_data)
410 {
411   GstSplitMuxSrc *splitmux = user_data;
412 
413   switch (GST_MESSAGE_TYPE (msg)) {
414     case GST_MESSAGE_ASYNC_DONE:{
415       guint idx = splitmux->num_prepared_parts;
416       gboolean need_no_more_pads;
417 
418       if (idx >= splitmux->num_parts) {
419         /* Shouldn't really happen! */
420         do_async_done (splitmux);
421         g_warn_if_reached ();
422         break;
423       }
424 
425       GST_DEBUG_OBJECT (splitmux, "Prepared file part %s (%u)",
426           splitmux->parts[idx]->path, idx);
427 
428       /* signal no-more-pads as we have all pads at this point now */
429       SPLITMUX_SRC_LOCK (splitmux);
430       need_no_more_pads = !splitmux->pads_complete;
431       splitmux->pads_complete = TRUE;
432       SPLITMUX_SRC_UNLOCK (splitmux);
433 
434       if (need_no_more_pads) {
435         GST_DEBUG_OBJECT (splitmux, "Signalling no-more-pads");
436         gst_element_no_more_pads (GST_ELEMENT_CAST (splitmux));
437       }
438 
439       /* Extend our total duration to cover this part */
440       GST_OBJECT_LOCK (splitmux);
441       splitmux->total_duration +=
442           gst_splitmux_part_reader_get_duration (splitmux->parts[idx]);
443       splitmux->play_segment.duration = splitmux->total_duration;
444       GST_OBJECT_UNLOCK (splitmux);
445 
446       splitmux->end_offset =
447           gst_splitmux_part_reader_get_end_offset (splitmux->parts[idx]);
448 
449       GST_DEBUG_OBJECT (splitmux,
450           "Duration %" GST_TIME_FORMAT ", total duration now: %" GST_TIME_FORMAT
451           " and end offset %" GST_TIME_FORMAT,
452           GST_TIME_ARGS (gst_splitmux_part_reader_get_duration (splitmux->parts
453                   [idx])), GST_TIME_ARGS (splitmux->total_duration),
454           GST_TIME_ARGS (splitmux->end_offset));
455 
456       splitmux->num_prepared_parts++;
457 
458       /* If we're done or preparing the next part fails, finish here */
459       if (splitmux->num_prepared_parts >= splitmux->num_parts
460           || !gst_splitmux_src_prepare_next_part (splitmux)) {
461         /* Store how many parts we actually prepared in the end */
462         splitmux->num_parts = splitmux->num_prepared_parts;
463         do_async_done (splitmux);
464 
465         /* All done preparing, activate the first part */
466         GST_INFO_OBJECT (splitmux,
467             "All parts prepared. Total duration %" GST_TIME_FORMAT
468             " Activating first part", GST_TIME_ARGS (splitmux->total_duration));
469         gst_element_call_async (GST_ELEMENT_CAST (splitmux),
470             (GstElementCallAsyncFunc) gst_splitmux_src_activate_first_part,
471             NULL, NULL);
472       }
473 
474       break;
475     }
476     case GST_MESSAGE_ERROR:{
477       GST_ERROR_OBJECT (splitmux,
478           "Got error message from part %" GST_PTR_FORMAT ": %" GST_PTR_FORMAT,
479           GST_MESSAGE_SRC (msg), msg);
480       if (splitmux->num_prepared_parts < splitmux->num_parts) {
481         guint idx = splitmux->num_prepared_parts;
482 
483         if (idx == 0) {
484           GST_ERROR_OBJECT (splitmux,
485               "Failed to prepare first file part %s for playback",
486               splitmux->parts[idx]->path);
487           GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, (NULL),
488               ("Failed to prepare first file part %s for playback",
489                   splitmux->parts[idx]->path));
490         } else {
491           GST_WARNING_OBJECT (splitmux,
492               "Failed to prepare file part %s. Cannot play past there.",
493               splitmux->parts[idx]->path);
494           GST_ELEMENT_WARNING (splitmux, RESOURCE, READ, (NULL),
495               ("Failed to prepare file part %s. Cannot play past there.",
496                   splitmux->parts[idx]->path));
497         }
498 
499         /* Store how many parts we actually prepared in the end */
500         splitmux->num_parts = splitmux->num_prepared_parts;
501         do_async_done (splitmux);
502 
503         if (idx > 0) {
504           /* All done preparing, activate the first part */
505           GST_INFO_OBJECT (splitmux,
506               "All parts prepared. Total duration %" GST_TIME_FORMAT
507               " Activating first part",
508               GST_TIME_ARGS (splitmux->total_duration));
509           gst_element_call_async (GST_ELEMENT_CAST (splitmux),
510               (GstElementCallAsyncFunc) gst_splitmux_src_activate_first_part,
511               NULL, NULL);
512         }
513       } else {
514         /* Need to update the message source so that it's part of the element
515          * hierarchy the application would expect */
516         msg = gst_message_copy (msg);
517         gst_object_replace ((GstObject **) & msg->src, (GstObject *) splitmux);
518         gst_element_post_message (GST_ELEMENT_CAST (splitmux), msg);
519       }
520       break;
521     }
522     default:
523       break;
524   }
525 
526   return GST_BUS_PASS;
527 }
528 
529 static GstSplitMuxPartReader *
gst_splitmux_part_create(GstSplitMuxSrc * splitmux,char * filename)530 gst_splitmux_part_create (GstSplitMuxSrc * splitmux, char *filename)
531 {
532   GstSplitMuxPartReader *r;
533   GstBus *bus;
534 
535   r = g_object_new (GST_TYPE_SPLITMUX_PART_READER, NULL);
536 
537   gst_splitmux_part_reader_set_callbacks (r, splitmux,
538       (GstSplitMuxPartReaderPadCb) gst_splitmux_find_output_pad);
539   gst_splitmux_part_reader_set_location (r, filename);
540 
541   bus = gst_element_get_bus (GST_ELEMENT_CAST (r));
542   gst_bus_set_sync_handler (bus, gst_splitmux_part_bus_handler, splitmux, NULL);
543   gst_object_unref (bus);
544 
545   return r;
546 }
547 
548 static gboolean
gst_splitmux_check_new_caps(SplitMuxSrcPad * splitpad,GstEvent * event)549 gst_splitmux_check_new_caps (SplitMuxSrcPad * splitpad, GstEvent * event)
550 {
551   GstCaps *curcaps = gst_pad_get_current_caps ((GstPad *) (splitpad));
552   GstCaps *newcaps;
553   GstCaps *tmpcaps;
554   GstCaps *tmpcurcaps;
555 
556   GstStructure *s;
557   gboolean res = TRUE;
558 
559   gst_event_parse_caps (event, &newcaps);
560 
561   GST_LOG_OBJECT (splitpad, "Comparing caps %" GST_PTR_FORMAT
562       " and %" GST_PTR_FORMAT, curcaps, newcaps);
563 
564   if (curcaps == NULL)
565     return TRUE;
566 
567   /* If caps are exactly equal exit early */
568   if (gst_caps_is_equal (curcaps, newcaps)) {
569     gst_caps_unref (curcaps);
570     return FALSE;
571   }
572 
573   /* More extensive check, ignore changes in framerate, because
574    * demuxers get that wrong */
575   tmpcaps = gst_caps_copy (newcaps);
576   s = gst_caps_get_structure (tmpcaps, 0);
577   gst_structure_remove_field (s, "framerate");
578 
579   tmpcurcaps = gst_caps_copy (curcaps);
580   gst_caps_unref (curcaps);
581   s = gst_caps_get_structure (tmpcurcaps, 0);
582   gst_structure_remove_field (s, "framerate");
583 
584   /* Now check if these filtered caps are equal */
585   if (gst_caps_is_equal (tmpcurcaps, tmpcaps)) {
586     GST_INFO_OBJECT (splitpad, "Ignoring framerate-only caps change");
587     res = FALSE;
588   }
589 
590   gst_caps_unref (tmpcaps);
591   gst_caps_unref (tmpcurcaps);
592   return res;
593 }
594 
595 static void
gst_splitmux_handle_event(GstSplitMuxSrc * splitmux,SplitMuxSrcPad * splitpad,GstPad * part_pad,GstEvent * event)596 gst_splitmux_handle_event (GstSplitMuxSrc * splitmux,
597     SplitMuxSrcPad * splitpad, GstPad * part_pad, GstEvent * event)
598 {
599   switch (GST_EVENT_TYPE (event)) {
600     case GST_EVENT_STREAM_START:{
601       if (splitpad->sent_stream_start)
602         goto drop_event;
603       splitpad->sent_stream_start = TRUE;
604       break;
605     }
606     case GST_EVENT_EOS:{
607       if (gst_splitmux_end_of_part (splitmux, splitpad))
608         // Continuing to next part, drop the EOS
609         goto drop_event;
610       if (splitmux->segment_seqnum) {
611         event = gst_event_make_writable (event);
612         gst_event_set_seqnum (event, splitmux->segment_seqnum);
613       }
614       break;
615     }
616     case GST_EVENT_SEGMENT:{
617       GstClockTime duration;
618       GstSegment seg;
619 
620       gst_event_copy_segment (event, &seg);
621 
622       splitpad->segment.position = seg.position;
623 
624       if (splitpad->sent_segment)
625         goto drop_event;        /* We already forwarded a segment event */
626 
627       /* Calculate output segment */
628       GST_LOG_OBJECT (splitpad, "Pad seg %" GST_SEGMENT_FORMAT
629           " got seg %" GST_SEGMENT_FORMAT
630           " play seg %" GST_SEGMENT_FORMAT,
631           &splitpad->segment, &seg, &splitmux->play_segment);
632 
633       /* If playing forward, take the stop time from the overall
634        * seg or play_segment */
635       if (splitmux->play_segment.rate > 0.0) {
636         if (splitmux->play_segment.stop != -1)
637           seg.stop = splitmux->play_segment.stop;
638         else
639           seg.stop = splitpad->segment.stop;
640       } else {
641         /* Reverse playback from stop time to start time */
642         /* See if an end point was requested in the seek */
643         if (splitmux->play_segment.start != -1) {
644           seg.start = splitmux->play_segment.start;
645           seg.time = splitmux->play_segment.time;
646         } else {
647           seg.start = splitpad->segment.start;
648           seg.time = splitpad->segment.time;
649         }
650       }
651 
652       GST_OBJECT_LOCK (splitmux);
653       duration = splitmux->total_duration;
654       GST_OBJECT_UNLOCK (splitmux);
655 
656       if (duration > 0)
657         seg.duration = duration;
658       else
659         seg.duration = GST_CLOCK_TIME_NONE;
660 
661       GST_INFO_OBJECT (splitpad,
662           "Forwarding segment %" GST_SEGMENT_FORMAT, &seg);
663 
664       gst_event_unref (event);
665       event = gst_event_new_segment (&seg);
666       if (splitmux->segment_seqnum)
667         gst_event_set_seqnum (event, splitmux->segment_seqnum);
668       splitpad->sent_segment = TRUE;
669       break;
670     }
671     case GST_EVENT_CAPS:{
672       if (!gst_splitmux_check_new_caps (splitpad, event))
673         goto drop_event;
674       splitpad->sent_caps = TRUE;
675       break;
676     }
677     default:
678       break;
679   }
680 
681   gst_pad_push_event ((GstPad *) (splitpad), event);
682   return;
683 drop_event:
684   gst_event_unref (event);
685   return;
686 }
687 
688 static GstFlowReturn
gst_splitmux_handle_buffer(GstSplitMuxSrc * splitmux,SplitMuxSrcPad * splitpad,GstBuffer * buf)689 gst_splitmux_handle_buffer (GstSplitMuxSrc * splitmux,
690     SplitMuxSrcPad * splitpad, GstBuffer * buf)
691 {
692   GstFlowReturn ret;
693 
694   if (splitpad->clear_next_discont) {
695     GST_LOG_OBJECT (splitpad, "Clearing discont flag on buffer");
696     GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
697     splitpad->clear_next_discont = FALSE;
698   }
699   if (splitpad->set_next_discont) {
700     GST_LOG_OBJECT (splitpad, "Setting discont flag on buffer");
701     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
702     splitpad->set_next_discont = FALSE;
703   }
704 
705   ret = gst_pad_push (GST_PAD_CAST (splitpad), buf);
706 
707   GST_LOG_OBJECT (splitpad, "Pad push returned %d", ret);
708   return ret;
709 }
710 
711 static guint
count_not_linked(GstSplitMuxSrc * splitmux)712 count_not_linked (GstSplitMuxSrc * splitmux)
713 {
714   GList *cur;
715   guint ret = 0;
716 
717   for (cur = g_list_first (splitmux->pads);
718       cur != NULL; cur = g_list_next (cur)) {
719     SplitMuxSrcPad *splitpad = (SplitMuxSrcPad *) (cur->data);
720     if (GST_PAD_LAST_FLOW_RETURN (splitpad) == GST_FLOW_NOT_LINKED)
721       ret++;
722   }
723 
724   return ret;
725 }
726 
727 static void
gst_splitmux_pad_loop(GstPad * pad)728 gst_splitmux_pad_loop (GstPad * pad)
729 {
730   /* Get one event/buffer from the associated part and push */
731   SplitMuxSrcPad *splitpad = (SplitMuxSrcPad *) (pad);
732   GstSplitMuxSrc *splitmux = (GstSplitMuxSrc *) gst_pad_get_parent (pad);
733   GstDataQueueItem *item = NULL;
734   GstSplitMuxPartReader *reader = splitpad->reader;
735   GstPad *part_pad;
736   GstFlowReturn ret;
737 
738   GST_OBJECT_LOCK (splitpad);
739   if (splitpad->part_pad == NULL) {
740     GST_OBJECT_UNLOCK (splitpad);
741     return;
742   }
743   part_pad = gst_object_ref (splitpad->part_pad);
744   GST_OBJECT_UNLOCK (splitpad);
745 
746   GST_LOG_OBJECT (splitpad, "Popping data queue item from %" GST_PTR_FORMAT
747       " pad %" GST_PTR_FORMAT, reader, part_pad);
748   ret = gst_splitmux_part_reader_pop (reader, part_pad, &item);
749   if (ret == GST_FLOW_ERROR)
750     goto error;
751   if (ret == GST_FLOW_FLUSHING || item == NULL)
752     goto flushing;
753 
754   GST_DEBUG_OBJECT (splitpad, "Got data queue item %" GST_PTR_FORMAT,
755       item->object);
756 
757   if (GST_IS_EVENT (item->object)) {
758     GstEvent *event = (GstEvent *) (item->object);
759     gst_splitmux_handle_event (splitmux, splitpad, part_pad, event);
760   } else {
761     GstBuffer *buf = (GstBuffer *) (item->object);
762     GstFlowReturn ret = gst_splitmux_handle_buffer (splitmux, splitpad, buf);
763     if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_EOS)) {
764       /* Stop immediately on error or flushing */
765       GST_INFO_OBJECT (splitpad, "Stopping due to pad_push() result %d", ret);
766       gst_pad_pause_task (pad);
767       if (ret < GST_FLOW_EOS) {
768         GST_ELEMENT_FLOW_ERROR (splitmux, ret);
769       } else if (ret == GST_FLOW_NOT_LINKED) {
770         gboolean post_error;
771         guint n_notlinked;
772 
773         /* Only post not-linked if all pads are not-linked */
774         SPLITMUX_SRC_PADS_LOCK (splitmux);
775         n_notlinked = count_not_linked (splitmux);
776         post_error = (splitmux->pads_complete
777             && n_notlinked == splitmux->n_pads);
778         SPLITMUX_SRC_PADS_UNLOCK (splitmux);
779 
780         if (post_error)
781           GST_ELEMENT_FLOW_ERROR (splitmux, ret);
782       }
783     }
784   }
785   g_slice_free (GstDataQueueItem, item);
786 
787   gst_object_unref (part_pad);
788   gst_object_unref (splitmux);
789   return;
790 
791 error:
792   /* Fall through */
793   GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, (NULL),
794       ("Error reading part file %s", GST_STR_NULL (reader->path)));
795 flushing:
796   gst_pad_pause_task (pad);
797   gst_object_unref (part_pad);
798   gst_object_unref (splitmux);
799   return;
800 }
801 
802 static gboolean
gst_splitmux_src_activate_part(GstSplitMuxSrc * splitmux,guint part,GstSeekFlags extra_flags)803 gst_splitmux_src_activate_part (GstSplitMuxSrc * splitmux, guint part,
804     GstSeekFlags extra_flags)
805 {
806   GList *cur;
807 
808   GST_DEBUG_OBJECT (splitmux, "Activating part %d", part);
809 
810   splitmux->cur_part = part;
811   if (!gst_splitmux_part_reader_activate (splitmux->parts[part],
812           &splitmux->play_segment, extra_flags))
813     return FALSE;
814 
815   SPLITMUX_SRC_PADS_LOCK (splitmux);
816   for (cur = g_list_first (splitmux->pads);
817       cur != NULL; cur = g_list_next (cur)) {
818     SplitMuxSrcPad *splitpad = (SplitMuxSrcPad *) (cur->data);
819     splitpad->cur_part = part;
820     splitpad->reader = splitmux->parts[splitpad->cur_part];
821     if (splitpad->part_pad)
822       gst_object_unref (splitpad->part_pad);
823     splitpad->part_pad =
824         gst_splitmux_part_reader_lookup_pad (splitpad->reader,
825         (GstPad *) (splitpad));
826 
827     /* Make sure we start with a DISCONT */
828     splitpad->set_next_discont = TRUE;
829     splitpad->clear_next_discont = FALSE;
830 
831     gst_pad_start_task (GST_PAD (splitpad),
832         (GstTaskFunction) gst_splitmux_pad_loop, splitpad, NULL);
833   }
834   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
835 
836   return TRUE;
837 }
838 
839 static gboolean
gst_splitmux_src_prepare_next_part(GstSplitMuxSrc * splitmux)840 gst_splitmux_src_prepare_next_part (GstSplitMuxSrc * splitmux)
841 {
842   guint idx = splitmux->num_prepared_parts;
843 
844   g_assert (idx < splitmux->num_parts);
845 
846   GST_DEBUG_OBJECT (splitmux, "Preparing file part %s (%u)",
847       splitmux->parts[idx]->path, idx);
848 
849   gst_splitmux_part_reader_set_start_offset (splitmux->parts[idx],
850       splitmux->end_offset);
851   if (!gst_splitmux_part_reader_prepare (splitmux->parts[idx])) {
852     GST_WARNING_OBJECT (splitmux,
853         "Failed to prepare file part %s. Cannot play past there.",
854         splitmux->parts[idx]->path);
855     GST_ELEMENT_WARNING (splitmux, RESOURCE, READ, (NULL),
856         ("Failed to prepare file part %s. Cannot play past there.",
857             splitmux->parts[idx]->path));
858     gst_splitmux_part_reader_unprepare (splitmux->parts[idx]);
859     g_object_unref (splitmux->parts[idx]);
860     splitmux->parts[idx] = NULL;
861     return FALSE;
862   }
863 
864   return TRUE;
865 }
866 
867 static gboolean
gst_splitmux_src_start(GstSplitMuxSrc * splitmux)868 gst_splitmux_src_start (GstSplitMuxSrc * splitmux)
869 {
870   gboolean ret = FALSE;
871   GError *err = NULL;
872   gchar *basename = NULL;
873   gchar *dirname = NULL;
874   gchar **files;
875   guint i;
876 
877   GST_DEBUG_OBJECT (splitmux, "Starting");
878 
879   g_signal_emit (splitmux, signals[SIGNAL_FORMAT_LOCATION], 0, &files);
880 
881   if (files == NULL || *files == NULL) {
882     GST_OBJECT_LOCK (splitmux);
883     if (splitmux->location != NULL && splitmux->location[0] != '\0') {
884       basename = g_path_get_basename (splitmux->location);
885       dirname = g_path_get_dirname (splitmux->location);
886     }
887     GST_OBJECT_UNLOCK (splitmux);
888 
889     g_strfreev (files);
890     files = gst_split_util_find_files (dirname, basename, &err);
891 
892     if (files == NULL || *files == NULL)
893       goto no_files;
894   }
895 
896   SPLITMUX_SRC_LOCK (splitmux);
897   splitmux->pads_complete = FALSE;
898   splitmux->running = TRUE;
899   SPLITMUX_SRC_UNLOCK (splitmux);
900 
901   splitmux->num_parts = g_strv_length (files);
902 
903   splitmux->parts = g_new0 (GstSplitMuxPartReader *, splitmux->num_parts);
904 
905   /* Create all part pipelines */
906   for (i = 0; i < splitmux->num_parts; i++) {
907     splitmux->parts[i] = gst_splitmux_part_create (splitmux, files[i]);
908     if (splitmux->parts[i] == NULL)
909       break;
910   }
911 
912   /* Store how many parts we actually created */
913   splitmux->num_created_parts = splitmux->num_parts = i;
914   splitmux->num_prepared_parts = 0;
915 
916   /* Update total_duration state variable */
917   GST_OBJECT_LOCK (splitmux);
918   splitmux->total_duration = 0;
919   splitmux->end_offset = 0;
920   GST_OBJECT_UNLOCK (splitmux);
921 
922   /* Then start the first: it will asynchronously go to PAUSED
923    * or error out and then we can proceed with the next one
924    */
925   if (!gst_splitmux_src_prepare_next_part (splitmux) || splitmux->num_parts < 1)
926     goto failed_part;
927 
928   /* All good now: we have to wait for all parts to be asynchronously
929    * prepared to know the total duration we can play */
930   ret = TRUE;
931 
932 done:
933   if (err != NULL)
934     g_error_free (err);
935   g_strfreev (files);
936   g_free (basename);
937   g_free (dirname);
938 
939   return ret;
940 
941 /* ERRORS */
942 no_files:
943   {
944     GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, ("%s", err->message),
945         ("Failed to find files in '%s' for pattern '%s'",
946             GST_STR_NULL (dirname), GST_STR_NULL (basename)));
947     goto done;
948   }
949 failed_part:
950   {
951     GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, (NULL),
952         ("Failed to open any files for reading"));
953     goto done;
954   }
955 }
956 
957 static gboolean
gst_splitmux_src_stop(GstSplitMuxSrc * splitmux)958 gst_splitmux_src_stop (GstSplitMuxSrc * splitmux)
959 {
960   gboolean ret = TRUE;
961   guint i;
962   GList *cur, *pads_list;
963 
964   SPLITMUX_SRC_LOCK (splitmux);
965   if (!splitmux->running)
966     goto out;
967 
968   GST_DEBUG_OBJECT (splitmux, "Stopping");
969 
970   /* Stop and destroy all parts  */
971   for (i = 0; i < splitmux->num_created_parts; i++) {
972     if (splitmux->parts[i] == NULL)
973       continue;
974     gst_splitmux_part_reader_unprepare (splitmux->parts[i]);
975     g_object_unref (splitmux->parts[i]);
976     splitmux->parts[i] = NULL;
977   }
978 
979   SPLITMUX_SRC_PADS_LOCK (splitmux);
980   pads_list = splitmux->pads;
981   splitmux->pads = NULL;
982   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
983 
984   SPLITMUX_SRC_UNLOCK (splitmux);
985   for (cur = g_list_first (pads_list); cur != NULL; cur = g_list_next (cur)) {
986     SplitMuxSrcPad *tmp = (SplitMuxSrcPad *) (cur->data);
987     gst_pad_stop_task (GST_PAD (tmp));
988     gst_element_remove_pad (GST_ELEMENT (splitmux), GST_PAD (tmp));
989   }
990   g_list_free (pads_list);
991   SPLITMUX_SRC_LOCK (splitmux);
992 
993   g_free (splitmux->parts);
994   splitmux->parts = NULL;
995   splitmux->num_parts = 0;
996   splitmux->num_prepared_parts = 0;
997   splitmux->num_created_parts = 0;
998   splitmux->running = FALSE;
999   splitmux->total_duration = GST_CLOCK_TIME_NONE;
1000   /* Reset playback segment */
1001   gst_segment_init (&splitmux->play_segment, GST_FORMAT_TIME);
1002 out:
1003   SPLITMUX_SRC_UNLOCK (splitmux);
1004   return ret;
1005 }
1006 
1007 typedef struct
1008 {
1009   GstSplitMuxSrc *splitmux;
1010   SplitMuxSrcPad *splitpad;
1011 } SplitMuxAndPad;
1012 
1013 static gboolean
handle_sticky_events(GstPad * pad,GstEvent ** event,gpointer user_data)1014 handle_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
1015 {
1016   SplitMuxAndPad *splitmux_and_pad;
1017   GstSplitMuxSrc *splitmux;
1018   SplitMuxSrcPad *splitpad;
1019 
1020   splitmux_and_pad = user_data;
1021   splitmux = splitmux_and_pad->splitmux;
1022   splitpad = splitmux_and_pad->splitpad;
1023 
1024   GST_DEBUG_OBJECT (splitpad, "handle sticky event %" GST_PTR_FORMAT, *event);
1025   gst_event_ref (*event);
1026   gst_splitmux_handle_event (splitmux, splitpad, pad, *event);
1027 
1028   return TRUE;
1029 }
1030 
1031 static GstPad *
gst_splitmux_find_output_pad(GstSplitMuxPartReader * part,GstPad * pad,GstSplitMuxSrc * splitmux)1032 gst_splitmux_find_output_pad (GstSplitMuxPartReader * part, GstPad * pad,
1033     GstSplitMuxSrc * splitmux)
1034 {
1035   GList *cur;
1036   gchar *pad_name = gst_pad_get_name (pad);
1037   GstPad *target = NULL;
1038   gboolean is_new_pad = FALSE;
1039 
1040   SPLITMUX_SRC_LOCK (splitmux);
1041   SPLITMUX_SRC_PADS_LOCK (splitmux);
1042   for (cur = g_list_first (splitmux->pads);
1043       cur != NULL; cur = g_list_next (cur)) {
1044     GstPad *tmp = (GstPad *) (cur->data);
1045     if (g_str_equal (GST_PAD_NAME (tmp), pad_name)) {
1046       target = tmp;
1047       break;
1048     }
1049   }
1050 
1051   if (target == NULL && !splitmux->pads_complete) {
1052     SplitMuxAndPad splitmux_and_pad;
1053 
1054     /* No pad found, create one */
1055     target = g_object_new (SPLITMUX_TYPE_SRC_PAD,
1056         "name", pad_name, "direction", GST_PAD_SRC, NULL);
1057     splitmux->pads = g_list_prepend (splitmux->pads, target);
1058     splitmux->n_pads++;
1059 
1060     gst_pad_set_active (target, TRUE);
1061 
1062     splitmux_and_pad.splitmux = splitmux;
1063     splitmux_and_pad.splitpad = (SplitMuxSrcPad *) target;
1064     gst_pad_sticky_events_foreach (pad, handle_sticky_events,
1065         &splitmux_and_pad);
1066     is_new_pad = TRUE;
1067   }
1068   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1069   SPLITMUX_SRC_UNLOCK (splitmux);
1070 
1071   g_free (pad_name);
1072 
1073   if (target == NULL)
1074     goto pad_not_found;
1075 
1076   if (is_new_pad)
1077     gst_element_add_pad (GST_ELEMENT_CAST (splitmux), target);
1078 
1079   return target;
1080 
1081 pad_not_found:
1082   GST_ELEMENT_ERROR (splitmux, STREAM, FAILED, (NULL),
1083       ("Stream part %s contains extra unknown pad %" GST_PTR_FORMAT,
1084           part->path, pad));
1085   return NULL;
1086 }
1087 
1088 static void
gst_splitmux_push_event(GstSplitMuxSrc * splitmux,GstEvent * e,guint32 seqnum)1089 gst_splitmux_push_event (GstSplitMuxSrc * splitmux, GstEvent * e,
1090     guint32 seqnum)
1091 {
1092   GList *cur;
1093 
1094   if (seqnum) {
1095     e = gst_event_make_writable (e);
1096     gst_event_set_seqnum (e, seqnum);
1097   }
1098 
1099   SPLITMUX_SRC_PADS_LOCK (splitmux);
1100   for (cur = g_list_first (splitmux->pads);
1101       cur != NULL; cur = g_list_next (cur)) {
1102     GstPad *pad = GST_PAD_CAST (cur->data);
1103     gst_event_ref (e);
1104     gst_pad_push_event (pad, e);
1105   }
1106   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1107 
1108   gst_event_unref (e);
1109 }
1110 
1111 static void
gst_splitmux_push_flush_stop(GstSplitMuxSrc * splitmux,guint32 seqnum)1112 gst_splitmux_push_flush_stop (GstSplitMuxSrc * splitmux, guint32 seqnum)
1113 {
1114   GstEvent *e = gst_event_new_flush_stop (TRUE);
1115   GList *cur;
1116 
1117   if (seqnum) {
1118     e = gst_event_make_writable (e);
1119     gst_event_set_seqnum (e, seqnum);
1120   }
1121 
1122   SPLITMUX_SRC_PADS_LOCK (splitmux);
1123   for (cur = g_list_first (splitmux->pads);
1124       cur != NULL; cur = g_list_next (cur)) {
1125     SplitMuxSrcPad *target = (SplitMuxSrcPad *) (cur->data);
1126 
1127     gst_event_ref (e);
1128     gst_pad_push_event (GST_PAD_CAST (target), e);
1129     target->sent_caps = FALSE;
1130     target->sent_stream_start = FALSE;
1131     target->sent_segment = FALSE;
1132   }
1133   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1134 
1135   gst_event_unref (e);
1136 }
1137 
1138 /* Callback for when a part finishes and we need to move to the next */
1139 static gboolean
gst_splitmux_end_of_part(GstSplitMuxSrc * splitmux,SplitMuxSrcPad * splitpad)1140 gst_splitmux_end_of_part (GstSplitMuxSrc * splitmux, SplitMuxSrcPad * splitpad)
1141 {
1142   gint next_part = -1;
1143   gint cur_part = splitpad->cur_part;
1144   gboolean res = FALSE;
1145 
1146   if (splitmux->play_segment.rate >= 0.0) {
1147     if (cur_part + 1 < splitmux->num_parts)
1148       next_part = cur_part + 1;
1149     /* Make sure the transition is seamless */
1150     splitpad->set_next_discont = FALSE;
1151     splitpad->clear_next_discont = TRUE;
1152   } else {
1153     /* Reverse play - move to previous segment */
1154     if (cur_part > 0) {
1155       next_part = cur_part - 1;
1156       /* Non-seamless transition in reverse */
1157       splitpad->set_next_discont = TRUE;
1158       splitpad->clear_next_discont = FALSE;
1159     }
1160   }
1161 
1162   SPLITMUX_SRC_LOCK (splitmux);
1163 
1164   /* If all pads are done with this part, deactivate it */
1165   if (gst_splitmux_part_is_eos (splitmux->parts[splitpad->cur_part]))
1166     gst_splitmux_part_reader_deactivate (splitmux->parts[cur_part]);
1167 
1168   if (splitmux->play_segment.rate >= 0.0) {
1169     if (splitmux->play_segment.stop != -1) {
1170       GstClockTime part_end =
1171           gst_splitmux_part_reader_get_end_offset (splitmux->parts[cur_part]);
1172       if (part_end >= splitmux->play_segment.stop) {
1173         GST_DEBUG_OBJECT (splitmux,
1174             "Stop position was within that part. Finishing");
1175         next_part = -1;
1176       }
1177     }
1178   } else {
1179     if (splitmux->play_segment.start != -1) {
1180       GstClockTime part_start =
1181           gst_splitmux_part_reader_get_start_offset (splitmux->parts[cur_part]);
1182       if (part_start <= splitmux->play_segment.start) {
1183         GST_DEBUG_OBJECT (splitmux,
1184             "Start position %" GST_TIME_FORMAT
1185             " was within that part. Finishing",
1186             GST_TIME_ARGS (splitmux->play_segment.start));
1187         next_part = -1;
1188       }
1189     }
1190   }
1191 
1192   if (next_part != -1) {
1193     GST_DEBUG_OBJECT (splitmux, "At EOS on pad %" GST_PTR_FORMAT
1194         " moving to part %d", splitpad, next_part);
1195     splitpad->cur_part = next_part;
1196     splitpad->reader = splitmux->parts[splitpad->cur_part];
1197     if (splitpad->part_pad)
1198       gst_object_unref (splitpad->part_pad);
1199     splitpad->part_pad =
1200         gst_splitmux_part_reader_lookup_pad (splitpad->reader,
1201         (GstPad *) (splitpad));
1202 
1203     if (splitmux->cur_part != next_part) {
1204       if (!gst_splitmux_part_reader_is_active (splitpad->reader)) {
1205         GstSegment tmp;
1206         /* If moving backward into a new part, set stop
1207          * to -1 to ensure we play the entire file - workaround
1208          * a bug in qtdemux that misses bits at the end */
1209         gst_segment_copy_into (&splitmux->play_segment, &tmp);
1210         if (tmp.rate < 0)
1211           tmp.stop = -1;
1212 
1213         /* This is the first pad to move to the new part, activate it */
1214         GST_DEBUG_OBJECT (splitpad,
1215             "First pad to change part. Activating part %d with seg %"
1216             GST_SEGMENT_FORMAT, next_part, &tmp);
1217         if (!gst_splitmux_part_reader_activate (splitpad->reader, &tmp,
1218                 GST_SEEK_FLAG_NONE))
1219           goto error;
1220       }
1221       splitmux->cur_part = next_part;
1222     }
1223     res = TRUE;
1224   }
1225 
1226   SPLITMUX_SRC_UNLOCK (splitmux);
1227   return res;
1228 error:
1229   SPLITMUX_SRC_UNLOCK (splitmux);
1230   GST_ELEMENT_ERROR (splitmux, RESOURCE, READ, (NULL),
1231       ("Failed to activate part %d", splitmux->cur_part));
1232   return FALSE;
1233 }
1234 
1235 G_DEFINE_TYPE (SplitMuxSrcPad, splitmux_src_pad, GST_TYPE_PAD);
1236 
1237 static void
splitmux_src_pad_constructed(GObject * pad)1238 splitmux_src_pad_constructed (GObject * pad)
1239 {
1240   gst_pad_set_event_function (GST_PAD (pad),
1241       GST_DEBUG_FUNCPTR (splitmux_src_pad_event));
1242   gst_pad_set_query_function (GST_PAD (pad),
1243       GST_DEBUG_FUNCPTR (splitmux_src_pad_query));
1244 
1245   G_OBJECT_CLASS (splitmux_src_pad_parent_class)->constructed (pad);
1246 }
1247 
1248 static void
gst_splitmux_src_pad_dispose(GObject * object)1249 gst_splitmux_src_pad_dispose (GObject * object)
1250 {
1251   SplitMuxSrcPad *pad = (SplitMuxSrcPad *) (object);
1252 
1253   GST_OBJECT_LOCK (pad);
1254   if (pad->part_pad) {
1255     gst_object_unref (pad->part_pad);
1256     pad->part_pad = NULL;
1257   }
1258   GST_OBJECT_UNLOCK (pad);
1259 
1260   G_OBJECT_CLASS (splitmux_src_pad_parent_class)->dispose (object);
1261 }
1262 
1263 static void
splitmux_src_pad_class_init(SplitMuxSrcPadClass * klass)1264 splitmux_src_pad_class_init (SplitMuxSrcPadClass * klass)
1265 {
1266   GObjectClass *gobject_klass = (GObjectClass *) (klass);
1267 
1268   gobject_klass->constructed = splitmux_src_pad_constructed;
1269   gobject_klass->dispose = gst_splitmux_src_pad_dispose;
1270 }
1271 
1272 static void
splitmux_src_pad_init(SplitMuxSrcPad * pad)1273 splitmux_src_pad_init (SplitMuxSrcPad * pad)
1274 {
1275 }
1276 
1277 /* Event handler for source pads. Proxy events into the child
1278  * parts as needed
1279  */
1280 static gboolean
splitmux_src_pad_event(GstPad * pad,GstObject * parent,GstEvent * event)1281 splitmux_src_pad_event (GstPad * pad, GstObject * parent, GstEvent * event)
1282 {
1283   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (parent);
1284   gboolean ret = FALSE;
1285 
1286   GST_DEBUG_OBJECT (parent, "event %" GST_PTR_FORMAT
1287       " on %" GST_PTR_FORMAT, event, pad);
1288 
1289   switch (GST_EVENT_TYPE (event)) {
1290     case GST_EVENT_SEEK:{
1291       GstFormat format;
1292       gdouble rate;
1293       GstSeekFlags flags;
1294       GstSeekType start_type, stop_type;
1295       gint64 start, stop;
1296       guint32 seqnum;
1297       gint i;
1298       GstClockTime part_start, position;
1299       GList *cur;
1300       GstSegment tmp;
1301 
1302       gst_event_parse_seek (event, &rate, &format, &flags,
1303           &start_type, &start, &stop_type, &stop);
1304 
1305       if (format != GST_FORMAT_TIME) {
1306         GST_DEBUG_OBJECT (splitmux, "can only seek on TIME");
1307         goto error;
1308       }
1309       /* FIXME: Support non-flushing seeks, which might never wake up */
1310       if (!(flags & GST_SEEK_FLAG_FLUSH)) {
1311         GST_DEBUG_OBJECT (splitmux, "Only flushing seeks supported");
1312         goto error;
1313       }
1314       seqnum = gst_event_get_seqnum (event);
1315 
1316       SPLITMUX_SRC_LOCK (splitmux);
1317       if (!splitmux->running || splitmux->num_parts < 1) {
1318         /* Not started yet */
1319         SPLITMUX_SRC_UNLOCK (splitmux);
1320         goto error;
1321       }
1322 
1323       gst_segment_copy_into (&splitmux->play_segment, &tmp);
1324 
1325       if (!gst_segment_do_seek (&tmp, rate,
1326               format, flags, start_type, start, stop_type, stop, NULL)) {
1327         /* Invalid seek requested, ignore it */
1328         SPLITMUX_SRC_UNLOCK (splitmux);
1329         goto error;
1330       }
1331       position = tmp.position;
1332 
1333       GST_DEBUG_OBJECT (splitmux, "Performing seek with seg %"
1334           GST_SEGMENT_FORMAT, &tmp);
1335 
1336       GST_DEBUG_OBJECT (splitmux,
1337           "Handling flushing seek. Sending flush start");
1338 
1339       /* Send flush_start */
1340       gst_splitmux_push_event (splitmux, gst_event_new_flush_start (), seqnum);
1341 
1342       /* Stop all parts, which will work because of the flush */
1343       SPLITMUX_SRC_PADS_LOCK (splitmux);
1344       SPLITMUX_SRC_UNLOCK (splitmux);
1345       for (cur = g_list_first (splitmux->pads);
1346           cur != NULL; cur = g_list_next (cur)) {
1347         SplitMuxSrcPad *target = (SplitMuxSrcPad *) (cur->data);
1348         GstSplitMuxPartReader *reader = splitmux->parts[target->cur_part];
1349         gst_splitmux_part_reader_deactivate (reader);
1350       }
1351 
1352       /* Shut down pad tasks */
1353       GST_DEBUG_OBJECT (splitmux, "Pausing pad tasks");
1354       for (cur = g_list_first (splitmux->pads);
1355           cur != NULL; cur = g_list_next (cur)) {
1356         GstPad *splitpad = (GstPad *) (cur->data);
1357         gst_pad_pause_task (GST_PAD (splitpad));
1358       }
1359       SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1360       SPLITMUX_SRC_LOCK (splitmux);
1361 
1362       /* Send flush stop */
1363       GST_DEBUG_OBJECT (splitmux, "Sending flush stop");
1364       gst_splitmux_push_flush_stop (splitmux, seqnum);
1365 
1366       /* Everything is stopped, so update the play_segment */
1367       gst_segment_copy_into (&tmp, &splitmux->play_segment);
1368       splitmux->segment_seqnum = seqnum;
1369 
1370       /* Work out where to start from now */
1371       for (i = 0; i < splitmux->num_parts; i++) {
1372         GstSplitMuxPartReader *reader = splitmux->parts[i];
1373         GstClockTime part_end =
1374             gst_splitmux_part_reader_get_end_offset (reader);
1375 
1376         if (part_end > position)
1377           break;
1378       }
1379       if (i == splitmux->num_parts)
1380         i = splitmux->num_parts - 1;
1381 
1382       part_start =
1383           gst_splitmux_part_reader_get_start_offset (splitmux->parts[i]);
1384 
1385       GST_DEBUG_OBJECT (splitmux,
1386           "Seek to time %" GST_TIME_FORMAT " landed in part %d offset %"
1387           GST_TIME_FORMAT, GST_TIME_ARGS (position),
1388           i, GST_TIME_ARGS (position - part_start));
1389 
1390       ret = gst_splitmux_src_activate_part (splitmux, i, flags);
1391       SPLITMUX_SRC_UNLOCK (splitmux);
1392     }
1393     default:
1394       break;
1395   }
1396 
1397   gst_event_unref (event);
1398 error:
1399   return ret;
1400 }
1401 
1402 static gboolean
splitmux_src_pad_query(GstPad * pad,GstObject * parent,GstQuery * query)1403 splitmux_src_pad_query (GstPad * pad, GstObject * parent, GstQuery * query)
1404 {
1405   /* Query handler for source pads. Proxy queries into the child
1406    * parts as needed
1407    */
1408   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (parent);
1409   gboolean ret = FALSE;
1410 
1411   GST_LOG_OBJECT (parent, "query %" GST_PTR_FORMAT
1412       " on %" GST_PTR_FORMAT, query, pad);
1413   switch (GST_QUERY_TYPE (query)) {
1414     case GST_QUERY_CAPS:
1415     case GST_QUERY_POSITION:{
1416       GstSplitMuxPartReader *part;
1417       SplitMuxSrcPad *anypad;
1418 
1419       SPLITMUX_SRC_LOCK (splitmux);
1420       SPLITMUX_SRC_PADS_LOCK (splitmux);
1421       anypad = (SplitMuxSrcPad *) (splitmux->pads->data);
1422       part = splitmux->parts[anypad->cur_part];
1423       ret = gst_splitmux_part_reader_src_query (part, pad, query);
1424       SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1425       SPLITMUX_SRC_UNLOCK (splitmux);
1426       break;
1427     }
1428     case GST_QUERY_DURATION:{
1429       GstClockTime duration;
1430       GstFormat fmt;
1431 
1432       gst_query_parse_duration (query, &fmt, NULL);
1433       if (fmt != GST_FORMAT_TIME)
1434         break;
1435 
1436       GST_OBJECT_LOCK (splitmux);
1437       duration = splitmux->total_duration;
1438       GST_OBJECT_UNLOCK (splitmux);
1439 
1440       if (duration > 0 && duration != GST_CLOCK_TIME_NONE) {
1441         gst_query_set_duration (query, GST_FORMAT_TIME, duration);
1442         ret = TRUE;
1443       }
1444       break;
1445     }
1446     case GST_QUERY_SEEKING:{
1447       GstFormat format;
1448 
1449       gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
1450       if (format != GST_FORMAT_TIME)
1451         break;
1452 
1453       GST_OBJECT_LOCK (splitmux);
1454       gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0,
1455           splitmux->total_duration);
1456       ret = TRUE;
1457       GST_OBJECT_UNLOCK (splitmux);
1458 
1459       break;
1460     }
1461     default:
1462       break;
1463   }
1464   return ret;
1465 }
1466 
1467 
1468 gboolean
register_splitmuxsrc(GstPlugin * plugin)1469 register_splitmuxsrc (GstPlugin * plugin)
1470 {
1471   GST_DEBUG_CATEGORY_INIT (splitmux_debug, "splitmuxsrc", 0,
1472       "Split File Demuxing Source");
1473 
1474   return gst_element_register (plugin, "splitmuxsrc", GST_RANK_NONE,
1475       GST_TYPE_SPLITMUX_SRC);
1476 }
1477