Getting the name of an audio device from it’s GUID : Using the Oculus Rift selected audio device with OpenAL
By Ybalrid
- 4 minutes read - 833 wordsSo, while working on my game engine, I was curious about looking at the technical requirement for submitting an application to the Oculus Store.
One of the things required is that you need to target the audio output (and input) devices selected by the user in the Oculus app
So, how does the Oculus SDK tells you what is the selected device?
Well, take a look at the OVR_CAPI_Audio.h header in the OculusSDK (/OculusSDK/LibOVR/include/)
/// Gets the GUID of the preferred VR audio device.
///
/// \param[out] deviceOutGuid The GUID of the user's preferred VR audio device to use, which will be valid upon a successful return value, else it will be NULL.
///
/// \return Returns an ovrResult indicating success or failure. In the case of failure, use
/// ovr_GetLastErrorInfo to get more information.
///
OVR_PUBLIC_FUNCTION(ovrResult) ovr_GetAudioDeviceOutGuid(GUID* deviceOutGuid);
`GUID` is a type declared by the Win32 libraries.
As far as I know, there isn’t any way with OpenAL to select an audio device by anything but it’s name. You can enumerate them by name using an extension (that every platform has available), but that’s about it/
After some research (and frustration) looking at the core audio libraries pages on the MSDN (Microsoft’s documentation for developers), I discovered that theses `GUID` are actually used by a higher level API from the DirectX package: DirectSound.
At this point, know that you’ll need to link against dsound.lib, I’m sorry… ^^”
And, there isn’t a direct way to get the name of the device from it’s `GUID`. But, you can enumerate ALL the present devices, and, in a callback function, you can get at the same time, a pointer to the `GUID` and the name of the device. This can be done with the `DirectSoundEnumerate` function.
As most function that uses text in the Windows API, the actual name is a macro that points to either a wide-char version of the funciton, or an ASCII version. Here we’ll force it being ASCII for the sake of simplicity.
So, here’s the prototype of `DirectSoundEnumerateA` from the dsound.h header
extern HRESULT WINAPI DirectSoundEnumerateA(_In_ LPDSENUMCALLBACKA pDSEnumCallback, _In_opt_ LPVOID pContext);
Okay, so, at that point, if you never ever saw code from Microsoft’s C library, you are probably thinking “WHAT THE HELL IS GOING ON WHIT THIS?!??!”, and you have some write to feel like that, don’t worry.
The Windows library redefine every single type that way, and has a very heavy usage of pre-processor macros.
An `HRESULT` is a `long`, a `VOID` is a `void` and each time you see `LP` in the begining of a type, it means pointer.
`LPDSENUMCALLBACKA` is a typedefed function pointer, that will point to a callback funciton you’ll have to write yourself. LP means pointer, and the A at the end means ASCII.
Here’s the typedef that shows how your callback function will be defined :
typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
Since there isn’t the names of these arguments here, it’s not that much helpfull, so we have to turn to the MSDN documentation to see how it works : https://msdn.microsoft.com/en-us/library/microsoft.directx_sdk.reference.dsenumcallback(v=vs.85).aspx
So, the idea is, you define a funciton that follow this signature, you give it to DirectSoundEnumerate, and it will call it with the GUID and the name of the sound device as the 2 parameters. great… Problem, you can’t get the out of the function without some work.
Both of theses functions takes a `LPVOID` as their last argument, they are just `void*` and permit you to give an “user defined context”.
It means that you can pass the address of anything you want to it, like… I don’t know, a data structure that the function will insert the guid and name together so that we can get the correspondence. Well, you can do what ever you want.
The thing is, you need to define a new function for that, and it’s annoying. thankfully, (and this is the quite clever bit IMO) C++11 lambda “decay” as function pointers. More or less in the same way a C array of int will “decay” as an int* pointing to it.
So, you can totally do this instead of declaring a plain old C function :
DirectSoundEnumerateA([](LPGUID guid, LPCSTR descr, LPCSTR modname, LPVOID ctx) {
auto names = static_cast<stupidAudioDescriptorVect*>(ctx);
names->push_back({ descr, guid });
return TRUE; }, &descriptors);
Here, `descriptor` is a std::vector of “stupidAudioDescriptor” that has a constructor that will take the `LPCSTR` and the `LPGUID`, figure out if the `GUID` is not null, and store them side by side.
Then I just std::find_if my way in to get the one with a corresponding `GUID` and get the name. There’s probably a cleverer way of doing it by giving the GUID as context and using a lambda capture but I did not took the time to look into that, as this is just called once during my engine initialization.
And, That’s a bit convoluted, I agree, but it works.The hacked together version of this can be seen here on Annwvyn’s github repo.