Hello there,
ASG is running on a custom game engine based on SharpDX to access DirectX in a managed .NET framework environment. I just made the move from SharpDX 2.6.3 to the latest 4.2.0 version (better late than never! ^^) to be able to use XAudio2 directly on Win10 machines. That is XAudio2.8/9 instead of XAudio2.7 and thus without the dependency on the “June 2010 DirectX” redistributables.
However the initialization of XAudio2.8 is very different and acquiring the right “DeviceIDs” to be able to select a specific audio device proved quite painful. I could not find a C#/SharpDX-ready solution so following is my take on a “SoundDevicesEnumerator” class for this task.
The code is based on the SetupAPI approach described here (thanks a lot!), with additional help from pinvoke.net. It can handle both the XAudio2.7 and 2.8+ cases, and requires the SharpDX.XAudio2 (4.2.0) and PInvoke.SetupAPI nugets.
Cheers 🙂
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using SharpDX.XAudio2; using PInvoke; using static PInvoke.SetupApi; namespace LibDirectX.Sound { /// <summary> /// A sound devices enumerator for XAudio2 through SharpDX (4.2.0), XAudio2_7 (Win7) or XAudio2_8+ (Win8, Win10) /// </summary> public static class SoundDevicesEnumerator { /// <summary> /// Attempts to enumerate the audio devices /// </summary> /// <param name="xAudio2Device">the XAudio2 device, must have been created</param> /// <param name="deviceNames">the names of the found devices, or null if error</param> /// <param name="deviceIDs">the deviceID of the found devices (XAudio2_8 or above only), or null if XAudio2_7 or error</param> /// <param name="errMsg">"OK" if returned true else the error message</param> /// <returns>true if suceeded, check errMsg if false</returns> /// <remarks> /// XAudio2_7: create the mastering voice using the device index (integer). /// XAudio2_8+: create the mastering voice using the device ID = deviceIDs[device index] /// </remarks> public static bool TryEnumerateDevices(XAudio2 xAudio2Device, out string[] deviceNames, out string[] deviceIDs, out string errMsg) { deviceNames = deviceIDs = null; errMsg = "OK"; // check the XAudio2 device if (xAudio2Device == null || xAudio2Device.IsDisposed) { errMsg = "The XAudio2 device must have been created"; return false; } bool enumerationSucceeded = true; // the enumeration depends on the XAudio2 dll version if (xAudio2Device.Version == XAudio2Version.Version27) { ///////////////////////////////////////////////////////////////////////////////////////////// // XAudio2_7.dll try { int numDevices = xAudio2Device.DeviceCount; if (numDevices < 1) { throw new Exception("No device found"); } deviceNames = new string[numDevices]; for (int i = 0; i < numDevices; i++) { deviceNames[i] = xAudio2Device.GetDeviceDetails(i).DisplayName; } } catch (Exception ex) { deviceNames = null; enumerationSucceeded = false; errMsg = "Enumeration failed: " + ex.Message; } } else { ///////////////////////////////////////////////////////////////////////////////////////////// // XAudio2_8 and above SafeDeviceInfoSetHandle deviceInfoSetHandle = null; List<string> devNames = new List<string>(4); List<string> devIDs = new List<string>(4); try { // The GUID for audio renderers (Win8 and above, defined in mmdeviceapi.h) Guid GUID_DEVINTERFACE_AUDIO_RENDER = new Guid(0xe6327cad, 0xdcec, 0x4949, 0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2); // returns a handle to the set of audio renderers deviceInfoSetHandle = SetupDiGetClassDevs(GUID_DEVINTERFACE_AUDIO_RENDER, null, IntPtr.Zero, GetClassDevsFlags.DIGCF_PRESENT | GetClassDevsFlags.DIGCF_DEVICEINTERFACE); // check the handle validity if (deviceInfoSetHandle == null || deviceInfoSetHandle.IsInvalid) { throw new Exception("Can't retrieve a handle to the set of audio renderers"); } // enumerate the devices // will receive the current device interface while enumerating SP_DEVICE_INTERFACE_DATA devIntData = SP_DEVICE_INTERFACE_DATA.Create(); bool goOnEnumerating = true; int deviceIndex = 0; while (goOnEnumerating) { // get the device interface data for the device index goOnEnumerating = SetupDiEnumDeviceInterfaces(deviceInfoSetHandle, (SP_DEVINFO_DATA?)null, ref GUID_DEVINTERFACE_AUDIO_RENDER, deviceIndex, ref devIntData); if (goOnEnumerating) { unsafe { // dummy call to get the required size for the device interface detail data structure int requiredSize = 0; SetupDiGetDeviceInterfaceDetail(deviceInfoSetHandle, ref devIntData, null, 0, &requiredSize, null); // to receive the device interface detail data SP_DEVICE_INTERFACE_DETAIL_DATA* pDevIntDetailData = null; // pointer to a SP_DEVICE_INTERFACE_DETAIL_DATA struct pDevIntDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA*)Marshal.AllocHGlobal(requiredSize); // allocate the required memory pDevIntDetailData->cbSize = SP_DEVICE_INTERFACE_DETAIL_DATA.ReportableStructSize; // represents the fixed members besides the returned string // to receive the device information data SP_DEVINFO_DATA devInfoData = SP_DEVINFO_DATA.Create(); // get the interface detail (device ID) and device information data if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSetHandle, ref devIntData, pDevIntDetailData, requiredSize, null, &devInfoData)) { Marshal.FreeHGlobal((IntPtr)pDevIntDetailData); // free the allocated memory throw new Exception(Kernel32.GetLastError().GetMessage()); } devIDs.Add(SP_DEVICE_INTERFACE_DETAIL_DATA.GetDevicePath(pDevIntDetailData)); // store the device ID Marshal.FreeHGlobal((IntPtr)pDevIntDetailData); // free the allocated memory // get the device name IntPtr pDeviceNameBuf = Marshal.AllocHGlobal(2048); // allocate memory for the string if (!SetupDiGetDeviceRegistryProperty(deviceInfoSetHandle, ref devInfoData, SPDRP_FRIENDLYNAME, out uint propertyRegDataType, pDeviceNameBuf, 2048, out uint pBufSize)) { Marshal.FreeHGlobal(pDeviceNameBuf); // free the allocated memory throw new Exception(Kernel32.GetLastError().GetMessage()); } devNames.Add(Marshal.PtrToStringAuto(pDeviceNameBuf)); // store the device name Marshal.FreeHGlobal(pDeviceNameBuf); // free the allocated memory } // OK next device deviceIndex++; } else { // "ERROR_NO_MORE_ITEMS" is the normal output Win32ErrorCode errCode = Kernel32.GetLastError(); if (errCode != Win32ErrorCode.ERROR_NO_MORE_ITEMS) { throw new Exception(errCode.GetMessage()); } } } // done return the results if (deviceIndex == 0) { throw new Exception("No device found"); } deviceNames = devNames.ToArray(); deviceIDs = devIDs.ToArray(); } catch (Exception ex) { errMsg = "Enumeration failed: " + ex.Message; enumerationSucceeded = false; } finally { // release the handle deviceInfoSetHandle?.Close(); } } return enumerationSucceeded; } /// <summary> /// The code to query the device's name /// </summary> private const uint SPDRP_FRIENDLYNAME = 0x0000000C; /// <summary> /// To read device properties /// </summary> [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetupDiGetDeviceRegistryProperty( SafeDeviceInfoSetHandle deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint property, out UInt32 propertyRegDataType, IntPtr propertyBuffer, uint propertyBufferSize, out UInt32 requiredSize ); } }
Hi. Do you allow guest posts on alliancespaceguard.com ?
Hello, I’m afraid not because this blog is dedicated to the game’s development. But if you have a specific question don’t hesitate to ask me using the contact form.