74191cb01dd36cac2ed2f9a38140f78e9a173da9
[platform/upstream/gstreamer.git] / plugins / elements / gstoutputselector.c
1 /* GStreamer
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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-output-selector
22  * @see_also: #GstTee, #GstInputSelector
23  *
24  * Direct input stream to one out of N output pads.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <string.h>
32
33 #include "gstoutputselector.h"
34
35 GST_DEBUG_CATEGORY_STATIC (output_selector_debug);
36 #define GST_CAT_DEFAULT output_selector_debug
37
38 static const GstElementDetails gst_output_selector_details =
39 GST_ELEMENT_DETAILS ("Output selector",
40     "Generic",
41     "1-to-N output stream selectoring",
42     "Stefan Kost <stefan.kost@nokia.com>");
43
44 static GstStaticPadTemplate gst_output_selector_sink_factory =
45 GST_STATIC_PAD_TEMPLATE ("sink",
46     GST_PAD_SINK,
47     GST_PAD_ALWAYS,
48     GST_STATIC_CAPS_ANY);
49
50 static GstStaticPadTemplate gst_output_selector_src_factory =
51 GST_STATIC_PAD_TEMPLATE ("src%d",
52     GST_PAD_SRC,
53     GST_PAD_REQUEST,
54     GST_STATIC_CAPS_ANY);
55
56 enum
57 {
58   PROP_0,
59   PROP_ACTIVE_PAD,
60   PROP_RESEND_LATEST,
61   PROP_LAST
62 };
63
64 GST_BOILERPLATE (GstOutputSelector, gst_output_selector, GstElement,
65     GST_TYPE_ELEMENT);
66
67 static void gst_output_selector_dispose (GObject * object);
68 static void gst_output_selector_set_property (GObject * object,
69     guint prop_id, const GValue * value, GParamSpec * pspec);
70 static void gst_output_selector_get_property (GObject * object,
71     guint prop_id, GValue * value, GParamSpec * pspec);
72 static GstPad *gst_output_selector_request_new_pad (GstElement * element,
73     GstPadTemplate * templ, const gchar * unused);
74 static void gst_output_selector_release_pad (GstElement * element,
75     GstPad * pad);
76 static GstFlowReturn gst_output_selector_chain (GstPad * pad, GstBuffer * buf);
77 static GstStateChangeReturn gst_output_selector_change_state (GstElement *
78     element, GstStateChange transition);
79 static gboolean gst_output_selector_handle_sink_event (GstPad * pad,
80     GstEvent * event);
81
82 static void
83 gst_output_selector_base_init (gpointer g_class)
84 {
85   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
86
87   gst_element_class_set_details (element_class, &gst_output_selector_details);
88   gst_element_class_add_pad_template (element_class,
89       gst_static_pad_template_get (&gst_output_selector_sink_factory));
90   gst_element_class_add_pad_template (element_class,
91       gst_static_pad_template_get (&gst_output_selector_src_factory));
92 }
93
94 static void
95 gst_output_selector_class_init (GstOutputSelectorClass * klass)
96 {
97   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
98   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
99
100   parent_class = g_type_class_peek_parent (klass);
101
102   gobject_class->dispose = gst_output_selector_dispose;
103
104   gobject_class->set_property =
105       GST_DEBUG_FUNCPTR (gst_output_selector_set_property);
106   gobject_class->get_property =
107       GST_DEBUG_FUNCPTR (gst_output_selector_get_property);
108
109   g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD,
110       g_param_spec_object ("active-pad", "Active pad",
111           "Currently active src pad", GST_TYPE_PAD, G_PARAM_READWRITE));
112   g_object_class_install_property (gobject_class, PROP_RESEND_LATEST,
113       g_param_spec_boolean ("resend-latest", "Resend latest buffer",
114           "Resend latest buffer after a switch to a new pad", FALSE,
115           G_PARAM_READWRITE));
116
117   gstelement_class->request_new_pad =
118       GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad);
119   gstelement_class->release_pad =
120       GST_DEBUG_FUNCPTR (gst_output_selector_release_pad);
121
122   gstelement_class->change_state = gst_output_selector_change_state;
123
124   GST_DEBUG_CATEGORY_INIT (output_selector_debug,
125       "output-selector", 0, "An output stream selector element");
126 }
127
128 static void
129 gst_output_selector_init (GstOutputSelector * sel,
130     GstOutputSelectorClass * g_class)
131 {
132   sel->sinkpad =
133       gst_pad_new_from_static_template (&gst_output_selector_sink_factory,
134       "sink");
135   gst_pad_set_chain_function (sel->sinkpad,
136       GST_DEBUG_FUNCPTR (gst_output_selector_chain));
137   gst_pad_set_event_function (sel->sinkpad,
138       GST_DEBUG_FUNCPTR (gst_output_selector_handle_sink_event));
139
140   gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad);
141
142   /*
143      gst_pad_set_bufferalloc_function (sel->sinkpad,
144      GST_DEBUG_FUNCPTR (gst_output_selector_bufferalloc));
145      gst_pad_set_setcaps_function (sel->sinkpad,
146      GST_DEBUG_FUNCPTR (gst_pad_proxy_setcaps));
147      gst_pad_set_getcaps_function (sel->sinkpad,
148      GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
149    */
150   /* srcpad management */
151   sel->active_srcpad = NULL;
152   sel->nb_srcpads = 0;
153   gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED);
154   sel->pending_srcpad = NULL;
155
156   sel->resend_latest = FALSE;
157   sel->latest_buffer = NULL;
158 }
159
160 static void
161 gst_output_selector_dispose (GObject * object)
162 {
163   GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object);
164
165   if (osel->pending_srcpad != NULL) {
166     gst_object_unref (osel->pending_srcpad);
167     osel->pending_srcpad = NULL;
168   }
169   if (osel->latest_buffer != NULL) {
170     gst_buffer_unref (osel->latest_buffer);
171     osel->latest_buffer = NULL;
172   }
173
174   G_OBJECT_CLASS (parent_class)->dispose (object);
175 }
176
177 static void
178 gst_output_selector_set_property (GObject * object, guint prop_id,
179     const GValue * value, GParamSpec * pspec)
180 {
181   GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
182
183   switch (prop_id) {
184     case PROP_ACTIVE_PAD:
185     {
186       GstPad *next_pad;
187
188       next_pad = g_value_get_object (value);
189
190       GST_LOG_OBJECT (sel, "Activating pad %s:%s",
191           GST_DEBUG_PAD_NAME (next_pad));
192
193       GST_OBJECT_LOCK (object);
194       if (next_pad != sel->active_srcpad) {
195         /* switch to new srcpad in next chain run */
196         if (sel->pending_srcpad != NULL) {
197           GST_INFO ("replacing pending switch");
198           gst_object_unref (sel->pending_srcpad);
199         }
200         if (next_pad)
201           gst_object_ref (next_pad);
202         sel->pending_srcpad = next_pad;
203       } else {
204         GST_INFO ("pad already active");
205         if (sel->pending_srcpad != NULL) {
206           gst_object_unref (sel->pending_srcpad);
207           sel->pending_srcpad = NULL;
208         }
209       }
210       GST_OBJECT_UNLOCK (object);
211       break;
212     }
213     case PROP_RESEND_LATEST:{
214       sel->resend_latest = g_value_get_boolean (value);
215       break;
216     }
217     default:
218       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
219       break;
220   }
221 }
222
223 static void
224 gst_output_selector_get_property (GObject * object, guint prop_id,
225     GValue * value, GParamSpec * pspec)
226 {
227   GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object);
228
229   switch (prop_id) {
230     case PROP_ACTIVE_PAD:
231       GST_OBJECT_LOCK (object);
232       g_value_set_object (value, sel->active_srcpad);
233       GST_OBJECT_UNLOCK (object);
234       break;
235     case PROP_RESEND_LATEST:{
236       GST_OBJECT_LOCK (object);
237       g_value_set_boolean (value, sel->resend_latest);
238       GST_OBJECT_UNLOCK (object);
239       break;
240     }
241     default:
242       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
243       break;
244   }
245 }
246
247 static GstPad *
248 gst_output_selector_request_new_pad (GstElement * element,
249     GstPadTemplate * templ, const gchar * name)
250 {
251   gchar *padname;
252   GstPad *srcpad;
253   GstOutputSelector *osel;
254
255   osel = GST_OUTPUT_SELECTOR (element);
256
257   GST_DEBUG_OBJECT (osel, "requesting pad");
258
259   GST_OBJECT_LOCK (osel);
260   padname = g_strdup_printf ("src%d", osel->nb_srcpads++);
261   srcpad = gst_pad_new_from_template (templ, padname);
262   GST_OBJECT_UNLOCK (osel);
263
264   gst_pad_set_active (srcpad, TRUE);
265   gst_element_add_pad (GST_ELEMENT (osel), srcpad);
266
267   /* Set the first requested src pad as active by default */
268   if (osel->active_srcpad == NULL) {
269     osel->active_srcpad = srcpad;
270   }
271   g_free (padname);
272
273   return srcpad;
274 }
275
276 static void
277 gst_output_selector_release_pad (GstElement * element, GstPad * pad)
278 {
279   GstOutputSelector *osel;
280
281   osel = GST_OUTPUT_SELECTOR (element);
282
283   GST_DEBUG_OBJECT (osel, "releasing pad");
284
285   gst_pad_set_active (pad, FALSE);
286
287   gst_element_remove_pad (GST_ELEMENT_CAST (osel), pad);
288 }
289
290 static gboolean
291 gst_output_selector_switch (GstOutputSelector * osel)
292 {
293   gboolean res = TRUE;
294   GstEvent *ev = NULL;
295   GstSegment *seg = NULL;
296   gint64 start = 0, position = 0;
297
298   GST_INFO ("switching to pad %" GST_PTR_FORMAT, osel->pending_srcpad);
299
300   if (gst_pad_is_linked (osel->pending_srcpad)) {
301     /* Send NEWSEGMENT to the pad we are going to switch to */
302     seg = &osel->segment;
303     /* If resending then mark newsegment start and position accordingly */
304     if (osel->resend_latest && osel->latest_buffer &&
305         GST_BUFFER_TIMESTAMP_IS_VALID (osel->latest_buffer)) {
306       start = position = GST_BUFFER_TIMESTAMP (osel->latest_buffer);
307     } else {
308       start = position = seg->last_stop;
309     }
310     ev = gst_event_new_new_segment (TRUE, seg->rate,
311         seg->format, start, seg->stop, position);
312     if (!gst_pad_push_event (osel->pending_srcpad, ev)) {
313       GST_WARNING_OBJECT (osel,
314           "newsegment handling failed in %" GST_PTR_FORMAT,
315           osel->pending_srcpad);
316     }
317
318     /* Resend latest buffer to newly switched pad */
319     if (osel->resend_latest && osel->latest_buffer) {
320       GST_INFO ("resending latest buffer");
321       gst_pad_push (osel->pending_srcpad, osel->latest_buffer);
322       osel->latest_buffer = NULL;
323     }
324
325     /* Switch */
326     osel->active_srcpad = osel->pending_srcpad;
327   } else {
328     GST_WARNING_OBJECT (osel, "switch failed, pad not linked");
329     res = FALSE;
330   }
331
332   gst_object_unref (osel->pending_srcpad);
333   osel->pending_srcpad = NULL;
334
335   return res;
336 }
337
338 static GstFlowReturn
339 gst_output_selector_chain (GstPad * pad, GstBuffer * buf)
340 {
341   GstFlowReturn res;
342   GstOutputSelector *osel;
343   GstClockTime last_stop, duration;
344
345   osel = GST_OUTPUT_SELECTOR (gst_pad_get_parent (pad));
346
347   if (osel->pending_srcpad) {
348     /* Do the switch */
349     gst_output_selector_switch (osel);
350   }
351
352   /* Keep reference to latest buffer to resend it after switch */
353   if (osel->latest_buffer)
354     gst_buffer_unref (osel->latest_buffer);
355   osel->latest_buffer = gst_buffer_ref (buf);
356
357   /* Keep track of last stop and use it in NEWSEGMENT start after 
358      switching to a new src pad */
359   last_stop = GST_BUFFER_TIMESTAMP (buf);
360   if (GST_CLOCK_TIME_IS_VALID (last_stop)) {
361     duration = GST_BUFFER_DURATION (buf);
362     if (GST_CLOCK_TIME_IS_VALID (duration)) {
363       last_stop += duration;
364     }
365     GST_LOG_OBJECT (osel, "setting last stop %" GST_TIME_FORMAT,
366         GST_TIME_ARGS (last_stop));
367     gst_segment_set_last_stop (&osel->segment, osel->segment.format, last_stop);
368   }
369
370   GST_LOG_OBJECT (osel, "pushing buffer to %" GST_PTR_FORMAT,
371       osel->active_srcpad);
372   res = gst_pad_push (osel->active_srcpad, buf);
373   gst_object_unref (osel);
374
375   return res;
376 }
377
378 static GstStateChangeReturn
379 gst_output_selector_change_state (GstElement * element,
380     GstStateChange transition)
381 {
382   return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
383 }
384
385 static gboolean
386 gst_output_selector_handle_sink_event (GstPad * pad, GstEvent * event)
387 {
388   gboolean res = TRUE;
389   GstOutputSelector *sel;
390   GstPad *output_pad = NULL;
391
392   sel = GST_OUTPUT_SELECTOR (gst_pad_get_parent (pad));
393
394   switch (GST_EVENT_TYPE (event)) {
395     case GST_EVENT_NEWSEGMENT:
396     {
397       gboolean update;
398       GstFormat format;
399       gdouble rate, arate;
400       gint64 start, stop, time;
401
402       gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
403           &start, &stop, &time);
404
405       GST_DEBUG_OBJECT (sel,
406           "configured NEWSEGMENT update %d, rate %lf, applied rate %lf, "
407           "format %d, " "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %"
408           G_GINT64_FORMAT, update, rate, arate, format, start, stop, time);
409
410       gst_segment_set_newsegment_full (&sel->segment, update,
411           rate, arate, format, start, stop, time);
412
413       /* Send newsegment to all src pads */
414       gst_pad_event_default (pad, event);
415
416       break;
417     }
418     case GST_EVENT_EOS:
419       /* Send eos to all src pads */
420       gst_pad_event_default (pad, event);
421       break;
422     default:
423       /* Send other events to pending or active src pad */
424       output_pad =
425           sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad;
426       res = gst_pad_push_event (output_pad, event);
427       break;
428   }
429
430   gst_object_unref (sel);
431
432   return res;
433 }