03cf2062112bf8e2241a12429c23be05c33037a4
[platform/upstream/gstreamer.git] / plugins / elements / gstoutputselector.c
1 /* GStreamer output selector
2  * Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.com>)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-output-selector
22  * @title: output-selector
23  * @see_also: #GstOutputSelector, #GstInputSelector
24  *
25  * Direct input stream to one out of N output pads.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <string.h>
33
34 #include "gstoutputselector.h"
35
36 GST_DEBUG_CATEGORY_STATIC (output_selector_debug);
37 #define GST_CAT_DEFAULT output_selector_debug
38
39 static GstStaticPadTemplate gst_output_selector_sink_factory =
40 GST_STATIC_PAD_TEMPLATE ("sink",
41     GST_PAD_SINK,
42     GST_PAD_ALWAYS,
43     GST_STATIC_CAPS_ANY);
44
45 static GstStaticPadTemplate gst_output_selector_src_factory =
46 GST_STATIC_PAD_TEMPLATE ("src_%u",
47     GST_PAD_SRC,
48     GST_PAD_REQUEST,
49     GST_STATIC_CAPS_ANY);
50
51 #define GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE (gst_output_selector_pad_negotiation_mode_get_type())
52 static GType
53 gst_output_selector_pad_negotiation_mode_get_type (void)
54 {
55   static GType pad_negotiation_mode_type = 0;
56   static const GEnumValue pad_negotiation_modes[] = {
57     {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE, "None", "none"},
58     {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL, "All", "all"},
59     {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE, "Active", "active"},
60     {0, NULL, NULL}
61   };
62
63   if (!pad_negotiation_mode_type) {
64     pad_negotiation_mode_type =
65         g_enum_register_static ("GstOutputSelectorPadNegotiationMode",
66         pad_negotiation_modes);
67   }
68   return pad_negotiation_mode_type;
69 }
70
71
72 enum
73 {
74   PROP_0,
75   PROP_ACTIVE_PAD,
76   PROP_RESEND_LATEST,
77   PROP_PAD_NEGOTIATION_MODE
78 };
79
80 #define DEFAULT_PAD_NEGOTIATION_MODE GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL
81
82 #define _do_init \
83 GST_DEBUG_CATEGORY_INIT (output_selector_debug, \
84         "output-selector", 0, "Output stream selector");
85 #define gst_output_selector_parent_class parent_class
86 G_DEFINE_TYPE_WITH_CODE (GstOutputSelector, gst_output_selector,
87     GST_TYPE_ELEMENT, _do_init);
88
89 static void gst_output_selector_dispose (GObject * object);
90 static void gst_output_selector_set_property (GObject * object,
91     guint prop_id, const GValue * value, GParamSpec * pspec);
92 static void gst_output_selector_get_property (GObject * object,
93     guint prop_id, GValue * value, GParamSpec * pspec);
94 static GstPad *gst_output_selector_request_new_pad (GstElement * element,
95     GstPadTemplate * templ, const gchar * unused, const GstCaps * caps);
96 static void gst_output_selector_release_pad (GstElement * element,
97     GstPad * pad);
98 static GstFlowReturn gst_output_selector_chain (GstPad * pad,
99     GstObject * parent, GstBuffer * buf);
100 static GstStateChangeReturn gst_output_selector_change_state (GstElement *
101     element, GstStateChange transition);
102 static gboolean gst_output_selector_event (GstPad * pad, GstObject * parent,
103     GstEvent * event);
104 static gboolean gst_output_selector_query (GstPad * pad, GstObject * parent,
105     GstQuery * query);
106 static void gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector *
107     sel, gint mode);
108
109 static void
110 gst_output_selector_class_init (GstOutputSelectorClass * klass)
111 {
112   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
113   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
114
115   gobject_class->dispose = gst_output_selector_dispose;
116
117   gobject_class->set_property = gst_output_selector_set_property;
118   gobject_class->get_property = gst_output_selector_get_property;
119
120   g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
121       g_param_spec_object ("active-pad", "Active pad",
122           "Currently active src pad", GST_TYPE_PAD,
123           G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
124           G_PARAM_STATIC_STRINGS));
125   g_object_class_install_property (gobject_class, PROP_RESEND_LATEST,
126       g_param_spec_boolean ("resend-latest", "Resend latest buffer",
127           "Resend latest buffer after a switch to a new pad", FALSE,
128           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
129   g_object_class_install_property (gobject_class, PROP_PAD_NEGOTIATION_MODE,
130       g_param_spec_enum ("pad-negotiation-mode", "Pad negotiation mode",
131           "The mode to be used for pad negotiation",
132           GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE,
133           DEFAULT_PAD_NEGOTIATION_MODE,
134           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
135
136   gst_element_class_set_static_metadata (gstelement_class, "Output selector",
137       "Generic", "1-to-N output stream selector",
138       "Stefan Kost <stefan.kost@nokia.com>");
139   gst_element_class_add_static_pad_template (gstelement_class,
140       &gst_output_selector_sink_factory);
141   gst_element_class_add_static_pad_template (gstelement_class,
142       &gst_output_selector_src_factory);
143
144   gstelement_class->request_new_pad =
145       GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
146   gstelement_class->release_pad =
147       GST_DEBUG_FUNCPTR (gst_output_selector_release_pad);
148
149   gstelement_class->change_state = gst_output_selector_change_state;
150 }
151
152 static void
153 gst_output_selector_init (GstOutputSelector * sel)
154 {
155   sel->sinkpad =
156       gst_pad_new_from_static_template (&gst_output_selector_sink_factory,
157       "sink");
158   gst_pad_set_chain_function (sel->sinkpad,
159       GST_DEBUG_FUNCPTR (gst_output_selector_chain));
160   gst_pad_set_event_function (sel->sinkpad,
161       GST_DEBUG_FUNCPTR (gst_output_selector_event));
162   gst_pad_set_query_function (sel->sinkpad,
163       GST_DEBUG_FUNCPTR (gst_output_selector_query));
164
165   gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad);
166
167   /* srcpad management */
168   sel->active_srcpad = NULL;
169   sel->nb_srcpads = 0;
170   gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED);
171   sel->pending_srcpad = NULL;
172
173   sel->resend_latest = FALSE;
174   sel->latest_buffer = NULL;
175   gst_output_selector_switch_pad_negotiation_mode (sel,
176       DEFAULT_PAD_NEGOTIATION_MODE);
177 }
178
179 static void
180 gst_output_selector_reset (GstOutputSelector * osel)
181 {
182   GST_OBJECT_LOCK (osel);
183   if (osel->pending_srcpad != NULL) {
184     gst_object_unref (osel->pending_srcpad);
185     osel->pending_srcpad = NULL;
186   }
187
188   if (osel->latest_buffer != NULL) {
189     gst_buffer_unref (osel->latest_buffer);
190     osel->latest_buffer = NULL;
191   }
192   GST_OBJECT_UNLOCK (osel);
193   gst_segment_init (&osel->segment, GST_FORMAT_UNDEFINED);
194 }
195
196 static void
197 gst_output_selector_dispose (GObject * object)
198 {
199   GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object);
200
201   gst_output_selector_reset (osel);
202
203   G_OBJECT_CLASS (parent_class)->dispose (object);
204 }
205
206 static void
207 gst_output_selector_set_property (GObject * object, guint prop_id,
208     const GValue * value, GParamSpec * pspec)
209 {
210   GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
211
212   switch (prop_id) {
213     case PROP_ACTIVE_PAD:
214     {
215       GstPad *next_pad;
216
217       next_pad = g_value_get_object (value);
218
219       GST_INFO_OBJECT (sel, "Activating pad %s:%s",
220           GST_DEBUG_PAD_NAME (next_pad));
221
222       GST_OBJECT_LOCK (object);
223       if (next_pad != sel->active_srcpad) {
224         /* switch to new srcpad in next chain run */
225         if (sel->pending_srcpad != NULL) {
226           GST_INFO ("replacing pending switch");
227           gst_object_unref (sel->pending_srcpad);
228         }
229         if (next_pad)
230           gst_object_ref (next_pad);
231         sel->pending_srcpad = next_pad;
232       } else {
233         GST_INFO ("pad already active");
234         if (sel->pending_srcpad != NULL) {
235           gst_object_unref (sel->pending_srcpad);
236           sel->pending_srcpad = NULL;
237         }
238       }
239       GST_OBJECT_UNLOCK (object);
240       break;
241     }
242     case PROP_RESEND_LATEST:{
243       sel->resend_latest = g_value_get_boolean (value);
244       break;
245     }
246     case PROP_PAD_NEGOTIATION_MODE:{
247       gst_output_selector_switch_pad_negotiation_mode (sel,
248           g_value_get_enum (value));
249       break;
250     }
251     default:
252       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
253       break;
254   }
255 }
256
257 static void
258 gst_output_selector_get_property (GObject * object, guint prop_id,
259     GValue * value, GParamSpec * pspec)
260 {
261   GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
262
263   switch (prop_id) {
264     case PROP_ACTIVE_PAD:
265       GST_OBJECT_LOCK (object);
266       g_value_set_object (value,
267           sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad);
268       GST_OBJECT_UNLOCK (object);
269       break;
270     case PROP_RESEND_LATEST:{
271       GST_OBJECT_LOCK (object);
272       g_value_set_boolean (value, sel->resend_latest);
273       GST_OBJECT_UNLOCK (object);
274       break;
275     }
276     case PROP_PAD_NEGOTIATION_MODE:
277       g_value_set_enum (value, sel->pad_negotiation_mode);
278       break;
279     default:
280       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
281       break;
282   }
283 }
284
285 static GstPad *
286 gst_output_selector_get_active (GstOutputSelector * sel)
287 {
288   GstPad *active = NULL;
289
290   GST_OBJECT_LOCK (sel);
291   if (sel->pending_srcpad)
292     active = gst_object_ref (sel->pending_srcpad);
293   else if (sel->active_srcpad)
294     active = gst_object_ref (sel->active_srcpad);
295   GST_OBJECT_UNLOCK (sel);
296
297   return active;
298 }
299
300 static void
301 gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector * sel,
302     gint mode)
303 {
304   sel->pad_negotiation_mode = mode;
305 }
306
307 static gboolean
308 forward_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
309 {
310   GstPad *srcpad = GST_PAD_CAST (user_data);
311
312   gst_pad_push_event (srcpad, gst_event_ref (*event));
313
314   return TRUE;
315 }
316
317 static GstPad *
318 gst_output_selector_request_new_pad (GstElement * element,
319     GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
320 {
321   gchar *padname;
322   GstPad *srcpad;
323   GstOutputSelector *osel;
324
325   osel = GST_OUTPUT_SELECTOR (element);
326
327   GST_DEBUG_OBJECT (osel, "requesting pad");
328
329   GST_OBJECT_LOCK (osel);
330   padname = g_strdup_printf ("src_%u", osel->nb_srcpads++);
331   srcpad = gst_pad_new_from_template (templ, padname);
332   GST_OBJECT_UNLOCK (osel);
333
334   gst_pad_set_active (srcpad, TRUE);
335
336   /* Forward sticky events to the new srcpad */
337   gst_pad_sticky_events_foreach (osel->sinkpad, forward_sticky_events, srcpad);
338
339   gst_element_add_pad (GST_ELEMENT (osel), srcpad);
340
341   /* Set the first requested src pad as active by default */
342   GST_OBJECT_LOCK (osel);
343   if (osel->active_srcpad == NULL) {
344     osel->active_srcpad = srcpad;
345     GST_OBJECT_UNLOCK (osel);
346     g_object_notify (G_OBJECT (osel), "active-pad");
347   } else {
348     GST_OBJECT_UNLOCK (osel);
349   }
350   g_free (padname);
351
352   return srcpad;
353 }
354
355 static void
356 gst_output_selector_release_pad (GstElement * element, GstPad * pad)
357 {
358   GstOutputSelector *osel;
359
360   osel = GST_OUTPUT_SELECTOR (element);
361
362   GST_DEBUG_OBJECT (osel, "releasing pad");
363
364   /* Disable active pad if it's the to be removed pad */
365   GST_OBJECT_LOCK (osel);
366   if (osel->active_srcpad == pad) {
367     osel->active_srcpad = NULL;
368     GST_OBJECT_UNLOCK (osel);
369     g_object_notify (G_OBJECT (osel), "active-pad");
370   } else {
371     GST_OBJECT_UNLOCK (osel);
372   }
373
374   gst_pad_set_active (pad, FALSE);
375
376   gst_element_remove_pad (GST_ELEMENT_CAST (osel), pad);
377 }
378
379 static gboolean
380 gst_output_selector_switch (GstOutputSelector * osel)
381 {
382   gboolean res = FALSE;
383   GstEvent *ev = NULL;
384   GstSegment *seg = NULL;
385   GstPad *active_srcpad;
386
387   /* Switch */
388   GST_OBJECT_LOCK (osel);
389   GST_INFO_OBJECT (osel, "switching to pad %" GST_PTR_FORMAT,
390       osel->pending_srcpad);
391   if (!osel->pending_srcpad) {
392     GST_OBJECT_UNLOCK (osel);
393     return TRUE;
394   }
395
396   if (gst_pad_is_linked (osel->pending_srcpad)) {
397     osel->active_srcpad = osel->pending_srcpad;
398     res = TRUE;
399   }
400   gst_object_unref (osel->pending_srcpad);
401   osel->pending_srcpad = NULL;
402   active_srcpad = res ? gst_object_ref (osel->active_srcpad) : NULL;
403   GST_OBJECT_UNLOCK (osel);
404
405   /* Send SEGMENT event and latest buffer if switching succeeded
406    * and we already have a valid segment configured */
407   if (res) {
408     GstBuffer *latest_buffer;
409
410     g_object_notify (G_OBJECT (osel), "active-pad");
411
412     GST_OBJECT_LOCK (osel);
413     latest_buffer =
414         osel->latest_buffer ? gst_buffer_ref (osel->latest_buffer) : NULL;
415     GST_OBJECT_UNLOCK (osel);
416
417     gst_pad_sticky_events_foreach (osel->sinkpad, forward_sticky_events,
418         active_srcpad);
419
420     /* update segment if required */
421     if (osel->segment.format != GST_FORMAT_UNDEFINED) {
422       /* Send SEGMENT to the pad we are going to switch to */
423       seg = &osel->segment;
424       /* If resending then mark segment start and position accordingly */
425       if (osel->resend_latest && latest_buffer &&
426           GST_BUFFER_TIMESTAMP_IS_VALID (latest_buffer)) {
427         seg->position = GST_BUFFER_TIMESTAMP (latest_buffer);
428       }
429
430       ev = gst_event_new_segment (seg);
431
432       if (!gst_pad_push_event (active_srcpad, ev)) {
433         GST_WARNING_OBJECT (osel,
434             "newsegment handling failed in %" GST_PTR_FORMAT, active_srcpad);
435       }
436     }
437
438     /* Resend latest buffer to newly switched pad */
439     if (osel->resend_latest && latest_buffer) {
440       GST_INFO ("resending latest buffer");
441       gst_pad_push (active_srcpad, latest_buffer);
442     } else if (latest_buffer) {
443       gst_buffer_unref (latest_buffer);
444     }
445
446     gst_object_unref (active_srcpad);
447   } else {
448     GST_WARNING_OBJECT (osel, "switch failed, pad not linked");
449   }
450
451   return res;
452 }
453
454 static GstFlowReturn
455 gst_output_selector_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
456 {
457   GstFlowReturn res;
458   GstOutputSelector *osel;
459   GstClockTime position, duration;
460   GstPad *active_srcpad;
461
462   osel = GST_OUTPUT_SELECTOR (parent);
463
464   /*
465    * The _switch function might push a buffer if 'resend-latest' is true.
466    *
467    * Elements/Applications (e.g. camerabin) might use pad probes to
468    * switch output-selector's active pad. If we simply switch and don't
469    * recheck any pending pad switch the following codepath could end
470    * up pushing a buffer on a non-active pad. This is bad.
471    *
472    * So we always should check the pending_srcpad before going further down
473    * the chain and pushing the new buffer
474    */
475   while (osel->pending_srcpad) {
476     /* Do the switch */
477     gst_output_selector_switch (osel);
478   }
479
480   active_srcpad = gst_output_selector_get_active (osel);
481   if (!active_srcpad) {
482     GST_DEBUG_OBJECT (osel, "No active srcpad");
483     gst_buffer_unref (buf);
484     return GST_FLOW_OK;
485   }
486
487   GST_OBJECT_LOCK (osel);
488   if (osel->latest_buffer) {
489     gst_buffer_unref (osel->latest_buffer);
490     osel->latest_buffer = NULL;
491   }
492
493   if (osel->resend_latest) {
494     /* Keep reference to latest buffer to resend it after switch */
495     osel->latest_buffer = gst_buffer_ref (buf);
496   }
497   GST_OBJECT_UNLOCK (osel);
498
499   /* Keep track of last stop and use it in SEGMENT start after
500      switching to a new src pad */
501   position = GST_BUFFER_TIMESTAMP (buf);
502   if (GST_CLOCK_TIME_IS_VALID (position)) {
503     duration = GST_BUFFER_DURATION (buf);
504     if (GST_CLOCK_TIME_IS_VALID (duration)) {
505       position += duration;
506     }
507     GST_LOG_OBJECT (osel, "setting last stop %" GST_TIME_FORMAT,
508         GST_TIME_ARGS (position));
509     osel->segment.position = position;
510   }
511
512   GST_LOG_OBJECT (osel, "pushing buffer to %" GST_PTR_FORMAT, active_srcpad);
513   res = gst_pad_push (active_srcpad, buf);
514
515   gst_object_unref (active_srcpad);
516
517   return res;
518 }
519
520 static GstStateChangeReturn
521 gst_output_selector_change_state (GstElement * element,
522     GstStateChange transition)
523 {
524   GstOutputSelector *sel;
525   GstStateChangeReturn result;
526
527   sel = GST_OUTPUT_SELECTOR (element);
528
529   switch (transition) {
530     case GST_STATE_CHANGE_READY_TO_PAUSED:
531       break;
532     default:
533       break;
534   }
535
536   result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
537
538   switch (transition) {
539     case GST_STATE_CHANGE_PAUSED_TO_READY:
540       gst_output_selector_reset (sel);
541       break;
542     default:
543       break;
544   }
545
546   return result;
547 }
548
549 static gboolean
550 gst_output_selector_forward_event (GstOutputSelector * sel, GstEvent * event)
551 {
552   gboolean res = TRUE;
553   GstPad *active;
554
555   switch (sel->pad_negotiation_mode) {
556     case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL:
557       /* Send to all src pads */
558       res = gst_pad_event_default (sel->sinkpad, GST_OBJECT_CAST (sel), event);
559       break;
560     case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE:
561       gst_event_unref (event);
562       break;
563     default:
564       active = gst_output_selector_get_active (sel);
565       if (active) {
566         res = gst_pad_push_event (active, event);
567         gst_object_unref (active);
568       } else {
569         gst_event_unref (event);
570       }
571       break;
572   }
573
574   return res;
575 }
576
577 static gboolean
578 gst_output_selector_event (GstPad * pad, GstObject * parent, GstEvent * event)
579 {
580   gboolean res = TRUE;
581   GstOutputSelector *sel;
582   GstPad *active = NULL;
583
584   sel = GST_OUTPUT_SELECTOR (parent);
585
586   switch (GST_EVENT_TYPE (event)) {
587     case GST_EVENT_EOS:
588     {
589       res = gst_output_selector_forward_event (sel, event);
590       break;
591     }
592     case GST_EVENT_SEGMENT:
593     {
594       gst_event_copy_segment (event, &sel->segment);
595       GST_DEBUG_OBJECT (sel, "configured SEGMENT %" GST_SEGMENT_FORMAT,
596           &sel->segment);
597       /* fall through */
598     }
599     default:
600     {
601       active = gst_output_selector_get_active (sel);
602       if (active) {
603         res = gst_pad_push_event (active, event);
604         gst_object_unref (active);
605       } else {
606         gst_event_unref (event);
607       }
608       break;
609     }
610   }
611
612   return res;
613 }
614
615 static gboolean
616 gst_output_selector_query (GstPad * pad, GstObject * parent, GstQuery * query)
617 {
618   gboolean res = TRUE;
619   GstOutputSelector *sel;
620   GstPad *active = NULL;
621
622   sel = GST_OUTPUT_SELECTOR (parent);
623
624   switch (GST_QUERY_TYPE (query)) {
625     case GST_QUERY_CAPS:
626     {
627       switch (sel->pad_negotiation_mode) {
628         case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL:
629           /* Send caps to all src pads */
630           res = gst_pad_proxy_query_caps (pad, query);
631           break;
632         case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE:
633           res = FALSE;
634           break;
635         default:
636           active = gst_output_selector_get_active (sel);
637           if (active) {
638             res = gst_pad_peer_query (active, query);
639             gst_object_unref (active);
640           } else {
641             res = FALSE;
642           }
643           break;
644       }
645       break;
646     }
647     case GST_QUERY_DRAIN:
648       if (sel->latest_buffer) {
649         gst_buffer_unref (sel->latest_buffer);
650         sel->latest_buffer = NULL;
651       }
652       /* fall through */
653     default:
654       res = gst_pad_query_default (pad, parent, query);
655       break;
656   }
657
658   return res;
659 }