1 /*
2 *
3 * GStreamer
4 * Copyright (C) 2015 Matthew Waters <matthew@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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <gst/gst.h>
27
28 #include "gstglmixerbin.h"
29
30 #define GST_CAT_DEFAULT gst_gl_mixer_bin_debug
31 GST_DEBUG_CATEGORY (gst_gl_mixer_bin_debug);
32
33 #define DEFAULT_LATENCY 0
34 #define DEFAULT_START_TIME_SELECTION 0
35 #define DEFAULT_START_TIME (-1)
36
37 typedef enum
38 {
39 GST_GL_MIXER_BIN_START_TIME_SELECTION_ZERO,
40 GST_GL_MIXER_BIN_START_TIME_SELECTION_FIRST,
41 GST_GL_MIXER_BIN_START_TIME_SELECTION_SET
42 } GstGLMixerBinStartTimeSelection;
43
44 static GType
gst_gl_mixer_bin_start_time_selection_get_type(void)45 gst_gl_mixer_bin_start_time_selection_get_type (void)
46 {
47 static GType gtype = 0;
48
49 if (gtype == 0) {
50 static const GEnumValue values[] = {
51 {GST_GL_MIXER_BIN_START_TIME_SELECTION_ZERO,
52 "Start at 0 running time (default)", "zero"},
53 {GST_GL_MIXER_BIN_START_TIME_SELECTION_FIRST,
54 "Start at first observed input running time", "first"},
55 {GST_GL_MIXER_BIN_START_TIME_SELECTION_SET,
56 "Set start time with start-time property", "set"},
57 {0, NULL, NULL}
58 };
59
60 gtype = g_enum_register_static ("GstGLMixerBinStartTimeSelection", values);
61 }
62 return gtype;
63 }
64
65 struct input_chain
66 {
67 GstGLMixerBin *self;
68 GstGhostPad *ghost_pad;
69 GstElement *upload;
70 GstElement *in_convert;
71 GstElement *in_overlay;
72 GstPad *mixer_pad;
73 };
74
75 static void
_free_input_chain(struct input_chain * chain)76 _free_input_chain (struct input_chain *chain)
77 {
78 if (!chain)
79 return;
80
81 chain->ghost_pad = NULL;
82
83 if (chain->upload) {
84 gst_element_set_state (chain->upload, GST_STATE_NULL);
85 gst_bin_remove (GST_BIN (chain->self), chain->upload);
86 chain->upload = NULL;
87 }
88
89 if (chain->in_convert) {
90 gst_element_set_state (chain->in_convert, GST_STATE_NULL);
91 gst_bin_remove (GST_BIN (chain->self), chain->in_convert);
92 chain->in_convert = NULL;
93 }
94
95 if (chain->in_overlay) {
96 gst_element_set_state (chain->in_overlay, GST_STATE_NULL);
97 gst_bin_remove (GST_BIN (chain->self), chain->in_overlay);
98 chain->in_overlay = NULL;
99 }
100
101 if (chain->mixer_pad) {
102 gst_element_release_request_pad (chain->self->mixer, chain->mixer_pad);
103 gst_object_unref (chain->mixer_pad);
104 chain->mixer_pad = NULL;
105 }
106
107 g_free (chain);
108 }
109
110 struct _GstGLMixerBinPrivate
111 {
112 gboolean running;
113
114 GList *input_chains;
115 };
116
117 enum
118 {
119 PROP_0,
120 PROP_MIXER,
121 PROP_LATENCY,
122 PROP_START_TIME_SELECTION,
123 PROP_START_TIME,
124 };
125
126 enum
127 {
128 SIGNAL_0,
129 SIGNAL_CREATE_ELEMENT,
130 LAST_SIGNAL
131 };
132
133 static void gst_gl_mixer_bin_child_proxy_init (gpointer g_iface,
134 gpointer iface_data);
135
136 G_DEFINE_TYPE_WITH_CODE (GstGLMixerBin, gst_gl_mixer_bin, GST_TYPE_BIN,
137 G_ADD_PRIVATE (GstGLMixerBin)
138 G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
139 gst_gl_mixer_bin_child_proxy_init));
140
141 static guint gst_gl_mixer_bin_signals[LAST_SIGNAL] = { 0 };
142
143 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
144 GST_PAD_SRC,
145 GST_PAD_ALWAYS,
146 GST_STATIC_CAPS ("video/x-raw(ANY)")
147 );
148
149 static void gst_gl_mixer_bin_set_property (GObject * object, guint prop_id,
150 const GValue * value, GParamSpec * pspec);
151 static void gst_gl_mixer_bin_get_property (GObject * object, guint prop_id,
152 GValue * value, GParamSpec * pspec);
153 static void gst_gl_mixer_bin_dispose (GObject * object);
154
155 static GstPad *gst_gl_mixer_bin_request_new_pad (GstElement * element,
156 GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps);
157 static void gst_gl_mixer_bin_release_pad (GstElement * element, GstPad * pad);
158 static GstStateChangeReturn gst_gl_mixer_bin_change_state (GstElement *
159 element, GstStateChange transition);
160
161 static void
gst_gl_mixer_bin_class_init(GstGLMixerBinClass * klass)162 gst_gl_mixer_bin_class_init (GstGLMixerBinClass * klass)
163 {
164 GObjectClass *gobject_class = (GObjectClass *) klass;
165 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
166 GstCaps *upload_caps;
167
168 GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "glmixerbin", 0,
169 "opengl mixer bin");
170
171 element_class->request_new_pad = gst_gl_mixer_bin_request_new_pad;
172 element_class->release_pad = gst_gl_mixer_bin_release_pad;
173 element_class->change_state = gst_gl_mixer_bin_change_state;
174
175 gobject_class->get_property = gst_gl_mixer_bin_get_property;
176 gobject_class->set_property = gst_gl_mixer_bin_set_property;
177 gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_gl_mixer_bin_dispose);
178
179 g_object_class_install_property (gobject_class, PROP_MIXER,
180 g_param_spec_object ("mixer",
181 "GL mixer element",
182 "The GL mixer chain to use",
183 GST_TYPE_ELEMENT,
184 GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
185 G_PARAM_STATIC_STRINGS));
186
187 g_object_class_install_property (gobject_class, PROP_LATENCY,
188 g_param_spec_uint64 ("latency", "Buffer latency",
189 "Additional latency in live mode to allow upstream "
190 "to take longer to produce buffers for the current "
191 "position (in nanoseconds)", 0, G_MAXUINT64,
192 DEFAULT_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
193
194 g_object_class_install_property (gobject_class, PROP_START_TIME_SELECTION,
195 g_param_spec_enum ("start-time-selection", "Start Time Selection",
196 "Decides which start time is output",
197 gst_gl_mixer_bin_start_time_selection_get_type (),
198 DEFAULT_START_TIME_SELECTION,
199 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
200
201 g_object_class_install_property (gobject_class, PROP_START_TIME,
202 g_param_spec_uint64 ("start-time", "Start Time",
203 "Start time to use if start-time-selection=set", 0,
204 G_MAXUINT64,
205 DEFAULT_START_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
206
207 /**
208 * GstMixerBin::create-element:
209 * @object: the #GstGLMixerBin
210 *
211 * Will be emitted when we need the processing element/s that this bin will use
212 *
213 * Returns: a new #GstElement
214 */
215 gst_gl_mixer_bin_signals[SIGNAL_CREATE_ELEMENT] =
216 g_signal_new ("create-element", G_TYPE_FROM_CLASS (klass),
217 G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
218 GST_TYPE_ELEMENT, 0);
219
220 gst_element_class_add_static_pad_template (element_class, &src_factory);
221
222 upload_caps = gst_gl_upload_get_input_template_caps ();
223 gst_element_class_add_pad_template (element_class,
224 gst_pad_template_new ("sink_%u", GST_PAD_SINK, GST_PAD_REQUEST,
225 upload_caps));
226 gst_caps_unref (upload_caps);
227
228 gst_element_class_set_metadata (element_class, "OpenGL video_mixer empty bin",
229 "Bin/Filter/Effect/Video/Mixer", "OpenGL video_mixer empty bin",
230 "Matthew Waters <matthew@centricular.com>");
231 }
232
233 static void
gst_gl_mixer_bin_init(GstGLMixerBin * self)234 gst_gl_mixer_bin_init (GstGLMixerBin * self)
235 {
236 gboolean res = TRUE;
237 GstPad *pad;
238
239 self->priv = gst_gl_mixer_bin_get_instance_private (self);
240
241 self->out_convert = gst_element_factory_make ("glcolorconvert", NULL);
242 self->download = gst_element_factory_make ("gldownload", NULL);
243 res &= gst_bin_add (GST_BIN (self), self->out_convert);
244 res &= gst_bin_add (GST_BIN (self), self->download);
245
246 res &=
247 gst_element_link_pads (self->out_convert, "src", self->download, "sink");
248
249 pad = gst_element_get_static_pad (self->download, "src");
250 if (!pad) {
251 res = FALSE;
252 } else {
253 GST_DEBUG_OBJECT (self, "setting target src pad %" GST_PTR_FORMAT, pad);
254 self->srcpad = gst_ghost_pad_new ("src", pad);
255 gst_element_add_pad (GST_ELEMENT_CAST (self), self->srcpad);
256 gst_object_unref (pad);
257 }
258
259 if (!res)
260 GST_ERROR_OBJECT (self, "failed to create output chain");
261 }
262
263 static void
gst_gl_mixer_bin_dispose(GObject * object)264 gst_gl_mixer_bin_dispose (GObject * object)
265 {
266 GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
267 GList *l = self->priv->input_chains;
268
269 while (l) {
270 struct input_chain *chain = l->data;
271
272 if (self->mixer && chain->mixer_pad) {
273 gst_element_release_request_pad (GST_ELEMENT (self->mixer),
274 chain->mixer_pad);
275 gst_object_unref (chain->mixer_pad);
276 chain->mixer_pad = NULL;
277 }
278
279 l = l->next;
280 }
281
282 g_list_free_full (self->priv->input_chains, (GDestroyNotify) g_free);
283
284 G_OBJECT_CLASS (gst_gl_mixer_bin_parent_class)->dispose (object);
285 }
286
287 static gboolean
_create_input_chain(GstGLMixerBin * self,struct input_chain * chain,GstPad * mixer_pad)288 _create_input_chain (GstGLMixerBin * self, struct input_chain *chain,
289 GstPad * mixer_pad)
290 {
291 GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
292 GstPad *pad;
293 gboolean res = TRUE;
294 gchar *name;
295
296 chain->self = self;
297 chain->mixer_pad = mixer_pad;
298
299 chain->upload = gst_element_factory_make ("glupload", NULL);
300 chain->in_convert = gst_element_factory_make ("glcolorconvert", NULL);
301 chain->in_overlay = gst_element_factory_make ("gloverlaycompositor", NULL);
302
303 res &= gst_bin_add (GST_BIN (self), chain->in_convert);
304 res &= gst_bin_add (GST_BIN (self), chain->in_overlay);
305 res &= gst_bin_add (GST_BIN (self), chain->upload);
306
307 pad = gst_element_get_static_pad (chain->in_overlay, "src");
308 if (gst_pad_link (pad, mixer_pad) != GST_PAD_LINK_OK) {
309 gst_object_unref (pad);
310 return FALSE;
311 }
312 gst_object_unref (pad);
313 res &=
314 gst_element_link_pads (chain->in_convert, "src", chain->in_overlay,
315 "sink");
316 res &=
317 gst_element_link_pads (chain->upload, "src", chain->in_convert, "sink");
318
319 pad = gst_element_get_static_pad (chain->upload, "sink");
320 if (!pad) {
321 return FALSE;
322 } else {
323 GST_DEBUG_OBJECT (self, "setting target sink pad %" GST_PTR_FORMAT, pad);
324 name = gst_object_get_name (GST_OBJECT (mixer_pad));
325 if (klass->create_input_pad) {
326 chain->ghost_pad = klass->create_input_pad (self, chain->mixer_pad);
327 gst_object_set_name (GST_OBJECT (chain->ghost_pad), name);
328 gst_ghost_pad_set_target (chain->ghost_pad, pad);
329 } else {
330 chain->ghost_pad =
331 GST_GHOST_PAD (gst_ghost_pad_new (GST_PAD_NAME (chain->mixer_pad),
332 pad));
333 }
334 g_free (name);
335
336 GST_OBJECT_LOCK (self);
337 if (self->priv->running)
338 gst_pad_set_active (GST_PAD (chain->ghost_pad), TRUE);
339 GST_OBJECT_UNLOCK (self);
340
341 gst_element_add_pad (GST_ELEMENT_CAST (self), GST_PAD (chain->ghost_pad));
342 gst_object_unref (pad);
343 }
344
345 gst_element_sync_state_with_parent (chain->upload);
346 gst_element_sync_state_with_parent (chain->in_convert);
347 gst_element_sync_state_with_parent (chain->in_overlay);
348
349 return TRUE;
350 }
351
352 static GstPadTemplate *
_find_element_pad_template(GstElement * element,GstPadDirection direction,GstPadPresence presence)353 _find_element_pad_template (GstElement * element,
354 GstPadDirection direction, GstPadPresence presence)
355 {
356 GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
357 GList *templ_list = gst_element_class_get_pad_template_list (klass);
358 GstPadTemplate *templ;
359
360 /* find suitable template */
361 while (templ_list) {
362 templ = (GstPadTemplate *) templ_list->data;
363
364 if (GST_PAD_TEMPLATE_DIRECTION (templ) != direction
365 || GST_PAD_TEMPLATE_PRESENCE (templ) != presence) {
366 templ_list = templ_list->next;
367 templ = NULL;
368 continue;
369 }
370
371 break;
372 }
373
374 return templ;
375 }
376
377 static gboolean
_connect_mixer_element(GstGLMixerBin * self)378 _connect_mixer_element (GstGLMixerBin * self)
379 {
380 gboolean res = TRUE;
381
382 g_return_val_if_fail (self->priv->input_chains == NULL, FALSE);
383
384 gst_object_set_name (GST_OBJECT (self->mixer), "mixer");
385 res &= gst_bin_add (GST_BIN (self), self->mixer);
386
387 res &= gst_element_link_pads (self->mixer, "src", self->out_convert, "sink");
388
389 if (!res)
390 GST_ERROR_OBJECT (self, "Failed to link mixer element into the pipeline");
391
392 gst_element_sync_state_with_parent (self->mixer);
393
394 return res;
395 }
396
397 void
gst_gl_mixer_bin_finish_init_with_element(GstGLMixerBin * self,GstElement * element)398 gst_gl_mixer_bin_finish_init_with_element (GstGLMixerBin * self,
399 GstElement * element)
400 {
401 g_return_if_fail (GST_IS_ELEMENT (element));
402
403 self->mixer = element;
404
405 if (!_connect_mixer_element (self)) {
406 gst_object_unref (self->mixer);
407 self->mixer = NULL;
408 }
409 }
410
411 void
gst_gl_mixer_bin_finish_init(GstGLMixerBin * self)412 gst_gl_mixer_bin_finish_init (GstGLMixerBin * self)
413 {
414 GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
415 GstElement *element = NULL;
416
417 if (klass->create_element)
418 element = klass->create_element ();
419
420 if (element)
421 gst_gl_mixer_bin_finish_init_with_element (self, element);
422 }
423
424 static void
gst_gl_mixer_bin_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)425 gst_gl_mixer_bin_get_property (GObject * object,
426 guint prop_id, GValue * value, GParamSpec * pspec)
427 {
428 GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
429
430 switch (prop_id) {
431 case PROP_MIXER:
432 g_value_set_object (value, self->mixer);
433 break;
434 default:
435 if (self->mixer)
436 g_object_get_property (G_OBJECT (self->mixer), pspec->name, value);
437 break;
438 }
439 }
440
441 static void
gst_gl_mixer_bin_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)442 gst_gl_mixer_bin_set_property (GObject * object,
443 guint prop_id, const GValue * value, GParamSpec * pspec)
444 {
445 GstGLMixerBin *self = GST_GL_MIXER_BIN (object);
446
447 switch (prop_id) {
448 case PROP_MIXER:
449 {
450 GstElement *mixer = g_value_get_object (value);
451 /* FIXME: deal with replacing a mixer */
452 g_return_if_fail (!self->mixer || (self->mixer == mixer));
453 self->mixer = mixer;
454 if (mixer) {
455 gst_object_ref_sink (mixer);
456 _connect_mixer_element (self);
457 }
458 break;
459 }
460 default:
461 if (self->mixer)
462 g_object_set_property (G_OBJECT (self->mixer), pspec->name, value);
463 break;
464 }
465 }
466
467 static GstPad *
gst_gl_mixer_bin_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * req_name,const GstCaps * caps)468 gst_gl_mixer_bin_request_new_pad (GstElement * element, GstPadTemplate * templ,
469 const gchar * req_name, const GstCaps * caps)
470 {
471 GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
472 GstPadTemplate *mixer_templ;
473 struct input_chain *chain;
474 GstPad *mixer_pad;
475
476 chain = g_new0 (struct input_chain, 1);
477
478 mixer_templ = _find_element_pad_template (self->mixer,
479 GST_PAD_TEMPLATE_DIRECTION (templ), GST_PAD_TEMPLATE_PRESENCE (templ));
480 g_return_val_if_fail (mixer_templ, NULL);
481
482 mixer_pad =
483 gst_element_request_pad (self->mixer, mixer_templ, req_name, NULL);
484 g_return_val_if_fail (mixer_pad, NULL);
485
486 if (!_create_input_chain (self, chain, mixer_pad)) {
487 gst_element_release_request_pad (self->mixer, mixer_pad);
488 _free_input_chain (chain);
489 return NULL;
490 }
491
492 GST_OBJECT_LOCK (element);
493 self->priv->input_chains = g_list_prepend (self->priv->input_chains, chain);
494 GST_OBJECT_UNLOCK (element);
495
496 gst_child_proxy_child_added (GST_CHILD_PROXY (self),
497 G_OBJECT (chain->ghost_pad), GST_OBJECT_NAME (chain->ghost_pad));
498
499 return GST_PAD (chain->ghost_pad);
500 }
501
502 static void
gst_gl_mixer_bin_release_pad(GstElement * element,GstPad * pad)503 gst_gl_mixer_bin_release_pad (GstElement * element, GstPad * pad)
504 {
505 GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
506 GList *l = self->priv->input_chains;
507 gboolean released = FALSE;
508
509 GST_OBJECT_LOCK (element);
510 while (l) {
511 struct input_chain *chain = l->data;
512 if (GST_PAD (chain->ghost_pad) == pad) {
513 self->priv->input_chains =
514 g_list_delete_link (self->priv->input_chains, l);
515 GST_OBJECT_UNLOCK (element);
516
517 _free_input_chain (chain);
518 gst_element_remove_pad (element, pad);
519 released = TRUE;
520 break;
521 }
522 l = l->next;
523 }
524 if (!released)
525 GST_OBJECT_UNLOCK (element);
526 }
527
528 static GstStateChangeReturn
gst_gl_mixer_bin_change_state(GstElement * element,GstStateChange transition)529 gst_gl_mixer_bin_change_state (GstElement * element, GstStateChange transition)
530 {
531 GstGLMixerBin *self = GST_GL_MIXER_BIN (element);
532 GstGLMixerBinClass *klass = GST_GL_MIXER_BIN_GET_CLASS (self);
533 GstStateChangeReturn ret;
534
535 switch (transition) {
536 case GST_STATE_CHANGE_NULL_TO_READY:
537 GST_OBJECT_LOCK (element);
538 if (!self->mixer) {
539 if (klass->create_element)
540 self->mixer = klass->create_element ();
541
542 if (!self->mixer)
543 g_signal_emit (element,
544 gst_gl_mixer_bin_signals[SIGNAL_CREATE_ELEMENT], 0, &self->mixer);
545
546 if (!self->mixer) {
547 GST_ERROR_OBJECT (element, "Failed to retrieve element");
548 GST_OBJECT_UNLOCK (element);
549 return GST_STATE_CHANGE_FAILURE;
550 }
551 GST_OBJECT_UNLOCK (element);
552 if (!_connect_mixer_element (self))
553 return GST_STATE_CHANGE_FAILURE;
554
555 GST_OBJECT_LOCK (element);
556 }
557 self->priv->running = TRUE;
558 GST_OBJECT_UNLOCK (element);
559 break;
560 default:
561 break;
562 }
563
564 ret =
565 GST_ELEMENT_CLASS (gst_gl_mixer_bin_parent_class)->change_state (element,
566 transition);
567 if (ret == GST_STATE_CHANGE_FAILURE)
568 return ret;
569
570 switch (transition) {
571 case GST_STATE_CHANGE_READY_TO_NULL:
572 GST_OBJECT_LOCK (self);
573 self->priv->running = FALSE;
574 GST_OBJECT_UNLOCK (self);
575 default:
576 break;
577 }
578
579 return ret;
580 }
581
582 static GObject *
gst_gl_mixer_bin_child_proxy_get_child_by_index(GstChildProxy * child_proxy,guint index)583 gst_gl_mixer_bin_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
584 guint index)
585 {
586 GstGLMixerBin *mixer = GST_GL_MIXER_BIN (child_proxy);
587 GstBin *bin = GST_BIN_CAST (child_proxy);
588 GObject *res = NULL;
589
590 GST_OBJECT_LOCK (bin);
591 /* XXX: not exactly thread safe with ordering */
592 if (index < bin->numchildren) {
593 if ((res = g_list_nth_data (bin->children, index)))
594 gst_object_ref (res);
595 } else {
596 struct input_chain *chain;
597 if ((chain =
598 g_list_nth_data (mixer->priv->input_chains,
599 index - bin->numchildren))) {
600 res = gst_object_ref (chain->ghost_pad);
601 }
602 }
603 GST_OBJECT_UNLOCK (bin);
604
605 return res;
606 }
607
608 static guint
gst_gl_mixer_bin_child_proxy_get_children_count(GstChildProxy * child_proxy)609 gst_gl_mixer_bin_child_proxy_get_children_count (GstChildProxy * child_proxy)
610 {
611 GstGLMixerBin *mixer = GST_GL_MIXER_BIN (child_proxy);
612 GstBin *bin = GST_BIN_CAST (child_proxy);
613 guint num;
614
615 GST_OBJECT_LOCK (bin);
616 num = bin->numchildren + g_list_length (mixer->priv->input_chains);
617 GST_OBJECT_UNLOCK (bin);
618
619 return num;
620 }
621
622 static void
gst_gl_mixer_bin_child_proxy_init(gpointer g_iface,gpointer iface_data)623 gst_gl_mixer_bin_child_proxy_init (gpointer g_iface, gpointer iface_data)
624 {
625 GstChildProxyInterface *iface = g_iface;
626
627 iface->get_children_count = gst_gl_mixer_bin_child_proxy_get_children_count;
628 iface->get_child_by_index = gst_gl_mixer_bin_child_proxy_get_child_by_index;
629 }
630