multifile: attempt to fix docs build issue on build bot
[platform/upstream/gst-plugins-good.git] / gst / multifile / gstsplitmuxsrc.c
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 static GstStaticPadTemplate video_src_template =
65 GST_STATIC_PAD_TEMPLATE ("video",
66     GST_PAD_SRC,
67     GST_PAD_SOMETIMES,
68     GST_STATIC_CAPS_ANY);
69
70 static GstStaticPadTemplate audio_src_template =
71 GST_STATIC_PAD_TEMPLATE ("audio_%u",
72     GST_PAD_SRC,
73     GST_PAD_SOMETIMES,
74     GST_STATIC_CAPS_ANY);
75
76 static GstStaticPadTemplate subtitle_src_template =
77 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
78     GST_PAD_SRC,
79     GST_PAD_SOMETIMES,
80     GST_STATIC_CAPS_ANY);
81
82 static GstStateChangeReturn gst_splitmux_src_change_state (GstElement *
83     element, GstStateChange transition);
84 static void gst_splitmux_src_set_property (GObject * object, guint prop_id,
85     const GValue * value, GParamSpec * pspec);
86 static void gst_splitmux_src_get_property (GObject * object, guint prop_id,
87     GValue * value, GParamSpec * pspec);
88 static void gst_splitmux_src_dispose (GObject * object);
89 static void gst_splitmux_src_finalize (GObject * object);
90 static gboolean gst_splitmux_src_start (GstSplitMuxSrc * splitmux);
91 static gboolean gst_splitmux_src_stop (GstSplitMuxSrc * splitmux);
92 static void splitmux_src_pad_constructed (GObject * pad);
93 static gboolean splitmux_src_pad_event (GstPad * pad, GstObject * parent,
94     GstEvent * event);
95 static gboolean splitmux_src_pad_query (GstPad * pad, GstObject * parent,
96     GstQuery * query);
97 static void splitmux_src_uri_handler_init (gpointer g_iface,
98     gpointer iface_data);
99
100
101 static GstPad *gst_splitmux_find_output_pad (GstSplitMuxPartReader * part,
102     GstPad * pad, GstSplitMuxSrc * splitmux);
103 static void gst_splitmux_part_prepared (GstSplitMuxPartReader * reader,
104     GstSplitMuxSrc * splitmux);
105 static gboolean gst_splitmux_end_of_part (GstSplitMuxSrc * splitmux,
106     SplitMuxSrcPad * pad);
107 static gboolean gst_splitmux_check_new_caps (SplitMuxSrcPad * splitpad,
108     GstEvent * event);
109
110 #define _do_init \
111     G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, splitmux_src_uri_handler_init);
112 #define gst_splitmux_src_parent_class parent_class
113
114 G_DEFINE_TYPE_EXTENDED (GstSplitMuxSrc, gst_splitmux_src, GST_TYPE_BIN, 0,
115     _do_init);
116
117 static GstURIType
118 splitmux_src_uri_get_type (GType type)
119 {
120   return GST_URI_SRC;
121 }
122
123 static const gchar *const *
124 splitmux_src_uri_get_protocols (GType type)
125 {
126   static const gchar *protocols[] = { "splitmux", NULL };
127
128   return protocols;
129 }
130
131 static gchar *
132 splitmux_src_uri_get_uri (GstURIHandler * handler)
133 {
134   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (handler);
135   gchar *ret = NULL;
136
137   GST_OBJECT_LOCK (splitmux);
138   if (splitmux->location)
139     ret = g_strdup_printf ("splitmux://%s", splitmux->location);
140   GST_OBJECT_UNLOCK (splitmux);
141   return ret;
142 }
143
144 static gboolean
145 splitmux_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
146     GError ** err)
147 {
148   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (handler);
149   gchar *protocol, *location;
150
151   protocol = gst_uri_get_protocol (uri);
152   if (protocol == NULL || !g_str_equal (protocol, "splitmux"))
153     goto wrong_uri;
154   g_free (protocol);
155
156   location = gst_uri_get_location (uri);
157   GST_OBJECT_LOCK (splitmux);
158   g_free (splitmux->location);
159   splitmux->location = location;
160   GST_OBJECT_UNLOCK (splitmux);
161
162   return TRUE;
163
164 wrong_uri:
165   g_free (protocol);
166   GST_ELEMENT_ERROR (splitmux, RESOURCE, READ, (NULL),
167       ("Error parsing uri %s", uri));
168   g_set_error_literal (err, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
169       "Could not parse splitmux URI");
170   return FALSE;
171 }
172
173 static void
174 splitmux_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
175 {
176   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) (g_iface);
177
178   iface->get_type = splitmux_src_uri_get_type;
179   iface->get_protocols = splitmux_src_uri_get_protocols;
180   iface->set_uri = splitmux_src_uri_set_uri;
181   iface->get_uri = splitmux_src_uri_get_uri;
182 }
183
184
185 static void
186 gst_splitmux_src_class_init (GstSplitMuxSrcClass * klass)
187 {
188   GObjectClass *gobject_class = (GObjectClass *) klass;
189   GstElementClass *gstelement_class = (GstElementClass *) klass;
190
191   gobject_class->set_property = gst_splitmux_src_set_property;
192   gobject_class->get_property = gst_splitmux_src_get_property;
193   gobject_class->dispose = gst_splitmux_src_dispose;
194   gobject_class->finalize = gst_splitmux_src_finalize;
195
196   gst_element_class_set_static_metadata (gstelement_class,
197       "Split File Demuxing Bin", "Generic/Bin/Demuxer",
198       "Source that reads a set of files created by splitmuxsink",
199       "Jan Schmidt <jan@centricular.com>");
200
201   gst_element_class_add_pad_template (gstelement_class,
202       gst_static_pad_template_get (&video_src_template));
203   gst_element_class_add_pad_template (gstelement_class,
204       gst_static_pad_template_get (&audio_src_template));
205   gst_element_class_add_pad_template (gstelement_class,
206       gst_static_pad_template_get (&subtitle_src_template));
207
208   gstelement_class->change_state =
209       GST_DEBUG_FUNCPTR (gst_splitmux_src_change_state);
210
211   g_object_class_install_property (gobject_class, PROP_LOCATION,
212       g_param_spec_string ("location", "File Input Pattern",
213           "Glob pattern for the location of the files to read", NULL,
214           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
215 }
216
217 static void
218 gst_splitmux_src_init (GstSplitMuxSrc * splitmux)
219 {
220   g_mutex_init (&splitmux->lock);
221   g_mutex_init (&splitmux->pads_lock);
222   splitmux->total_duration = GST_CLOCK_TIME_NONE;
223   gst_segment_init (&splitmux->play_segment, GST_FORMAT_TIME);
224 }
225
226 static void
227 gst_splitmux_src_dispose (GObject * object)
228 {
229   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
230   GList *cur;
231
232   SPLITMUX_SRC_PADS_LOCK (splitmux);
233
234   for (cur = g_list_first (splitmux->pads);
235       cur != NULL; cur = g_list_next (cur)) {
236     GstPad *pad = GST_PAD (cur->data);
237     gst_element_remove_pad (GST_ELEMENT (splitmux), pad);
238   }
239   g_list_free (splitmux->pads);
240   splitmux->pads = NULL;
241   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
242
243   G_OBJECT_CLASS (parent_class)->dispose (object);
244 }
245
246 static void
247 gst_splitmux_src_finalize (GObject * object)
248 {
249   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
250   g_mutex_clear (&splitmux->lock);
251   g_mutex_clear (&splitmux->pads_lock);
252   g_free (splitmux->location);
253
254   G_OBJECT_CLASS (parent_class)->finalize (object);
255 }
256
257 static void
258 gst_splitmux_src_set_property (GObject * object, guint prop_id,
259     const GValue * value, GParamSpec * pspec)
260 {
261   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
262
263   switch (prop_id) {
264     case PROP_LOCATION:{
265       GST_OBJECT_LOCK (splitmux);
266       g_free (splitmux->location);
267       splitmux->location = g_value_dup_string (value);
268       GST_OBJECT_UNLOCK (splitmux);
269       break;
270     }
271     default:
272       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
273       break;
274   }
275 }
276
277 static void
278 gst_splitmux_src_get_property (GObject * object, guint prop_id,
279     GValue * value, GParamSpec * pspec)
280 {
281   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (object);
282
283   switch (prop_id) {
284     case PROP_LOCATION:
285       GST_OBJECT_LOCK (splitmux);
286       g_value_set_string (value, splitmux->location);
287       GST_OBJECT_UNLOCK (splitmux);
288       break;
289     default:
290       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291       break;
292   }
293 }
294
295 static GstStateChangeReturn
296 gst_splitmux_src_change_state (GstElement * element, GstStateChange transition)
297 {
298   GstStateChangeReturn ret;
299   GstSplitMuxSrc *splitmux = (GstSplitMuxSrc *) element;
300
301   switch (transition) {
302     case GST_STATE_CHANGE_NULL_TO_READY:{
303       break;
304     }
305     case GST_STATE_CHANGE_READY_TO_PAUSED:{
306       if (!gst_splitmux_src_start (splitmux))
307         return GST_STATE_CHANGE_FAILURE;
308       break;
309     }
310     case GST_STATE_CHANGE_PAUSED_TO_READY:
311     case GST_STATE_CHANGE_READY_TO_NULL:
312       break;
313     default:
314       break;
315   }
316
317   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
318   if (ret == GST_STATE_CHANGE_FAILURE)
319     goto beach;
320
321   switch (transition) {
322     case GST_STATE_CHANGE_READY_TO_NULL:
323       if (!gst_splitmux_src_stop (splitmux))
324         return GST_STATE_CHANGE_FAILURE;
325       break;
326     default:
327       break;
328   }
329
330 beach:
331   return ret;
332 }
333
334 static GstSplitMuxPartReader *
335 gst_splitmux_part_create (GstSplitMuxSrc * splitmux, char *filename)
336 {
337   GstSplitMuxPartReader *r;
338
339   r = g_object_new (GST_TYPE_SPLITMUX_PART_READER, NULL);
340
341   g_signal_connect (r, "prepared", (GCallback) gst_splitmux_part_prepared,
342       splitmux);
343
344   gst_splitmux_part_reader_set_callbacks (r, splitmux,
345       (GstSplitMuxPartReaderPadCb) gst_splitmux_find_output_pad);
346   gst_splitmux_part_reader_set_location (r, filename);
347
348   return r;
349 }
350
351 static gboolean
352 resend_sticky (GstPad * pad, GstEvent ** event, GstPad * target)
353 {
354   switch (GST_EVENT_TYPE (*event)) {
355     case GST_EVENT_CAPS:
356       if (!gst_splitmux_check_new_caps (SPLITMUX_SRC_PAD (target), *event))
357         return TRUE;
358       return gst_pad_push_event (target, gst_event_ref (*event));
359     case GST_EVENT_STREAM_START:
360       return gst_pad_push_event (target, gst_event_ref (*event));
361     default:
362       return TRUE;
363   }
364
365   return TRUE;
366 }
367
368 static gboolean
369 gst_splitmux_check_new_caps (SplitMuxSrcPad * splitpad, GstEvent * event)
370 {
371   GstCaps *curcaps = gst_pad_get_current_caps ((GstPad *) (splitpad));
372   GstCaps *newcaps;
373   GstCaps *tmpcaps;
374   GstCaps *tmpcurcaps;
375
376   GstStructure *s;
377   gboolean res = TRUE;
378
379   gst_event_parse_caps (event, &newcaps);
380
381   GST_LOG_OBJECT (splitpad, "Comparing caps %" GST_PTR_FORMAT
382       " and %" GST_PTR_FORMAT, curcaps, newcaps);
383
384   if (curcaps == NULL)
385     return TRUE;
386
387   /* If caps are exactly equal exit early */
388   if (gst_caps_is_equal (curcaps, newcaps)) {
389     gst_caps_unref (curcaps);
390     return FALSE;
391   }
392
393   /* More extensive check, ignore changes in framerate, because
394    * demuxers get that wrong */
395   tmpcaps = gst_caps_copy (newcaps);
396   s = gst_caps_get_structure (tmpcaps, 0);
397   gst_structure_remove_field (s, "framerate");
398
399   tmpcurcaps = gst_caps_copy (curcaps);
400   gst_caps_unref (curcaps);
401   s = gst_caps_get_structure (tmpcurcaps, 0);
402   gst_structure_remove_field (s, "framerate");
403
404   /* Now check if these filtered caps are equal */
405   if (gst_caps_is_equal (tmpcurcaps, tmpcaps)) {
406     GST_INFO_OBJECT (splitpad, "Ignoring framerate-only caps change");
407     res = FALSE;
408   }
409
410   gst_caps_unref (tmpcaps);
411   gst_caps_unref (tmpcurcaps);
412   return res;
413 }
414
415 static void
416 gst_splitmux_handle_event (GstSplitMuxSrc * splitmux,
417     SplitMuxSrcPad * splitpad, GstPad * part_pad, GstEvent * event)
418 {
419   switch (GST_EVENT_TYPE (event)) {
420     case GST_EVENT_STREAM_START:{
421       if (splitpad->sent_stream_start)
422         goto drop_event;
423       splitpad->sent_stream_start = TRUE;
424       break;
425     }
426     case GST_EVENT_EOS:{
427       if (gst_splitmux_end_of_part (splitmux, splitpad))
428         // Continuing to next part, drop the EOS
429         goto drop_event;
430       break;
431     }
432     case GST_EVENT_SEGMENT:{
433       GstSegment seg;
434
435       gst_event_copy_segment (event, &seg);
436
437       splitpad->segment.position = seg.position;
438
439       if (splitpad->sent_segment)
440         goto drop_event;        /* We already forwarded a segment event */
441
442       /* Calculate output segment */
443       GST_LOG_OBJECT (splitpad, "Pad seg %" GST_SEGMENT_FORMAT
444           " got seg %" GST_SEGMENT_FORMAT
445           " play seg %" GST_SEGMENT_FORMAT,
446           &splitpad->segment, &seg, &splitmux->play_segment);
447
448       /* If playing forward, take the stop time from the overall
449        * seg or play_segment */
450       if (splitmux->play_segment.rate > 0.0) {
451         if (splitmux->play_segment.stop != -1)
452           seg.stop = splitmux->play_segment.stop;
453         else
454           seg.stop = splitpad->segment.stop;
455       } else {
456         /* Reverse playback from stop time to start time */
457         /* See if an end point was requested in the seek */
458         if (splitmux->play_segment.start != -1) {
459           seg.start = splitmux->play_segment.start;
460           seg.time = splitmux->play_segment.time;
461         } else {
462           seg.start = splitpad->segment.start;
463           seg.time = splitpad->segment.time;
464         }
465       }
466
467       GST_INFO_OBJECT (splitpad,
468           "Forwarding segment %" GST_SEGMENT_FORMAT, &seg);
469
470       gst_event_unref (event);
471       event = gst_event_new_segment (&seg);
472       splitpad->sent_segment = TRUE;
473       break;
474     }
475     case GST_EVENT_CAPS:{
476       if (!gst_splitmux_check_new_caps (splitpad, event))
477         goto drop_event;
478       splitpad->sent_caps = TRUE;
479       break;
480     }
481     default:
482       break;
483   }
484
485   /* Make sure to send sticky events - from the part_pad directly */
486   if (splitpad->sent_caps == FALSE || splitpad->sent_stream_start == FALSE) {
487     gst_pad_sticky_events_foreach (GST_PAD_CAST (part_pad),
488         (GstPadStickyEventsForeachFunction) (resend_sticky), splitpad);
489     splitpad->sent_caps = splitpad->sent_stream_start = TRUE;
490   }
491
492   gst_pad_push_event ((GstPad *) (splitpad), event);
493   return;
494 drop_event:
495   gst_event_unref (event);
496   return;
497 }
498
499 static GstFlowReturn
500 gst_splitmux_handle_buffer (GstSplitMuxSrc * splitmux,
501     SplitMuxSrcPad * splitpad, GstBuffer * buf)
502 {
503   GstFlowReturn ret;
504
505   if (splitpad->clear_next_discont) {
506     GST_LOG_OBJECT (splitpad, "Clearing discont flag on buffer");
507     GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
508     splitpad->clear_next_discont = FALSE;
509   }
510   if (splitpad->set_next_discont) {
511     GST_LOG_OBJECT (splitpad, "Setting discont flag on buffer");
512     GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
513     splitpad->set_next_discont = FALSE;
514   }
515
516   ret = gst_pad_push (GST_PAD_CAST (splitpad), buf);
517
518   GST_LOG_OBJECT (splitpad, "Pad push returned %d", ret);
519   return ret;
520 }
521
522 static void
523 gst_splitmux_pad_loop (GstPad * pad)
524 {
525   /* Get one event/buffer from the associated part and push */
526   SplitMuxSrcPad *splitpad = (SplitMuxSrcPad *) (pad);
527   GstSplitMuxSrc *splitmux = (GstSplitMuxSrc *) gst_pad_get_parent (pad);
528   GstDataQueueItem *item = NULL;
529   GstSplitMuxPartReader *reader = splitpad->reader;
530   GstPad *part_pad = splitpad->part_pad;
531   GstFlowReturn ret;
532
533   GST_LOG_OBJECT (splitpad, "Popping data queue item from %" GST_PTR_FORMAT
534       " pad %" GST_PTR_FORMAT, reader, part_pad);
535   ret = gst_splitmux_part_reader_pop (reader, part_pad, &item);
536   if (ret == GST_FLOW_ERROR)
537     goto error;
538   if (ret == GST_FLOW_FLUSHING || item == NULL)
539     goto flushing;
540
541   GST_DEBUG_OBJECT (splitpad, "Got data queue item %" GST_PTR_FORMAT,
542       item->object);
543
544   if (GST_IS_EVENT (item->object)) {
545     GstEvent *event = (GstEvent *) (item->object);
546     gst_splitmux_handle_event (splitmux, splitpad, part_pad, event);
547   } else {
548     GstBuffer *buf = (GstBuffer *) (item->object);
549     GstFlowReturn ret = gst_splitmux_handle_buffer (splitmux, splitpad, buf);
550     if (G_UNLIKELY (ret != GST_FLOW_OK)) {
551       /* Stop immediately on error or flushing */
552       GST_INFO_OBJECT (splitpad, "Stopping due to pad_push() result %d", ret);
553       gst_pad_pause_task (pad);
554       if (ret <= GST_FLOW_EOS) {
555         const gchar *reason = gst_flow_get_name (ret);
556         GST_ELEMENT_ERROR (splitmux, STREAM, FAILED,
557             (_("Internal data flow error.")),
558             ("streaming task paused, reason %s (%d)", reason, ret));
559       }
560     }
561   }
562   g_slice_free (GstDataQueueItem, item);
563
564   gst_object_unref (splitmux);
565   return;
566
567 error:
568   /* Fall through */
569   GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ,
570       ("Error reading part file %s", GST_STR_NULL (reader->path)), (NULL));
571 flushing:
572   gst_pad_pause_task (pad);
573   gst_object_unref (splitmux);
574   return;
575 }
576
577 static void
578 gst_splitmux_src_activate_part (GstSplitMuxSrc * splitmux, guint part)
579 {
580   GList *cur;
581
582   GST_DEBUG_OBJECT (splitmux, "Activating part %d", part);
583
584   splitmux->cur_part = part;
585   gst_splitmux_part_reader_activate (splitmux->parts[part],
586       &splitmux->play_segment);
587
588   SPLITMUX_SRC_PADS_LOCK (splitmux);
589   for (cur = g_list_first (splitmux->pads);
590       cur != NULL; cur = g_list_next (cur)) {
591     SplitMuxSrcPad *splitpad = (SplitMuxSrcPad *) (cur->data);
592     splitpad->cur_part = part;
593     splitpad->reader = splitmux->parts[splitpad->cur_part];
594     splitpad->part_pad =
595         gst_splitmux_part_reader_lookup_pad (splitpad->reader,
596         (GstPad *) (splitpad));
597
598     /* Make sure we start with a DISCONT */
599     splitpad->set_next_discont = TRUE;
600     splitpad->clear_next_discont = FALSE;
601
602     gst_pad_start_task (GST_PAD (splitpad),
603         (GstTaskFunction) gst_splitmux_pad_loop, splitpad, NULL);
604   }
605   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
606 }
607
608 static gboolean
609 gst_splitmux_src_start (GstSplitMuxSrc * splitmux)
610 {
611   gboolean ret = FALSE;
612   GError *err = NULL;
613   gchar *basename = NULL;
614   gchar *dirname = NULL;
615   gchar **files;
616   GstClockTime next_offset = 0;
617   guint i;
618
619   GST_DEBUG_OBJECT (splitmux, "Starting");
620
621   GST_OBJECT_LOCK (splitmux);
622   if (splitmux->location != NULL && splitmux->location[0] != '\0') {
623     basename = g_path_get_basename (splitmux->location);
624     dirname = g_path_get_dirname (splitmux->location);
625   }
626   GST_OBJECT_UNLOCK (splitmux);
627
628   files = gst_split_util_find_files (dirname, basename, &err);
629
630   if (files == NULL || *files == NULL)
631     goto no_files;
632
633   SPLITMUX_SRC_LOCK (splitmux);
634   splitmux->running = TRUE;
635   SPLITMUX_SRC_UNLOCK (splitmux);
636
637   splitmux->num_parts = g_strv_length (files);
638
639   splitmux->parts = g_new0 (GstSplitMuxPartReader *, splitmux->num_parts);
640
641   for (i = 0; i < splitmux->num_parts; i++) {
642     splitmux->parts[i] = gst_splitmux_part_create (splitmux, files[i]);
643     if (splitmux->parts[i] == NULL)
644       break;
645
646     /* Figure out the next offset - the smallest one */
647     gst_splitmux_part_reader_set_start_offset (splitmux->parts[i], next_offset);
648     if (!gst_splitmux_part_reader_prepare (splitmux->parts[i])) {
649       GST_WARNING_OBJECT (splitmux,
650           "Failed to prepare file part %s. Cannot play past there.", files[i]);
651       GST_ELEMENT_WARNING (splitmux, RESOURCE, READ, (NULL),
652           ("Failed to prepare file part %s. Cannot play past there.",
653               files[i]));
654       gst_splitmux_part_reader_unprepare (splitmux->parts[i]);
655       g_object_unref (splitmux->parts[i]);
656       splitmux->parts[i] = NULL;
657       break;
658     }
659
660     /* Extend our total duration to cover this part */
661     splitmux->total_duration =
662         next_offset +
663         gst_splitmux_part_reader_get_duration (splitmux->parts[i]);
664     splitmux->play_segment.duration = splitmux->total_duration;
665
666     next_offset = gst_splitmux_part_reader_get_end_offset (splitmux->parts[i]);
667   }
668   /* Store how many parts we actually created */
669   splitmux->num_parts = i;
670
671   if (splitmux->num_parts < 1)
672     goto failed_part;
673
674   /* All done preparing, activate the first part */
675   GST_INFO_OBJECT (splitmux,
676       "All parts prepared. Total duration %" GST_TIME_FORMAT
677       " Activating first part", GST_TIME_ARGS (splitmux->total_duration));
678   gst_splitmux_src_activate_part (splitmux, 0);
679
680   ret = TRUE;
681 done:
682   if (err != NULL)
683     g_error_free (err);
684   g_strfreev (files);
685   g_free (basename);
686   g_free (dirname);
687
688   return ret;
689
690 /* ERRORS */
691 no_files:
692   {
693     GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, ("%s", err->message),
694         ("Failed to find files in '%s' for pattern '%s'",
695             GST_STR_NULL (dirname), GST_STR_NULL (basename)));
696     goto done;
697   }
698 failed_part:
699   {
700     GST_ELEMENT_ERROR (splitmux, RESOURCE, OPEN_READ, (NULL),
701         ("Failed to open any files for reading"));
702     goto done;
703   }
704 }
705
706 static gboolean
707 gst_splitmux_src_stop (GstSplitMuxSrc * splitmux)
708 {
709   gboolean ret = TRUE;
710   guint i;
711   GList *cur;
712
713   SPLITMUX_SRC_LOCK (splitmux);
714   if (!splitmux->running)
715     goto out;
716
717   GST_DEBUG_OBJECT (splitmux, "Stopping");
718
719   /* Stop and destroy all parts  */
720   for (i = 0; i < splitmux->num_parts; i++) {
721     if (splitmux->parts[i] == NULL)
722       continue;
723     gst_splitmux_part_reader_unprepare (splitmux->parts[i]);
724     g_object_unref (splitmux->parts[i]);
725     splitmux->parts[i] = NULL;
726   }
727
728   SPLITMUX_SRC_PADS_LOCK (splitmux);
729   for (cur = g_list_first (splitmux->pads);
730       cur != NULL; cur = g_list_next (cur)) {
731     SplitMuxSrcPad *tmp = (SplitMuxSrcPad *) (cur->data);
732     gst_pad_stop_task (GST_PAD (tmp));
733   }
734   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
735
736   g_free (splitmux->parts);
737   splitmux->parts = NULL;
738   splitmux->num_parts = 0;
739   splitmux->running = FALSE;
740   splitmux->total_duration = GST_CLOCK_TIME_NONE;
741   /* Reset playback segment */
742   gst_segment_init (&splitmux->play_segment, GST_FORMAT_TIME);
743 out:
744   SPLITMUX_SRC_UNLOCK (splitmux);
745   return ret;
746 }
747
748 static GstPad *
749 gst_splitmux_find_output_pad (GstSplitMuxPartReader * part, GstPad * pad,
750     GstSplitMuxSrc * splitmux)
751 {
752   GList *cur;
753   gchar *pad_name = gst_pad_get_name (pad);
754   GstPad *target = NULL;
755   gboolean is_new_pad = FALSE;
756
757   SPLITMUX_SRC_LOCK (splitmux);
758   SPLITMUX_SRC_PADS_LOCK (splitmux);
759   for (cur = g_list_first (splitmux->pads);
760       cur != NULL; cur = g_list_next (cur)) {
761     GstPad *tmp = (GstPad *) (cur->data);
762     if (g_str_equal (GST_PAD_NAME (tmp), pad_name)) {
763       target = tmp;
764       break;
765     }
766   }
767
768   if (target == NULL && !splitmux->pads_complete) {
769     /* No pad found, create one */
770     target = g_object_new (SPLITMUX_TYPE_SRC_PAD,
771         "name", pad_name, "direction", GST_PAD_SRC, NULL);
772     splitmux->pads = g_list_prepend (splitmux->pads, target);
773
774     gst_pad_set_active (target, TRUE);
775     is_new_pad = TRUE;
776   }
777   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
778   SPLITMUX_SRC_UNLOCK (splitmux);
779
780   g_free (pad_name);
781
782   if (target == NULL)
783     goto pad_not_found;
784
785   if (is_new_pad)
786     gst_element_add_pad (GST_ELEMENT_CAST (splitmux), target);
787
788   return target;
789
790 pad_not_found:
791   GST_ELEMENT_ERROR (splitmux, STREAM, FAILED, (NULL),
792       ("Stream part %s contains extra unknown pad %" GST_PTR_FORMAT,
793           part->path, pad));
794   return NULL;
795 }
796
797 static void
798 gst_splitmux_part_prepared (GstSplitMuxPartReader * reader,
799     GstSplitMuxSrc * splitmux)
800 {
801   gboolean need_no_more_pads;
802
803   SPLITMUX_SRC_LOCK (splitmux);
804   need_no_more_pads = !splitmux->pads_complete;
805   splitmux->pads_complete = TRUE;
806   SPLITMUX_SRC_UNLOCK (splitmux);
807
808   if (need_no_more_pads) {
809     GST_DEBUG_OBJECT (splitmux, "Signalling no-more-pads");
810     gst_element_no_more_pads (GST_ELEMENT_CAST (splitmux));
811   }
812 }
813
814 static void
815 gst_splitmux_push_event (GstSplitMuxSrc * splitmux, GstEvent * e,
816     guint32 seqnum)
817 {
818   GList *cur;
819
820   if (seqnum)
821     gst_event_set_seqnum (e, seqnum);
822
823   SPLITMUX_SRC_PADS_LOCK (splitmux);
824   for (cur = g_list_first (splitmux->pads);
825       cur != NULL; cur = g_list_next (cur)) {
826     GstPad *pad = GST_PAD_CAST (cur->data);
827     gst_event_ref (e);
828     gst_pad_push_event (pad, e);
829   }
830   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
831
832   gst_event_unref (e);
833 }
834
835 static void
836 gst_splitmux_push_flush_stop (GstSplitMuxSrc * splitmux, guint32 seqnum)
837 {
838   GstEvent *e = gst_event_new_flush_stop (TRUE);
839   GList *cur;
840
841   if (seqnum)
842     gst_event_set_seqnum (e, seqnum);
843
844   SPLITMUX_SRC_PADS_LOCK (splitmux);
845   for (cur = g_list_first (splitmux->pads);
846       cur != NULL; cur = g_list_next (cur)) {
847     SplitMuxSrcPad *target = (SplitMuxSrcPad *) (cur->data);
848
849     gst_event_ref (e);
850     gst_pad_push_event (GST_PAD_CAST (target), e);
851     target->sent_caps = FALSE;
852     target->sent_stream_start = FALSE;
853     target->sent_segment = FALSE;
854   }
855   SPLITMUX_SRC_PADS_UNLOCK (splitmux);
856
857   gst_event_unref (e);
858 }
859
860 /* Callback for when a part finishes and we need to move to the next */
861 static gboolean
862 gst_splitmux_end_of_part (GstSplitMuxSrc * splitmux, SplitMuxSrcPad * splitpad)
863 {
864   gint next_part = -1;
865   gint cur_part = splitpad->cur_part;
866   gboolean res = FALSE;
867
868   if (splitmux->play_segment.rate >= 0.0) {
869     if (cur_part + 1 < splitmux->num_parts)
870       next_part = cur_part + 1;
871     /* Make sure the transition is seamless */
872     splitpad->set_next_discont = FALSE;
873     splitpad->clear_next_discont = TRUE;
874   } else {
875     /* Reverse play - move to previous segment */
876     if (cur_part > 0) {
877       next_part = cur_part - 1;
878       /* Non-seamless transition in reverse */
879       splitpad->set_next_discont = TRUE;
880       splitpad->clear_next_discont = FALSE;
881     }
882   }
883
884   SPLITMUX_SRC_LOCK (splitmux);
885
886   /* If all pads are done with this part, deactivate it */
887   if (gst_splitmux_part_is_eos (splitmux->parts[splitpad->cur_part]))
888     gst_splitmux_part_reader_deactivate (splitmux->parts[cur_part]);
889
890   if (next_part != -1) {
891     GST_DEBUG_OBJECT (splitmux, "At EOS on pad %" GST_PTR_FORMAT
892         " moving to part %d", splitpad, next_part);
893     splitpad->cur_part = next_part;
894     splitpad->reader = splitmux->parts[splitpad->cur_part];
895     splitpad->part_pad =
896         gst_splitmux_part_reader_lookup_pad (splitpad->reader,
897         (GstPad *) (splitpad));
898
899     if (splitmux->cur_part != next_part) {
900       GstSegment tmp;
901       /* If moving backward into a new part, set stop
902        * to -1 to ensure we play the entire file - workaround
903        * a bug in qtdemux that misses bits at the end */
904       gst_segment_copy_into (&splitmux->play_segment, &tmp);
905       if (tmp.rate < 0)
906         tmp.stop = -1;
907
908       /* This is the first pad to move to the new part, activate it */
909       splitmux->cur_part = next_part;
910       GST_DEBUG_OBJECT (splitpad,
911           "First pad to change part. Activating part %d with seg %"
912           GST_SEGMENT_FORMAT, next_part, &tmp);
913       gst_splitmux_part_reader_activate (splitpad->reader, &tmp);
914     }
915     res = TRUE;
916   }
917
918   SPLITMUX_SRC_UNLOCK (splitmux);
919   return res;
920 }
921
922 G_DEFINE_TYPE (SplitMuxSrcPad, splitmux_src_pad, GST_TYPE_PAD);
923
924 static void
925 splitmux_src_pad_constructed (GObject * pad)
926 {
927   gst_pad_set_event_function (GST_PAD (pad),
928       GST_DEBUG_FUNCPTR (splitmux_src_pad_event));
929   gst_pad_set_query_function (GST_PAD (pad),
930       GST_DEBUG_FUNCPTR (splitmux_src_pad_query));
931
932   G_OBJECT_CLASS (splitmux_src_pad_parent_class)->constructed (pad);
933 }
934
935 static void
936 splitmux_src_pad_class_init (SplitMuxSrcPadClass * klass)
937 {
938   GObjectClass *gobject_klass = (GObjectClass *) (klass);
939
940   gobject_klass->constructed = splitmux_src_pad_constructed;
941 }
942
943 static void
944 splitmux_src_pad_init (SplitMuxSrcPad * pad)
945 {
946 }
947
948 /* Event handler for source pads. Proxy events into the child
949  * parts as needed
950  */
951 static gboolean
952 splitmux_src_pad_event (GstPad * pad, GstObject * parent, GstEvent * event)
953 {
954   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (parent);
955   gboolean ret = FALSE;
956
957   GST_DEBUG_OBJECT (parent, "event %" GST_PTR_FORMAT
958       " on %" GST_PTR_FORMAT, event, pad);
959
960   switch (GST_EVENT_TYPE (event)) {
961     case GST_EVENT_SEEK:{
962       GstFormat format;
963       gdouble rate;
964       GstSeekFlags flags;
965       GstSeekType start_type, stop_type;
966       gint64 start, stop;
967       guint32 seqnum;
968       gint i;
969       GstClockTime part_start, position;
970       GList *cur;
971       GstSegment tmp;
972
973       gst_event_parse_seek (event, &rate, &format, &flags,
974           &start_type, &start, &stop_type, &stop);
975
976       if (format != GST_FORMAT_TIME) {
977         GST_DEBUG_OBJECT (splitmux, "can only seek on TIME");
978         goto error;
979       }
980       /* FIXME: Support non-flushing seeks, which might never wake up */
981       if (!(flags & GST_SEEK_FLAG_FLUSH)) {
982         GST_DEBUG_OBJECT (splitmux, "Only flushing seeks supported");
983         goto error;
984       }
985       seqnum = gst_event_get_seqnum (event);
986
987       SPLITMUX_SRC_LOCK (splitmux);
988       if (!splitmux->running || splitmux->num_parts < 1) {
989         /* Not started yet */
990         SPLITMUX_SRC_UNLOCK (splitmux);
991         goto error;
992       }
993
994       gst_segment_copy_into (&splitmux->play_segment, &tmp);
995
996       if (!gst_segment_do_seek (&tmp, rate,
997               format, flags, start_type, start, stop_type, stop, NULL)) {
998         /* Invalid seek requested, ignore it */
999         SPLITMUX_SRC_UNLOCK (splitmux);
1000         goto error;
1001       }
1002       position = tmp.position;
1003
1004       GST_DEBUG_OBJECT (splitmux, "Performing seek with seg %"
1005           GST_SEGMENT_FORMAT, &tmp);
1006
1007       GST_DEBUG_OBJECT (splitmux,
1008           "Handling flushing seek. Sending flush start");
1009
1010       /* Send flush_start */
1011       gst_splitmux_push_event (splitmux, gst_event_new_flush_start (), seqnum);
1012
1013       /* Stop all parts, which will work because of the flush */
1014       SPLITMUX_SRC_PADS_LOCK (splitmux);
1015       SPLITMUX_SRC_UNLOCK (splitmux);
1016       for (cur = g_list_first (splitmux->pads);
1017           cur != NULL; cur = g_list_next (cur)) {
1018         SplitMuxSrcPad *target = (SplitMuxSrcPad *) (cur->data);
1019         GstSplitMuxPartReader *reader = splitmux->parts[target->cur_part];
1020         gst_splitmux_part_reader_deactivate (reader);
1021       }
1022
1023       /* Shut down pad tasks */
1024       GST_DEBUG_OBJECT (splitmux, "Pausing pad tasks");
1025       for (cur = g_list_first (splitmux->pads);
1026           cur != NULL; cur = g_list_next (cur)) {
1027         GstPad *splitpad = (GstPad *) (cur->data);
1028         gst_pad_pause_task (GST_PAD (splitpad));
1029       }
1030       SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1031       SPLITMUX_SRC_LOCK (splitmux);
1032
1033       /* Send flush stop */
1034       GST_DEBUG_OBJECT (splitmux, "Sending flush stop");
1035       gst_splitmux_push_flush_stop (splitmux, seqnum);
1036
1037       /* Everything is stopped, so update the play_segment */
1038       gst_segment_copy_into (&tmp, &splitmux->play_segment);
1039
1040       /* Work out where to start from now */
1041       for (i = 0; i < splitmux->num_parts; i++) {
1042         GstSplitMuxPartReader *reader = splitmux->parts[i];
1043         GstClockTime part_end =
1044             gst_splitmux_part_reader_get_end_offset (reader);
1045
1046         if (part_end > position)
1047           break;
1048       }
1049       if (i == splitmux->num_parts)
1050         i = splitmux->num_parts - 1;
1051
1052       part_start =
1053           gst_splitmux_part_reader_get_start_offset (splitmux->parts[i]);
1054
1055       GST_DEBUG_OBJECT (splitmux,
1056           "Seek to time %" GST_TIME_FORMAT " landed in part %d offset %"
1057           GST_TIME_FORMAT, GST_TIME_ARGS (position),
1058           i, GST_TIME_ARGS (position - part_start));
1059
1060       gst_splitmux_src_activate_part (splitmux, i);
1061
1062       ret = TRUE;
1063       SPLITMUX_SRC_UNLOCK (splitmux);
1064     }
1065     default:
1066       break;
1067   }
1068
1069   gst_event_unref (event);
1070 error:
1071   return ret;
1072 }
1073
1074 static gboolean
1075 splitmux_src_pad_query (GstPad * pad, GstObject * parent, GstQuery * query)
1076 {
1077   /* Query handler for source pads. Proxy queries into the child
1078    * parts as needed
1079    */
1080   GstSplitMuxSrc *splitmux = GST_SPLITMUX_SRC (parent);
1081   gboolean ret = FALSE;
1082
1083   GST_LOG_OBJECT (parent, "query %" GST_PTR_FORMAT
1084       " on %" GST_PTR_FORMAT, query, pad);
1085   switch (GST_QUERY_TYPE (query)) {
1086     case GST_QUERY_CAPS:
1087     case GST_QUERY_POSITION:{
1088       GstSplitMuxPartReader *part;
1089       SplitMuxSrcPad *anypad;
1090
1091       SPLITMUX_SRC_LOCK (splitmux);
1092       SPLITMUX_SRC_PADS_LOCK (splitmux);
1093       anypad = (SplitMuxSrcPad *) (splitmux->pads->data);
1094       part = splitmux->parts[anypad->cur_part];
1095       ret = gst_splitmux_part_reader_src_query (part, pad, query);
1096       SPLITMUX_SRC_PADS_UNLOCK (splitmux);
1097       SPLITMUX_SRC_UNLOCK (splitmux);
1098       break;
1099     }
1100     case GST_QUERY_DURATION:{
1101       GstFormat fmt;
1102       gst_query_parse_duration (query, &fmt, NULL);
1103       if (fmt != GST_FORMAT_TIME)
1104         break;
1105
1106       SPLITMUX_SRC_LOCK (splitmux);
1107       if (splitmux->total_duration > 0) {
1108         gst_query_set_duration (query, GST_FORMAT_TIME,
1109             splitmux->total_duration);
1110         ret = TRUE;
1111       }
1112       SPLITMUX_SRC_UNLOCK (splitmux);
1113       break;
1114     }
1115     case GST_QUERY_SEEKING:{
1116       GstFormat format;
1117
1118       gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
1119       if (format != GST_FORMAT_TIME)
1120         break;
1121
1122       SPLITMUX_SRC_LOCK (splitmux);
1123       gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0,
1124           splitmux->total_duration);
1125       ret = TRUE;
1126       SPLITMUX_SRC_UNLOCK (splitmux);
1127
1128       break;
1129     }
1130     default:
1131       break;
1132   }
1133   return ret;
1134 }
1135
1136
1137 gboolean
1138 register_splitmuxsrc (GstPlugin * plugin)
1139 {
1140   GST_DEBUG_CATEGORY_INIT (splitmux_debug, "splitmuxsrc", 0,
1141       "Split File Demuxing Source");
1142
1143   return gst_element_register (plugin, "splitmuxsrc", GST_RANK_NONE,
1144       GST_TYPE_SPLITMUX_SRC);
1145 }