mackron / miniaudio

Audio playback and capture library written in C, in a single source file.
https://miniaud.io
Other
3.98k stars 349 forks source link

Crash when changing default playback device. #764

Closed mackron closed 2 months ago

mackron commented 11 months ago

https://github.com/raysan5/raylib/issues/3489

CC @jestarray

I've been unable to replicate this. Are you able to try miniaudio's simple_playback_sine example? It's easy to compile - there's no dependencies or anything. It'll just play a sine wave, and it'll attempt to reroute audio when you try to change the default device.

Could you also show me the contents of the ma_device object at the time of the crash? There's not quite enough information for me to go on so far.

Is this 100% reproducible for you?

jestarray commented 11 months ago
onecore\com\combase\objact\objact.cxx(826)\combase.dll!00007FFFD296DC99: (caller: 00007FFFD296CFBA) ReturnHr(1) tid(3a38) 800401F0 CoInitialize has not been called.

<!DOCTYPE html>

  | Name | Value | Type -- | -- | -- | -- ◢ | pDevice | audio_music_stream.exe!0x00007ff6cc198170 {pContext=0x00007ff6cc197ea0 {audio_music_stream.exe!AudioData AUDIO} {...} ...} | ma_device *   | ▶ pContext | 0x00007ff6cc197ea0 {audio_music_stream.exe!AudioData AUDIO} {callbacks={onContextInit=0x00007ff6cbf9b460 {audio_music_stream.exe!ma_context_init__wasapi(ma_context *, const ma_context_config *, ma_backend_callbacks *)} ...} ...} | ma_context *   | type | ma_device_type_playback (1) | ma_device_type   | sampleRate | 48000 | unsigned int   | ▶ state | {value=ma_device_state_starting (3) } | ma_atomic_device_state   | onData | 0x00007ff6cbf3c010 {audio_music_stream.exe!OnSendAudioDataToDevice(ma_device *, void *, const void *, unsigned int)} | void(*)(ma_device *, void *, const void *, unsigned int)   | onNotification | 0x0000000000000000 | void(*)(const ma_device_notification *)   | onStop | 0x0000000000000000 | void(*)(ma_device *)   | pUserData | 0x0000000000000000 | void *   | startStopLock | 0x00000000000005d4 | void *   | wakeupEvent | 0x00000000000005d8 | void *   | startEvent | 0x00000000000005dc | void *   | stopEvent | 0x00000000000005e0 | void *   | thread | 0x0000000000000688 | void *   | workResult | MA_SUCCESS (0) | ma_result   | isOwnerOfContext | 0 '\0' | unsigned char   | noPreSilencedOutputBuffer | 0 '\0' | unsigned char   | noClip | 0 '\0' | unsigned char   | noDisableDenormals | 0 '\0' | unsigned char   | noFixedSizedCallback | 0 '\0' | unsigned char   | ▶ masterVolumeFactor | {value=1.00000000 } | ma_atomic_float   | ▶ duplexRB | {rb={ds={vtable=0x0000000000000000 {...} rangeBegInFrames=0 rangeEndInFrames=0 ...} rb={pBuffer=0x0000000000000000 {...} ...} ...} } | ma_duplex_rb   | ▶ resampling | {algorithm=ma_resample_algorithm_linear (0) pBackendVTable=0x0000000000000000 pBackendUserData=...} |   | ▶ playback | {pID=0x0000000000000000 id={wasapi=0x00007ff6cc1982a0 L"{0.0.0.00000000}.{6f836b77-268f-482d-838e-9f6df82af4d6}" ...} ...} |   | ▶ capture | {pID=0x0000000000000000 id={wasapi=0x00007ff6cc198838 L"" dsound=0x00007ff6cc198838 "" winmm=...} ...} |   | ▶ wasapi | {pAudioClientPlayback=0x0000000000000000 pAudioClientCapture=0x0000000000000000 pRenderClient=0x0000000000000000 ...} |   | ▶ dsound | {pPlayback=0x0000000000000000 pPlaybackPrimaryBuffer=0x0000000000000000 pPlaybackBuffer=0x0000000000000000 ...} |   | ▶ winmm | {hDevicePlayback=0x0000000000000000 hDeviceCapture=0x0000000000000000 hEventPlayback=0x0000000000000000 ...} |   | ▶ jack | {pClient=0x0000000000000000 ppPortsPlayback=0x0000000000000000 {???} ppPortsCapture=0x0000000000000000 {...} ...} |   | ▶ null_device | {deviceThread=0x0000000000000000 operationEvent=0x0000000000000000 operationCompletionEvent=0x0000000000000000 ...} |

So I could not reproduce it with simple_playback_sine example(compiled with zig cc) interestingly but I could reproduce it with raylibs audio_music_stream on master branch:

https://github.com/raysan5/raylib/blob/master/examples/audio/audio_music_stream.c

https://github.com/raysan5/raylib/blob/master/src/external/miniaudio.h

https://github.com/mackron/miniaudio/assets/34615798/27efecf4-8710-425f-88ac-15577b294158

mackron commented 11 months ago

OK, it's definitely suspicious that it works fine with simple_playback_sine. I can see at the time of the crash that pAudioClientPlayer and pRenderClient in the wasapi structure is NULL which is a concern, and most likely what's causing this crash. I just don't understand why that's the case in raylib but not simple_playback_sine.

Are you compiling raylib from source? If so, there's the commented line #define MA_DEBUG_OUTPUT in raudio.c. Would you be able to uncomment that, recompile, and the post the output? I wonder if maybe that might give us some more intelligence as to what's going on. It'll print stuff to stdout.

In src/raudio.c:

#define MINIAUDIO_IMPLEMENTATION
//#define MA_DEBUG_OUTPUT // <-- Try uncommenting this.
#include "external/miniaudio.h"
jestarray commented 11 months ago
INFO: Initializing raylib 5.0-dev
INFO: Platform backend: DESKTOP (GLFW)
INFO: Supported raylib modules:
INFO:     > rcore:..... loaded (mandatory)
INFO:     > rlgl:...... loaded (mandatory)
INFO:     > rshapes:... loaded (optional)
INFO:     > rtextures:. loaded (optional)
INFO:     > rtext:..... loaded (optional)
INFO:     > rmodels:... loaded (optional)
INFO:     > raudio:.... loaded (optional)
INFO: DISPLAY: Device initialized successfully
INFO:     > Display size: 3840 x 2160
INFO:     > Screen size:  800 x 450
INFO:     > Render size:  800 x 450
INFO:     > Viewport offsets: 0, 0
INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 285
INFO: GL: OpenGL device information:
INFO:     > Vendor:   ATI Technologies Inc.
INFO:     > Renderer: Radeon RX 580 Series
INFO:     > Version:  3.3.0 Core Profile Context 23.9.2.230827
INFO:     > GLSL:     4.60
INFO: GL: VAO extension detected, VAO functions loaded successfully
INFO: GL: NPOT textures extension detected, full NPOT textures supported
INFO: GL: DXT compressed textures supported
INFO: GL: ETC2/EAC compressed textures supported
INFO: PLATFORM: DESKTOP (GLFW): Initialized successfully
INFO: TEXTURE: [ID 1] Texture loaded successfully (1x1 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 1] Default texture loaded successfully
INFO: SHADER: [ID 1] Vertex shader compiled successfully
INFO: SHADER: [ID 2] Fragment shader compiled successfully
INFO: SHADER: [ID 3] Program shader loaded successfully
INFO: SHADER: [ID 3] Default shader loaded successfully
INFO: RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)
INFO: RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)
INFO: RLGL: Default OpenGL state initialized successfully
INFO: TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
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
DEBUG: [WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=480)
DEBUG:     defaultPeriodInFrames=480
DEBUG:     fundamentalPeriodInFrames=32
DEBUG:     minPeriodInFrames=128
DEBUG:     maxPeriodInFrames=480
DEBUG: [WASAPI] Using IAudioClient3
DEBUG:     periodSizeInFramesOut=480
INFO: [WASAPI]
INFO:   Speakers (2- High Definition Audio Device) (Playback)
INFO:     Format:      32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
INFO:     Channels:    2 -> 2
INFO:     Sample Rate: 48000 -> 48000
INFO:     Buffer Size: 480*3 (1440)
INFO:     Conversion:
INFO:       Pre Format Conversion:  NO
INFO:       Post Format Conversion: NO
INFO:       Channel Routing:        NO
INFO:       Resampling:             NO
INFO:       Passthrough:            YES
INFO:       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
INFO:       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
INFO: AUDIO: Device initialized successfully
INFO:     > Backend:       miniaudio / WASAPI
INFO:     > Format:        32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
INFO:     > Channels:      2 -> 2
INFO:     > Sample rate:   48000 -> 48000
INFO:     > Periods size:  1440
INFO: STREAM: Initialized successfully (44100 Hz, 32 bit, Stereo)
INFO: FILEIO: [resources/country.mp3] Music file loaded successfully
INFO:     > Sample rate:   44100 Hz
INFO:     > Sample size:   32 bits
INFO:     > Channels:      2 (Stereo)
INFO:     > Total frames:  4128768
INFO: TIMER: Target time per frame: 33.333 milliseconds
DEBUG: === CHANGING DEVICE ===
ERROR: [WASAPI] Failed to create IMMDeviceEnumerator.
WARNING: [WASAPI] Reinitializing device after route change failed.

and here is the call stack from visuals studio in case it might help:

Call Stack:
audio_music_stream.exe!ma_IAudioClient_Start(ma_IAudioClient * pThis) Line 20501    C
    audio_music_stream.exe!ma_device_start__wasapi_nolock(ma_device * pDevice) Line 22971   C
    audio_music_stream.exe!ma_device_start__wasapi(ma_device * pDevice) Line 22992  C
    audio_music_stream.exe!ma_worker_thread(void * pData) Line 40868    C
    audio_music_stream.exe!ma_thread_entry_proxy(void * pData) Line 16507   C
mackron commented 11 months ago

Well this is a start:

DEBUG: === CHANGING DEVICE ===
ERROR: [WASAPI] Failed to create IMMDeviceEnumerator.
WARNING: [WASAPI] Reinitializing device after route change failed.

Now the question is why is that failing. At a minimum I'll need to review the code to better handle that - obviously when these particular errors are happening it's putting the device into an invalid state. Once that's addressed it might fix the crash, but the actual rerouting functionality still won't work. I still don't understand why you're getting those errors with raylib and not simple_playback_sine though.

jestarray commented 11 months ago

Yeah, its really strange.... It's reproducable on raylib by multiple people though.

karl-zylinski commented 9 months ago

Any updates on this? I need to ship a game using raylib soon, so if you need any help to reproduce it, then I'd gladly help in whatever way I can.

jestarray commented 8 months ago

Any updates on this? I need to ship a game using raylib soon, so if you need any help to reproduce it, then I'd gladly help in whatever way I can.

Can you reproduce it? It's unlikely your users are going to be switching audio context in the middle of games so its going to be a somewhat rare crash. Sucks but works!

karl-zylinski commented 8 months ago

@jestarray I haven't tried reproducing it outside of Raylib, with Raylib it 100% crashes inside miniaudio, even with a tiny program like this:

#include "raylib/raylib.h"

int main() {
    InitAudioDevice();

    Music music = LoadMusicStream("some_song.ogg");
    PlayMusicStream(music);

    while (true) {
        UpdateMusicStream(music);
    }
}

(above program translated from Odin to C by hand, it might contain errors)

so its going to be a somewhat rare crash

I think it depends on the user. I often start games and realize I had my output set to speakers instead of my headphones or the other way around, and alt-tab out to change it with the game running (which is how I ran into this issue)

I don't have time to look into a miniaudio-only repro right now, but maybe in a few weeks.

karl-zylinski commented 8 months ago

An update on how frequent this can happen: Some laptops switch audio device when you plug the headphones in, i.e. they don't do a hardware switch of the audio, they actually instruct windows to switch audio output. So in that case the game will crash because you plug in the headphones when you notice the audio coming out of your laptop speakers.

raysan5 commented 8 months ago

Related issue: https://github.com/raysan5/raylib/issues/3743

veins1 commented 8 months ago

Here's my attempt to figure this out: https://github.com/raysan5/raylib/issues/3743#issuecomment-1925792674 Maybe I'm wrong in my conclusion, but surrounding ma_CoCreateInstance with ma_CoInitializeEx and ma_CoUninitialize stops the crashing and correctly switches audio device.

mackron commented 8 months ago

Thanks for the heads up on the CoCreateInstance() thing. I've got plans to update the WASAPI backend so that every single WASAPI call gets deferred to a dedicated WASAPI thread. I think WASAPI is not only thread-unsafe, but downright thread hostile. Perhaps when that's done this will be fixed naturally. The thing is, that's a big change and I just haven't had the chance to get to it. But I suppose I could do the CoInitializeEx()/CoUninitialize() wrapper like you mentioned as a temporary workaround and see how the feedback with that goes.

karl-zylinski commented 7 months ago

Hi, I put in the work-around above and it does indeed make my game not crash when I switch audio device.

I wrapped that call to CoCreateInstance with CoInitializeEx()/CoUninitialize() like you said. Do you think there may be any other issues because of this?

I'm looking into this because I am shipping my game on Steam in 5 days and I would love to not have this crash then.

jkaup commented 3 months ago

What is the status of this issue - is anyone looking into this?

Please understand that this issue is causing shipped games to crash for real. People nowadays have bluetooth headphones etc which, believe me or not, they will turn on and off even when a game is launched.

karl-zylinski commented 3 months ago

@jkaup I did this workaround

I wrapped that call to CoCreateInstance with CoInitializeEx()/CoUninitialize()

It hasn't crashed when changing device since that. No idea if it could have any nasty side effects though. My game shipped with the workaround anyways!

jkaup commented 3 months ago

@jkaup I did this workaround

I wrapped that call to CoCreateInstance with CoInitializeEx()/CoUninitialize()

It hasn't crashed when changing device since that. No idea if it could have any nasty side effects though. My game shipped with the workaround anyways!

Hi Karl, thanks for your tip! I did the same workaround as well for my game now and it works. However, it would be great to have this solved in the miniaudio library itself, or at least have the workaround "officially" in the miniaudio side and not as separate fixes on each users' forks.

raysan5 commented 3 months ago

Thanks for the follow up, I can wait for a miniaudio review or just merge https://github.com/raysan5/raylib/pull/4102 in the meantime, that seems to address the issue. Please, @karl-zylinski could you review that PR? is it the same solution you used?

karl-zylinski commented 3 months ago

@raysan5 it is more or less the same solution, I put in a few comments on that PR

raysan5 commented 3 months ago

@karl-zylinski thanks for the review, I'll wait for a while for this issue to be addressed in miniaudio side to avoid library missalignments...

mackron commented 2 months ago

Very sorry for the delay with this one. This should be fixed in the dev branch. If this issue continues feel free to reopen this issue or create a new one.