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
This commit is contained in:
Nirbheek Chauhan 2018-01-25 00:51:22 +05:30
parent d6d31064b4
commit ec6a10ed06
6 changed files with 451 additions and 17 deletions

View file

@ -3,7 +3,8 @@ plugin_LTLIBRARIES = libgstwasapi.la
libgstwasapi_la_SOURCES = gstwasapi.c \ libgstwasapi_la_SOURCES = gstwasapi.c \
gstwasapisrc.c \ gstwasapisrc.c \
gstwasapisink.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_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) -DCOBJMACROS=1
libgstwasapi_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \ 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 \ noinst_HEADERS = gstwasapisrc.h \
gstwasapisink.h \ gstwasapisink.h \
gstwasapiutil.h gstwasapiutil.h \
gstwasapidevice.h

View file

@ -23,15 +23,22 @@
#include "gstwasapisink.h" #include "gstwasapisink.h"
#include "gstwasapisrc.h" #include "gstwasapisrc.h"
#include "gstwasapidevice.h"
static gboolean static gboolean
plugin_init (GstPlugin * plugin) plugin_init (GstPlugin * plugin)
{ {
gst_element_register (plugin, "wasapisink", GST_RANK_NONE, if (!gst_element_register (plugin, "wasapisink", GST_RANK_NONE,
GST_TYPE_WASAPI_SINK); GST_TYPE_WASAPI_SINK))
gst_element_register (plugin, "wasapisrc", GST_RANK_NONE, return FALSE;
GST_TYPE_WASAPI_SRC);
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; return TRUE;
} }

View file

@ -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;
}
}

View file

@ -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__ */

View file

@ -23,12 +23,25 @@
#endif #endif
#include "gstwasapiutil.h" #include "gstwasapiutil.h"
#include "gstwasapidevice.h"
#include <mmdeviceapi.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 #ifdef __uuidof
const CLSID CLSID_MMDeviceEnumerator = __uuidof (MMDeviceEnumerator); const CLSID CLSID_MMDeviceEnumerator = __uuidof (MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof (IMMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof (IMMDeviceEnumerator);
const IID IID_IMMEndpoint = __uuidof (IMMEndpoint);
const IID IID_IAudioClient = __uuidof (IAudioClient); const IID IID_IAudioClient = __uuidof (IAudioClient);
const IID IID_IAudioRenderClient = __uuidof (IAudioRenderClient); const IID IID_IAudioRenderClient = __uuidof (IAudioRenderClient);
const IID IID_IAudioCaptureClient = __uuidof (IAudioCaptureClient); 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} {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, const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,
{0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
}; };
@ -235,6 +252,176 @@ gst_wasapi_util_hresult_to_string (HRESULT hr)
return s; 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 gboolean
gst_wasapi_util_get_device_client (GstElement * element, gst_wasapi_util_get_device_client (GstElement * element,
gboolean capture, gint role, const wchar_t * device_strid, gboolean capture, gint role, const wchar_t * device_strid,
@ -246,13 +433,8 @@ gst_wasapi_util_get_device_client (GstElement * element,
IMMDevice *device = NULL; IMMDevice *device = NULL;
IAudioClient *client = NULL; IAudioClient *client = NULL;
hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, if (!(enumerator = gst_wasapi_util_get_device_enumerator (element)))
&IID_IMMDeviceEnumerator, (void **) &enumerator);
if (hr != S_OK) {
GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed"
": %s", gst_wasapi_util_hresult_to_string (hr));
goto beach; goto beach;
}
if (!device_strid) { if (!device_strid) {
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator, hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator,

View file

@ -52,10 +52,12 @@ gint gst_wasapi_erole_to_device_role (gint erole);
const gchar *gst_wasapi_util_hresult_to_string (HRESULT hr); const gchar *gst_wasapi_util_hresult_to_string (HRESULT hr);
gboolean gboolean gst_wasapi_util_get_devices (GstElement * element, gboolean active,
gst_wasapi_util_get_device_client (GstElement * element, GList ** devices);
gboolean capture,
gint role, const wchar_t * device_name, IAudioClient ** ret_client); 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, gboolean gst_wasapi_util_get_render_client (GstElement * element,
IAudioClient * client, IAudioRenderClient ** ret_render_client); IAudioClient * client, IAudioRenderClient ** ret_render_client);