My application uses XAudio2 to play audio. When it calls CreateMasteringVoice
it passes NULL
to the szDeviceId
parameter, which according to this documentation page does the following:
If you specified NULL or szDeviceId parameter to IXAudio2::CreateMasteringVoice, then the system uses a Virtual Audio Client to represent the audio endpoint. In this case, if the underlying WASAPI rendering device becomes unavailable, the system automatically selects a new audio rendering device for rendering, audio processing continues, and OnCriticalError is not raised.
However, I have discovered that if all of the audio devices are removed or disabled, then OnCriticalError
is still called, at which point, if I ever want audio to work in my application again, it needs to call CreateMasteringVoice
again once there is at least one audio device plugged in and enabled.
So my question is, how does my application tell when it should recreate the mastering voice? (I.E., when there is at least one functioning audio device.) Is there any better way other than repeatedly attempt to recreate the mastering voice until it succeeds?
Note that I can't check the result of GetDeviceCount
because that's been removed as of XAudio2 2.8.
The XAudio 2.8 virtual voice migration in Windows 10 makes it less common, but you still need to handle OnCriticalError
scenarios. Typically you would try to reset the voice whenever a new audio device has been added to the system.
In Win32 desktop apps:
#include <Dbt.h>
HDEVNOTIFY g_hNewAudio = nullptr;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
case WM_CREATE:
if (!g_hNewAudio)
{
// Ask for notification of new audio devices
DEV_BROADCAST_DEVICEINTERFACE filter = { 0 };
filter.dbcc_size = sizeof(filter);
filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
filter.dbcc_classguid = KSCATEGORY_AUDIO;
g_hNewAudio = RegisterDeviceNotification(hWnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE);
}
break;
case WM_CLOSE:
if (g_hNewAudio)
{
UnregisterDeviceNotification(g_hNewAudio);
g_hNewAudio = nullptr;
}
DestroyWindow(hWnd);
break;
case WM_DEVICECHANGE:
switch (wParam)
{
case DBT_DEVICEARRIVAL:
{
auto pDev = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam);
if (pDev)
{
if (pDev->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
auto pInter = reinterpret_cast<const PDEV_BROADCAST_DEVICEINTERFACE>(pDev);
if (pInter->dbcc_classguid == KSCATEGORY_AUDIO)
{
if (g_game)
g_game->NewAudioDevice();
}
}
}
}
break;
case DBT_DEVICEREMOVECOMPLETE:
{
auto pDev = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam);
if (pDev)
{
if (pDev->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
auto pInter = reinterpret_cast<const PDEV_BROADCAST_DEVICEINTERFACE>(pDev);
if (pInter->dbcc_classguid == KSCATEGORY_AUDIO)
{
if (g_game)
g_game->NewAudioDevice();
}
}
}
}
break;
}
return 0;
In UWP apps, you use the DeviceWatcher
:
Windows::Devices::Enumeration::DeviceWatcher^ m_audioWatcher;
virtual void Initialize(CoreApplicationView^ applicationView)
{
m_audioWatcher = DeviceInformation::CreateWatcher(DeviceClass::AudioRender);
m_audioWatcher->Added += ref new TypedEventHandler<DeviceWatcher^, DeviceInformation^>(this, &ViewProvider::OnAudioDeviceAdded);
m_audioWatcher->Updated += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>(this, &ViewProvider::OnAudioDeviceUpdated);
m_audioWatcher->Start();
}
void OnAudioDeviceAdded(Windows::Devices::Enumeration::DeviceWatcher^ sender, Windows::Devices::Enumeration::DeviceInformation^ args)
{
m_game->NewAudioDevice();
}
void OnAudioDeviceUpdated(Windows::Devices::Enumeration::DeviceWatcher^ sender, Windows::Devices::Enumeration::DeviceInformationUpdate^ args)
{
m_game->NewAudioDevice();
}