microsoft / DirectXTK

The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing DirectX 11.x code in C++
https://walbourn.github.io/directxtk/
MIT License
2.55k stars 506 forks source link

xaudio2 XAUDIO2_E_DEVICE_INVALIDATED HRESULT not handled #366

Closed star69rem closed 1 year ago

star69rem commented 1 year ago

If you're using the DirectXTK AudioEngine, and another program takes over your device in exclusive mode, AudioEngine will bomb out because of this.

walbourn commented 1 year ago

What function is returning this hresult?

star69rem commented 1 year ago

Resume()

i ended up changing the soundeffect class in my project so that it doesn't have ownership of the wav data, because the problem is that if some other program takes exclusive control of the audio device and audioengine bombs out, after the other program does its thing and you go to reinitialize audioengine, you don't really want to load all the wav files over again when only the instances need to be recreated--not the actual wav bytes so i basically have to look for the lost device HRESULT, reset all my directxtk soundeffect instances, and then reinitalize audio engine using the wav data that i've stored elsewhere

walbourn commented 1 year ago

There's already support for 'silent' mode that should avoid the need to load all the wave data again.

Do you have an example of software that grabs the exclusive ownership?

walbourn commented 1 year ago

Note the bug here is that Resume assumes the device is still there. That's not necessarily true as you've noted.

star69rem commented 1 year ago

Foobar, MAME with port audio in exclusive/low latency mode, any WASAPI software using exclusive mode, etc.

I had the understanding that if you created the audio engine in silent mode that you wouldn't get any sounds. Are you saying if you start it in silent mode, then you can reset it, and then on future failures it will be able to reset itself without reloading the data?

star69rem commented 1 year ago

After more testing, this also impacts initializing as well as resume. If you already started a program that is taking exclusive control of an audio device, the instantiation bombs out. I worked around it by adding this:

else if (hr == AUDCLNT_E_DEVICE_IN_USE) { DebugTrace("ERROR: AudioEngine failed (%08X) to initialize using device [%ls] because it was already in use."); }

walbourn commented 1 year ago

Makes sense. Thanks for digging. I'll add both the explicit checking of AUDCLNT_E_DEVICE_IN_USE as well as improving the Resume method later this week.

walbourn commented 1 year ago

If you can, please try out the code in the PR for DX11 or DX12 and let me know if it works for you. I'll do some testing as well.

star69rem commented 1 year ago

Seems to work okay. This is unrelated, but I'm wondering why you're guessing 16 on the bit depth on line 1332. Couldn't you get the PKEY_AudioEngine_DeviceFormat property and get the actual wBitsPerSample? You're already accessing the properties to get the device name on Windows.

walbourn commented 1 year ago

The PKEY_AudioEngine_DeviceFormat doesn't seem entirely reliable. On my system it reports "32" which I know isn't right--I have basic on-board motherboard audio.

If I use PKEY_AudioEngine_OEMFormat I get "16".

What values are you seeing for your audio part?

walbourn commented 1 year ago

It looks like PKEY_AudioEngine_DeviceFormat just reports the information set in the Sound Settings dialog for the end-point. It was set to '32' for my speakers, but that's not at all what the DAC actually supports.

Is that information useful?

walbourn commented 1 year ago

Please open a new issue for the reporting of the bit-depth, and I'll look into it.

walbourn commented 1 year ago

For the bit-depth issue, see:

https://github.com/microsoft/DirectXTK/pull/370

https://github.com/microsoft/DirectXTK12/pull/162

star69rem commented 1 year ago

Thanks. Is it useful to get WAVEFORMATEXTENSIBLE instead of WAVEFORMATEX?

The WAVEFORMATEXTENSIBLE structure defines the format of waveform-audio data for formats having more than two channels or higher sample resolutions than allowed by WAVEFORMATEX. It can also be used to define any format that can be defined by WAVEFORMATEX.

Also, would it make sense to also get the sample rate from the waveformat returned from this property, too?

star69rem commented 1 year ago

This was what I was doing in my hacked up version:

if (SUCCEEDED(props->GetValue(PKEY_AudioEngine_DeviceFormat, &var)))
                    {
                        if (var.vt == VT_BLOB && var.blob.cbSize >= sizeof(WAVEFORMATEX))
                        {
                            const WAVEFORMATEXTENSIBLE* deviceFormatProperties = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(var.blob.pBlobData);
                            device.sampleRate = deviceFormatProperties->Format.nSamplesPerSec;
                            device.bitDepth = deviceFormatProperties->Format.wBitsPerSample;

                            PropVariantClear(&var);

                            return true;
                        }

                        PropVariantClear(&var);
                    }
walbourn commented 1 year ago

There's no difference at all between:

auto deviceFormatProperties = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(var.blob.pBlobData);
device.sampleRate = deviceFormatProperties->Format.nSamplesPerSec;
device.bitDepth = deviceFormatProperties->Format.wBitsPerSample;

and

auto deviceFormatProperties = reinterpret_cast<const WAVEFORMATEX*>(var.blob.pBlobData);
device.sampleRate = deviceFormatProperties->nSamplesPerSec;
device.bitDepth = deviceFormatProperties->wBitsPerSample;