gstpad: Probes that return HANDLED can reset the data info field
[platform/upstream/gstreamer.git] / gst / gstdevicemonitor.c
index 82324f2..088eb4c 100644 (file)
@@ -1,7 +1,7 @@
 /* GStreamer
- * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
+ * Copyright (C) 2013 Olivier Crete <olivier.crete@collabora.com>
  *
- * gstdevicemonitor.c: Device probing and monitoring
+ * gstdevicemonitor.c: device monitor
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  * Boston, MA 02111-1307, USA.
  */
 
+/**
+ * SECTION:gstdevicemonitor
+ * @title: GstDeviceMonitor
+ * @short_description: A device monitor and prober
+ * @see_also: #GstDevice, #GstDeviceProvider
+ *
+ * Applications should create a #GstDeviceMonitor when they want
+ * to probe, list and monitor devices of a specific type. The
+ * #GstDeviceMonitor will create the appropriate
+ * #GstDeviceProvider objects and manage them. It will then post
+ * messages on its #GstBus for devices that have been added and
+ * removed.
+ *
+ * The device monitor will monitor all devices matching the filters that
+ * the application has set.
+ *
+ * The basic use pattern of a device monitor is as follows:
+ * |[
+ *   static gboolean
+ *   my_bus_func (GstBus * bus, GstMessage * message, gpointer user_data)
+ *   {
+ *      GstDevice *device;
+ *      gchar *name;
+ *
+ *      switch (GST_MESSAGE_TYPE (message)) {
+ *        case GST_MESSAGE_DEVICE_ADDED:
+ *          gst_message_parse_device_added (message, &device);
+ *          name = gst_device_get_display_name (device);
+ *          g_print("Device added: %s\n", name);
+ *          g_free (name);
+ *          gst_object_unref (device);
+ *          break;
+ *        case GST_MESSAGE_DEVICE_REMOVED:
+ *          gst_message_parse_device_removed (message, &device);
+ *          name = gst_device_get_display_name (device);
+ *          g_print("Device removed: %s\n", name);
+ *          g_free (name);
+ *          gst_object_unref (device);
+ *          break;
+ *        default:
+ *          break;
+ *      }
+ *
+ *      return G_SOURCE_CONTINUE;
+ *   }
+ *
+ *   GstDeviceMonitor *
+ *   setup_raw_video_source_device_monitor (void) {
+ *      GstDeviceMonitor *monitor;
+ *      GstBus *bus;
+ *      GstCaps *caps;
+ *
+ *      monitor = gst_device_monitor_new ();
+ *
+ *      bus = gst_device_monitor_get_bus (monitor);
+ *      gst_bus_add_watch (bus, my_bus_func, NULL);
+ *      gst_object_unref (bus);
+ *
+ *      caps = gst_caps_new_empty_simple ("video/x-raw");
+ *      gst_device_monitor_add_filter (monitor, "Video/Source", caps);
+ *      gst_caps_unref (caps);
+ *
+ *      gst_device_monitor_start (monitor);
+ *
+ *      return monitor;
+ *   }
+ * ]|
+ *
+ * Since: 1.4
+ */
+
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
 #include "gst_private.h"
-
 #include "gstdevicemonitor.h"
 
-#include "gstelementmetadata.h"
-#include "gstquark.h"
-
 struct _GstDeviceMonitorPrivate
 {
+  gboolean started;
+
   GstBus *bus;
 
-  GMutex start_lock;
+  GPtrArray *providers;
+  guint cookie;
+
+  GPtrArray *filters;
+
+  guint last_id;
+  GList *hidden;
+  gboolean show_all;
+};
+
+#define DEFAULT_SHOW_ALL        FALSE
 
-  gboolean started_count;
+enum
+{
+  PROP_SHOW_ALL = 1,
 };
 
-/* this is used in gstelementfactory.c:gst_element_register() */
-GQuark __gst_devicemonitorclass_factory = 0;
+G_DEFINE_TYPE_WITH_PRIVATE (GstDeviceMonitor, gst_device_monitor,
+    GST_TYPE_OBJECT);
 
-static void gst_device_monitor_class_init (GstDeviceMonitorClass * klass);
-static void gst_device_monitor_init (GstDeviceMonitor * element);
-static void gst_device_monitor_base_class_init (gpointer g_class);
-static void gst_device_monitor_base_class_finalize (gpointer g_class);
 static void gst_device_monitor_dispose (GObject * object);
-static void gst_device_monitor_finalize (GObject * object);
 
-static gpointer gst_device_monitor_parent_class = NULL;
+struct DeviceFilter
+{
+  guint id;
 
-GType
-gst_device_monitor_get_type (void)
+  gchar **classesv;
+  GstCaps *caps;
+};
+
+static void
+device_filter_free (struct DeviceFilter *filter)
 {
-  static volatile gsize gst_device_monitor_type = 0;
-
-  if (g_once_init_enter (&gst_device_monitor_type)) {
-    GType _type;
-    static const GTypeInfo element_info = {
-      sizeof (GstDeviceMonitorClass),
-      gst_device_monitor_base_class_init,
-      gst_device_monitor_base_class_finalize,
-      (GClassInitFunc) gst_device_monitor_class_init,
-      NULL,
-      NULL,
-      sizeof (GstDeviceMonitor),
-      0,
-      (GInstanceInitFunc) gst_device_monitor_init,
-      NULL
-    };
-
-    _type = g_type_register_static (GST_TYPE_OBJECT, "GstDeviceMonitor",
-        &element_info, G_TYPE_FLAG_ABSTRACT);
-
-    __gst_devicemonitorclass_factory =
-        g_quark_from_static_string ("GST_DEVICEMONITORCLASS_FACTORY");
-    g_once_init_leave (&gst_device_monitor_type, _type);
-  }
-  return gst_device_monitor_type;
+  g_strfreev (filter->classesv);
+  gst_caps_unref (filter->caps);
+
+  g_slice_free (struct DeviceFilter, filter);
 }
 
 static void
-gst_device_monitor_base_class_init (gpointer g_class)
+gst_device_monitor_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
 {
-  GstDeviceMonitorClass *klass = GST_DEVICE_MONITOR_CLASS (g_class);
-
-  /* Copy the element details here so elements can inherit the
-   * details from their base class and classes only need to set
-   * the details in class_init instead of base_init */
-  klass->metadata =
-      klass->metadata ? gst_structure_copy (klass->metadata) :
-      gst_structure_new_empty ("metadata");
+  GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (object);
 
-  klass->factory = g_type_get_qdata (G_TYPE_FROM_CLASS (klass),
-      __gst_devicemonitorclass_factory);
+  switch (prop_id) {
+    case PROP_SHOW_ALL:
+      g_value_set_boolean (value,
+          gst_device_monitor_get_show_all_devices (monitor));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
 }
 
 static void
-gst_device_monitor_base_class_finalize (gpointer g_class)
+gst_device_monitor_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
 {
-  GstDeviceMonitorClass *klass = GST_DEVICE_MONITOR_CLASS (g_class);
+  GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (object);
 
-  gst_structure_free (klass->metadata);
+  switch (prop_id) {
+    case PROP_SHOW_ALL:
+      gst_device_monitor_set_show_all_devices (monitor,
+          g_value_get_boolean (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
 }
 
+
 static void
 gst_device_monitor_class_init (GstDeviceMonitorClass * klass)
 {
-  GObjectClass *gobject_class = (GObjectClass *) klass;
-
-  gst_device_monitor_parent_class = g_type_class_peek_parent (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  g_type_class_add_private (klass, sizeof (GstDeviceMonitorPrivate));
+  object_class->get_property = gst_device_monitor_get_property;
+  object_class->set_property = gst_device_monitor_set_property;
+  object_class->dispose = gst_device_monitor_dispose;
 
-  gobject_class->dispose = gst_device_monitor_dispose;
-  gobject_class->finalize = gst_device_monitor_finalize;
+  g_object_class_install_property (object_class, PROP_SHOW_ALL,
+      g_param_spec_boolean ("show-all", "Show All",
+          "Show all devices, even those from hidden providers",
+          DEFAULT_SHOW_ALL, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
 }
 
-static void
-gst_device_monitor_init (GstDeviceMonitor * monitor)
+/* must be called with monitor lock */
+static gboolean
+is_provider_hidden (GstDeviceMonitor * monitor, GList * hidden,
+    GstDeviceProvider * provider)
 {
-  monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor,
-      GST_TYPE_DEVICE_MONITOR, GstDeviceMonitorPrivate);
+  GstDeviceProviderFactory *factory;
 
-  g_mutex_init (&monitor->priv->start_lock);
+  if (monitor->priv->show_all)
+    return FALSE;
 
-  monitor->priv->bus = gst_bus_new ();
-  gst_bus_set_flushing (monitor->priv->bus, TRUE);
-}
+  factory = gst_device_provider_get_factory (provider);
+  if (g_list_find_custom (hidden, GST_OBJECT_NAME (factory),
+          (GCompareFunc) g_strcmp0))
+    return TRUE;
 
+  return FALSE;
+}
 
+/* must be called with monitor lock */
 static void
-gst_device_monitor_dispose (GObject * object)
+update_hidden_providers_list (GList ** hidden, GstDeviceProvider * provider)
 {
-  GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (object);
+  gchar **obs;
 
-  gst_object_replace ((GstObject **) & monitor->priv->bus, NULL);
+  obs = gst_device_provider_get_hidden_providers (provider);
+  if (obs) {
+    gint i;
 
-  GST_OBJECT_LOCK (monitor);
-  g_list_free_full (monitor->devices, (GDestroyNotify) gst_object_unparent);
-  monitor->devices = NULL;
-  GST_OBJECT_UNLOCK (monitor);
+    for (i = 0; obs[i]; i++)
+      *hidden = g_list_prepend (*hidden, obs[i]);
 
-  G_OBJECT_CLASS (gst_device_monitor_parent_class)->dispose (object);
+    g_free (obs);
+  }
 }
 
 static void
-gst_device_monitor_finalize (GObject * object)
+bus_sync_message (GstBus * bus, GstMessage * message,
+    GstDeviceMonitor * monitor)
 {
-  GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (object);
+  GstMessageType type = GST_MESSAGE_TYPE (message);
 
-  g_mutex_clear (&monitor->priv->start_lock);
+  if (type == GST_MESSAGE_DEVICE_ADDED || type == GST_MESSAGE_DEVICE_REMOVED ||
+      type == GST_MESSAGE_DEVICE_CHANGED) {
+    gboolean matches = TRUE;
+    GstDevice *device;
+    GstDeviceProvider *provider;
 
-  G_OBJECT_CLASS (gst_device_monitor_parent_class)->finalize (object);
-}
+    if (type == GST_MESSAGE_DEVICE_ADDED)
+      gst_message_parse_device_added (message, &device);
+    else if (type == GST_MESSAGE_DEVICE_REMOVED)
+      gst_message_parse_device_removed (message, &device);
+    else
+      gst_message_parse_device_changed (message, &device, NULL);
 
-/**
- * gst_device_monitor_class_add_metadata:
- * @klass: class to set metadata for
- * @key: the key to set
- * @value: the value to set
- *
- * Set @key with @value as metadata in @klass.
- */
-void
-gst_device_monitor_class_add_metadata (GstDeviceMonitorClass * klass,
-    const gchar * key, const gchar * value)
-{
-  g_return_if_fail (GST_IS_DEVICE_MONITOR_CLASS (klass));
-  g_return_if_fail (key != NULL);
-  g_return_if_fail (value != NULL);
+    GST_OBJECT_LOCK (monitor);
+    provider =
+        GST_DEVICE_PROVIDER (gst_object_get_parent (GST_OBJECT (device)));
+    if (is_provider_hidden (monitor, monitor->priv->hidden, provider)) {
+      matches = FALSE;
+    } else {
+      guint i;
+
+      for (i = 0; i < monitor->priv->filters->len; i++) {
+        struct DeviceFilter *filter =
+            g_ptr_array_index (monitor->priv->filters, i);
+        GstCaps *caps;
+
+        caps = gst_device_get_caps (device);
+        matches = gst_caps_can_intersect (filter->caps, caps) &&
+            gst_device_has_classesv (device, filter->classesv);
+        gst_caps_unref (caps);
+        if (matches)
+          break;
+      }
+    }
+    GST_OBJECT_UNLOCK (monitor);
+
+    gst_object_unref (provider);
+    gst_object_unref (device);
 
-  gst_structure_set ((GstStructure *) klass->metadata,
-      key, G_TYPE_STRING, value, NULL);
+    if (matches)
+      gst_bus_post (monitor->priv->bus, gst_message_ref (message));
+  }
 }
 
-/**
- * gst_device_monitor_class_add_static_metadata:
- * @klass: class to set metadata for
- * @key: the key to set
- * @value: the value to set
- *
- * Set @key with @value as metadata in @klass.
- *
- * Same as gst_device_monitor_class_add_metadata(), but @value must be a static string
- * or an inlined string, as it will not be copied. (GStreamer plugins will
- * be made resident once loaded, so this function can be used even from
- * dynamically loaded plugins.)
- *
- * Since: 1.4
- */
-void
-gst_device_monitor_class_add_static_metadata (GstDeviceMonitorClass * klass,
-    const gchar * key, const gchar * value)
+
+static void
+gst_device_monitor_init (GstDeviceMonitor * self)
 {
-  GValue val = G_VALUE_INIT;
+  self->priv = gst_device_monitor_get_instance_private (self);
 
-  g_return_if_fail (GST_IS_DEVICE_MONITOR_CLASS (klass));
-  g_return_if_fail (key != NULL);
-  g_return_if_fail (value != NULL);
+  self->priv->show_all = DEFAULT_SHOW_ALL;
 
-  g_value_init (&val, G_TYPE_STRING);
-  g_value_set_static_string (&val, value);
-  gst_structure_take_value ((GstStructure *) klass->metadata, key, &val);
-}
+  self->priv->bus = gst_bus_new ();
+  gst_bus_set_flushing (self->priv->bus, TRUE);
 
-/**
- * gst_device_monitor_class_set_metadata:
- * @klass: class to set metadata for
- * @longname: The long English name of the device monitor. E.g. "File Sink"
- * @classification: String describing the type of device monitor, as an unordered list
- * separated with slashes ('/'). See draft-klass.txt of the design docs
- * for more details and common types. E.g: "Sink/File"
- * @description: Sentence describing the purpose of the device monitor.
- * E.g: "Write stream to a file"
- * @author: Name and contact details of the author(s). Use \n to separate
- * multiple author metadata. E.g: "Joe Bloggs &lt;joe.blogs at foo.com&gt;"
- *
- * Sets the detailed information for a #GstDeviceMonitorClass.
- * <note>This function is for use in _class_init functions only.</note>
- *
- * Since: 1.4
- */
-void
-gst_device_monitor_class_set_metadata (GstDeviceMonitorClass * klass,
-    const gchar * longname, const gchar * classification,
-    const gchar * description, const gchar * author)
-{
-  g_return_if_fail (GST_IS_DEVICE_MONITOR_CLASS (klass));
-  g_return_if_fail (longname != NULL && *longname != '\0');
-  g_return_if_fail (classification != NULL && *classification != '\0');
-  g_return_if_fail (description != NULL && *description != '\0');
-  g_return_if_fail (author != NULL && *author != '\0');
-
-  gst_structure_id_set ((GstStructure *) klass->metadata,
-      GST_QUARK (ELEMENT_METADATA_LONGNAME), G_TYPE_STRING, longname,
-      GST_QUARK (ELEMENT_METADATA_KLASS), G_TYPE_STRING, classification,
-      GST_QUARK (ELEMENT_METADATA_DESCRIPTION), G_TYPE_STRING, description,
-      GST_QUARK (ELEMENT_METADATA_AUTHOR), G_TYPE_STRING, author, NULL);
+  self->priv->providers = g_ptr_array_new ();
+  self->priv->filters = g_ptr_array_new_with_free_func (
+      (GDestroyNotify) device_filter_free);
+
+  self->priv->last_id = 1;
 }
 
-/**
- * gst_device_monitor_class_set_static_metadata:
- * @klass: class to set metadata for
- * @longname: The long English name of the element. E.g. "File Sink"
- * @classification: String describing the type of element, as an unordered list
- * separated with slashes ('/'). See draft-klass.txt of the design docs
- * for more details and common types. E.g: "Sink/File"
- * @description: Sentence describing the purpose of the element.
- * E.g: "Write stream to a file"
- * @author: Name and contact details of the author(s). Use \n to separate
- * multiple author metadata. E.g: "Joe Bloggs &lt;joe.blogs at foo.com&gt;"
- *
- * Sets the detailed information for a #GstDeviceMonitorClass.
- * <note>This function is for use in _class_init functions only.</note>
- *
- * Same as gst_device_monitor_class_set_metadata(), but @longname, @classification,
- * @description, and @author must be static strings or inlined strings, as
- * they will not be copied. (GStreamer plugins will be made resident once
- * loaded, so this function can be used even from dynamically loaded plugins.)
- *
- * Since: 1.4
- */
-void
-gst_device_monitor_class_set_static_metadata (GstDeviceMonitorClass * klass,
-    const gchar * longname, const gchar * classification,
-    const gchar * description, const gchar * author)
+
+static void
+gst_device_monitor_remove (GstDeviceMonitor * self, guint i)
 {
-  GstStructure *s = (GstStructure *) klass->metadata;
-  GValue val = G_VALUE_INIT;
+  GstDeviceProvider *provider = g_ptr_array_index (self->priv->providers, i);
+  GstBus *bus;
 
-  g_return_if_fail (GST_IS_DEVICE_MONITOR_CLASS (klass));
-  g_return_if_fail (longname != NULL && *longname != '\0');
-  g_return_if_fail (classification != NULL && *classification != '\0');
-  g_return_if_fail (description != NULL && *description != '\0');
-  g_return_if_fail (author != NULL && *author != '\0');
+  g_ptr_array_remove_index (self->priv->providers, i);
 
-  g_value_init (&val, G_TYPE_STRING);
+  bus = gst_device_provider_get_bus (provider);
+  g_signal_handlers_disconnect_by_func (bus, bus_sync_message, self);
+  gst_object_unref (bus);
 
-  g_value_set_static_string (&val, longname);
-  gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_LONGNAME), &val);
+  gst_object_unref (provider);
+}
 
-  g_value_set_static_string (&val, classification);
-  gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_KLASS), &val);
+static void
+gst_device_monitor_dispose (GObject * object)
+{
+  GstDeviceMonitor *self = GST_DEVICE_MONITOR (object);
 
-  g_value_set_static_string (&val, description);
-  gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_DESCRIPTION),
-      &val);
+  g_return_if_fail (!self->priv->started);
 
-  g_value_set_static_string (&val, author);
-  gst_structure_id_take_value (s, GST_QUARK (ELEMENT_METADATA_AUTHOR), &val);
-}
+  if (self->priv->providers) {
+    while (self->priv->providers->len)
+      gst_device_monitor_remove (self, self->priv->providers->len - 1);
+    g_ptr_array_unref (self->priv->providers);
+    self->priv->providers = NULL;
+  }
 
-/**
- * gst_device_monitor_class_get_metadata:
- * @klass: class to get metadata for
- * @key: the key to get
- *
- * Get metadata with @key in @klass.
- *
- * Returns: the metadata for @key.
- *
- * Since: 1.4
- */
-const gchar *
-gst_device_monitor_class_get_metadata (GstDeviceMonitorClass * klass,
-    const gchar * key)
-{
-  g_return_val_if_fail (GST_IS_DEVICE_MONITOR_CLASS (klass), NULL);
-  g_return_val_if_fail (key != NULL, NULL);
+  if (self->priv->filters) {
+    g_ptr_array_unref (self->priv->filters);
+    self->priv->filters = NULL;
+  }
+
+  gst_object_replace ((GstObject **) & self->priv->bus, NULL);
 
-  return gst_structure_get_string ((GstStructure *) klass->metadata, key);
+  G_OBJECT_CLASS (gst_device_monitor_parent_class)->dispose (object);
 }
 
 /**
  * gst_device_monitor_get_devices:
- * @monitor: A #GstDeviceMonitor
+ * @monitor: A #GstDeviceProvider
  *
- * Gets a list of devices that this monitor understands. This may actually
+ * Gets a list of devices from all of the relevant monitors. This may actually
  * probe the hardware if the monitor is not currently started.
  *
- * Returns: (transfer full) (element-type GstDevice): a #GList of
+ * Returns: (transfer full) (element-type GstDevice) (nullable): a #GList of
  *   #GstDevice
  *
  * Since: 1.4
@@ -330,42 +352,91 @@ gst_device_monitor_class_get_metadata (GstDeviceMonitorClass * klass,
 GList *
 gst_device_monitor_get_devices (GstDeviceMonitor * monitor)
 {
-  GstDeviceMonitorClass *klass;
-  GList *devices = NULL;
-  gboolean started;
-  GList *item;
+  GList *devices = NULL, *hidden = NULL;
+  guint i;
+  guint cookie;
 
   g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), NULL);
-  klass = GST_DEVICE_MONITOR_GET_CLASS (monitor);
 
-  g_mutex_lock (&monitor->priv->start_lock);
-  started = (monitor->priv->started_count > 0);
+  GST_OBJECT_LOCK (monitor);
+
+  if (monitor->priv->filters->len == 0) {
+    GST_OBJECT_UNLOCK (monitor);
+    GST_WARNING_OBJECT (monitor, "No filters have been set");
+    return NULL;
+  }
 
-  if (started) {
-    GST_OBJECT_LOCK (monitor);
-    for (item = monitor->devices; item; item = item->next)
-      devices = g_list_prepend (devices, gst_object_ref (item->data));
+  if (monitor->priv->providers->len == 0) {
     GST_OBJECT_UNLOCK (monitor);
-  } else if (klass->probe)
-    devices = klass->probe (monitor);
+    GST_WARNING_OBJECT (monitor, "No providers match the current filters");
+    return NULL;
+  }
+
+again:
+
+  g_list_free_full (devices, gst_object_unref);
+  g_list_free_full (hidden, g_free);
+  devices = NULL;
+  hidden = NULL;
+
+  cookie = monitor->priv->cookie;
+
+  for (i = 0; i < monitor->priv->providers->len; i++) {
+    GList *tmpdev;
+    GstDeviceProvider *provider =
+        gst_object_ref (g_ptr_array_index (monitor->priv->providers, i));
+    GList *item;
+
+    if (!is_provider_hidden (monitor, hidden, provider)) {
+      GST_OBJECT_UNLOCK (monitor);
+
+      tmpdev = gst_device_provider_get_devices (provider);
 
-  g_mutex_unlock (&monitor->priv->start_lock);
+      GST_OBJECT_LOCK (monitor);
+      update_hidden_providers_list (&hidden, provider);
+    } else {
+      tmpdev = NULL;
+    }
 
-  return devices;
+
+    for (item = tmpdev; item; item = item->next) {
+      GstDevice *dev = GST_DEVICE (item->data);
+      GstCaps *caps = gst_device_get_caps (dev);
+      guint j;
+
+      for (j = 0; j < monitor->priv->filters->len; j++) {
+        struct DeviceFilter *filter =
+            g_ptr_array_index (monitor->priv->filters, j);
+
+        if (gst_caps_can_intersect (filter->caps, caps) &&
+            gst_device_has_classesv (dev, filter->classesv)) {
+          devices = g_list_prepend (devices, gst_object_ref (dev));
+          break;
+        }
+      }
+      gst_caps_unref (caps);
+    }
+
+    g_list_free_full (tmpdev, gst_object_unref);
+    gst_object_unref (provider);
+
+    if (monitor->priv->cookie != cookie)
+      goto again;
+  }
+  g_list_free_full (hidden, g_free);
+
+  GST_OBJECT_UNLOCK (monitor);
+
+  return g_list_reverse (devices);
 }
 
 /**
  * gst_device_monitor_start:
  * @monitor: A #GstDeviceMonitor
  *
- * Starts monitoring the devices. This will cause #GST_MESSAGE_DEVICE messages
- * to be posted on the monitor's bus when devices are added or removed from
- * the system.
- *
- * Since the #GstDeviceMonitor is a singleton,
- * gst_device_monitor_start() may already have been called by another
- * user of the object, gst_device_monitor_stop() needs to be called the same
- * number of times.
+ * Starts monitoring the devices, one this has succeeded, the
+ * %GST_MESSAGE_DEVICE_ADDED and %GST_MESSAGE_DEVICE_REMOVED messages
+ * will be emitted on the bus when the list of devices changes.
  *
  * Returns: %TRUE if the device monitoring could be started
  *
@@ -375,118 +446,364 @@ gst_device_monitor_get_devices (GstDeviceMonitor * monitor)
 gboolean
 gst_device_monitor_start (GstDeviceMonitor * monitor)
 {
-  GstDeviceMonitorClass *klass;
-  gboolean ret = FALSE;
+  guint cookie, i;
+  GList *pending = NULL, *started = NULL, *removed = NULL;
 
   g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), FALSE);
-  klass = GST_DEVICE_MONITOR_GET_CLASS (monitor);
 
-  g_mutex_lock (&monitor->priv->start_lock);
+  GST_OBJECT_LOCK (monitor);
 
-  if (monitor->priv->started_count > 0) {
-    ret = TRUE;
-    goto started;
+  if (monitor->priv->filters->len == 0) {
+    GST_OBJECT_UNLOCK (monitor);
+    GST_WARNING_OBJECT (monitor, "No filters have been set, will expose all "
+        "devices found");
+    gst_device_monitor_add_filter (monitor, NULL, NULL);
+    GST_OBJECT_LOCK (monitor);
   }
 
-  if (klass->start)
-    ret = klass->start (monitor);
+  if (monitor->priv->providers->len == 0) {
+    GST_OBJECT_UNLOCK (monitor);
+    GST_WARNING_OBJECT (monitor, "No providers match the current filters");
+    return FALSE;
+  }
+
+  gst_bus_set_flushing (monitor->priv->bus, FALSE);
+
+again:
+  cookie = monitor->priv->cookie;
+
+  g_list_free_full (pending, gst_object_unref);
+  pending = NULL;
+  removed = started;
+  started = NULL;
 
-  if (ret) {
-    monitor->priv->started_count++;
-    gst_bus_set_flushing (monitor->priv->bus, FALSE);
+  for (i = 0; i < monitor->priv->providers->len; i++) {
+    GstDeviceProvider *provider;
+    GList *find;
+
+    provider = g_ptr_array_index (monitor->priv->providers, i);
+
+    find = g_list_find (removed, provider);
+    if (find) {
+      /* this was already started, move to started list */
+      removed = g_list_remove_link (removed, find);
+      started = g_list_concat (started, find);
+    } else {
+      /* not started, add to pending list */
+      pending = g_list_append (pending, gst_object_ref (provider));
+    }
   }
+  g_list_free_full (removed, gst_object_unref);
+  removed = NULL;
+
+  while (pending) {
+    GstDeviceProvider *provider = pending->data;
+
+    if (gst_device_provider_can_monitor (provider)) {
+      GST_OBJECT_UNLOCK (monitor);
+
+      if (!gst_device_provider_start (provider))
+        goto start_failed;
+
+      GST_OBJECT_LOCK (monitor);
+    }
+    started = g_list_prepend (started, provider);
+    pending = g_list_delete_link (pending, pending);
+
+    if (monitor->priv->cookie != cookie)
+      goto again;
+  }
+  monitor->priv->started = TRUE;
+  GST_OBJECT_UNLOCK (monitor);
+
+  g_list_free_full (started, gst_object_unref);
+
+  return TRUE;
 
-started:
+start_failed:
+  {
+    GST_OBJECT_LOCK (monitor);
+    gst_bus_set_flushing (monitor->priv->bus, TRUE);
+    GST_OBJECT_UNLOCK (monitor);
+
+    while (started) {
+      GstDeviceProvider *provider = started->data;
 
-  g_mutex_unlock (&monitor->priv->start_lock);
+      gst_device_provider_stop (provider);
+      gst_object_unref (provider);
 
-  return ret;
+      started = g_list_delete_link (started, started);
+    }
+    return FALSE;
+  }
 }
 
 /**
  * gst_device_monitor_stop:
- * @monitor: A #GstDeviceMonitor
+ * @monitor: A #GstDeviceProvider
  *
- * Decreases the use-count by one. If the use count reaches zero, this
- * #GstDeviceMonitor will stop monitoring the devices. This needs to be
- * called the same number of times that gst_device_monitor_start() was called.
+ * Stops monitoring the devices.
  *
  * Since: 1.4
  */
-
 void
 gst_device_monitor_stop (GstDeviceMonitor * monitor)
 {
-  GstDeviceMonitorClass *klass;
+  guint i;
+  GList *started = NULL;
 
   g_return_if_fail (GST_IS_DEVICE_MONITOR (monitor));
-  klass = GST_DEVICE_MONITOR_GET_CLASS (monitor);
 
-  g_mutex_lock (&monitor->priv->start_lock);
+  gst_bus_set_flushing (monitor->priv->bus, TRUE);
 
-  if (monitor->priv->started_count == 1) {
-    gst_bus_set_flushing (monitor->priv->bus, TRUE);
-    if (klass->stop)
-      klass->stop (monitor);
-    GST_OBJECT_LOCK (monitor);
-    g_list_free_full (monitor->devices, (GDestroyNotify) gst_object_unparent);
-    monitor->devices = NULL;
-    GST_OBJECT_UNLOCK (monitor);
-  } else if (monitor->priv->started_count < 1) {
-    g_critical ("Trying to stop a GstDeviceMonitor %s which is already stopped",
-        GST_OBJECT_NAME (monitor));
+  GST_OBJECT_LOCK (monitor);
+  for (i = 0; i < monitor->priv->providers->len; i++) {
+    GstDeviceProvider *provider =
+        g_ptr_array_index (monitor->priv->providers, i);
+
+    started = g_list_prepend (started, gst_object_ref (provider));
+  }
+  GST_OBJECT_UNLOCK (monitor);
+
+  while (started) {
+    GstDeviceProvider *provider = started->data;
+
+    if (gst_device_provider_can_monitor (provider))
+      gst_device_provider_stop (provider);
+
+    started = g_list_delete_link (started, started);
+    gst_object_unref (provider);
   }
 
-  monitor->priv->started_count--;
-  g_mutex_unlock (&monitor->priv->start_lock);
+  GST_OBJECT_LOCK (monitor);
+  monitor->priv->started = FALSE;
+  GST_OBJECT_UNLOCK (monitor);
+
 }
 
+static void
+provider_hidden (GstDeviceProvider * provider, const gchar * hidden,
+    GstDeviceMonitor * monitor)
+{
+  GST_OBJECT_LOCK (monitor);
+  monitor->priv->hidden =
+      g_list_prepend (monitor->priv->hidden, g_strdup (hidden));
+  GST_OBJECT_UNLOCK (monitor);
+}
+
+static void
+provider_unhidden (GstDeviceProvider * provider, const gchar * hidden,
+    GstDeviceMonitor * monitor)
+{
+  GList *find;
+
+  GST_OBJECT_LOCK (monitor);
+  find =
+      g_list_find_custom (monitor->priv->hidden, hidden,
+      (GCompareFunc) g_strcmp0);
+  if (find) {
+    g_free (find->data);
+    monitor->priv->hidden = g_list_delete_link (monitor->priv->hidden, find);
+  }
+  GST_OBJECT_UNLOCK (monitor);
+}
 
 /**
- * gst_device_monitor_get_factory:
- * @monitor: a #GstDeviceMonitor to request the device monitor factory of.
+ * gst_device_monitor_add_filter:
+ * @monitor: a device monitor
+ * @classes: (allow-none): device classes to use as filter or %NULL for any class
+ * @caps: (allow-none): the #GstCaps to filter or %NULL for ANY
+ *
+ * Adds a filter for which #GstDevice will be monitored, any device that matches
+ * all these classes and the #GstCaps will be returned.
+ *
+ * If this function is called multiple times to add more filters, each will be
+ * matched independently. That is, adding more filters will not further restrict
+ * what devices are matched.
+ *
+ * The #GstCaps supported by the device as returned by gst_device_get_caps() are
+ * not intersected with caps filters added using this function.
  *
- * Retrieves the factory that was used to create this device monitor.
+ * Filters must be added before the #GstDeviceMonitor is started.
  *
- * Returns: (transfer none): the #GstDeviceMonitorFactory used for creating this
- *     device monitor. no refcounting is needed.
+ * Returns: The id of the new filter or 0 if no provider matched the filter's
+ *  classes.
  *
  * Since: 1.4
  */
-GstDeviceMonitorFactory *
-gst_device_monitor_get_factory (GstDeviceMonitor * monitor)
+guint
+gst_device_monitor_add_filter (GstDeviceMonitor * monitor,
+    const gchar * classes, GstCaps * caps)
 {
-  g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), NULL);
+  GList *factories = NULL;
+  struct DeviceFilter *filter;
+  guint id = 0;
+  gboolean matched = FALSE;
+
+  g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), 0);
+  g_return_val_if_fail (!monitor->priv->started, 0);
+
+  GST_OBJECT_LOCK (monitor);
+
+  filter = g_slice_new0 (struct DeviceFilter);
+  filter->id = monitor->priv->last_id++;
+  if (caps)
+    filter->caps = gst_caps_ref (caps);
+  else
+    filter->caps = gst_caps_new_any ();
+  if (classes)
+    filter->classesv = g_strsplit (classes, "/", 0);
+
+  factories = gst_device_provider_factory_list_get_device_providers (1);
+
+  while (factories) {
+    GstDeviceProviderFactory *factory = factories->data;
+
+    if (gst_device_provider_factory_has_classesv (factory, filter->classesv)) {
+      GstDeviceProvider *provider;
+
+      provider = gst_device_provider_factory_get (factory);
+
+      if (provider) {
+        guint i;
+
+        for (i = 0; i < monitor->priv->providers->len; i++) {
+          if (g_ptr_array_index (monitor->priv->providers, i) == provider) {
+            gst_object_unref (provider);
+            provider = NULL;
+            matched = TRUE;
+            break;
+          }
+        }
+      }
+
+      if (provider) {
+        GstBus *bus = gst_device_provider_get_bus (provider);
+
+        update_hidden_providers_list (&monitor->priv->hidden, provider);
+        g_signal_connect (provider, "provider-hidden",
+            (GCallback) provider_hidden, monitor);
+        g_signal_connect (provider, "provider-unhidden",
+            (GCallback) provider_unhidden, monitor);
+
+        matched = TRUE;
+        gst_bus_enable_sync_message_emission (bus);
+        g_signal_connect (bus, "sync-message",
+            G_CALLBACK (bus_sync_message), monitor);
+        gst_object_unref (bus);
+        g_ptr_array_add (monitor->priv->providers, provider);
+        monitor->priv->cookie++;
+      }
+    }
+
+    factories = g_list_remove (factories, factory);
+    gst_object_unref (factory);
+  }
 
-  return GST_DEVICE_MONITOR_GET_CLASS (monitor)->factory;
+  /* Ensure there is no leak here */
+  g_assert (factories == NULL);
+
+  if (matched)
+    id = filter->id;
+  g_ptr_array_add (monitor->priv->filters, filter);
+
+  GST_OBJECT_UNLOCK (monitor);
+
+  return id;
 }
 
 /**
- * gst_device_monitor_can_monitor:
- * @monitor: a #GstDeviceMonitor
+ * gst_device_monitor_remove_filter:
+ * @monitor: a device monitor
+ * @filter_id: the id of the filter
+ *
+ * Removes a filter from the #GstDeviceMonitor using the id that was returned
+ * by gst_device_monitor_add_filter().
  *
- * If this function returns %TRUE, then the device monitor can monitor if
- * devices are added or removed. Otherwise, it can only do static probing.
+ * Returns: %TRUE of the filter id was valid, %FALSE otherwise
  *
- * Returns: %TRUE if the #GstDeviceMonitor support monitoring, %FALSE otherwise
+ * Since: 1.4
  */
 gboolean
-gst_device_monitor_can_monitor (GstDeviceMonitor * monitor)
+gst_device_monitor_remove_filter (GstDeviceMonitor * monitor, guint filter_id)
 {
-  GstDeviceMonitorClass *klass;
+  guint i, j;
+  gboolean removed = FALSE;
 
   g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), FALSE);
-  klass = GST_DEVICE_MONITOR_GET_CLASS (monitor);
+  g_return_val_if_fail (!monitor->priv->started, FALSE);
+  g_return_val_if_fail (filter_id > 0, FALSE);
 
-  if (klass->start)
-    return TRUE;
-  else
-    return FALSE;
+  GST_OBJECT_LOCK (monitor);
+  for (i = 0; i < monitor->priv->filters->len; i++) {
+    struct DeviceFilter *filter = g_ptr_array_index (monitor->priv->filters, i);
+
+    if (filter->id == filter_id) {
+      g_ptr_array_remove_index (monitor->priv->filters, i);
+      removed = TRUE;
+      break;
+    }
+  }
+
+  if (removed) {
+    for (i = 0; i < monitor->priv->providers->len; i++) {
+      GstDeviceProvider *provider =
+          g_ptr_array_index (monitor->priv->providers, i);
+      GstDeviceProviderFactory *factory =
+          gst_device_provider_get_factory (provider);
+      gboolean valid = FALSE;
+
+      for (j = 0; j < monitor->priv->filters->len; j++) {
+        struct DeviceFilter *filter =
+            g_ptr_array_index (monitor->priv->filters, j);
+
+        if (gst_device_provider_factory_has_classesv (factory,
+                filter->classesv)) {
+          valid = TRUE;
+          break;
+        }
+      }
+
+      if (!valid) {
+        monitor->priv->cookie++;
+        gst_device_monitor_remove (monitor, i);
+        i--;
+      }
+    }
+  }
+
+  GST_OBJECT_UNLOCK (monitor);
+
+  return removed;
+}
+
+
+
+/**
+ * gst_device_monitor_new:
+ *
+ * Create a new #GstDeviceMonitor
+ *
+ * Returns: (transfer full): a new device monitor.
+ *
+ * Since: 1.4
+ */
+GstDeviceMonitor *
+gst_device_monitor_new (void)
+{
+  GstDeviceMonitor *monitor;
+
+  monitor = g_object_new (GST_TYPE_DEVICE_MONITOR, NULL);
+
+  /* Clear floating flag */
+  gst_object_ref_sink (monitor);
+
+  return monitor;
 }
 
 /**
  * gst_device_monitor_get_bus:
- * @monitor: a #GstDeviceMonitor
+ * @monitor: a #GstDeviceProvider
  *
  * Gets the #GstBus of this #GstDeviceMonitor
  *
@@ -503,67 +820,92 @@ gst_device_monitor_get_bus (GstDeviceMonitor * monitor)
 }
 
 /**
- * gst_device_monitor_device_add:
+ * gst_device_monitor_get_providers:
  * @monitor: a #GstDeviceMonitor
- * @device: (transfer full): a #GstDevice that has been added
  *
- * Posts a message on the monitor's #GstBus to inform applications that
- * a new device has been added.
+ * Get a list of the currently selected device provider factories.
  *
- * This is for use by subclasses.
+ * This
  *
- * Since: 1.4
+ * Returns: (transfer full) (array zero-terminated=1) (element-type gchar*):
+ *     A list of device provider factory names that are currently being
+ *     monitored by @monitor or %NULL when nothing is being monitored.
+ *
+ * Since: 1.6
  */
-void
-gst_device_monitor_device_add (GstDeviceMonitor * monitor, GstDevice * device)
+gchar **
+gst_device_monitor_get_providers (GstDeviceMonitor * monitor)
 {
-  GstMessage *message;
+  guint i, len;
+  gchar **res = NULL;
 
-  if (!gst_object_set_parent (GST_OBJECT (device), GST_OBJECT (monitor))) {
-    GST_WARNING_OBJECT (monitor, "Could not parent device %p to monitor,"
-        " it already has a parent", device);
-    return;
-  }
+  g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), NULL);
 
   GST_OBJECT_LOCK (monitor);
-  monitor->devices = g_list_prepend (monitor->devices, gst_object_ref (device));
+  len = monitor->priv->providers->len;
+  if (len == 0)
+    goto done;
+
+  res = g_new (gchar *, len + 1);
+
+  for (i = 0; i < len; i++) {
+    GstDeviceProvider *provider =
+        g_ptr_array_index (monitor->priv->providers, i);
+    GstDeviceProviderFactory *factory =
+        gst_device_provider_get_factory (provider);
+
+    res[i] = g_strdup (GST_OBJECT_NAME (factory));
+  }
+  res[i] = NULL;
+
+done:
   GST_OBJECT_UNLOCK (monitor);
 
-  message = gst_message_new_device_added (GST_OBJECT (monitor), device);
-  gst_bus_post (monitor->priv->bus, message);
-  gst_object_unref (device);
+  return res;
 }
 
+/**
+ * gst_device_monitor_set_show_all_devices:
+ * @monitor: a #GstDeviceMonitor
+ * @show_all: show all devices
+ *
+ * Set if all devices should be visible, even those devices from hidden
+ * providers. Setting @show_all to true might show some devices multiple times.
+ *
+ * Since: 1.6
+ */
+void
+gst_device_monitor_set_show_all_devices (GstDeviceMonitor * monitor,
+    gboolean show_all)
+{
+  g_return_if_fail (GST_IS_DEVICE_MONITOR (monitor));
+
+  GST_OBJECT_LOCK (monitor);
+  monitor->priv->show_all = show_all;
+  GST_OBJECT_UNLOCK (monitor);
+}
 
 /**
- * gst_device_monitor_device_remove:
+ * gst_device_monitor_get_show_all_devices:
  * @monitor: a #GstDeviceMonitor
- * @device: a #GstDevice that has been removed
  *
- * Posts a message on the monitor's #GstBus to inform applications that
- * a device has been removed.
+ * Get if @monitor is curretly showing all devices, even those from hidden
+ * providers.
  *
- * This is for use by subclasses.
+ * Returns: %TRUE when all devices will be shown.
  *
- * Since: 1.4
+ * Since: 1.6
  */
-void
-gst_device_monitor_device_remove (GstDeviceMonitor * monitor,
-    GstDevice * device)
+gboolean
+gst_device_monitor_get_show_all_devices (GstDeviceMonitor * monitor)
 {
-  GstMessage *message;
-  GList *item;
+  gboolean res;
+
+  g_return_val_if_fail (GST_IS_DEVICE_MONITOR (monitor), FALSE);
 
   GST_OBJECT_LOCK (monitor);
-  item = g_list_find (monitor->devices, device);
-  if (item) {
-    monitor->devices = g_list_delete_link (monitor->devices, item);
-  }
+  res = monitor->priv->show_all;
   GST_OBJECT_UNLOCK (monitor);
 
-  message = gst_message_new_device_removed (GST_OBJECT (monitor), device);
-  g_signal_emit_by_name (device, "removed");
-  gst_bus_post (monitor->priv->bus, message);
-  if (item)
-    gst_object_unparent (GST_OBJECT (device));
+  return res;
 }