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 
43 static GstStaticPadTemplate video_src_template =
44 GST_STATIC_PAD_TEMPLATE ("video_src_%u",
45     GST_PAD_SRC,
46     GST_PAD_SOMETIMES,
47     GST_STATIC_CAPS ("video/x-raw(ANY)"));
48 
49 static GstStaticPadTemplate audio_src_template =
50     GST_STATIC_PAD_TEMPLATE ("audio_src_%u",
51     GST_PAD_SRC,
52     GST_PAD_SOMETIMES,
53     GST_STATIC_CAPS ("audio/x-raw(ANY);"));
54 
55 #define GST_TYPE_TEST_SRC_BIN  gst_test_src_bin_get_type()
56 #define GST_TEST_SRC_BIN(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), GST_TYPE_TEST_SRC_BIN, GstTestSrcBin))
57 
58 typedef struct _GstTestSrcBin GstTestSrcBin;
59 typedef struct _GstTestSrcBinClass GstTestSrcBinClass;
60 
61 /* *INDENT-OFF* */
62 GType gst_test_src_bin_get_type (void) G_GNUC_CONST;
63 /* *INDENT-ON* */
64 
65 struct _GstTestSrcBinClass
66 {
67   GstBinClass parent_class;
68 };
69 
70 struct _GstTestSrcBin
71 {
72   GstBin parent;
73 
74   gchar *uri;
75   GstStreamCollection *collection;
76   gint group_id;
77   GstFlowCombiner *flow_combiner;
78 };
79 
80 enum
81 {
82   PROP_0,
83   PROP_STREAM_TYPES,
84   PROP_LAST
85 };
86 
87 #define DEFAULT_TYPES GST_STREAM_TYPE_AUDIO & GST_STREAM_TYPE_VIDEO
88 
89 static GstURIType
gst_test_src_bin_uri_handler_get_type(GType type)90 gst_test_src_bin_uri_handler_get_type (GType type)
91 {
92   return GST_URI_SRC;
93 }
94 
95 static const gchar *const *
gst_test_src_bin_uri_handler_get_protocols(GType type)96 gst_test_src_bin_uri_handler_get_protocols (GType type)
97 {
98   static const gchar *protocols[] = { "testbin", NULL };
99 
100   return protocols;
101 }
102 
103 static gchar *
gst_test_src_bin_uri_handler_get_uri(GstURIHandler * handler)104 gst_test_src_bin_uri_handler_get_uri (GstURIHandler * handler)
105 {
106   GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
107   gchar *uri;
108 
109   GST_OBJECT_LOCK (self);
110   uri = g_strdup (self->uri);
111   GST_OBJECT_UNLOCK (self);
112 
113   return uri;
114 }
115 
116 typedef struct
117 {
118   GstEvent *stream_start;
119   GstStreamCollection *collection;
120 } ProbeData;
121 
122 static ProbeData *
_probe_data_new(GstEvent * stream_start,GstStreamCollection * collection)123 _probe_data_new (GstEvent * stream_start, GstStreamCollection * collection)
124 {
125   ProbeData *data = g_malloc0 (sizeof (ProbeData));
126 
127   data->stream_start = gst_event_ref (stream_start);
128   data->collection = gst_object_ref (collection);
129 
130   return data;
131 }
132 
133 static void
_probe_data_free(ProbeData * data)134 _probe_data_free (ProbeData * data)
135 {
136   gst_event_replace (&data->stream_start, NULL);
137   gst_object_replace ((GstObject **) & data->collection, NULL);
138 
139   g_free (data);
140 }
141 
142 static GstPadProbeReturn
src_pad_probe_cb(GstPad * pad,GstPadProbeInfo * info,ProbeData * data)143 src_pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, ProbeData * data)
144 {
145   GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
146 
147   switch (GST_EVENT_TYPE (event)) {
148     case GST_EVENT_STREAM_START:{
149       gst_event_unref (event);
150       info->data = gst_event_ref (data->stream_start);
151       return GST_PAD_PROBE_OK;
152     }
153     case GST_EVENT_CAPS:{
154       if (data->collection) {
155         GstStreamCollection *collection = data->collection;
156         /* Make sure the collection is NULL so that when caps get unstickied
157          * we let them pass through. */
158         data->collection = NULL;
159         gst_pad_push_event (pad, gst_event_new_stream_collection (collection));
160         gst_object_unref (collection);
161       }
162       return GST_PAD_PROBE_REMOVE;
163     }
164     default:
165       break;
166   }
167 
168   return GST_PAD_PROBE_OK;
169 }
170 
171 static GstFlowReturn
gst_test_src_bin_chain(GstPad * pad,GstObject * object,GstBuffer * buffer)172 gst_test_src_bin_chain (GstPad * pad, GstObject * object, GstBuffer * buffer)
173 {
174   GstFlowReturn res, chain_res;
175 
176   GstTestSrcBin *self = GST_TEST_SRC_BIN (gst_object_get_parent (object));
177 
178   chain_res = gst_proxy_pad_chain_default (pad, GST_OBJECT (self), buffer);
179   res = gst_flow_combiner_update_pad_flow (self->flow_combiner, pad, chain_res);
180   gst_object_unref (self);
181 
182   if (res == GST_FLOW_FLUSHING)
183     return chain_res;
184 
185   return res;
186 }
187 
188 static gboolean
gst_test_src_bin_set_element_property(GQuark property_id,const GValue * value,GObject * element)189 gst_test_src_bin_set_element_property (GQuark property_id, const GValue * value,
190     GObject * element)
191 {
192   if (G_VALUE_HOLDS_STRING (value))
193     gst_util_set_object_arg (element, g_quark_to_string (property_id),
194         g_value_get_string (value));
195   else
196     g_object_set_property (element, g_quark_to_string (property_id), value);
197 
198   return TRUE;
199 }
200 
201 typedef struct
202 {
203   GstEvent *event;
204   gboolean res;
205   GstObject *parent;
206 } ForwardEventData;
207 
208 
209 static gboolean
forward_seeks(GstElement * element,GstPad * pad,ForwardEventData * data)210 forward_seeks (GstElement * element, GstPad * pad, ForwardEventData * data)
211 {
212   data->res &=
213       gst_pad_event_default (pad, data->parent, gst_event_ref (data->event));
214 
215   return TRUE;
216 }
217 
218 static gboolean
gst_test_src_event_function(GstPad * pad,GstObject * parent,GstEvent * event)219 gst_test_src_event_function (GstPad * pad, GstObject * parent, GstEvent * event)
220 {
221   switch (GST_EVENT_TYPE (event)) {
222     case GST_EVENT_SEEK:{
223       ForwardEventData data = { event, TRUE, parent };
224 
225       gst_element_foreach_src_pad (GST_ELEMENT (parent),
226           (GstElementForeachPadFunc) forward_seeks, &data);
227       return data.res;
228     }
229     default:
230       break;
231   }
232   return gst_pad_event_default (pad, parent, event);
233 }
234 
235 static void
gst_test_src_bin_setup_src(GstTestSrcBin * self,const gchar * srcfactory,GstStaticPadTemplate * template,GstStreamType stype,GstStreamCollection * collection,gint * n_stream,GstStructure * props)236 gst_test_src_bin_setup_src (GstTestSrcBin * self, const gchar * srcfactory,
237     GstStaticPadTemplate * template, GstStreamType stype,
238     GstStreamCollection * collection, gint * n_stream, GstStructure * props)
239 {
240   GstElement *src = gst_element_factory_make (srcfactory, NULL);
241   GstPad *proxypad, *ghost, *pad = gst_element_get_static_pad (src, "src");
242   gchar *stream_id = g_strdup_printf ("%s_stream_%d", srcfactory, *n_stream);
243   gchar *pad_name = g_strdup_printf (template->name_template, *n_stream);
244   GstStream *stream = gst_stream_new (stream_id, NULL, stype,
245       (*n_stream == 0) ? GST_STREAM_FLAG_SELECT : GST_STREAM_FLAG_UNSELECT);
246   GstEvent *stream_start =
247       gst_event_new_stream_start (gst_stream_get_stream_id (stream));
248 
249   gst_structure_foreach (props,
250       (GstStructureForeachFunc) gst_test_src_bin_set_element_property, src);
251 
252   gst_event_set_stream (stream_start, stream);
253   gst_event_set_group_id (stream_start, self->group_id);
254 
255   gst_pad_add_probe (pad, (GstPadProbeType) GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
256       (GstPadProbeCallback) src_pad_probe_cb, _probe_data_new (stream_start,
257           collection), (GDestroyNotify) _probe_data_free);
258 
259   gst_stream_collection_add_stream (collection, stream);
260   g_free (stream_id);
261 
262   gst_bin_add (GST_BIN (self), src);
263 
264   ghost =
265       gst_ghost_pad_new_from_template (pad_name, pad,
266       gst_static_pad_template_get (template));
267   proxypad = GST_PAD (gst_proxy_pad_get_internal (GST_PROXY_PAD (ghost)));
268   gst_flow_combiner_add_pad (self->flow_combiner, ghost);
269   gst_pad_set_chain_function (proxypad,
270       (GstPadChainFunction) gst_test_src_bin_chain);
271   gst_pad_set_event_function (ghost,
272       (GstPadEventFunction) gst_test_src_event_function);
273   gst_object_unref (proxypad);
274   gst_element_add_pad (GST_ELEMENT (self), ghost);
275   gst_object_unref (pad);
276   gst_element_sync_state_with_parent (src);
277   *n_stream += 1;
278 }
279 
280 static void
gst_test_src_bin_remove_child(GValue * val,GstBin * self)281 gst_test_src_bin_remove_child (GValue * val, GstBin * self)
282 {
283   GstElement *child = g_value_get_object (val);
284 
285   gst_bin_remove (self, child);
286 }
287 
288 static gboolean
gst_test_src_bin_uri_handler_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)289 gst_test_src_bin_uri_handler_set_uri (GstURIHandler * handler,
290     const gchar * uri, GError ** error)
291 {
292   GstTestSrcBin *self = GST_TEST_SRC_BIN (handler);
293   gchar *tmp, *location = gst_uri_get_location (uri);
294   gint i, n_audio = 0, n_video = 0;
295   GstStreamCollection *collection = gst_stream_collection_new (NULL);
296   GstIterator *it;
297   GstCaps *streams_defs;
298 
299   for (tmp = location; *tmp != '\0'; tmp++)
300     if (*tmp == '+')
301       *tmp = ';';
302 
303   streams_defs = gst_caps_from_string (location);
304   g_free (location);
305 
306   if (!streams_defs)
307     goto failed;
308 
309   /* Clear us up */
310   it = gst_bin_iterate_elements (GST_BIN (self));
311   while (gst_iterator_foreach (it,
312           (GstIteratorForeachFunction) gst_test_src_bin_remove_child,
313           self) == GST_ITERATOR_RESYNC)
314     gst_iterator_resync (it);
315 
316   gst_iterator_free (it);
317 
318   self->group_id = gst_util_group_id_next ();
319   for (i = 0; i < gst_caps_get_size (streams_defs); i++) {
320     GstStructure *stream_def = gst_caps_get_structure (streams_defs, i);
321 
322     if (gst_structure_has_name (stream_def, "video"))
323       gst_test_src_bin_setup_src (self, "videotestsrc", &video_src_template,
324           GST_STREAM_TYPE_VIDEO, collection, &n_video, stream_def);
325     else if (gst_structure_has_name (stream_def, "audio"))
326       gst_test_src_bin_setup_src (self, "audiotestsrc", &audio_src_template,
327           GST_STREAM_TYPE_AUDIO, collection, &n_audio, stream_def);
328     else
329       GST_ERROR_OBJECT (self, "Unknown type %s",
330           gst_structure_get_name (stream_def));
331   }
332 
333   if (!n_video && !n_audio)
334     goto failed;
335 
336   self->uri = g_strdup (uri);
337   gst_element_post_message (GST_ELEMENT (self),
338       gst_message_new_stream_collection (GST_OBJECT (self), collection));
339 
340   return TRUE;
341 
342 failed:
343   if (error)
344     *error =
345         g_error_new_literal (GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
346         "No media type specified in the testbin:// URL.");
347 
348   return FALSE;
349 }
350 
351 static void
gst_test_src_bin_uri_handler_init(gpointer g_iface,gpointer unused)352 gst_test_src_bin_uri_handler_init (gpointer g_iface, gpointer unused)
353 {
354   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
355 
356   iface->get_type = gst_test_src_bin_uri_handler_get_type;
357   iface->get_protocols = gst_test_src_bin_uri_handler_get_protocols;
358   iface->get_uri = gst_test_src_bin_uri_handler_get_uri;
359   iface->set_uri = gst_test_src_bin_uri_handler_set_uri;
360 }
361 
362 /* *INDENT-OFF* */
G_DEFINE_TYPE_WITH_CODE(GstTestSrcBin,gst_test_src_bin,GST_TYPE_BIN,G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,gst_test_src_bin_uri_handler_init))363 G_DEFINE_TYPE_WITH_CODE (GstTestSrcBin, gst_test_src_bin, GST_TYPE_BIN,
364     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_test_src_bin_uri_handler_init))
365 /* *INDENT-ON* */
366 
367 static void
368 gst_test_src_bin_set_property (GObject * object, guint prop_id,
369     const GValue * value, GParamSpec * pspec)
370 {
371   GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
372 
373   switch (prop_id) {
374     case PROP_STREAM_TYPES:
375     {
376       gchar *uri = g_strdup_printf ("testbin://%s", g_value_get_string (value));
377 
378       g_assert (gst_uri_handler_set_uri (GST_URI_HANDLER (self), uri, NULL));
379       g_free (uri);
380       break;
381     }
382     default:
383       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
384       break;
385   }
386 }
387 
388 static void
gst_test_src_bin_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)389 gst_test_src_bin_get_property (GObject * object, guint prop_id, GValue * value,
390     GParamSpec * pspec)
391 {
392   GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
393 
394   switch (prop_id) {
395     case PROP_STREAM_TYPES:
396     {
397       gchar *uri = gst_uri_handler_get_uri (GST_URI_HANDLER (self));
398       if (uri) {
399         gchar *types = gst_uri_get_location (uri);
400         g_value_set_string (value, types);
401         g_free (uri);
402         g_free (types);
403       }
404       break;
405     }
406     default:
407       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
408       break;
409   }
410 }
411 
412 static GstStateChangeReturn
gst_test_src_bin_change_state(GstElement * element,GstStateChange transition)413 gst_test_src_bin_change_state (GstElement * element, GstStateChange transition)
414 {
415   GstTestSrcBin *self = GST_TEST_SRC_BIN (element);
416   GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
417 
418   result =
419       GST_ELEMENT_CLASS (gst_test_src_bin_parent_class)->change_state (element,
420       transition);
421 
422   switch (transition) {
423     case GST_STATE_CHANGE_PAUSED_TO_READY:{
424       gst_flow_combiner_reset (self->flow_combiner);
425       break;
426     }
427     default:
428       break;
429   }
430 
431   return result;
432 }
433 
434 static void
gst_test_src_bin_finalize(GObject * object)435 gst_test_src_bin_finalize (GObject * object)
436 {
437   GstTestSrcBin *self = GST_TEST_SRC_BIN (object);
438 
439   g_free (self->uri);
440   gst_flow_combiner_free (self->flow_combiner);
441 }
442 
443 static void
gst_test_src_bin_init(GstTestSrcBin * self)444 gst_test_src_bin_init (GstTestSrcBin * self)
445 {
446   self->flow_combiner = gst_flow_combiner_new ();
447 }
448 
449 static void
gst_test_src_bin_class_init(GstTestSrcBinClass * klass)450 gst_test_src_bin_class_init (GstTestSrcBinClass * klass)
451 {
452   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
453   GstElementClass *gstelement_klass = (GstElementClass *) klass;
454 
455   gobject_class->finalize = gst_test_src_bin_finalize;
456   gobject_class->get_property = gst_test_src_bin_get_property;
457   gobject_class->set_property = gst_test_src_bin_set_property;
458 
459   /**
460    * GstTestSrcBin::stream-types:
461    *
462    * String describing the stream types to expose, eg. "video+audio".
463    */
464   g_object_class_install_property (gobject_class, PROP_STREAM_TYPES,
465       g_param_spec_string ("stream-types", "Stream types",
466           "String describing the stream types to expose, eg. \"video+audio\".",
467           NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
468 
469   gstelement_klass->change_state =
470       GST_DEBUG_FUNCPTR (gst_test_src_bin_change_state);
471   gst_element_class_add_pad_template (gstelement_klass,
472       gst_static_pad_template_get (&video_src_template));
473   gst_element_class_add_pad_template (gstelement_klass,
474       gst_static_pad_template_get (&audio_src_template));
475 }
476