Skip to content

Commit

Permalink
[win32] List all audio devices for Windows (#338)
Browse files Browse the repository at this point in the history
Previously, FAudio with `PLATFORM_WIN32` would only list the default audio device. This reworks the FAudio `PLATFORM_WIN32` code to enable access to every audio device on the system. In order to preserve the existing behavior (and XAudio2 compabitility), the audio devices are re-ordered so the first device is always the default one.

In addition:

* Properly populates the `DisplayName` field in `FAudioDeviceDetails`. Previously, it was set to the device GUID.
* [windows] Return error code for DefaultDeviceIndex
* Rework `FAudio_DefaultDeviceIndex` to return the Windows API error code and take the `defaultDeviceIndex` as parameter.

Test: Local build with visualboyadvance-m
  • Loading branch information
Steelskin committed May 23, 2024
1 parent 23ebe9e commit bae43ab
Showing 1 changed file with 180 additions and 33 deletions.
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 @@ -207,6 +208,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 @@ -236,7 +372,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 @@ -270,17 +405,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 @@ -411,29 +537,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 @@ -444,35 +576,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

0 comments on commit bae43ab

Please sign in to comment.