1 /*
2 * GStreamer Funnel element
3 *
4 * Copyright 2007 Collabora Ltd.
5 * @author: Olivier Crete <olivier.crete@collabora.co.uk>
6 * Copyright 2007 Nokia Corp.
7 *
8 * gstfunnel.c: Simple Funnel element
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 /**
26 * SECTION:element-funnel
27 * @title: funnel
28 *
29 * Takes packets from various input sinks into one output source.
30 *
31 * #GstFunnel will forward sticky events from its first active sink pad
32 * and, if #GstFunnel:forward-sticky-events is set to true, it will
33 * forward sticky events each time a pad becomes active.
34 */
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include "gstfunnel.h"
41 #include "gstcoreelementselements.h"
42
43 GST_DEBUG_CATEGORY_STATIC (gst_funnel_debug);
44 #define GST_CAT_DEFAULT gst_funnel_debug
45
46 GType gst_funnel_pad_get_type (void);
47 #define GST_TYPE_FUNNEL_PAD \
48 (gst_funnel_pad_get_type())
49 #define GST_FUNNEL_PAD(obj) \
50 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_FUNNEL_PAD, GstFunnelPad))
51 #define GST_FUNNEL_PAD_CLASS(klass) \
52 (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_FUNNEL_PAD, GstFunnelPadClass))
53 #define GST_IS_FUNNEL_PAD(obj) \
54 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_FUNNEL_PAD))
55 #define GST_IS_FUNNEL_PAD_CLASS(klass) \
56 (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_FUNNEL_PAD))
57 #define GST_FUNNEL_PAD_CAST(obj) \
58 ((GstFunnelPad *)(obj))
59
60 typedef struct _GstFunnelPad GstFunnelPad;
61 typedef struct _GstFunnelPadClass GstFunnelPadClass;
62
63 struct _GstFunnelPad
64 {
65 GstPad parent;
66
67 gboolean got_eos;
68 };
69
70 struct _GstFunnelPadClass
71 {
72 GstPadClass parent;
73 };
74
75 G_DEFINE_TYPE (GstFunnelPad, gst_funnel_pad, GST_TYPE_PAD);
76
77 #define DEFAULT_FORWARD_STICKY_EVENTS TRUE
78
79 enum
80 {
81 PROP_0,
82 PROP_FORWARD_STICKY_EVENTS
83 };
84
85 static void
gst_funnel_pad_class_init(GstFunnelPadClass * klass)86 gst_funnel_pad_class_init (GstFunnelPadClass * klass)
87 {
88 }
89
90 static void
gst_funnel_pad_init(GstFunnelPad * pad)91 gst_funnel_pad_init (GstFunnelPad * pad)
92 {
93 pad->got_eos = FALSE;
94 }
95
96 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink_%u",
97 GST_PAD_SINK,
98 GST_PAD_REQUEST,
99 GST_STATIC_CAPS_ANY);
100
101 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
102 GST_PAD_SRC,
103 GST_PAD_ALWAYS,
104 GST_STATIC_CAPS_ANY);
105
106 #define _do_init \
107 GST_DEBUG_CATEGORY_INIT (gst_funnel_debug, "funnel", 0, "funnel element");
108 #define gst_funnel_parent_class parent_class
109 G_DEFINE_TYPE_WITH_CODE (GstFunnel, gst_funnel, GST_TYPE_ELEMENT, _do_init);
110 GST_ELEMENT_REGISTER_DEFINE (funnel, "funnel", GST_RANK_NONE, GST_TYPE_FUNNEL);
111
112 static GstStateChangeReturn gst_funnel_change_state (GstElement * element,
113 GstStateChange transition);
114 static GstPad *gst_funnel_request_new_pad (GstElement * element,
115 GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
116 static void gst_funnel_release_pad (GstElement * element, GstPad * pad);
117
118 static GstFlowReturn gst_funnel_sink_chain (GstPad * pad, GstObject * parent,
119 GstBuffer * buffer);
120 static GstFlowReturn gst_funnel_sink_chain_list (GstPad * pad,
121 GstObject * parent, GstBufferList * list);
122 static gboolean gst_funnel_sink_event (GstPad * pad, GstObject * parent,
123 GstEvent * event);
124
125 static void
gst_funnel_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)126 gst_funnel_set_property (GObject * object, guint prop_id,
127 const GValue * value, GParamSpec * pspec)
128 {
129 GstFunnel *funnel = GST_FUNNEL_CAST (object);
130
131 switch (prop_id) {
132 case PROP_FORWARD_STICKY_EVENTS:
133 funnel->forward_sticky_events = g_value_get_boolean (value);
134 break;
135 default:
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137 break;
138 }
139 }
140
141 static void
gst_funnel_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)142 gst_funnel_get_property (GObject * object, guint prop_id, GValue * value,
143 GParamSpec * pspec)
144 {
145 GstFunnel *funnel = GST_FUNNEL_CAST (object);
146
147 switch (prop_id) {
148 case PROP_FORWARD_STICKY_EVENTS:
149 g_value_set_boolean (value, funnel->forward_sticky_events);
150 break;
151 default:
152 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
153 break;
154 }
155 }
156
157 static void
gst_funnel_dispose(GObject * object)158 gst_funnel_dispose (GObject * object)
159 {
160 GstFunnel *funnel = GST_FUNNEL_CAST (object);
161 GList *item;
162
163 gst_object_replace ((GstObject **) & funnel->last_sinkpad, NULL);
164
165 restart:
166 for (item = GST_ELEMENT_PADS (object); item; item = g_list_next (item)) {
167 GstPad *pad = GST_PAD (item->data);
168
169 if (GST_PAD_IS_SINK (pad)) {
170 gst_element_release_request_pad (GST_ELEMENT (object), pad);
171 goto restart;
172 }
173 }
174
175 G_OBJECT_CLASS (parent_class)->dispose (object);
176 }
177
178 static void
gst_funnel_class_init(GstFunnelClass * klass)179 gst_funnel_class_init (GstFunnelClass * klass)
180 {
181 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
182 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
183
184 gobject_class->set_property = gst_funnel_set_property;
185 gobject_class->get_property = gst_funnel_get_property;
186 gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_funnel_dispose);
187
188 g_object_class_install_property (gobject_class, PROP_FORWARD_STICKY_EVENTS,
189 g_param_spec_boolean ("forward-sticky-events", "Forward sticky events",
190 "Forward sticky events on stream changes",
191 DEFAULT_FORWARD_STICKY_EVENTS,
192 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
193 GST_PARAM_MUTABLE_READY));
194
195 gst_element_class_set_static_metadata (gstelement_class,
196 "Funnel pipe fitting", "Generic", "N-to-1 pipe fitting",
197 "Olivier Crete <olivier.crete@collabora.co.uk>");
198
199 gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
200 gst_element_class_add_static_pad_template (gstelement_class, &src_template);
201
202 gstelement_class->request_new_pad =
203 GST_DEBUG_FUNCPTR (gst_funnel_request_new_pad);
204 gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_funnel_release_pad);
205 gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_funnel_change_state);
206 }
207
208 static void
gst_funnel_init(GstFunnel * funnel)209 gst_funnel_init (GstFunnel * funnel)
210 {
211 funnel->srcpad = gst_pad_new_from_static_template (&src_template, "src");
212 gst_pad_use_fixed_caps (funnel->srcpad);
213
214 gst_element_add_pad (GST_ELEMENT (funnel), funnel->srcpad);
215
216 funnel->forward_sticky_events = DEFAULT_FORWARD_STICKY_EVENTS;
217 }
218
219 static GstPad *
gst_funnel_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * name,const GstCaps * caps)220 gst_funnel_request_new_pad (GstElement * element, GstPadTemplate * templ,
221 const gchar * name, const GstCaps * caps)
222 {
223 GstPad *sinkpad;
224
225 GST_DEBUG_OBJECT (element, "requesting pad");
226
227 sinkpad = GST_PAD_CAST (g_object_new (GST_TYPE_FUNNEL_PAD,
228 "name", name, "direction", templ->direction, "template", templ,
229 NULL));
230
231 gst_pad_set_chain_function (sinkpad,
232 GST_DEBUG_FUNCPTR (gst_funnel_sink_chain));
233 gst_pad_set_chain_list_function (sinkpad,
234 GST_DEBUG_FUNCPTR (gst_funnel_sink_chain_list));
235 gst_pad_set_event_function (sinkpad,
236 GST_DEBUG_FUNCPTR (gst_funnel_sink_event));
237
238 GST_OBJECT_FLAG_SET (sinkpad, GST_PAD_FLAG_PROXY_CAPS);
239 GST_OBJECT_FLAG_SET (sinkpad, GST_PAD_FLAG_PROXY_ALLOCATION);
240
241 gst_pad_set_active (sinkpad, TRUE);
242
243 gst_element_add_pad (element, sinkpad);
244
245 GST_DEBUG_OBJECT (element, "requested pad %s:%s",
246 GST_DEBUG_PAD_NAME (sinkpad));
247
248 return sinkpad;
249 }
250
251 static gboolean
gst_funnel_all_sinkpads_eos_unlocked(GstFunnel * funnel,GstPad * pad)252 gst_funnel_all_sinkpads_eos_unlocked (GstFunnel * funnel, GstPad * pad)
253 {
254 GstElement *element = GST_ELEMENT_CAST (funnel);
255 GList *item;
256 gboolean all_eos = FALSE;
257
258
259 if (element->numsinkpads == 0)
260 goto done;
261
262 for (item = element->sinkpads; item != NULL; item = g_list_next (item)) {
263 GstFunnelPad *sinkpad = GST_FUNNEL_PAD_CAST (item->data);
264
265 if (!sinkpad->got_eos) {
266 return FALSE;
267 }
268 }
269
270 all_eos = TRUE;
271
272 done:
273 return all_eos;
274 }
275
276 static void
gst_funnel_release_pad(GstElement * element,GstPad * pad)277 gst_funnel_release_pad (GstElement * element, GstPad * pad)
278 {
279 GstFunnel *funnel = GST_FUNNEL_CAST (element);
280 GstFunnelPad *fpad = GST_FUNNEL_PAD_CAST (pad);
281 gboolean got_eos;
282 gboolean send_eos = FALSE;
283
284 GST_DEBUG_OBJECT (funnel, "releasing pad %s:%s", GST_DEBUG_PAD_NAME (pad));
285
286 gst_pad_set_active (pad, FALSE);
287
288 got_eos = fpad->got_eos;
289
290 gst_element_remove_pad (GST_ELEMENT_CAST (funnel), pad);
291
292 GST_OBJECT_LOCK (funnel);
293 if (!got_eos && gst_funnel_all_sinkpads_eos_unlocked (funnel, NULL)) {
294 GST_DEBUG_OBJECT (funnel, "Pad removed. All others are EOS. Sending EOS");
295 send_eos = TRUE;
296 }
297 GST_OBJECT_UNLOCK (funnel);
298
299 if (send_eos)
300 if (!gst_pad_push_event (funnel->srcpad, gst_event_new_eos ()))
301 GST_WARNING_OBJECT (funnel, "Failure pushing EOS");
302 }
303
304 static gboolean
forward_events(GstPad * pad,GstEvent ** event,gpointer user_data)305 forward_events (GstPad * pad, GstEvent ** event, gpointer user_data)
306 {
307 GstPad *srcpad = user_data;
308
309 if (GST_EVENT_TYPE (*event) != GST_EVENT_EOS)
310 gst_pad_push_event (srcpad, gst_event_ref (*event));
311
312 return TRUE;
313 }
314
315 static GstFlowReturn
gst_funnel_sink_chain_object(GstPad * pad,GstFunnel * funnel,gboolean is_list,GstMiniObject * obj)316 gst_funnel_sink_chain_object (GstPad * pad, GstFunnel * funnel,
317 gboolean is_list, GstMiniObject * obj)
318 {
319 GstFlowReturn res;
320
321 GST_DEBUG_OBJECT (pad, "received %" GST_PTR_FORMAT, obj);
322
323 GST_PAD_STREAM_LOCK (funnel->srcpad);
324
325 if ((funnel->last_sinkpad == NULL) || (funnel->forward_sticky_events
326 && (funnel->last_sinkpad != pad))) {
327 gst_object_replace ((GstObject **) & funnel->last_sinkpad,
328 GST_OBJECT (pad));
329
330 GST_DEBUG_OBJECT (pad, "Forwarding sticky events");
331 gst_pad_sticky_events_foreach (pad, forward_events, funnel->srcpad);
332 }
333
334 if (is_list)
335 res = gst_pad_push_list (funnel->srcpad, GST_BUFFER_LIST_CAST (obj));
336 else
337 res = gst_pad_push (funnel->srcpad, GST_BUFFER_CAST (obj));
338
339 GST_PAD_STREAM_UNLOCK (funnel->srcpad);
340
341 GST_LOG_OBJECT (pad, "handled buffer%s %s", (is_list ? "list" : ""),
342 gst_flow_get_name (res));
343
344 return res;
345 }
346
347 static GstFlowReturn
gst_funnel_sink_chain_list(GstPad * pad,GstObject * parent,GstBufferList * list)348 gst_funnel_sink_chain_list (GstPad * pad, GstObject * parent,
349 GstBufferList * list)
350 {
351 GstFunnel *funnel = GST_FUNNEL_CAST (parent);
352
353 return gst_funnel_sink_chain_object (pad, funnel, TRUE,
354 GST_MINI_OBJECT_CAST (list));
355 }
356
357 static GstFlowReturn
gst_funnel_sink_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)358 gst_funnel_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
359 {
360 GstFunnel *funnel = GST_FUNNEL_CAST (parent);
361
362 return gst_funnel_sink_chain_object (pad, funnel, FALSE,
363 GST_MINI_OBJECT_CAST (buffer));
364 }
365
366 static gboolean
gst_funnel_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)367 gst_funnel_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
368 {
369 GstFunnel *funnel = GST_FUNNEL_CAST (parent);
370 GstFunnelPad *fpad = GST_FUNNEL_PAD_CAST (pad);
371 gboolean forward = TRUE;
372 gboolean res = TRUE;
373 gboolean unlock = FALSE;
374
375 GST_DEBUG_OBJECT (pad, "received event %" GST_PTR_FORMAT, event);
376
377 if (GST_EVENT_IS_STICKY (event)) {
378 unlock = TRUE;
379 GST_PAD_STREAM_LOCK (funnel->srcpad);
380
381 if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
382 GST_OBJECT_LOCK (funnel);
383 fpad->got_eos = TRUE;
384 if (!gst_funnel_all_sinkpads_eos_unlocked (funnel, pad)) {
385 forward = FALSE;
386 } else {
387 forward = TRUE;
388 }
389 GST_OBJECT_UNLOCK (funnel);
390 } else if (pad != funnel->last_sinkpad) {
391 forward = FALSE;
392 }
393 } else if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) {
394 unlock = TRUE;
395 GST_PAD_STREAM_LOCK (funnel->srcpad);
396 GST_OBJECT_LOCK (funnel);
397 fpad->got_eos = FALSE;
398 GST_OBJECT_UNLOCK (funnel);
399 }
400
401 if (forward && GST_EVENT_IS_SERIALIZED (event)) {
402 /* If no data is coming and we receive serialized event, need to forward all sticky events.
403 * Otherwise downstream has an inconsistent set of sticky events when
404 * handling the new event. */
405 if (!unlock) {
406 unlock = TRUE;
407 GST_PAD_STREAM_LOCK (funnel->srcpad);
408 }
409
410 if ((funnel->last_sinkpad == NULL) || (funnel->forward_sticky_events
411 && (funnel->last_sinkpad != pad))) {
412 gst_object_replace ((GstObject **) & funnel->last_sinkpad,
413 GST_OBJECT (pad));
414 gst_pad_sticky_events_foreach (pad, forward_events, funnel->srcpad);
415 }
416 }
417
418 if (forward)
419 res = gst_pad_push_event (funnel->srcpad, event);
420 else
421 gst_event_unref (event);
422
423 if (unlock)
424 GST_PAD_STREAM_UNLOCK (funnel->srcpad);
425
426 return res;
427 }
428
429 static void
reset_pad(const GValue * data,gpointer user_data)430 reset_pad (const GValue * data, gpointer user_data)
431 {
432 GstPad *pad = g_value_get_object (data);
433 GstFunnelPad *fpad = GST_FUNNEL_PAD_CAST (pad);
434 GstFunnel *funnel = user_data;
435
436 GST_OBJECT_LOCK (funnel);
437 fpad->got_eos = FALSE;
438 GST_OBJECT_UNLOCK (funnel);
439 }
440
441 static GstStateChangeReturn
gst_funnel_change_state(GstElement * element,GstStateChange transition)442 gst_funnel_change_state (GstElement * element, GstStateChange transition)
443 {
444 GstStateChangeReturn ret;
445
446 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
447 if (ret == GST_STATE_CHANGE_FAILURE)
448 return ret;
449
450 switch (transition) {
451 case GST_STATE_CHANGE_PAUSED_TO_READY:
452 {
453 GstIterator *iter = gst_element_iterate_sink_pads (element);
454 GstIteratorResult res;
455
456 do {
457 res = gst_iterator_foreach (iter, reset_pad, element);
458 if (res == GST_ITERATOR_RESYNC)
459 gst_iterator_resync (iter);
460 } while (res == GST_ITERATOR_RESYNC);
461 gst_iterator_free (iter);
462
463 if (res == GST_ITERATOR_ERROR)
464 return GST_STATE_CHANGE_FAILURE;
465
466 }
467 break;
468 default:
469 break;
470 }
471
472 return ret;
473 }
474