Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[windows] List all audio devices for Windows #338

Merged
merged 2 commits into from
May 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 180 additions & 33 deletions src/FAudio_platform_win32.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <initguid.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <devpkey.h>

DEFINE_GUID(CLSID_CWMADecMediaObject, 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a);

Expand Down Expand Up @@ -202,6 +203,141 @@ static DWORD WINAPI FAudio_AudioClientThread(void *user)
return 0;
}

/* Sets `defaultDeviceIndex` to the default audio device index in
* `deviceCollection`.
* On failure, `defaultDeviceIndex` is not modified and the latest error is
* returned. */
static HRESULT FAudio_DefaultDeviceIndex(
IMMDeviceCollection *deviceCollection,
uint32_t* defaultDeviceIndex
) {
IMMDevice *device;
HRESULT hr;
uint32_t i, count;
WCHAR *default_guid;
WCHAR *device_guid;

/* Open the default device and get its GUID. */
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
device_enumerator,
eRender,
eConsole,
&device
);
if (FAILED(hr))
{
return hr;
}
hr = IMMDevice_GetId(device, &default_guid);
if (FAILED(hr))
{
IMMDevice_Release(device);
return hr;
}

/* Free the default device. */
IMMDevice_Release(device);

hr = IMMDeviceCollection_GetCount(deviceCollection, &count);
if (FAILED(hr))
{
CoTaskMemFree(default_guid);
return hr;
}

for (i = 0; i < count; i += 1)
{
/* Open the device and get its GUID. */
hr = IMMDeviceCollection_Item(deviceCollection, i, &device);
if (FAILED(hr)) {
CoTaskMemFree(default_guid);
return hr;
}
hr = IMMDevice_GetId(device, &device_guid);
if (FAILED(hr))
{
CoTaskMemFree(default_guid);
IMMDevice_Release(device);
return hr;
}

if (lstrcmpW(default_guid, device_guid) == 0)
{
/* Device found. */
CoTaskMemFree(default_guid);
CoTaskMemFree(device_guid);
IMMDevice_Release(device);
*defaultDeviceIndex = i;
return S_OK;
}

CoTaskMemFree(device_guid);
IMMDevice_Release(device);
}

/* This should probably never happen. Just in case, set
* `defaultDeviceIndex` to 0 and return S_OK. */
CoTaskMemFree(default_guid);
*defaultDeviceIndex = 0;
return S_OK;
}

/* Open `device`, corresponding to `deviceIndex`. `deviceIndex` 0 always
* corresponds to the default device. XAudio reorders the devices so that the
* default device is always at index 0, so we mimick this behavior here by
* swapping the devices at indexes 0 and `defaultDeviceIndex`.
*/
static HRESULT FAudio_OpenDevice(uint32_t deviceIndex, IMMDevice **device)
{
IMMDeviceCollection *deviceCollection;
HRESULT hr;
uint32_t defaultDeviceIndex;
uint32_t actualIndex;

*device = NULL;

hr = IMMDeviceEnumerator_EnumAudioEndpoints(
device_enumerator,
eRender,
DEVICE_STATE_ACTIVE,
&deviceCollection
);
if (FAILED(hr))
{
return hr;
}

/* Get the default device index. */
hr = FAudio_DefaultDeviceIndex(deviceCollection, &defaultDeviceIndex);
if (FAILED(hr))
{
IMMDeviceCollection_Release(deviceCollection);
return hr;
}

if (deviceIndex == 0) {
/* Default device. */
actualIndex = defaultDeviceIndex;
} else if (deviceIndex == defaultDeviceIndex) {
/* Open the device at index 0 instead of the "correct" one. */
actualIndex = 0;
} else {
/* Otherwise, just open the device. */
actualIndex = deviceIndex;

}
hr = IMMDeviceCollection_Item(deviceCollection, actualIndex, device);
if (FAILED(hr))
{
IMMDeviceCollection_Release(deviceCollection);
return hr;
}

IMMDeviceCollection_Release(deviceCollection);

return hr;
}

void FAudio_PlatformInit(
FAudio *audio,
uint32_t flags,
Expand Down Expand Up @@ -231,7 +367,6 @@ void FAudio_PlatformInit(
FAudio_PlatformAddRef();

*platformDevice = NULL;
if (deviceIndex > 0) return;

args = FAudio_malloc(sizeof(*args));
FAudio_assert(!!args && "Failed to allocate FAudio thread args!");
Expand Down Expand Up @@ -265,17 +400,8 @@ void FAudio_PlatformInit(
data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!");

hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
device_enumerator,
eRender,
eConsole,
&device
);
if (hr == E_NOTFOUND) {
FAudio_PlatformRelease();
return FAUDIO_E_INVALID_CALL;
}
FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!");
hr = FAudio_OpenDevice(deviceIndex, &device);
FAudio_assert(!FAILED(hr) && "Failed to get audio device!");

hr = IMMDevice_Activate(
device,
Expand Down Expand Up @@ -406,29 +532,35 @@ void FAudio_PlatformRelease()

uint32_t FAudio_PlatformGetDeviceCount(void)
{
IMMDevice *device;
IMMDeviceCollection *device_collection;
uint32_t count;
HRESULT hr;

FAudio_PlatformAddRef();
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(

hr = IMMDeviceEnumerator_EnumAudioEndpoints(
device_enumerator,
eRender,
eConsole,
&device
DEVICE_STATE_ACTIVE,
&device_collection
);
if (FAILED(hr)) {
FAudio_PlatformRelease();
return 0;
}

if (hr == E_NOTFOUND) {
hr = IMMDeviceCollection_GetCount(device_collection, &count);
if (FAILED(hr)) {
IMMDeviceCollection_Release(device_collection);
FAudio_PlatformRelease();
return 0;
}

FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!");
IMMDeviceCollection_Release(device_collection);

IMMDevice_Release(device);
FAudio_PlatformRelease();

return 1;
return count;
}

uint32_t FAudio_PlatformGetDeviceDetails(
Expand All @@ -439,35 +571,50 @@ uint32_t FAudio_PlatformGetDeviceDetails(
WAVEFORMATEXTENSIBLE *ext;
IAudioClient *client;
IMMDevice *device;
IPropertyStore* properties;
PROPVARIANT deviceName;
uint32_t count = 0;
uint32_t ret = 0;
HRESULT hr;
WCHAR *str;
GUID sub;

FAudio_memset(details, 0, sizeof(FAudioDeviceDetails));
if (index > 0) return FAUDIO_E_INVALID_CALL;

FAudio_PlatformAddRef();

hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
device_enumerator,
eRender,
eConsole,
&device
);
if (hr == E_NOTFOUND) {
count = FAudio_PlatformGetDeviceCount();
if (index >= count)
{
FAudio_PlatformRelease();
return FAUDIO_E_INVALID_CALL;
}
FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!");

details->Role = FAudioGlobalDefaultDevice;
hr = FAudio_OpenDevice(index, &device);
FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint!");

if (index == 0)
{
details->Role = FAudioGlobalDefaultDevice;
}
else
{
details->Role = FAudioNotDefaultDevice;
}

/* Set the Device Display Name */
hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &properties);
FAudio_assert(!FAILED(hr) && "Failed to open device property store!");
hr = IPropertyStore_GetValue(properties, (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &deviceName);
FAudio_assert(!FAILED(hr) && "Failed to get audio device friendly name!");
lstrcpynW((LPWSTR)details->DisplayName, deviceName.pwszVal, ARRAYSIZE(details->DisplayName) - 1);
PropVariantClear(&deviceName);
IPropertyStore_Release(properties);

/* Set the Device ID */
hr = IMMDevice_GetId(device, &str);
FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint id!");

lstrcpynW(details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1);
lstrcpynW(details->DisplayName, str, ARRAYSIZE(details->DisplayName) - 1);
lstrcpynW((LPWSTR)details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1);
CoTaskMemFree(str);

hr = IMMDevice_Activate(
Expand Down
Loading