From: Nirbheek Chauhan Date: Wed, 24 Jan 2018 19:21:22 +0000 (+0530) Subject: wasapi: Implement a device provider for probing X-Git-Tag: 1.19.3~507^2~4524 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ec6a10ed0641cc18f9d1068f18c8882445c70dc0;p=platform%2Fupstream%2Fgstreamer.git wasapi: Implement a device provider for probing 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 --- diff --git a/sys/wasapi/Makefile.am b/sys/wasapi/Makefile.am index ce494be..9c08baf 100644 --- a/sys/wasapi/Makefile.am +++ b/sys/wasapi/Makefile.am @@ -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 diff --git a/sys/wasapi/gstwasapi.c b/sys/wasapi/gstwasapi.c index c082485..1e50c4f 100644 --- a/sys/wasapi/gstwasapi.c +++ b/sys/wasapi/gstwasapi.c @@ -23,15 +23,22 @@ #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 index 0000000..49254c5 --- /dev/null +++ b/sys/wasapi/gstwasapidevice.c @@ -0,0 +1,167 @@ +/* GStreamer + * Copyright (C) 2018 Nirbheek Chauhan + * + * 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 "); +} + +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 index 0000000..0a619c2 --- /dev/null +++ b/sys/wasapi/gstwasapidevice.h @@ -0,0 +1,75 @@ +/* GStreamer + * Copyright (C) 2018 Nirbheek Chauhan + * + * 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__ */ diff --git a/sys/wasapi/gstwasapiutil.c b/sys/wasapi/gstwasapiutil.c index 170de45..0a88222 100644 --- a/sys/wasapi/gstwasapiutil.c +++ b/sys/wasapi/gstwasapiutil.c @@ -23,12 +23,25 @@ #endif #include "gstwasapiutil.h" +#include "gstwasapidevice.h" #include +/* This was only added to MinGW in ~2015 and our Cerbero toolchain is too old */ +#if defined(_MSC_VER) + #include +#elif !defined(PKEY_Device_FriendlyName) + #include + #include + 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, diff --git a/sys/wasapi/gstwasapiutil.h b/sys/wasapi/gstwasapiutil.h index cf8d884..af44895 100644 --- a/sys/wasapi/gstwasapiutil.h +++ b/sys/wasapi/gstwasapiutil.h @@ -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);