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) \
noinst_HEADERS = gstwasapisrc.h \
gstwasapisink.h \
- gstwasapiutil.h
-
+ gstwasapiutil.h \
+ gstwasapidevice.h
#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;
}
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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__ */
#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);
{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}
};
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,
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,
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);