• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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