wasapi2deviceprovider: Probe device form factor and enumerator name

Adding form factor and enumerator information to device property struct

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9326>
This commit is contained in:
Seungha Yang 2025-07-04 21:55:13 +09:00 committed by GStreamer Marge Bot
parent 9dbc7a491e
commit fc861119ed
3 changed files with 247 additions and 35 deletions

View File

@ -42,6 +42,7 @@ struct _GstWasapi2Device
gchar *device_id;
const gchar *factory_name;
GstWasapi2EndpointClass device_class;
gboolean is_default;
};
G_DEFINE_TYPE (GstWasapi2Device, gst_wasapi2_device, GST_TYPE_DEVICE);
@ -216,24 +217,43 @@ gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
auto props = gst_structure_new ("wasapi2-proplist",
"device.api", G_TYPE_STRING, "wasapi2",
"device.id", G_TYPE_STRING, entry->device_id,
"device.id", G_TYPE_STRING, entry->device_id.c_str (),
"device.default", G_TYPE_BOOLEAN, entry->is_default,
"wasapi2.device.description", G_TYPE_STRING, entry->device_name,
nullptr);
"wasapi2.device.description", G_TYPE_STRING,
entry->device_name.c_str (),
"device.form-factor", G_TYPE_INT,
(gint) entry->device_props.form_factor,
"device.form-factor-name", G_TYPE_STRING,
gst_wasapi2_form_factor_to_string (entry->device_props.form_factor),
"device.enumerator-name", G_TYPE_STRING,
entry->device_props.enumerator_name.c_str (), nullptr);
if (entry->is_default) {
if (!entry->actual_device_id.empty ()) {
gst_structure_set (props, "device.actual-id", G_TYPE_STRING,
entry->actual_device_id.c_str (), nullptr);
}
if (!entry->actual_device_name.empty ()) {
gst_structure_set (props, "device.actual-name", G_TYPE_STRING,
entry->actual_device_name.c_str (), nullptr);
}
}
if (entry->flow == eCapture) {
gst_structure_set (props,
"wasapi2.device.loopback", G_TYPE_BOOLEAN, FALSE, nullptr);
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
"device", entry->device_id,
"display-name", entry->device_name, "caps", entry->caps,
"device", entry->device_id.c_str (),
"display-name", entry->device_name.c_str (), "caps", entry->caps,
"device-class", "Audio/Source", "properties", props, nullptr);
gst_structure_free (props);
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2src";
GST_WASAPI2_DEVICE (device)->device_class =
GST_WASAPI2_ENDPOINT_CLASS_CAPTURE;
auto wasapi2_dev = GST_WASAPI2_DEVICE (device);
wasapi2_dev->factory_name = "wasapi2src";
wasapi2_dev->device_class = GST_WASAPI2_ENDPOINT_CLASS_CAPTURE;
wasapi2_dev->is_default = entry->is_default;
devices = g_list_append (devices, device);
} else {
@ -242,26 +262,28 @@ gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
"wasapi2.device.loopback", G_TYPE_BOOLEAN, TRUE, nullptr);
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
"device", entry->device_id,
"display-name", entry->device_name, "caps", entry->caps,
"device", entry->device_id.c_str (),
"display-name", entry->device_name.c_str (), "caps", entry->caps,
"device-class", "Audio/Sink", "properties", props, nullptr);
gst_structure_free (props);
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2sink";
GST_WASAPI2_DEVICE (device)->device_class =
GST_WASAPI2_ENDPOINT_CLASS_RENDER;
auto wasapi2_dev = GST_WASAPI2_DEVICE (device);
wasapi2_dev->factory_name = "wasapi2sink";
wasapi2_dev->device_class = GST_WASAPI2_ENDPOINT_CLASS_RENDER;
wasapi2_dev->is_default = entry->is_default;
devices = g_list_append (devices, device);
device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
"device", entry->device_id,
"display-name", entry->device_name, "caps", entry->caps,
"device", entry->device_id.c_str (),
"display-name", entry->device_name.c_str (), "caps", entry->caps,
"device-class", "Audio/Source", "properties", prop_copy, nullptr);
gst_structure_free (prop_copy);
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2src";
GST_WASAPI2_DEVICE (device)->device_class =
GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE;
wasapi2_dev = GST_WASAPI2_DEVICE (device);
wasapi2_dev->factory_name = "wasapi2src";
wasapi2_dev->device_class = GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE;
wasapi2_dev->is_default = entry->is_default;
devices = g_list_append (devices, device);
}
@ -342,7 +364,7 @@ gst_wasapi2_device_provider_update_devices (GstWasapi2DeviceProvider * self)
GList *new_devices = nullptr;
GList *to_add = nullptr;
GList *to_remove = nullptr;
GList *iter;
GList *iter, *walk;
GST_OBJECT_LOCK (self);
prev_devices = g_list_copy_deep (provider->devices,
@ -371,6 +393,44 @@ gst_wasapi2_device_provider_update_devices (GstWasapi2DeviceProvider * self)
}
}
iter = to_remove;
while (iter) {
auto prev_dev = GST_WASAPI2_DEVICE (iter->data);
if (!prev_dev->is_default) {
iter = g_list_next (iter);
continue;
}
walk = to_add;
bool found = false;
while (walk) {
auto new_dev = GST_WASAPI2_DEVICE (walk->data);
if (!new_dev->is_default ||
prev_dev->device_class != new_dev->device_class) {
walk = g_list_next (walk);
continue;
}
gst_device_provider_device_changed (provider, GST_DEVICE (new_dev),
GST_DEVICE (prev_dev));
gst_object_unref (new_dev);
to_add = g_list_delete_link (to_add, walk);
found = true;
break;
}
if (found) {
gst_object_unref (prev_dev);
auto next = iter->next;
to_remove = g_list_delete_link (to_remove, iter);
iter = next;
} else {
iter = g_list_next (iter);
}
}
for (iter = to_remove; iter; iter = g_list_next (iter))
gst_device_provider_device_remove (provider, GST_DEVICE (iter->data));

View File

@ -433,10 +433,7 @@ gst_wasapi2_enumerator_activate_notification (GstWasapi2Enumerator * object,
void
gst_wasapi2_enumerator_entry_free (GstWasapi2EnumeratorEntry * entry)
{
g_free (entry->device_id);
g_free (entry->device_name);
gst_clear_caps (&entry->caps);
g_free (entry);
delete entry;
}
/* *INDENT-OFF* */
@ -462,7 +459,9 @@ static void
gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
IAudioClient * client,
GstCaps * static_caps, EDataFlow flow, gboolean is_default,
gchar * device_id, gchar * device_name, GPtrArray * device_list)
gchar * device_id, gchar * device_name,
gchar * actual_device_id, gchar * actual_device_name,
GstWasapi2DeviceProps * device_props, GPtrArray * device_list)
{
WAVEFORMATEX *mix_format = nullptr;
GstCaps *supported_caps = nullptr;
@ -471,6 +470,8 @@ gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
if (!mix_format) {
g_free (device_id);
g_free (device_name);
g_free (actual_device_id);
g_free (actual_device_name);
return;
}
@ -481,23 +482,103 @@ gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
if (!supported_caps) {
g_free (device_id);
g_free (device_name);
g_free (actual_device_id);
g_free (actual_device_name);
return;
}
auto entry = g_new0 (GstWasapi2EnumeratorEntry, 1);
auto entry = new GstWasapi2EnumeratorEntry ();
entry->device_id = device_id;
entry->device_name = device_name;
entry->caps = supported_caps;
entry->flow = flow;
entry->is_default = is_default;
if (actual_device_id)
entry->actual_device_id = actual_device_id;
if (actual_device_name)
entry->actual_device_name = actual_device_name;
if (device_props) {
entry->device_props.form_factor = device_props->form_factor;
entry->device_props.enumerator_name = device_props->enumerator_name;
}
GST_LOG_OBJECT (self, "Adding entry %s (%s), flow %d, caps %" GST_PTR_FORMAT,
device_id, device_name, flow, supported_caps);
g_free (device_id);
g_free (device_name);
g_free (actual_device_id);
g_free (actual_device_name);
g_ptr_array_add (device_list, entry);
}
static void
gst_wasapi2_enumerator_probe_props (IPropertyStore * store,
GstWasapi2DeviceProps * props)
{
PROPVARIANT var;
PropVariantInit (&var);
auto hr = store->GetValue (PKEY_AudioEndpoint_FormFactor, &var);
if (SUCCEEDED (hr) && var.vt == VT_UI4)
props->form_factor = (EndpointFormFactor) var.ulVal;
PropVariantClear (&var);
hr = store->GetValue (PKEY_Device_EnumeratorName, &var);
if (SUCCEEDED (hr) && var.vt == VT_LPWSTR) {
auto name = g_utf16_to_utf8 ((gunichar2 *) var.pwszVal,
-1, nullptr, nullptr, nullptr);
props->enumerator_name = name;
g_free (name);
}
PropVariantClear (&var);
}
static void
probe_default_device_props (GstWasapi2Enumerator * self, EDataFlow flow,
GstWasapi2DeviceProps * props, gchar ** actual_device_id,
gchar ** actual_device_name)
{
auto priv = self->priv;
ComPtr < IMMDevice > device;
ComPtr < IPropertyStore > prop;
*actual_device_id = nullptr;
*actual_device_name = nullptr;
auto hr = priv->handle->GetDefaultAudioEndpoint (flow,
eConsole, &device);
if (FAILED (hr))
return;
LPWSTR wid = nullptr;
hr = device->GetId (&wid);
if (SUCCEEDED (hr)) {
*actual_device_id = g_utf16_to_utf8 ((gunichar2 *) wid,
-1, nullptr, nullptr, nullptr);
CoTaskMemFree (wid);
}
hr = device->OpenPropertyStore (STGM_READ, &prop);
if (FAILED (hr))
return;
PROPVARIANT var;
PropVariantInit (&var);
hr = prop->GetValue (PKEY_Device_FriendlyName, &var);
if (SUCCEEDED (hr)) {
*actual_device_name = g_utf16_to_utf8 ((gunichar2 *) var.pwszVal,
-1, nullptr, nullptr, nullptr);
PropVariantClear (&var);
}
gst_wasapi2_enumerator_probe_props (prop.Get (), props);
}
static gboolean
gst_wasapi2_enumerator_enumerate_internal (EnumerateData * data)
{
@ -529,17 +610,37 @@ gst_wasapi2_enumerator_enumerate_internal (EnumerateData * data)
priv->render_activator->GetClient (&default_render_client, 10000);
if (default_capture_client) {
GstWasapi2DeviceProps props;
props.form_factor = UnknownFormFactor;
props.enumerator_name = "UNKNOWN";
gchar *actual_device_id = nullptr;
gchar *actual_device_name = nullptr;
probe_default_device_props (self, eCapture, &props, &actual_device_id,
&actual_device_name);
gst_wasapi2_enumerator_add_entry (self, default_capture_client.Get (),
scaps, eCapture, TRUE,
g_strdup (gst_wasapi2_get_default_device_id (eCapture)),
g_strdup ("Default Audio Capture Device"), data->device_list);
g_strdup ("Default Audio Capture Device"), actual_device_id,
actual_device_name, &props, data->device_list);
}
if (default_render_client) {
GstWasapi2DeviceProps props;
props.form_factor = UnknownFormFactor;
props.enumerator_name = "UNKNOWN";
gchar *actual_device_id = nullptr;
gchar *actual_device_name = nullptr;
probe_default_device_props (self, eRender, &props, &actual_device_id,
&actual_device_name);
gst_wasapi2_enumerator_add_entry (self, default_render_client.Get (),
scaps, eRender, TRUE,
g_strdup (gst_wasapi2_get_default_device_id (eRender)),
g_strdup ("Default Audio Render Device"), data->device_list);
g_strdup ("Default Audio Render Device"), actual_device_id,
actual_device_name, &props, data->device_list);
}
for (UINT i = 0; i < count; i++) {
@ -547,6 +648,10 @@ gst_wasapi2_enumerator_enumerate_internal (EnumerateData * data)
ComPtr < IMMEndpoint > endpoint;
EDataFlow flow;
GstWasapi2DeviceProps props;
props.form_factor = UnknownFormFactor;
props.enumerator_name = "UNKNOWN";
hr = collection->Item (i, &device);
if (!gst_wasapi2_result (hr))
continue;
@ -594,8 +699,10 @@ gst_wasapi2_enumerator_enumerate_internal (EnumerateData * data)
continue;
}
gst_wasapi2_enumerator_probe_props (prop.Get (), &props);
gst_wasapi2_enumerator_add_entry (self, client.Get (), scaps, flow, FALSE,
device_id, desc, data->device_list);
device_id, desc, nullptr, nullptr, &props, data->device_list);
}
gst_caps_unref (scaps);
@ -618,3 +725,31 @@ gst_wasapi2_enumerator_enumerate_devices (GstWasapi2Enumerator * object,
WaitForSingleObject (data.event, INFINITE);
}
const gchar *
gst_wasapi2_form_factor_to_string (EndpointFormFactor form_factor)
{
switch (form_factor) {
case RemoteNetworkDevice:
return "RemoteNetworkDevice";
case Speakers:
return "Speakers";
case LineLevel:
return "LineLevel";
case Microphone:
return "Microphone";
case Headset:
return "Headset";
case Handset:
return "Handset";
case UnknownDigitalPassthrough:
return "UnknownDigitalPassthrough";
case SPDIF:
return "SPDIF";
case DigitalAudioDisplayDevice:
return "DigitalAudioDisplayDevice";
case UnknownFormFactor:
default:
return "UnknownFormFactor";
}
}

View File

@ -21,6 +21,7 @@
#include <gst/gst.h>
#include "gstwasapi2util.h"
#include <string>
G_BEGIN_DECLS
@ -28,14 +29,30 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GstWasapi2Enumerator, gst_wasapi2_enumerator,
GST, WASAPI2_ENUMERATOR, GstObject);
typedef struct _GstWasapi2EnumeratorEntry
G_END_DECLS
struct GstWasapi2DeviceProps
{
gchar *device_id;
gchar *device_name;
gboolean is_default;
GstCaps *caps;
EndpointFormFactor form_factor;
std::string enumerator_name;
};
struct GstWasapi2EnumeratorEntry
{
~GstWasapi2EnumeratorEntry()
{
gst_clear_caps (&caps);
}
std::string device_id;
std::string device_name;
std::string actual_device_id;
std::string actual_device_name;
gboolean is_default = FALSE;
GstCaps *caps = nullptr;
EDataFlow flow;
} GstWasapi2EnumeratorEntry;
GstWasapi2DeviceProps device_props = { };
};
GstWasapi2Enumerator * gst_wasapi2_enumerator_new (void);
@ -47,5 +64,5 @@ void gst_wasapi2_enumerator_entry_free (GstWasapi2EnumeratorEntry * entry);
void gst_wasapi2_enumerator_enumerate_devices (GstWasapi2Enumerator * object,
GPtrArray * entry);
G_END_DECLS
const gchar * gst_wasapi2_form_factor_to_string (EndpointFormFactor form_factor);