wasapi: Implement a device provider for probing
authorNirbheek Chauhan <nirbheek@centricular.com>
Wed, 24 Jan 2018 19:21:22 +0000 (00:51 +0530)
committerNirbheek Chauhan <nirbheek@centricular.com>
Wed, 31 Jan 2018 09:28:21 +0000 (14:58 +0530)
Currently only does probing and does not handle messages from
endpoints/devices. In the future we want to do proper monitoring which
is well-supported in WASAPI.

https://bugzilla.gnome.org/show_bug.cgi?id=792897

sys/wasapi/Makefile.am
sys/wasapi/gstwasapi.c
sys/wasapi/gstwasapidevice.c [new file with mode: 0644]
sys/wasapi/gstwasapidevice.h [new file with mode: 0644]
sys/wasapi/gstwasapiutil.c
sys/wasapi/gstwasapiutil.h

index ce494be..9c08baf 100644 (file)
@@ -3,7 +3,8 @@ plugin_LTLIBRARIES = libgstwasapi.la
 libgstwasapi_la_SOURCES = gstwasapi.c \
        gstwasapisrc.c \
        gstwasapisink.c \
-       gstwasapiutil.c 
+       gstwasapiutil.c \
+       gstwasapidevice.c
 
 libgstwasapi_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) -DCOBJMACROS=1
 libgstwasapi_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
@@ -13,5 +14,5 @@ libgstwasapi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
 noinst_HEADERS = gstwasapisrc.h \
        gstwasapisink.h \
-       gstwasapiutil.h
-
+       gstwasapiutil.h \
+       gstwasapidevice.h
index c082485..1e50c4f 100644 (file)
 
 #include "gstwasapisink.h"
 #include "gstwasapisrc.h"
+#include "gstwasapidevice.h"
 
 static gboolean
 plugin_init (GstPlugin * plugin)
 {
-  gst_element_register (plugin, "wasapisink", GST_RANK_NONE,
-      GST_TYPE_WASAPI_SINK);
-  gst_element_register (plugin, "wasapisrc", GST_RANK_NONE,
-      GST_TYPE_WASAPI_SRC);
+  if (!gst_element_register (plugin, "wasapisink", GST_RANK_NONE,
+          GST_TYPE_WASAPI_SINK))
+    return FALSE;
 
+  if (!gst_element_register (plugin, "wasapisrc", GST_RANK_NONE,
+          GST_TYPE_WASAPI_SRC))
+    return FALSE;
+
+  if (!gst_device_provider_register (plugin, "wasapideviceprovider",
+          GST_RANK_PRIMARY, GST_TYPE_WASAPI_DEVICE_PROVIDER))
+    return FALSE;
 
   return TRUE;
 }
diff --git a/sys/wasapi/gstwasapidevice.c b/sys/wasapi/gstwasapidevice.c
new file mode 100644 (file)
index 0000000..49254c5
--- /dev/null
@@ -0,0 +1,167 @@
+/* GStreamer
+ * Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstwasapidevice.h"
+
+G_DEFINE_TYPE (GstWasapiDeviceProvider, gst_wasapi_device_provider,
+    GST_TYPE_DEVICE_PROVIDER);
+
+static void gst_wasapi_device_provider_finalize (GObject * object);
+static GList *gst_wasapi_device_provider_probe (GstDeviceProvider *
+    provider);
+
+static void
+gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass *
+    klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
+
+  gobject_class->finalize = gst_wasapi_device_provider_finalize;
+
+  dm_class->probe = gst_wasapi_device_provider_probe;
+
+  gst_device_provider_class_set_static_metadata (dm_class,
+      "WASAPI (Windows Audio Session API) Device Provider",
+      "Source/Sink/Audio", "List WASAPI source and sink devices",
+      "Nirbheek Chauhan <nirbheek@centricular.com>");
+}
+
+static void
+gst_wasapi_device_provider_init (GstWasapiDeviceProvider * provider)
+{
+  CoInitialize (NULL);
+}
+
+static void
+gst_wasapi_device_provider_finalize (GObject * object)
+{
+  CoUninitialize ();
+}
+
+static GList *
+gst_wasapi_device_provider_probe (GstDeviceProvider * provider)
+{
+  GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
+  GList *devices = NULL;
+
+  if (!gst_wasapi_util_get_devices (GST_ELEMENT (self), TRUE, &devices))
+    GST_ERROR_OBJECT (self, "Failed to enumerate devices");
+
+  return devices;
+}
+
+/* GstWasapiDevice begins */
+
+enum
+{
+  PROP_DEVICE_STRID = 1,
+};
+
+G_DEFINE_TYPE (GstWasapiDevice, gst_wasapi_device, GST_TYPE_DEVICE);
+
+static void gst_wasapi_device_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_wasapi_device_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_wasapi_device_finalize (GObject * object);
+static GstElement *gst_wasapi_device_create_element (GstDevice * device,
+    const gchar * name);
+
+static void
+gst_wasapi_device_class_init (GstWasapiDeviceClass * klass)
+{
+  GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  dev_class->create_element = gst_wasapi_device_create_element;
+
+  object_class->get_property = gst_wasapi_device_get_property;
+  object_class->set_property = gst_wasapi_device_set_property;
+  object_class->finalize = gst_wasapi_device_finalize;
+
+  g_object_class_install_property (object_class, PROP_DEVICE_STRID,
+      g_param_spec_string ("device", "Device string ID",
+          "Device strId", NULL,
+          G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_wasapi_device_init (GstWasapiDevice * device)
+{
+}
+
+static void
+gst_wasapi_device_finalize (GObject * object)
+{
+  GstWasapiDevice *device = GST_WASAPI_DEVICE (object);
+
+  g_free (device->strid);
+
+  G_OBJECT_CLASS (gst_wasapi_device_parent_class)->finalize (object);
+}
+
+static GstElement *
+gst_wasapi_device_create_element (GstDevice * device, const gchar * name)
+{
+  GstWasapiDevice *wasapi_dev = GST_WASAPI_DEVICE (device);
+  GstElement *elem;
+
+  elem = gst_element_factory_make (wasapi_dev->element, name);
+
+  g_object_set (elem, "device", wasapi_dev->strid, NULL);
+
+  return elem;
+}
+
+static void
+gst_wasapi_device_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstWasapiDevice *device = GST_WASAPI_DEVICE_CAST (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_STRID:
+      g_value_set_string (value, device->strid);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_wasapi_device_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstWasapiDevice *device = GST_WASAPI_DEVICE_CAST (object);
+
+  switch (prop_id) {
+    case PROP_DEVICE_STRID:
+      device->strid = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
diff --git a/sys/wasapi/gstwasapidevice.h b/sys/wasapi/gstwasapidevice.h
new file mode 100644 (file)
index 0000000..0a619c2
--- /dev/null
@@ -0,0 +1,75 @@
+/* GStreamer
+ * Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_WASAPI_DEVICE_H__
+#define __GST_WASAPI_DEVICE_H__
+
+#include "gstwasapiutil.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GstWasapiDeviceProvider GstWasapiDeviceProvider;
+typedef struct _GstWasapiDeviceProviderClass GstWasapiDeviceProviderClass;
+
+#define GST_TYPE_WASAPI_DEVICE_PROVIDER                 (gst_wasapi_device_provider_get_type())
+#define GST_IS_WASAPI_DEVICE_PROVIDER(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER))
+#define GST_IS_WASAPI_DEVICE_PROVIDER_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_DEVICE_PROVIDER))
+#define GST_WASAPI_DEVICE_PROVIDER_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER, GstWasapiDeviceProviderClass))
+#define GST_WASAPI_DEVICE_PROVIDER(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER, GstWasapiDeviceProvider))
+#define GST_WASAPI_DEVICE_PROVIDER_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstWasapiDeviceProviderClass))
+#define GST_WASAPI_DEVICE_PROVIDER_CAST(obj)            ((GstWasapiDeviceProvider *)(obj))
+
+struct _GstWasapiDeviceProvider {
+  GstDeviceProvider parent;
+};
+
+struct _GstWasapiDeviceProviderClass {
+  GstDeviceProviderClass parent_class;
+};
+
+GType gst_wasapi_device_provider_get_type (void);
+
+
+typedef struct _GstWasapiDevice GstWasapiDevice;
+typedef struct _GstWasapiDeviceClass GstWasapiDeviceClass;
+
+#define GST_TYPE_WASAPI_DEVICE                 (gst_wasapi_device_get_type())
+#define GST_IS_WASAPI_DEVICE(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_DEVICE))
+#define GST_IS_WASAPI_DEVICE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_DEVICE))
+#define GST_WASAPI_DEVICE_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_WASAPI_DEVICE, GstWasapiDeviceClass))
+#define GST_WASAPI_DEVICE(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_DEVICE, GstWasapiDevice))
+#define GST_WASAPI_DEVICE_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstWasapiDeviceClass))
+#define GST_WASAPI_DEVICE_CAST(obj)            ((GstWasapiDevice *)(obj))
+
+struct _GstWasapiDevice {
+  GstDevice parent;
+
+  gchar       *strid;
+  const gchar *element;
+};
+
+struct _GstWasapiDeviceClass {
+  GstDeviceClass parent_class;
+};
+
+GType gst_wasapi_device_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_WASAPI_DEVICE_H__ */
index 170de45..0a88222 100644 (file)
 #endif
 
 #include "gstwasapiutil.h"
+#include "gstwasapidevice.h"
 
 #include <mmdeviceapi.h>
 
+/* This was only added to MinGW in ~2015 and our Cerbero toolchain is too old */
+#if defined(_MSC_VER)
+  #include <functiondiscoverykeys_devpkey.h>
+#elif !defined(PKEY_Device_FriendlyName)
+  #include <initguid.h>
+  #include <propkey.h>
+  DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
+  DEFINE_PROPERTYKEY(PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0);
+#endif
+
+
 #ifdef __uuidof
 const CLSID CLSID_MMDeviceEnumerator = __uuidof (MMDeviceEnumerator);
 const IID IID_IMMDeviceEnumerator = __uuidof (IMMDeviceEnumerator);
+const IID IID_IMMEndpoint = __uuidof (IMMEndpoint);
 const IID IID_IAudioClient = __uuidof (IAudioClient);
 const IID IID_IAudioRenderClient = __uuidof (IAudioRenderClient);
 const IID IID_IAudioCaptureClient = __uuidof (IAudioCaptureClient);
@@ -44,6 +57,10 @@ const IID IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,
   {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
 };
 
+const IID IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,
+  {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5}
+};
+
 const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,
   {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
 };
@@ -235,6 +252,176 @@ gst_wasapi_util_hresult_to_string (HRESULT hr)
   return s;
 }
 
+static IMMDeviceEnumerator*
+gst_wasapi_util_get_device_enumerator (GstElement * element)
+{
+  HRESULT hr;
+  IMMDeviceEnumerator *enumerator = NULL;
+
+  hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
+      &IID_IMMDeviceEnumerator, (void **) &enumerator);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed"
+        ": %s", gst_wasapi_util_hresult_to_string (hr));
+    return NULL;
+  }
+
+  return enumerator;
+}
+
+gboolean
+gst_wasapi_util_get_devices (GstElement * element, gboolean active,
+    GList ** devices)
+{
+  gboolean ret = FALSE;
+  static GstStaticCaps scaps = GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS);
+  DWORD dwStateMask = active ? DEVICE_STATE_ACTIVE : DEVICE_STATEMASK_ALL;
+  IMMDeviceCollection *device_collection = NULL;
+  IMMDeviceEnumerator *enumerator = NULL;
+  const gchar *device_class, *element_name;
+  guint ii, count;
+  HRESULT hr;
+
+  *devices = NULL;
+
+  enumerator = gst_wasapi_util_get_device_enumerator (element);
+  if (!enumerator)
+    return FALSE;
+
+  hr = IMMDeviceEnumerator_EnumAudioEndpoints (enumerator, eAll, dwStateMask,
+      &device_collection);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (element, "IMMDeviceEnumerator::EnumAudioEndpoints "
+        "failed: %s", gst_wasapi_util_hresult_to_string (hr));
+    goto err;
+  }
+
+  hr = IMMDeviceCollection_GetCount (device_collection, &count);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (element, "Failed to count devices: %s",
+        gst_wasapi_util_hresult_to_string (hr));
+    goto err;
+  }
+
+  /* Create a GList of GstDevices* to return */
+  for (ii = 0; ii < count; ii++) {
+    IMMDevice *item = NULL;
+    IMMEndpoint *endpoint = NULL;
+    IAudioClient *client = NULL;
+    IPropertyStore *prop_store = NULL;
+    WAVEFORMATEX *format = NULL;
+    gchar *description = NULL;
+    gchar *strid = NULL;
+    EDataFlow dataflow;
+    PROPVARIANT var;
+    wchar_t *wstrid;
+    GstDevice *device;
+    GstStructure *props;
+    GstCaps *caps;
+
+    hr = IMMDeviceCollection_Item (device_collection, ii, &item);
+    if (hr != S_OK)
+      continue;
+
+    hr = IMMDevice_QueryInterface (item, &IID_IMMEndpoint, (void **) &endpoint);
+    if (hr != S_OK)
+      goto next;
+
+    hr = IMMEndpoint_GetDataFlow (endpoint, &dataflow);
+    if (hr != S_OK)
+      goto next;
+
+    if (dataflow == eRender) {
+      device_class = "Audio/Sink";
+      element_name = "wasapisink";
+    } else {
+      device_class = "Audio/Source";
+      element_name = "wasapisrc";
+    }
+
+    PropVariantInit (&var);
+
+    hr = IMMDevice_GetId (item, &wstrid);
+    if (hr != S_OK)
+      goto next;
+    strid = g_utf16_to_utf8 (wstrid, -1, NULL, NULL, NULL);
+    CoTaskMemFree (wstrid);
+
+    hr = IMMDevice_OpenPropertyStore (item, STGM_READ, &prop_store);
+    if (hr != S_OK)
+      goto next;
+
+    /* NOTE: More properties can be added as needed from here:
+     * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370794(v=vs.85).aspx */
+    hr = IPropertyStore_GetValue (prop_store, &PKEY_Device_FriendlyName, &var);
+    if (hr != S_OK)
+      goto next;
+    description = g_utf16_to_utf8 (var.pwszVal, -1, NULL, NULL, NULL);
+    PropVariantClear (&var);
+
+    /* Get the audio client so we can fetch the mix format for shared mode
+     * to get the device format for exclusive mode (or something close to that)
+     * fetch PKEY_AudioEngine_DeviceFormat from the property store. */
+    hr = IMMDevice_Activate (item, &IID_IAudioClient, CLSCTX_ALL, NULL,
+        (void **) &client);
+    if (hr != S_OK) {
+      GST_ERROR_OBJECT (element, "IMMDevice::Activate (IID_IAudioClient) failed"
+          "on %s: %s", strid, gst_wasapi_util_hresult_to_string (hr));
+      goto next;
+    }
+
+    hr = IAudioClient_GetMixFormat (client, &format);
+    if (hr != S_OK || format == NULL) {
+      GST_ERROR_OBJECT ("GetMixFormat failed on %s: %s", strid,
+          gst_wasapi_util_hresult_to_string (hr));
+      goto next;
+    }
+
+    if (!gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
+            gst_static_caps_get (&scaps), &caps, NULL))
+       goto next;
+
+    /* Set some useful properties */
+    props = gst_structure_new ("wasapi-proplist",
+        "device.api", G_TYPE_STRING, "wasapi",
+        "device.strid", G_TYPE_STRING, GST_STR_NULL (strid),
+        "wasapi.device.description", G_TYPE_STRING, description, NULL);
+
+    device = g_object_new (GST_TYPE_WASAPI_DEVICE, "device", strid,
+        "display-name", description, "caps", caps,
+        "device-class", device_class, "properties", props, NULL);
+    GST_WASAPI_DEVICE(device)->element = element_name;
+
+    gst_structure_free (props);
+    gst_caps_unref (caps);
+    *devices = g_list_prepend (*devices, device);
+
+next:
+    PropVariantClear (&var);
+    if (prop_store)
+      IUnknown_Release (prop_store);
+    if (endpoint)
+      IUnknown_Release (endpoint);
+    if (client)
+      IUnknown_Release (client);
+    if (item)
+      IUnknown_Release (item);
+    if (description)
+      g_free (description);
+    if (strid)
+      g_free (strid);
+  }
+
+  ret = TRUE;
+
+err:
+  if (enumerator)
+    IUnknown_Release (enumerator);
+  if (device_collection)
+    IUnknown_Release (device_collection);
+  return ret;
+}
+
 gboolean
 gst_wasapi_util_get_device_client (GstElement * element,
     gboolean capture, gint role, const wchar_t * device_strid,
@@ -246,13 +433,8 @@ gst_wasapi_util_get_device_client (GstElement * element,
   IMMDevice *device = NULL;
   IAudioClient *client = NULL;
 
-  hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
-      &IID_IMMDeviceEnumerator, (void **) &enumerator);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed"
-        ": %s", gst_wasapi_util_hresult_to_string (hr));
+  if (!(enumerator = gst_wasapi_util_get_device_enumerator (element)))
     goto beach;
-  }
 
   if (!device_strid) {
     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator,
index cf8d884..af44895 100644 (file)
@@ -52,10 +52,12 @@ gint gst_wasapi_erole_to_device_role (gint erole);
 
 const gchar *gst_wasapi_util_hresult_to_string (HRESULT hr);
 
-gboolean
-gst_wasapi_util_get_device_client (GstElement * element,
-    gboolean capture,
-    gint role, const wchar_t * device_name, IAudioClient ** ret_client);
+gboolean gst_wasapi_util_get_devices (GstElement * element, gboolean active,
+    GList ** devices);
+
+gboolean gst_wasapi_util_get_device_client (GstElement * element,
+    gboolean capture, gint role, const wchar_t * device_strid,
+    IAudioClient ** ret_client);
 
 gboolean gst_wasapi_util_get_render_client (GstElement * element,
     IAudioClient * client, IAudioRenderClient ** ret_render_client);