wasapi2: Probe exclusive mode formats

... and report it via device provider props

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9586>
This commit is contained in:
Seungha Yang 2025-08-20 03:47:08 +09:00 committed by GStreamer Marge Bot
parent 34a009d85c
commit 370499875c
6 changed files with 262 additions and 41 deletions

View File

@ -257,6 +257,11 @@ gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
gst_structure_set (props,
"wasapi2.device.loopback", G_TYPE_BOOLEAN, FALSE, nullptr);
if (!entry->is_default && entry->exclusive_caps) {
gst_structure_set (props, "device.exclusive-caps", GST_TYPE_CAPS,
entry->exclusive_caps, nullptr);
}
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
"device", entry->device_id.c_str (),
"display-name", entry->device_name.c_str (), "caps", entry->caps,
@ -274,6 +279,11 @@ gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
gst_structure_set (prop_copy,
"wasapi2.device.loopback", G_TYPE_BOOLEAN, TRUE, nullptr);
if (!entry->is_default && entry->exclusive_caps) {
gst_structure_set (props, "device.exclusive-caps", GST_TYPE_CAPS,
entry->exclusive_caps, nullptr);
}
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
"device", entry->device_id.c_str (),
"display-name", entry->device_name.c_str (), "caps", entry->caps,

View File

@ -48,8 +48,6 @@ ensure_debug_category (void)
}
#endif
static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
static void gst_wasapi2_on_device_updated (GstWasapi2Enumerator * object);
static std::string
@ -263,13 +261,14 @@ struct GstWasapi2EnumeratorPrivate
{
device_list = g_ptr_array_new_with_free_func ((GDestroyNotify)
gst_wasapi2_enumerator_entry_free);
scaps = gst_static_caps_get (&template_caps);
exclusive_formats = g_ptr_array_new_with_free_func ((GDestroyNotify)
gst_wasapi2_free_wfx);
}
~GstWasapi2EnumeratorPrivate ()
{
g_ptr_array_unref (device_list);
gst_caps_unref (scaps);
g_ptr_array_unref (exclusive_formats);
}
ComPtr<IMMDeviceEnumerator> handle;
@ -281,7 +280,7 @@ struct GstWasapi2EnumeratorPrivate
Wasapi2ActivationHandler *render_activator = nullptr;
std::atomic<int> notify_count = { 0 };
GPtrArray *device_list;
GstCaps *scaps;
GPtrArray *exclusive_formats;
void ClearCOM ()
{
@ -556,13 +555,12 @@ struct EnumerateData
};
/* *INDENT-ON* */
static void
gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
IAudioClient * client,
GstCaps * static_caps, EDataFlow flow, gboolean is_default,
static GstWasapi2EnumeratorEntry *
gst_wasapi2_enumerator_build_entry (GstWasapi2Enumerator * self,
IAudioClient * client, EDataFlow flow, gboolean is_default,
gchar * device_id, gchar * device_name,
gchar * actual_device_id, gchar * actual_device_name,
GstWasapi2DeviceProps * device_props, GPtrArray * device_list)
GstWasapi2DeviceProps * device_props)
{
WAVEFORMATEX *mix_format = nullptr;
GstCaps *supported_caps = nullptr;
@ -573,11 +571,10 @@ gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
g_free (device_name);
g_free (actual_device_id);
g_free (actual_device_name);
return;
return nullptr;
}
gst_wasapi2_util_parse_waveformatex (mix_format,
static_caps, &supported_caps, nullptr);
gst_wasapi2_util_parse_waveformatex (mix_format, &supported_caps, nullptr);
CoTaskMemFree (mix_format);
if (!supported_caps) {
@ -585,7 +582,7 @@ gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
g_free (device_name);
g_free (actual_device_id);
g_free (actual_device_name);
return;
return nullptr;
}
auto entry = new GstWasapi2EnumeratorEntry ();
@ -612,7 +609,7 @@ gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
g_free (actual_device_id);
g_free (actual_device_name);
g_ptr_array_add (device_list, entry);
return entry;
}
static void
@ -739,12 +736,15 @@ gst_wasapi2_enumerator_execute (GstWasapi2Enumerator * self,
if (default_capture_prop)
gst_wasapi2_enumerator_probe_props (default_capture_prop.Get (), &props);
gst_wasapi2_enumerator_add_entry (self, default_capture_client.Get (),
priv->scaps, eCapture, TRUE,
auto entry = gst_wasapi2_enumerator_build_entry (self,
default_capture_client.Get (), eCapture, TRUE,
g_strdup (gst_wasapi2_get_default_device_id (eCapture)),
g_strdup ("Default Audio Capture Device"),
g_strdup (default_capture_device_id),
g_strdup (default_capture_device_name), &props, priv->device_list);
g_strdup (default_capture_device_name), &props);
if (entry)
g_ptr_array_add (priv->device_list, entry);
}
if (default_render_client) {
@ -755,12 +755,15 @@ gst_wasapi2_enumerator_execute (GstWasapi2Enumerator * self,
if (default_render_prop)
gst_wasapi2_enumerator_probe_props (default_render_prop.Get (), &props);
gst_wasapi2_enumerator_add_entry (self, default_render_client.Get (),
priv->scaps, eRender, TRUE,
auto entry = gst_wasapi2_enumerator_build_entry (self,
default_render_client.Get (), eRender, TRUE,
g_strdup (gst_wasapi2_get_default_device_id (eRender)),
g_strdup ("Default Audio Render Device"),
g_strdup (default_render_device_id),
g_strdup (default_render_device_name), &props, priv->device_list);
g_strdup (default_render_device_name), &props);
if (entry)
g_ptr_array_add (priv->device_list, entry);
}
for (UINT i = 0; i < count; i++) {
@ -829,8 +832,19 @@ gst_wasapi2_enumerator_execute (GstWasapi2Enumerator * self,
gst_wasapi2_enumerator_probe_props (prop.Get (), &props);
gst_wasapi2_enumerator_add_entry (self, client.Get (), priv->scaps, flow,
FALSE, device_id, desc, nullptr, nullptr, &props, priv->device_list);
auto entry = gst_wasapi2_enumerator_build_entry (self, client.Get (), flow,
FALSE, device_id, desc, nullptr, nullptr, &props);
if (entry) {
g_ptr_array_set_size (priv->exclusive_formats, 0);
gst_wasapi2_get_exclusive_formats (client.Get (),
prop.Get (), priv->exclusive_formats);
auto exclusive_caps =
gst_wasapi2_wfx_list_to_caps (priv->exclusive_formats);
g_ptr_array_set_size (priv->exclusive_formats, 0);
entry->exclusive_caps = exclusive_caps;
g_ptr_array_add (priv->device_list, entry);
}
}
g_free (default_capture_device_id);

View File

@ -42,6 +42,7 @@ struct GstWasapi2EnumeratorEntry
~GstWasapi2EnumeratorEntry()
{
gst_clear_caps (&caps);
gst_clear_caps (&exclusive_caps);
}
std::string device_id;
@ -50,6 +51,7 @@ struct GstWasapi2EnumeratorEntry
std::string actual_device_name;
gboolean is_default = FALSE;
GstCaps *caps = nullptr;
GstCaps *exclusive_caps = nullptr;
EDataFlow flow;
GstWasapi2DeviceProps device_props = { };
};

View File

@ -76,8 +76,6 @@
GST_DEBUG_CATEGORY_STATIC (gst_wasapi2_rbuf_debug);
#define GST_CAT_DEFAULT gst_wasapi2_rbuf_debug
static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
/* Defined for _WIN32_WINNT >= _NT_TARGET_VERSION_WIN10_RS4 */
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
@ -710,15 +708,13 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
return;
}
auto scaps = gst_static_caps_get (&template_caps);
GstCaps *old_caps = nullptr;
GstCaps *new_caps = nullptr;
gst_wasapi2_util_parse_waveformatex (desc->mix_format,
scaps, &old_caps, nullptr);
&old_caps, nullptr);
gst_wasapi2_util_parse_waveformatex (closest,
scaps, &new_caps, nullptr);
gst_caps_unref (scaps);
&new_caps, nullptr);
if (!new_caps || !old_caps) {
GST_ERROR ("Couldn't get caps from format");
@ -750,10 +746,8 @@ gst_wasapi2_device_manager_create_ctx (IMMDeviceEnumerator * enumerator,
}
}
auto scaps = gst_static_caps_get (&template_caps);
gst_wasapi2_util_parse_waveformatex (ctx->mix_format,
scaps, &ctx->caps, nullptr);
gst_caps_unref (scaps);
&ctx->caps, nullptr);
hr = E_FAIL;
/* Try IAudioClient3 if low-latency is requested */
@ -2179,10 +2173,8 @@ gst_wasapi2_rbuf_loop_thread (GstWasapi2Rbuf * self)
if (priv->configured_allow_dummy) {
priv->mix_format = gst_wasapi2_get_default_mix_format ();
auto scaps = gst_static_caps_get (&template_caps);
gst_wasapi2_util_parse_waveformatex (priv->mix_format,
scaps, &priv->caps, nullptr);
gst_caps_unref (scaps);
&priv->caps, nullptr);
priv->opened = true;
cmd->hr = S_OK;

View File

@ -29,10 +29,18 @@
#include <winternl.h>
#include <mutex>
#include <string.h>
#include <wrl.h>
#include <vector>
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
#define GST_CAT_DEFAULT gst_wasapi2_debug
static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
/* Define GUIDs instead of linking ksuser.lib */
DEFINE_GUID (GST_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
@ -412,8 +420,7 @@ gst_wasapi2_util_waveformatex_to_audio_format (WAVEFORMATEX * format)
gboolean
gst_wasapi2_util_parse_waveformatex (WAVEFORMATEX * format,
GstCaps * template_caps, GstCaps ** out_caps,
GstAudioChannelPosition ** out_positions)
GstCaps ** out_caps, GstAudioChannelPosition ** out_positions)
{
const gchar *afmt;
guint64 channel_mask;
@ -437,21 +444,24 @@ gst_wasapi2_util_parse_waveformatex (WAVEFORMATEX * format,
if (afmt == NULL)
return FALSE;
*out_caps = gst_caps_copy (template_caps);
auto caps = gst_static_caps_get (&template_caps);
caps = gst_caps_make_writable (caps);
channel_mask = gst_wasapi2_util_waveformatex_to_channel_mask (format,
out_positions);
gst_caps_set_simple (*out_caps,
gst_caps_set_simple (caps,
"format", G_TYPE_STRING, afmt,
"channels", G_TYPE_INT, format->nChannels,
"rate", G_TYPE_INT, format->nSamplesPerSec, NULL);
if (channel_mask) {
gst_caps_set_simple (*out_caps,
gst_caps_set_simple (caps,
"channel-mask", GST_TYPE_BITMASK, channel_mask, NULL);
}
*out_caps = caps;
return TRUE;
}
@ -634,3 +644,185 @@ gst_wasapi2_role_to_string (ERole role)
return "Unknown";
}
void
gst_wasapi2_free_wfx (WAVEFORMATEX * wfx)
{
if (wfx)
CoTaskMemFree (wfx);
}
void
gst_wasapi2_clear_wfx (WAVEFORMATEX ** wfx)
{
if (*wfx) {
CoTaskMemFree (*wfx);
*wfx = nullptr;
}
}
WAVEFORMATEX *
gst_wasapi2_copy_wfx (WAVEFORMATEX * src)
{
guint total_size = sizeof (WAVEFORMATEX) + src->cbSize;
auto dst = (WAVEFORMATEX *) CoTaskMemAlloc (total_size);
memcpy (dst, src, total_size);
return dst;
}
static DWORD
make_channel_mask (WORD nChannels)
{
switch (nChannels) {
case 1:
return KSAUDIO_SPEAKER_MONO;
case 2:
return KSAUDIO_SPEAKER_STEREO;
case 4:
return KSAUDIO_SPEAKER_3POINT1;
case 6:
return KSAUDIO_SPEAKER_5POINT1;
case 8:
return KSAUDIO_SPEAKER_7POINT1;
default:
return 0;
}
}
static WAVEFORMATEXTENSIBLE
make_wfx_ext (DWORD nSamplesPerSec, WORD nChannels, WORD wBitsPerSample,
WORD wValidBitsPerSample, bool is_float)
{
WAVEFORMATEXTENSIBLE w = { };
w.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
w.Format.nChannels = nChannels;
w.Format.nSamplesPerSec = nSamplesPerSec;
w.Format.wBitsPerSample = wBitsPerSample;
w.Samples.wValidBitsPerSample = wValidBitsPerSample;
w.dwChannelMask = make_channel_mask (nChannels);
w.SubFormat = is_float ? GST_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
: GST_KSDATAFORMAT_SUBTYPE_PCM;
w.Format.nBlockAlign = (wBitsPerSample / 8) * nChannels;
w.Format.nAvgBytesPerSec = w.Format.nSamplesPerSec * w.Format.nBlockAlign;
w.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
return w;
}
/* *INDENT-OFF* */
gboolean
gst_wasapi2_get_exclusive_formats (IAudioClient * client,
IPropertyStore * props, GPtrArray * list)
{
PROPVARIANT var;
PropVariantInit (&var);
WAVEFORMATEX *device_format = nullptr;
WAVEFORMATEX *closest = nullptr;
/* Prefer device format if supported */
auto hr = props->GetValue (PKEY_AudioEngine_DeviceFormat, &var);
if (gst_wasapi2_result (hr)) {
if (var.vt == VT_BLOB && var.blob.cbSize >= sizeof (WAVEFORMATEX)
&& var.blob.pBlobData) {
device_format = (WAVEFORMATEX *) CoTaskMemAlloc (var.blob.cbSize);
memcpy (device_format, var.blob.pBlobData, var.blob.cbSize);
}
PropVariantClear (&var);
}
if (device_format) {
hr = client->IsFormatSupported (AUDCLNT_SHAREMODE_EXCLUSIVE, device_format,
&closest);
if (hr == S_OK) {
g_ptr_array_add (list, device_format);
device_format = nullptr;
} else if (hr == S_FALSE && closest) {
g_ptr_array_add (list, closest);
closest = nullptr;
}
}
gst_wasapi2_clear_wfx (&device_format);
/* Checks using pre-defined format list */
struct DepthPair
{
WORD wBitsPerSample;
WORD wValidBitsPerSample;
bool is_float;
};
const DepthPair depth_pairs[] = {
{32, 32, true}, /* 32-float */
{32, 32, false}, /* 32-int */
{32, 24, false}, /* 24-in-32 */
{24, 24, false}, /* 24-packed */
{16, 16, false}, /* 16-int */
};
const DWORD rates[] = { 192000, 176400, 96000, 88200, 48000, 44100 };
const WORD chs[] = { 8, 6, 2, 1 };
for (auto r : rates) {
for (auto c : chs) {
for (auto d : depth_pairs) {
auto wfx = make_wfx_ext (r, c, d.wBitsPerSample, d.wValidBitsPerSample,
d.is_float);
hr = client->IsFormatSupported (AUDCLNT_SHAREMODE_EXCLUSIVE,
(WAVEFORMATEX *) &wfx, &closest);
if (hr == S_OK) {
g_ptr_array_add (list, gst_wasapi2_copy_wfx ((WAVEFORMATEX *) &wfx));
} else if (hr == S_FALSE && closest) {
g_ptr_array_add (list, closest);
closest = nullptr;
}
}
}
}
return TRUE;
}
GstCaps *
gst_wasapi2_wfx_list_to_caps (GPtrArray * list)
{
if (!list || list->len == 0)
return nullptr;
std::vector <GstCaps *> caps_list;
for (guint i = 0; i < list->len; i++) {
auto wfx = (WAVEFORMATEX *) g_ptr_array_index (list, i);
GstCaps *tmp;
if (gst_wasapi2_util_parse_waveformatex (wfx, &tmp, nullptr)) {
bool unique = true;
for (auto it : caps_list) {
if (gst_caps_is_equal (it, tmp)) {
unique = false;
break;
}
}
if (unique)
caps_list.push_back (tmp);
else
gst_caps_unref (tmp);
}
}
if (caps_list.empty ())
return nullptr;
auto caps = gst_caps_new_empty ();
for (auto it : caps_list)
gst_caps_append (caps, it);
return caps;
}
/* *INDENT-ON* */

View File

@ -101,7 +101,6 @@ guint64 gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * form
const gchar * gst_wasapi2_util_waveformatex_to_audio_format (WAVEFORMATEX * format);
gboolean gst_wasapi2_util_parse_waveformatex (WAVEFORMATEX * format,
GstCaps * template_caps,
GstCaps ** out_caps,
GstAudioChannelPosition ** out_positions);
@ -121,6 +120,18 @@ const gchar * gst_wasapi2_data_flow_to_string (EDataFlow flow);
const gchar * gst_wasapi2_role_to_string (ERole role);
void gst_wasapi2_free_wfx (WAVEFORMATEX * wfx);
void gst_wasapi2_clear_wfx (WAVEFORMATEX ** wfx);
WAVEFORMATEX * gst_wasapi2_copy_wfx (WAVEFORMATEX * format);
gboolean gst_wasapi2_get_exclusive_formats (IAudioClient * client,
IPropertyStore * props,
GPtrArray * list);
GstCaps * gst_wasapi2_wfx_list_to_caps (GPtrArray * list);
G_END_DECLS
#ifdef __cplusplus