1 /* GStreamer
2 *
3 * Copyright (C) 2018 Igalia S.L. All rights reserved.
4 * @author: Thibault Saunier <tsaunier@igalia.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-testsrcbin
24 * @title: testsrc
25 *
26 * This is a simple GstBin source that wraps audiotestsrc/videotestsrc
27 * following specification passed in the URI (it implements the #GstURIHandler interface)
28 * in the form of `testbin://audio+video` or setting the "stream-types" property
29 * with the same format.
30 *
31 * This element also provides GstStream and GstStreamCollection and
32 * thus the element is useful for testing the new playbin3 infrastructure.
33 *
34 * Example pipeline:
35 * ```
36 * gst-launch-1.0 playbin uri=testbin://audio,volume=0.5+video,pattern=white
37 * ```
38 */
39 #include <gst/gst.h>
40 #include <gst/base/gstflowcombiner.h>
41 #include <gst/app/gstappsink.h>
42 #include "gstdebugutilsbadelements.h"
43
44 static GstStaticPadTemplate video_src_template =
45 GST_STATIC_PAD_TEMPLATE ("video_src_%u",
46 GST_PAD_SRC,
47 GST_PAD_SOMETIMES,
48 GST_STATIC_CAPS ("video/x-raw(ANY)"));
49
50 static GstStaticPadTemplate audio_src_template =
51 GST_STATIC_PAD_TEMPLATE ("audio_src_%u",
52 GST_PAD_SRC,
53 GST_PAD_SOMETIMES,
54 GST_STATIC_CAPS ("audio/x-raw(ANY);"));
55
56 #define GST_TYPE_TEST_SRC_BIN gst_test_src_bin_get_type()
57 #define GST_TEST_SRC_BIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GST_TYPE_TEST_SRC_BIN, GstTestSrcBin))
58
59 typedef struct _GstTestSrcBin GstTestSrcBin;
60 typedef struct _GstTestSrcBinClass GstTestSrcBinClass;
61
62 /* *INDENT-OFF* */
63 GType gst_test_src_bin_get_type (void) G_GNUC_CONST;
64 /* *INDENT-ON* */
65
66 struct _GstTestSrcBinClass
67 {
68 GstBinClass parent_class;
69 };
70
71 struct _GstTestSrcBin
72 {
73 GstBin parent;
74
75 gchar *uri;
76 GstStreamCollection *collection;
77 gint group_id;
78 GstFlowCombiner *flow_combiner;
79 GstCaps *streams_def;
80 };
81
82 enum
83 {
84 PROP_0,
85 PROP_STREAM_TYPES,
86 PROP_LAST
87 };
88
89 #define DEFAULT_TYPES GST_STREAM_TYPE_AUDIO & GST_STREAM_TYPE_VIDEO
90
91 static GstURIType
gst_test_src_bin_uri_handler_get_type(GType type)92 gst_test_src_bin_uri_handler_get_type (GType type)
93 {
94 return GST_URI_SRC;
95 }
96
97 static const gchar *const *
gst_test_src_bin_uri_handler_get_protocols(GType type)98 gst_test_src_bin_uri_handler_get_protocols (GType type)
99 {
100 static const gchar *protocols[] = { "testbin", NULL };
101
102 return protocols;
103 }
104
105 static gchar *
gst_test_src_bin_uri_handler_get_uri(GstURIHandler * handler)106 gst_test_src_bin_uri_handler_get_uri (GstURIHandler * handler)
107 {
108 GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
109 gchar *uri;
110
111 GST_OBJECT_LOCK (self);
112 uri = g_strdup (self->uri);
113 GST_OBJECT_UNLOCK (self);
114
115 return uri;
116 }
117
118 typedef struct
119 {
120 GstEvent *stream_start;
121 GstStreamCollection *collection;
122 } ProbeData;
123
124 static ProbeData *
_probe_data_new(GstEvent * stream_start,GstStreamCollection * collection)125 _probe_data_new (GstEvent * stream_start, GstStreamCollection * collection)
126 {
127 ProbeData *data = g_malloc0 (sizeof (ProbeData));
128
129 data->stream_start = stream_start;
130 data->collection = gst_object_ref (collection);
131
132 return data;
133 }
134
135 static void
_probe_data_free(ProbeData * data)136 _probe_data_free (ProbeData * data)
137 {
138 gst_event_replace (&data->stream_start, NULL);
139 gst_object_replace ((GstObject **) & data->collection, NULL);
140
141 g_free (data);
142 }
143
144 static GstPadProbeReturn
src_pad_probe_cb(GstPad * pad,GstPadProbeInfo * info,ProbeData * data)145 src_pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, ProbeData * data)
146 {
147 GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
148
149 switch (GST_EVENT_TYPE (event)) {
150 case GST_EVENT_STREAM_START:{
151 gst_event_unref (event);
152 info->data = gst_event_ref (data->stream_start);
153 return GST_PAD_PROBE_OK;
154 }
155 case GST_EVENT_CAPS:{
156 if (data->collection) {
157 GstStreamCollection *collection = data->collection;
158 /* Make sure the collection is NULL so that when caps get unstickied
159 * we let them pass through. */
160 data->collection = NULL;
161 gst_pad_push_event (pad, gst_event_new_stream_collection (collection));
162 gst_object_unref (collection);
163 }
164 return GST_PAD_PROBE_REMOVE;
165 }
166 default:
167 break;
168 }
169
170 return GST_PAD_PROBE_OK;
171 }
172
173 static GstFlowReturn
gst_test_src_bin_chain(GstPad * pad,GstObject * object,GstBuffer * buffer)174 gst_test_src_bin_chain (GstPad * pad, GstObject * object, GstBuffer * buffer)
175 {
176 GstFlowReturn res, chain_res;
177
178 GstTestSrcBin *self = GST_TEST_SRC_BIN (gst_object_get_parent (object));
179
180 chain_res = gst_proxy_pad_chain_default (pad, GST_OBJECT (self), buffer);
181 GST_OBJECT_LOCK (self);
182 res = gst_flow_combiner_update_pad_flow (self->flow_combiner, pad, chain_res);
183 GST_OBJECT_UNLOCK (self);
184 gst_object_unref (self);
185
186 if (res == GST_FLOW_FLUSHING)
187 return chain_res;
188
189 if (res == GST_FLOW_NOT_LINKED)
190 GST_WARNING_OBJECT (pad,
191 "all testsrcbin pads not linked, returning not-linked.");
192
193 return res;
194 }
195
196 static gboolean
gst_test_src_bin_set_element_property(GQuark property_id,const GValue * value,GObject * element)197 gst_test_src_bin_set_element_property (GQuark property_id, const GValue * value,
198 GObject * element)
199 {
200 if (property_id == g_quark_from_static_string ("__streamobj__"))
201 return TRUE;
202
203 if (G_VALUE_HOLDS_STRING (value))
204 gst_util_set_object_arg (element, g_quark_to_string (property_id),
205 g_value_get_string (value));
206 else
207 g_object_set_property (element, g_quark_to_string (property_id), value);
208
209 return TRUE;
210 }
211
212 typedef struct
213 {
214 GstEvent *event;
215 gboolean res;
216 GstObject *parent;
217 } ForwardEventData;
218
219
220 static gboolean
forward_seeks(GstElement * element,GstPad * pad,ForwardEventData * data)221 forward_seeks (GstElement * element, GstPad * pad, ForwardEventData * data)
222 {
223 data->res &=
224 gst_pad_event_default (pad, data->parent, gst_event_ref (data->event));
225
226 return TRUE;
227 }
228
229 static gboolean
gst_test_src_event_function(GstPad * pad,GstObject * parent,GstEvent * event)230 gst_test_src_event_function (GstPad * pad, GstObject * parent, GstEvent * event)
231 {
232 switch (GST_EVENT_TYPE (event)) {
233 case GST_EVENT_RECONFIGURE:{
234 GstTestSrcBin *self = GST_TEST_SRC_BIN (parent);
235 GST_OBJECT_LOCK (self);
236 gst_flow_combiner_reset (self->flow_combiner);
237 GST_OBJECT_UNLOCK (self);
238 break;
239 }
240 case GST_EVENT_SEEK:{
241 ForwardEventData data = { event, TRUE, parent };
242
243 gst_element_foreach_src_pad (GST_ELEMENT (parent),
244 (GstElementForeachPadFunc) forward_seeks, &data);
245 return data.res;
246 }
247 default:
248 break;
249 }
250 return gst_pad_event_default (pad, parent, event);
251 }
252
253 static void
gst_test_src_bin_setup_src(GstTestSrcBin * self,const gchar * srcfactory,GstStaticPadTemplate * template,GstStreamType stype,GstStreamCollection * collection,gint * n_stream,GstStructure * props)254 gst_test_src_bin_setup_src (GstTestSrcBin * self, const gchar * srcfactory,
255 GstStaticPadTemplate * template, GstStreamType stype,
256 GstStreamCollection * collection, gint * n_stream, GstStructure * props)
257 {
258 GstElement *src = gst_element_factory_make (srcfactory, NULL);
259 GstPad *proxypad, *ghost, *pad = gst_element_get_static_pad (src, "src");
260 gchar *stream_id = g_strdup_printf ("%s_stream_%d", srcfactory, *n_stream);
261 gchar *pad_name = g_strdup_printf (template->name_template, *n_stream);
262 GstStream *stream = gst_stream_new (stream_id, NULL, stype,
263 (*n_stream == 0) ? GST_STREAM_FLAG_SELECT : GST_STREAM_FLAG_UNSELECT);
264 GstEvent *stream_start =
265 gst_event_new_stream_start (gst_stream_get_stream_id (stream));
266
267 gst_structure_foreach (props,
268 (GstStructureForeachFunc) gst_test_src_bin_set_element_property, src);
269
270 gst_event_set_stream (stream_start, stream);
271 gst_event_set_group_id (stream_start, self->group_id);
272
273 gst_structure_set (props, "__streamobj__", GST_TYPE_STREAM, stream, NULL);
274 gst_stream_collection_add_stream (collection, stream);
275
276 gst_pad_add_probe (pad, (GstPadProbeType) GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
277 (GstPadProbeCallback) src_pad_probe_cb, _probe_data_new (stream_start,
278 collection), (GDestroyNotify) _probe_data_free);
279
280 g_free (stream_id);
281
282 gst_bin_add (GST_BIN (self), src);
283
284 ghost =
285 gst_ghost_pad_new_from_template (pad_name, pad,
286 gst_static_pad_template_get (template));
287 proxypad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (ghost)));
288 gst_flow_combiner_add_pad (self->flow_combiner, ghost);
289 gst_pad_set_chain_function (proxypad,
290 (GstPadChainFunction) gst_test_src_bin_chain);
291 gst_pad_set_event_function (ghost,
292 (GstPadEventFunction) gst_test_src_event_function);
293 gst_object_unref (proxypad);
294 gst_element_add_pad (GST_ELEMENT (self), ghost);
295 gst_object_unref (pad);
296 gst_element_sync_state_with_parent (src);
297 *n_stream += 1;
298
299 gst_structure_set (props, "__src__", GST_TYPE_OBJECT, src, NULL);
300 }
301
302 static void
gst_test_src_bin_remove_child(GstElement * self,GstElement * child)303 gst_test_src_bin_remove_child (GstElement * self, GstElement * child)
304 {
305 GstPad *pad = gst_element_get_static_pad (child, "src");
306 GstPad *ghost =
307 GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (gst_pad_get_peer
308 (pad))));
309
310
311 gst_element_set_locked_state (child, FALSE);
312 gst_element_set_state (child, GST_STATE_NULL);
313 gst_bin_remove (GST_BIN (self), child);
314 gst_element_remove_pad (self, ghost);
315 }
316
317 static GstStream *
gst_test_check_prev_stream_def(GstTestSrcBin * self,GstCaps * prev_streams,GstStructure * stream_def)318 gst_test_check_prev_stream_def (GstTestSrcBin * self, GstCaps * prev_streams,
319 GstStructure * stream_def)
320 {
321 gint i;
322
323 if (!prev_streams)
324 return NULL;
325
326 for (i = 0; i < gst_caps_get_size (prev_streams); i++) {
327 GstStructure *prev_stream = gst_caps_get_structure (prev_streams, i);
328 GstElement *e = NULL;
329 GstStream *stream = NULL;
330
331 gst_structure_get (prev_stream, "__src__", GST_TYPE_OBJECT, &e,
332 "__streamobj__", GST_TYPE_STREAM, &stream, NULL);
333 gst_structure_remove_fields (prev_stream, "__src__", "__streamobj__", NULL);
334 if (gst_structure_is_equal (prev_stream, stream_def)) {
335 g_assert (stream);
336
337 gst_caps_remove_structure (prev_streams, i);
338 gst_structure_set (stream_def, "__src__", GST_TYPE_OBJECT, e,
339 "__streamobj__", GST_TYPE_STREAM, stream, NULL);
340
341 g_assert (stream);
342 return stream;
343 }
344
345 gst_structure_set (stream_def, "__src__", GST_TYPE_OBJECT, e,
346 "__streamobj__", GST_TYPE_STREAM, stream, NULL);
347 }
348
349 return NULL;
350 }
351
352 static gboolean
gst_test_src_bin_uri_handler_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)353 gst_test_src_bin_uri_handler_set_uri (GstURIHandler * handler,
354 const gchar * uri, GError ** error)
355 {
356 GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
357 gchar *tmp, *location = gst_uri_get_location (uri);
358 gint i, n_audio = 0, n_video = 0;
359 GstStreamCollection *collection = gst_stream_collection_new (NULL);
360 GstCaps *streams_def, *prev_streams = self->streams_def;
361
362 for (tmp = location; *tmp != '\0'; tmp++)
363 if (*tmp == '+')
364 *tmp = ';';
365
366 streams_def = gst_caps_from_string (location);
367 g_free (location);
368
369 if (!streams_def)
370 goto failed;
371
372 self->group_id = gst_util_group_id_next ();
373 for (i = 0; i < gst_caps_get_size (streams_def); i++) {
374 GstStream *stream;
375 GstStructure *stream_def = gst_caps_get_structure (streams_def, i);
376
377 if ((stream =
378 gst_test_check_prev_stream_def (self, prev_streams, stream_def))) {
379 GST_INFO_OBJECT (self,
380 "Reusing already existing stream: %" GST_PTR_FORMAT, stream_def);
381 gst_stream_collection_add_stream (collection, stream);
382 if (gst_structure_has_name (stream_def, "video"))
383 n_video++;
384 else
385 n_audio++;
386 continue;
387 }
388
389 if (gst_structure_has_name (stream_def, "video"))
390 gst_test_src_bin_setup_src (self, "videotestsrc", &video_src_template,
391 GST_STREAM_TYPE_VIDEO, collection, &n_video, stream_def);
392 else if (gst_structure_has_name (stream_def, "audio"))
393 gst_test_src_bin_setup_src (self, "audiotestsrc", &audio_src_template,
394 GST_STREAM_TYPE_AUDIO, collection, &n_audio, stream_def);
395 else
396 GST_ERROR_OBJECT (self, "Unknown type %s",
397 gst_structure_get_name (stream_def));
398 }
399 self->streams_def = streams_def;
400
401 if (prev_streams) {
402 for (i = 0; i < gst_caps_get_size (prev_streams); i++) {
403 GstStructure *prev_stream = gst_caps_get_structure (prev_streams, i);
404 GstElement *child;
405
406 gst_structure_get (prev_stream, "__src__", GST_TYPE_OBJECT, &child, NULL);
407 gst_test_src_bin_remove_child (GST_ELEMENT (self), child);
408 }
409 gst_clear_caps (&prev_streams);
410 }
411
412 if (!n_video && !n_audio)
413 goto failed;
414
415 self->uri = g_strdup (uri);
416 gst_element_post_message (GST_ELEMENT (self),
417 gst_message_new_stream_collection (GST_OBJECT (self), collection));
418
419 return TRUE;
420
421 failed:
422 if (error)
423 *error =
424 g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
425 "No media type specified in the testbin:// URL.");
426
427 return FALSE;
428 }
429
430 static void
gst_test_src_bin_uri_handler_init(gpointer g_iface,gpointer unused)431 gst_test_src_bin_uri_handler_init (gpointer g_iface, gpointer unused)
432 {
433 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
434
435 iface->get_type = gst_test_src_bin_uri_handler_get_type;
436 iface->get_protocols = gst_test_src_bin_uri_handler_get_protocols;
437 iface->get_uri = gst_test_src_bin_uri_handler_get_uri;
438 iface->set_uri = gst_test_src_bin_uri_handler_set_uri;
439 }
440
441 /* *INDENT-OFF* */
442 G_DEFINE_TYPE_WITH_CODE (GstTestSrcBin, gst_test_src_bin, GST_TYPE_BIN,
443 G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_test_src_bin_uri_handler_init))
444 /* *INDENT-ON* */
445
446 GST_ELEMENT_REGISTER_DEFINE (testsrcbin, "testsrcbin",
447 GST_RANK_NONE, gst_test_src_bin_get_type ());
448
449 static void
gst_test_src_bin_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)450 gst_test_src_bin_set_property (GObject * object, guint prop_id,
451 const GValue * value, GParamSpec * pspec)
452 {
453 GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
454
455 switch (prop_id) {
456 case PROP_STREAM_TYPES:
457 {
458 gchar *uri = g_strdup_printf ("testbin://%s", g_value_get_string (value));
459
460 g_assert (gst_uri_handler_set_uri (GST_URI_HANDLER (self), uri, NULL));
461 g_free (uri);
462 break;
463 }
464 default:
465 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
466 break;
467 }
468 }
469
470 static void
gst_test_src_bin_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)471 gst_test_src_bin_get_property (GObject * object, guint prop_id, GValue * value,
472 GParamSpec * pspec)
473 {
474 GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
475
476 switch (prop_id) {
477 case PROP_STREAM_TYPES:
478 {
479 gchar *uri = gst_uri_handler_get_uri (GST_URI_HANDLER (self));
480 if (uri) {
481 gchar *types = gst_uri_get_location (uri);
482 g_value_set_string (value, types);
483 g_free (uri);
484 g_free (types);
485 }
486 break;
487 }
488 default:
489 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
490 break;
491 }
492 }
493
494 static GstStateChangeReturn
gst_test_src_bin_change_state(GstElement * element,GstStateChange transition)495 gst_test_src_bin_change_state (GstElement * element, GstStateChange transition)
496 {
497 GstTestSrcBin *self = GST_TEST_SRC_BIN (element);
498 GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
499
500 result =
501 GST_ELEMENT_CLASS (gst_test_src_bin_parent_class)->change_state (element,
502 transition);
503
504 switch (transition) {
505 case GST_STATE_CHANGE_PAUSED_TO_READY:{
506 gst_flow_combiner_reset (self->flow_combiner);
507 break;
508 }
509 default:
510 break;
511 }
512
513 return result;
514 }
515
516 static void
gst_test_src_bin_finalize(GObject * object)517 gst_test_src_bin_finalize (GObject * object)
518 {
519 GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
520
521 g_free (self->uri);
522 gst_clear_caps (&self->streams_def);
523 gst_flow_combiner_free (self->flow_combiner);
524 }
525
526 static void
gst_test_src_bin_init(GstTestSrcBin * self)527 gst_test_src_bin_init (GstTestSrcBin * self)
528 {
529 self->flow_combiner = gst_flow_combiner_new ();
530 }
531
532 static void
gst_test_src_bin_class_init(GstTestSrcBinClass * klass)533 gst_test_src_bin_class_init (GstTestSrcBinClass * klass)
534 {
535 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
536 GstElementClass *gstelement_klass = (GstElementClass *) klass;
537
538 gobject_class->finalize = gst_test_src_bin_finalize;
539 gobject_class->get_property = gst_test_src_bin_get_property;
540 gobject_class->set_property = gst_test_src_bin_set_property;
541
542 /**
543 * GstTestSrcBin::stream-types:
544 *
545 * String describing the stream types to expose, eg. "video+audio".
546 */
547 g_object_class_install_property (gobject_class, PROP_STREAM_TYPES,
548 g_param_spec_string ("stream-types", "Stream types",
549 "String describing the stream types to expose, eg. \"video+audio\".",
550 NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
551
552 gstelement_klass->change_state =
553 GST_DEBUG_FUNCPTR (gst_test_src_bin_change_state);
554 gst_element_class_add_pad_template (gstelement_klass,
555 gst_static_pad_template_get (&video_src_template));
556 gst_element_class_add_pad_template (gstelement_klass,
557 gst_static_pad_template_get (&audio_src_template));
558 }
559