1 /* GStreamer
2  * (c) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 
27 #include <gst/gst.h>
28 
29 #include "gstautodetect.h"
30 #include "gstautoaudiosink.h"
31 #include "gstautoaudiosrc.h"
32 #include "gstautovideosink.h"
33 #include "gstautovideosrc.h"
34 
35 GST_DEBUG_CATEGORY (autodetect_debug);
36 
37 #define DEFAULT_SYNC                TRUE
38 
39 /* Properties */
40 enum
41 {
42   PROP_0,
43   PROP_CAPS,
44   PROP_SYNC,
45 };
46 
47 static GstStateChangeReturn gst_auto_detect_change_state (GstElement * element,
48     GstStateChange transition);
49 static void gst_auto_detect_constructed (GObject * object);
50 static void gst_auto_detect_dispose (GObject * self);
51 static void gst_auto_detect_clear_kid (GstAutoDetect * self);
52 static void gst_auto_detect_set_property (GObject * object, guint prop_id,
53     const GValue * value, GParamSpec * pspec);
54 static void gst_auto_detect_get_property (GObject * object, guint prop_id,
55     GValue * value, GParamSpec * pspec);
56 
57 #define gst_auto_detect_parent_class parent_class
58 G_DEFINE_ABSTRACT_TYPE (GstAutoDetect, gst_auto_detect, GST_TYPE_BIN);
59 
60 static void
gst_auto_detect_class_init(GstAutoDetectClass * klass)61 gst_auto_detect_class_init (GstAutoDetectClass * klass)
62 {
63   GObjectClass *gobject_class;
64   GstElementClass *eklass;
65 
66   gobject_class = G_OBJECT_CLASS (klass);
67   eklass = GST_ELEMENT_CLASS (klass);
68 
69   gobject_class->constructed = gst_auto_detect_constructed;
70   gobject_class->dispose = gst_auto_detect_dispose;
71   gobject_class->set_property = gst_auto_detect_set_property;
72   gobject_class->get_property = gst_auto_detect_get_property;
73 
74   eklass->change_state = GST_DEBUG_FUNCPTR (gst_auto_detect_change_state);
75 
76   /**
77    * GstAutoDetect:filter-caps:
78    *
79    * This property will filter out candidate sinks that can handle the specified
80    * caps. By default only elements that support uncompressed data are selected.
81    *
82    * This property can only be set before the element goes to the READY state.
83    */
84   g_object_class_install_property (gobject_class, PROP_CAPS,
85       g_param_spec_boxed ("filter-caps", "Filter caps",
86           "Filter sink candidates using these caps.", GST_TYPE_CAPS,
87           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
88 
89   g_object_class_install_property (gobject_class, PROP_SYNC,
90       g_param_spec_boolean ("sync", "Sync",
91           "Sync on the clock", DEFAULT_SYNC,
92           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
93 }
94 
95 static void
gst_auto_detect_dispose(GObject * object)96 gst_auto_detect_dispose (GObject * object)
97 {
98   GstAutoDetect *self = GST_AUTO_DETECT (object);
99 
100   gst_auto_detect_clear_kid (self);
101 
102   if (self->filter_caps)
103     gst_caps_unref (self->filter_caps);
104   self->filter_caps = NULL;
105 
106   G_OBJECT_CLASS (parent_class)->dispose ((GObject *) self);
107 }
108 
109 static void
gst_auto_detect_clear_kid(GstAutoDetect * self)110 gst_auto_detect_clear_kid (GstAutoDetect * self)
111 {
112   if (self->kid) {
113     gst_element_set_state (self->kid, GST_STATE_NULL);
114     gst_bin_remove (GST_BIN (self), self->kid);
115     self->kid = NULL;
116   }
117 }
118 
119 static GstElement *
gst_auto_detect_create_fake_element_default(GstAutoDetect * self)120 gst_auto_detect_create_fake_element_default (GstAutoDetect * self)
121 {
122   GstElement *fake;
123   gchar dummy_factory[10], dummy_name[20];
124 
125   sprintf (dummy_factory, "fake%s", self->type_klass_lc);
126   sprintf (dummy_name, "fake-%s-%s", self->media_klass_lc, self->type_klass_lc);
127   fake = gst_element_factory_make (dummy_factory, dummy_name);
128   g_object_set (fake, "sync", self->sync, NULL);
129 
130   return fake;
131 }
132 
133 static GstElement *
gst_auto_detect_create_fake_element(GstAutoDetect * self)134 gst_auto_detect_create_fake_element (GstAutoDetect * self)
135 {
136   GstAutoDetectClass *klass = GST_AUTO_DETECT_GET_CLASS (self);
137   GstElement *fake;
138 
139   if (klass->create_fake_element)
140     fake = klass->create_fake_element (self);
141   else
142     fake = gst_auto_detect_create_fake_element_default (self);
143 
144   return fake;
145 }
146 
147 static gboolean
gst_auto_detect_attach_ghost_pad(GstAutoDetect * self)148 gst_auto_detect_attach_ghost_pad (GstAutoDetect * self)
149 {
150   GstPad *target = gst_element_get_static_pad (self->kid, self->type_klass_lc);
151   gboolean res = gst_ghost_pad_set_target (GST_GHOST_PAD (self->pad), target);
152   gst_object_unref (target);
153 
154   return res;
155 }
156 
157 /* Hack to make initial linking work; ideally, this'd work even when
158  * no target has been assigned to the ghostpad yet. */
159 static void
gst_auto_detect_reset(GstAutoDetect * self)160 gst_auto_detect_reset (GstAutoDetect * self)
161 {
162   gst_auto_detect_clear_kid (self);
163 
164   /* placeholder element */
165   self->kid = gst_auto_detect_create_fake_element (self);
166   gst_bin_add (GST_BIN (self), self->kid);
167 
168   gst_auto_detect_attach_ghost_pad (self);
169 }
170 
171 static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw");
172 static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw");
173 
174 static void
gst_auto_detect_init(GstAutoDetect * self)175 gst_auto_detect_init (GstAutoDetect * self)
176 {
177   self->sync = DEFAULT_SYNC;
178 }
179 
180 static void
gst_auto_detect_constructed(GObject * object)181 gst_auto_detect_constructed (GObject * object)
182 {
183   GstAutoDetect *self = GST_AUTO_DETECT (object);
184   gboolean is_audio;
185 
186   if (G_OBJECT_CLASS (parent_class)->constructed)
187     G_OBJECT_CLASS (parent_class)->constructed (object);
188 
189   is_audio = !g_strcmp0 (self->media_klass, "Audio");
190   self->type_klass = (self->flag == GST_ELEMENT_FLAG_SINK) ? "Sink" : "Source";
191   self->type_klass_lc = (self->flag == GST_ELEMENT_FLAG_SINK) ? "sink" : "src";
192   self->media_klass_lc = is_audio ? "audio" : "video";
193   /* set the default raw caps */
194   self->filter_caps = gst_static_caps_get (is_audio ? &raw_audio_caps :
195       &raw_video_caps);
196 
197   self->pad = gst_ghost_pad_new_no_target (self->type_klass_lc,
198       (self->flag == GST_ELEMENT_FLAG_SINK) ? GST_PAD_SINK : GST_PAD_SRC);
199   gst_element_add_pad (GST_ELEMENT (self), self->pad);
200 
201   gst_auto_detect_reset (self);
202 
203   /* mark element type */
204   GST_OBJECT_FLAG_SET (self, self->flag);
205   gst_bin_set_suppressed_flags (GST_BIN (self),
206       GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK);
207 }
208 
209 static gboolean
gst_auto_detect_factory_filter(GstPluginFeature * feature,gpointer data)210 gst_auto_detect_factory_filter (GstPluginFeature * feature, gpointer data)
211 {
212   GstAutoDetect *self = (GstAutoDetect *) data;
213   guint rank;
214   const gchar *klass;
215 
216   /* we only care about element factories */
217   if (!GST_IS_ELEMENT_FACTORY (feature))
218     return FALSE;
219 
220   /* audio sinks */
221   klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY (feature),
222       GST_ELEMENT_METADATA_KLASS);
223   if (!(strstr (klass, self->type_klass) && strstr (klass, self->media_klass)))
224     return FALSE;
225 
226   /* only select elements with autoplugging rank */
227   rank = gst_plugin_feature_get_rank (feature);
228   if (rank < GST_RANK_MARGINAL)
229     return FALSE;
230 
231   return TRUE;
232 }
233 
234 static GstElement *
create_element_with_pretty_name(GstAutoDetect * self,GstElementFactory * factory)235 create_element_with_pretty_name (GstAutoDetect * self,
236     GstElementFactory * factory)
237 {
238   GstElement *element;
239   gchar *name, *marker;
240 
241   marker = g_strdup (GST_OBJECT_NAME (factory));
242   if (g_str_has_suffix (marker, self->type_klass_lc))
243     marker[strlen (marker) - 4] = '\0';
244   if (g_str_has_prefix (marker, "gst"))
245     memmove (marker, marker + 3, strlen (marker + 3) + 1);
246   name = g_strdup_printf ("%s-actual-%s-%s", GST_OBJECT_NAME (self),
247       self->type_klass_lc, marker);
248   g_free (marker);
249 
250   element = gst_element_factory_create (factory, name);
251   g_free (name);
252 
253   return element;
254 }
255 
256 static GstElement *
gst_auto_detect_find_best(GstAutoDetect * self)257 gst_auto_detect_find_best (GstAutoDetect * self)
258 {
259   GList *list, *item;
260   GstElement *choice = NULL;
261   GstMessage *message = NULL;
262   GSList *errors = NULL;
263   GstBus *bus = gst_bus_new ();
264   GstPad *el_pad = NULL;
265   GstCaps *el_caps = NULL;
266   gboolean no_match = TRUE;
267 
268   /* We don't treat sound server sinks special. Our policy is that sound
269    * server sinks that have a rank must not auto-spawn a daemon under any
270    * circumstances, so there's nothing for us to worry about here */
271   list = gst_registry_feature_filter (gst_registry_get (),
272       (GstPluginFeatureFilter) gst_auto_detect_factory_filter, FALSE, self);
273   list =
274       g_list_sort (list, (GCompareFunc) gst_plugin_feature_rank_compare_func);
275 
276   GST_LOG_OBJECT (self, "Trying to find usable %s elements ...",
277       self->media_klass_lc);
278 
279   for (item = list; item != NULL; item = item->next) {
280     GstElementFactory *f = GST_ELEMENT_FACTORY (item->data);
281     GstElement *el;
282 
283     if ((el = create_element_with_pretty_name (self, f))) {
284       GstStateChangeReturn ret;
285 
286       GST_DEBUG_OBJECT (self, "Testing %s", GST_OBJECT_NAME (f));
287 
288       /* If autodetect has been provided with filter caps,
289        * accept only elements that match with the filter caps */
290       if (self->filter_caps) {
291         el_pad = gst_element_get_static_pad (el, self->type_klass_lc);
292         el_caps = gst_pad_query_caps (el_pad, NULL);
293         gst_object_unref (el_pad);
294         GST_DEBUG_OBJECT (self,
295             "Checking caps: %" GST_PTR_FORMAT " vs. %" GST_PTR_FORMAT,
296             self->filter_caps, el_caps);
297         no_match = !gst_caps_can_intersect (self->filter_caps, el_caps);
298         gst_caps_unref (el_caps);
299 
300         if (no_match) {
301           GST_DEBUG_OBJECT (self, "Incompatible caps");
302           gst_object_unref (el);
303           continue;
304         } else {
305           GST_DEBUG_OBJECT (self, "Found compatible caps");
306         }
307       }
308 
309       gst_element_set_bus (el, bus);
310       ret = gst_element_set_state (el, GST_STATE_READY);
311       if (ret == GST_STATE_CHANGE_SUCCESS) {
312         GST_DEBUG_OBJECT (self, "This worked!");
313         gst_element_set_state (el, GST_STATE_NULL);
314         choice = el;
315         break;
316       }
317 
318       /* collect all error messages */
319       while ((message = gst_bus_pop_filtered (bus, GST_MESSAGE_ERROR))) {
320         GST_DEBUG_OBJECT (self, "error message %" GST_PTR_FORMAT, message);
321         errors = g_slist_append (errors, message);
322       }
323 
324       gst_element_set_state (el, GST_STATE_NULL);
325       gst_object_unref (el);
326     }
327   }
328 
329   GST_DEBUG_OBJECT (self, "done trying");
330   if (!choice) {
331     /* We post a warning and plug a fake-element. This is convenient for running
332      * tests without requiring hardware src/sinks. */
333     if (errors) {
334       GError *err = NULL;
335       gchar *dbg = NULL;
336 
337       /* FIXME: we forward the first message for now; but later on it might make
338        * sense to forward all so that apps can actually analyse them. */
339       gst_message_parse_error (GST_MESSAGE (errors->data), &err, &dbg);
340       gst_element_post_message (GST_ELEMENT_CAST (self),
341           gst_message_new_warning (GST_OBJECT_CAST (self), err, dbg));
342       g_error_free (err);
343       g_free (dbg);
344     } else {
345       /* send warning message to application and use a fakesrc */
346       GST_ELEMENT_WARNING (self, RESOURCE, NOT_FOUND, (NULL),
347           ("Failed to find a usable %s %s", self->media_klass_lc,
348               self->type_klass_lc));
349     }
350     choice = gst_auto_detect_create_fake_element (self);
351     gst_element_set_state (choice, GST_STATE_READY);
352   }
353   gst_object_unref (bus);
354   gst_plugin_feature_list_free (list);
355   g_slist_foreach (errors, (GFunc) gst_mini_object_unref, NULL);
356   g_slist_free (errors);
357 
358   return choice;
359 }
360 
361 static gboolean
gst_auto_detect_detect(GstAutoDetect * self)362 gst_auto_detect_detect (GstAutoDetect * self)
363 {
364   GstElement *kid;
365   GstAutoDetectClass *klass = GST_AUTO_DETECT_GET_CLASS (self);
366 
367   gst_auto_detect_clear_kid (self);
368 
369   /* find element */
370   GST_DEBUG_OBJECT (self, "Creating new kid");
371   if (!(kid = gst_auto_detect_find_best (self)))
372     goto no_sink;
373 
374   self->has_sync =
375       g_object_class_find_property (G_OBJECT_GET_CLASS (kid), "sync") != NULL;
376   if (self->has_sync)
377     g_object_set (G_OBJECT (kid), "sync", self->sync, NULL);
378   if (klass->configure) {
379     klass->configure (self, kid);
380   }
381 
382   self->kid = kid;
383 
384   gst_bin_add (GST_BIN (self), kid);
385 
386   /* Ensure the child is brought up to the right state to match the parent. */
387   if (GST_STATE (self->kid) < GST_STATE (self))
388     gst_element_set_state (self->kid, GST_STATE (self));
389 
390   /* attach ghost pad */
391   GST_DEBUG_OBJECT (self, "Re-assigning ghostpad");
392   if (!gst_auto_detect_attach_ghost_pad (self))
393     goto target_failed;
394 
395   GST_DEBUG_OBJECT (self, "done changing auto %s %s", self->media_klass_lc,
396       self->type_klass_lc);
397 
398   return TRUE;
399 
400   /* ERRORS */
401 no_sink:
402   {
403     GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
404         ("Failed to find a supported audio sink"));
405     return FALSE;
406   }
407 target_failed:
408   {
409     GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
410         ("Failed to set target pad"));
411     return FALSE;
412   }
413 }
414 
415 static GstStateChangeReturn
gst_auto_detect_change_state(GstElement * element,GstStateChange transition)416 gst_auto_detect_change_state (GstElement * element, GstStateChange transition)
417 {
418   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
419   GstAutoDetect *sink = GST_AUTO_DETECT (element);
420 
421   switch (transition) {
422     case GST_STATE_CHANGE_NULL_TO_READY:
423       if (!gst_auto_detect_detect (sink))
424         return GST_STATE_CHANGE_FAILURE;
425       break;
426     default:
427       break;
428   }
429 
430   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
431   if (ret == GST_STATE_CHANGE_FAILURE)
432     return ret;
433 
434   switch (transition) {
435     case GST_STATE_CHANGE_READY_TO_NULL:
436       gst_auto_detect_reset (sink);
437       break;
438     default:
439       break;
440   }
441 
442   return ret;
443 }
444 
445 static void
gst_auto_detect_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)446 gst_auto_detect_set_property (GObject * object, guint prop_id,
447     const GValue * value, GParamSpec * pspec)
448 {
449   GstAutoDetect *self = GST_AUTO_DETECT (object);
450 
451   switch (prop_id) {
452     case PROP_CAPS:
453       if (self->filter_caps)
454         gst_caps_unref (self->filter_caps);
455       self->filter_caps = gst_caps_copy (gst_value_get_caps (value));
456       break;
457     case PROP_SYNC:
458       self->sync = g_value_get_boolean (value);
459       if (self->kid && self->has_sync)
460         g_object_set_property (G_OBJECT (self->kid), pspec->name, value);
461       break;
462     default:
463       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
464       break;
465   }
466 }
467 
468 static void
gst_auto_detect_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)469 gst_auto_detect_get_property (GObject * object, guint prop_id,
470     GValue * value, GParamSpec * pspec)
471 {
472   GstAutoDetect *self = GST_AUTO_DETECT (object);
473 
474   switch (prop_id) {
475     case PROP_CAPS:
476       gst_value_set_caps (value, self->filter_caps);
477       break;
478     case PROP_SYNC:
479       g_value_set_boolean (value, self->sync);
480       break;
481     default:
482       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
483       break;
484   }
485 }
486 
487 static gboolean
plugin_init(GstPlugin * plugin)488 plugin_init (GstPlugin * plugin)
489 {
490   GST_DEBUG_CATEGORY_INIT (autodetect_debug, "autodetect", 0,
491       "Autodetection audio/video output wrapper elements");
492 
493   return gst_element_register (plugin, "autovideosink",
494       GST_RANK_NONE, GST_TYPE_AUTO_VIDEO_SINK) &&
495       gst_element_register (plugin, "autovideosrc",
496       GST_RANK_NONE, GST_TYPE_AUTO_VIDEO_SRC) &&
497       gst_element_register (plugin, "autoaudiosink",
498       GST_RANK_NONE, GST_TYPE_AUTO_AUDIO_SINK) &&
499       gst_element_register (plugin, "autoaudiosrc",
500       GST_RANK_NONE, GST_TYPE_AUTO_AUDIO_SRC);
501 }
502 
503 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
504     GST_VERSION_MINOR,
505     autodetect,
506     "Plugin contains auto-detection plugins for video/audio in- and outputs",
507     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
508