Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crashes in MMDevAPI.dll #758

Open
larsk2009 opened this issue Oct 25, 2023 · 10 comments
Open

Crashes in MMDevAPI.dll #758

larsk2009 opened this issue Oct 25, 2023 · 10 comments

Comments

@larsk2009
Copy link

larsk2009 commented Oct 25, 2023

Hi,

I am trying to switch Audio devices when an audio device gets plugged in on Windows. I do this by checking if the current device is different from a set preferred device, and if so stopping the audio and restarting it with a different pDeviceID. This results in crashes of the program in MMDevAPI.dll because of access violations. In the callstack I see the last function is MMDevAPI.dll!CDeviceEnumerator::OnPropertyValueChanged().

Globally speaking, my code does this:

  • Initialize miniaudio and start playback using callbacks
  • Some thread checks if we need to switch audio devices,
  • If we need to switch devices, unit the current device and init the new device with new config

See some logs below:

DEBUG: Loading library: user32.dll
DEBUG: Loading symbol: GetForegroundWindow
DEBUG: Loading symbol: GetDesktopWindow
DEBUG: Loading library: advapi32.dll
DEBUG: Loading symbol: RegOpenKeyExA
DEBUG: Loading symbol: RegCloseKey
DEBUG: Loading symbol: RegQueryValueExA
DEBUG: Loading library: ole32.dll
DEBUG: Loading symbol: CoInitialize
DEBUG: Loading symbol: CoInitializeEx
DEBUG: Loading symbol: CoUninitialize
DEBUG: Loading symbol: CoCreateInstance
DEBUG: Loading symbol: CoTaskMemFree
DEBUG: Loading symbol: PropVariantClear
DEBUG: Loading symbol: StringFromGUID2
DEBUG: Attempting to initialize WASAPI backend...
DEBUG: Loading library: kernel32.dll
DEBUG: Loading symbol: VerifyVersionInfoW
DEBUG: Loading symbol: VerSetConditionMask
DEBUG: Loading library: avrt.dll
DEBUG: Loading symbol: AvSetMmThreadCharacteristicsA
DEBUG: Loading symbol: AvRevertMmThreadCharacteristics
DEBUG: System Architecture:
DEBUG:   Endian: LE
DEBUG:   SSE2:   YES
DEBUG:   AVX2:   YES
DEBUG:   NEON:   NO
Starting audio
INFO: [WASAPI]
INFO:   Microphone (2- MiniFuse 2) (Capture)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    4 -> 1
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
INFO:       Channel Map Out:        {CHANNEL_MONO}
INFO:   Speakers (2- MiniFuse 2) (Playback)
INFO:     Format:      16-bit Signed Integer -> 32-bit IEEE Floating Point
INFO:     Channels:    1 -> 4
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_MONO}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
Done starting audio
Started
DEBUG: [WASAPI] Capture Flags: 1
DEBUG: [WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=160
DEBUG: [WASAPI] Data discontinuity recovery: Buffer emptied.

=======================================unrelated logs=======================================

Starting audio
Audio already running, stopping audio
Stopped
Stopped audio
INFO: [WASAPI]
INFO:   Microphone (2- MiniFuse 2) (Capture)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    4 -> 1
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
INFO:       Channel Map Out:        {CHANNEL_MONO}
INFO:   Speakers (2- MiniFuse 2) (Playback)
INFO:     Format:      16-bit Signed Integer -> 32-bit IEEE Floating Point
INFO:     Channels:    1 -> 4
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_MONO}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
Started
Done starting audio
DEBUG: [WASAPI] Capture Flags: 1
DEBUG: [WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=160
DEBUG: [WASAPI] Data discontinuity recovery: Buffer emptied.
Starting audio
Audio already running, stopping audio
Stopped
Stopped audio
Resolving
INFO: [WASAPI]
INFO:   Microphone (2- MiniFuse 2) (Capture)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    4 -> 1
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
INFO:       Channel Map Out:        {CHANNEL_MONO}
INFO:   Speakers (2- MiniFuse 2) (Playback)
INFO:     Format:      16-bit Signed Integer -> 32-bit IEEE Floating Point
INFO:     Channels:    1 -> 4
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_MONO}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_FRONT_CENTER CHANNEL_BACK_CENTER}
Started
Done starting audio
DEBUG: [WASAPI] Capture Flags: 1
DEBUG: [WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=160
DEBUG: [WASAPI] Data discontinuity recovery: Buffer emptied.

=======================================unrelated logs=======================================

=======================================Removing USB audio thing=============================

DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
ERROR: [WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = -2004287484. Stopping device.
Stopped
Restarting audio because it has been stopped
Starting audio
INFO: [WASAPI]
INFO:   Microphone Array (Intel® Smart Sound Technology for Digital Microphones) (Capture)
INFO:     Format:      32-bit IEEE Floating Point -> 16-bit Signed Integer
INFO:     Channels:    4 -> 1
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT CHANNEL_BACK_LEFT CHANNEL_BACK_RIGHT}
INFO:       Channel Map Out:        {CHANNEL_MONO}
INFO:   Speakers (Realtek(R) Audio) (Playback)
INFO:     Format:      16-bit Signed Integer -> 32-bit IEEE Floating Point
INFO:     Channels:    1 -> 2
INFO:     Sample Rate: 16000 -> 16000
INFO:     Buffer Size: 320*4 (1280)
INFO:     Conversion:
INFO:       Pre Format Conversion:  YES
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        YES
INFO:       Resampling:             NO
INFO:       Passthrough:            NO
INFO:       Channel Map In:         {CHANNEL_MONO}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
Done starting audio
Started
DEBUG: [WASAPI] Capture Flags: 1
DEBUG: [WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=129
DEBUG: [WASAPI] Data discontinuity recovery: Buffer emptied.
DEBUG: [WASAPI] Capture Flags: 1
DEBUG: [WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=160
DEBUG: [WASAPI] Data discontinuity recovery: Buffer emptied.

=======================================Adding USB audio thing================================

DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
DEBUG: [WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.
Restarting audio because incorrect device is being used
Starting audio
Audio already running, stopping audio
Stopped
Stopped audio


3393 Segmentation fault

If I add a sleep between detecting the new device and switching to it, it will take more add/remove cycles before the crash happens, but it will still happen. It can also happen both when adding or removing the device.

I haven't tried to make a minimal reproduction to share yet as I wanted to first see if you have any ideas. If not, I can try to reproduce this issue in some files I can share.

@larsk2009
Copy link
Author

Some extra info: Even when I wait 10s between plugging the device in and switching the device in software the crash still happens sometimes. So around when the device is being stopped or restarted.

@larsk2009
Copy link
Author

larsk2009 commented Oct 25, 2023

It seems that changing the ma_device_uninit to ma_device_stop significanlty reduces the changes of this crash happening, of not prevent it entirely (EDIT: It does not seem to prevent it, just takes longer to happen. I thought that I should ma_device_uninit the device to change settings on it, so I am wondering if it is safe to not ma_device_uninit the device but just ma_device_stop and after that call ma_device_init and ma_device_start?

Also, is there a way to change the pDeviceID without using ma_device_init? Like changing it directly on the existing device after stopping it and then starting it again?

EDIT2: It doesn't actually help that much, guess I was just lucky the first couple of times. If you have any idea how to proceed please let me know.

@mackron
Copy link
Owner

mackron commented Oct 27, 2023

This is annoying. I'm at a bit of a loss with this one. Some clarification on a few things. How are you detecting that the preferred device has changed? Are you calling ma_device_uninit() the very moment you're detecting the device change? As an experiment, if you haven't already tried, I wonder what would happen if you waited a few seconds between detecting the new default device and uninitializing the device. Is that what you already tried with your 10s experiment?

Does the crash happen in direct response to ma_device_uninit()? Or is it ma_device_init() that's triggering it? I'm assuming there's no miniaudio functions in the call stack at the time of the crash?

My initial thought was that maybe WASAPI is calling OnPropertyValueChanged() one a separate thread, and that maybe that thread is still active at the time you call ma_device_uninit() which in turn will be destroying the WASAPI objects which that OnPropertyValueChanged() thread is still using. Hard to know for sure though.

@larsk2009
Copy link
Author

How are you detecting that the preferred device has changed?

I have a thread on a loop that detects the currently used device vs the preferred device. It then looks in the list of available devices to see if the preferred device is available. In that thread I do call ma_device_stop/ma_device_uninit (I have tried both). I have also tried putting a wait between the initial detecting of "We need to change device" and actually changing devices in case windows itself is doing something on connection/removal of a device, but that also doesn't seem to help.

Is that what you already tried with your 10s experiment?

Yes indeed

Does the crash happen in direct response to ma_device_uninit()? Or is it ma_device_init() that's triggering it? I'm assuming there's no miniaudio functions in the call stack at the time of the crash?

There is no miniaudio functions in the call stack that is causing the crash. I can find a couple of other threads which all seem to be waiting for a lock or semaphore. Sometimes there is a thread which is in ma_IAudioClient_Initialize (see below)

My initial thought was that maybe WASAPI is calling OnPropertyValueChanged() one a separate thread, and that maybe that thread is still active at the time you call ma_device_uninit() which in turn will be destroying the WASAPI objects which that OnPropertyValueChanged() thread is still using. Hard to know for sure though.

Testing some more, I see that the thread where my code runs is on the ma_device_init line. The last function in that call stack is ma_IAudioClient_Initialize

@larsk2009
Copy link
Author

Do you have any ideas?

@mackron
Copy link
Owner

mackron commented Nov 8, 2023

No I don't have any ideas yet and I haven't had a chance to properly look at this.

It then looks in the list of available devices to see if the preferred device is available.

How are you actually doing this though? What specific functions are you calling? Are you using miniaudio to do this?

@larsk2009
Copy link
Author

I am using miniaudio for that indeed, using the following functions:

if (ma_context_get_devices(&audioDeviceContext, &maPlaybackDevices, &playbackCount, &maCaptureDevices, &captureCount) == MA_SUCCESS)
{
  for (uint32_t i = 0; i < playbackCount; i++)
    {
      <here the playback device data is read and stored in our own struct>
    }

  <same as above put for capture devices>
}

@larsk2009
Copy link
Author

I see you mention threading issues with wasapi in #777. Might that also be causing the problems here?

@mackron
Copy link
Owner

mackron commented Nov 27, 2023

I'm not sure. It feels a bit different but hard to know. My experience with WASAPI has been that it's been very thread-hostile. I've had it where it'll crash when it does something on a different thread even though I've very clearly got valid mutual exclusion going on. I think WASAPI wants you to do absolutely every single WASAPI related operation on a single thread, even if you're using your own syncing. I have a known memory leak in the WASAPI backend when doing automatic rerouting because as soon as I uninitialize the previous device handle it'll deadlock, even though I've got it mutually excluded.

What I think I need to do is have a single worker thread and then just defer to that thread via an event queue or something and invoke all WASAPI functions from there. That'll require a very significant refactor to the WASAPI backend though, and I'm not even sure it'll fix either of these issues.

@larsk2009
Copy link
Author

Thanks for the info. I'm considering trying to disable WASAPI and using a different driver on Windows, maybe I'll have more luck with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants