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

ma_device_uninit crash on Android 15 #913

Closed
znakeeye opened this issue Dec 8, 2024 · 28 comments
Closed

ma_device_uninit crash on Android 15 #913

znakeeye opened this issue Dec 8, 2024 · 28 comments

Comments

@znakeeye
Copy link

znakeeye commented Dec 8, 2024

Our game engine orx uses miniaudio 0.11.22+ (#12a8d4e)).

Running Pixel 9 Pro, Android 15 (compileSdk 35 and targetSdk 35), I just encountered this rare crash (release build). I think I ran my game some days ago, so this is likely related to a "resume" operation where orx tears down miniaudio. What could be the culprit here?

Parts of my logcat error:

--------- beginning of crash
pid: 27821, tid: 15378, name: Thread-9
#07 pc 0000000000e90120  /foo/arm64/mygame.so (BuildId...)
#08 pc 0000000000e2aaf4  /foo/arm64/mygame.so (ma_device_uninit+128)
#09 pc 0000000000e5d640  /foo/arm64/mygame.so (ma_engine_uninit+36)
#10 pc 0000000000e760f8  /foo/arm64/mygame.so (orxSoundSystem_MiniAudio_Exit()+108)
#11 pc 0000000000db08c8  /foo/arm64/mygame.so (orxModule_Exit+232)
#12 pc 0000000000db0934  /foo/arm64/mygame.so (orxModule_Exit+340)
#13 pc 0000000000db0934  /foo/arm64/mygame.so (orxModule_Exit+340)
#14 pc 0000000000db0934  /foo/arm64/mygame.so (orxModule_Exit+340)
#15 pc 0000000000db0934  /foo/arm64/mygame.so (orxModule_Exit+340)
#16 pc 0000000000da7b50  /foo/arm64/mygame.so (main+476)
#17 pc 0000000000e0e6f8  /foo/arm64/mygame.so (android_main+392)
#18 pc 0000000000efbb80  /foo/arm64/mygame.so
@znakeeye
Copy link
Author

znakeeye commented Dec 9, 2024

It's actually reproducible. I open my game, wait for several hours. Resume it, crash! I'll try a debug build to see if I can get some more details.

@znakeeye
Copy link
Author

Dumping the .so I got some more clues.

Please correct me if I'm wrong! The crash offset ma_device_uninit+128 corresponds to e326e0 (hex) + 128 dec = e32760. That's where we join the thread, it seems. Could it be that the thread was killed by the OS, and it crashes when trying to wake it up and wait for it?

/* Wake up the worker thread and wait for it to properly terminate. */
if (!ma_context_is_backend_asynchronous(pDevice->pContext)) {
    ma_event_signal(&pDevice->wakeupEvent);
    ma_thread_wait(&pDevice->thread); // Crash here?
}
0000000000e326e0 <ma_device_uninit>:
  e326e0: a9bd7bfd     	stp	x29, x30, [sp, #-0x30]!
  e326e4: a90157f6     	stp	x22, x21, [sp, #0x10]
  e326e8: a9024ff4     	stp	x20, x19, [sp, #0x20]
  e326ec: 910003fd     	mov	x29, sp
  e326f0: b4001100     	cbz	x0, 0xe32910 <ma_device_uninit+0x230>
  e326f4: 91004008     	add	x8, x0, #0x10
  e326f8: aa0003f3     	mov	x19, x0
  e326fc: 88dffd09     	ldar	w9, [x8]
  e32700: 34001089     	cbz	w9, 0xe32910 <ma_device_uninit+0x230>
  e32704: 889ffd1f     	stlr	wzr, [x8]
  e32708: f9400268     	ldr	x8, [x19]
  e3270c: f9402109     	ldr	x9, [x8, #0x40]
  e32710: b50000a9     	cbnz	x9, 0xe32724 <ma_device_uninit+0x44>
  e32714: f9402509     	ldr	x9, [x8, #0x48]
  e32718: b5000069     	cbnz	x9, 0xe32724 <ma_device_uninit+0x44>
  e3271c: f9402909     	ldr	x9, [x8, #0x50]
  e32720: b40001a9     	cbz	x9, 0xe32754 <ma_device_uninit+0x74>
  e32724: 91019260     	add	x0, x19, #0x64
  e32728: 9403b81a     	bl	0xf20790 <pthread_mutex_lock@plt>
  e3272c: 52800028     	mov	w8, #0x1                // =1
  e32730: 91023260     	add	x0, x19, #0x8c
  e32734: b9006268     	str	w8, [x19, #0x60]
  e32738: 9403b842     	bl	0xf20840 <pthread_cond_signal@plt>
  e3273c: 91019260     	add	x0, x19, #0x64
  e32740: 9403b818     	bl	0xf207a0 <pthread_mutex_unlock@plt>
  e32744: f940be60     	ldr	x0, [x19, #0x178]
  e32748: aa1f03e1     	mov	x1, xzr
  e3274c: 9403ace1     	bl	0xf1dad0 <pthread_join@plt>
  e32750: f9400268     	ldr	x8, [x19]
  e32754: f9401508     	ldr	x8, [x8, #0x28]
  e32758: b4000068     	cbz	x8, 0xe32764 <ma_device_uninit+0x84>
  e3275c: aa1303e0     	mov	x0, x19
  e32760: d63f0100     	blr	x8        ; This is where the crash is?
  e32764: 91051260     	add	x0, x19, #0x144
  e32768: 9403b82a     	bl	0xf20810 <pthread_cond_destroy@plt>
  e3276c: 91047260     	add	x0, x19, #0x11c
  e32770: 9403b804     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e32774: 9103a260     	add	x0, x19, #0xe8
  e32778: 9403b826     	bl	0xf20810 <pthread_cond_destroy@plt>
  e3277c: 91030260     	add	x0, x19, #0xc0
  e32780: 9403b800     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e32784: 91023260     	add	x0, x19, #0x8c
  e32788: 9403b822     	bl	0xf20810 <pthread_cond_destroy@plt>
  e3278c: 91019260     	add	x0, x19, #0x64
  e32790: 9403b7fc     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e32794: 9100e260     	add	x0, x19, #0x38
  e32798: 9403b7fa     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e3279c: f9400268     	ldr	x8, [x19]
  e327a0: f9402109     	ldr	x9, [x8, #0x40]
  e327a4: b5000229     	cbnz	x9, 0xe327e8 <ma_device_uninit+0x108>
  e327a8: f9402509     	ldr	x9, [x8, #0x48]
  e327ac: b50001e9     	cbnz	x9, 0xe327e8 <ma_device_uninit+0x108>
  e327b0: f9402908     	ldr	x8, [x8, #0x50]
  e327b4: b50001a8     	cbnz	x8, 0xe327e8 <ma_device_uninit+0x108>
  e327b8: b9400a68     	ldr	w8, [x19, #0x8]
  e327bc: 71000d1f     	cmp	w8, #0x3
  e327c0: 54000141     	b.ne	0xe327e8 <ma_device_uninit+0x108>
  e327c4: 3947d268     	ldrb	w8, [x19, #0x1f4]
  e327c8: 34000108     	cbz	w8, 0xe327e8 <ma_device_uninit+0x108>
  e327cc: f940ee68     	ldr	x8, [x19, #0x1d8]
  e327d0: f85f8100     	ldur	x0, [x8, #-0x8]
  e327d4: b40000a0     	cbz	x0, 0xe327e8 <ma_device_uninit+0x108>
  e327d8: f9410a68     	ldr	x8, [x19, #0x210]
  e327dc: b4000068     	cbz	x8, 0xe327e8 <ma_device_uninit+0x108>
  e327e0: f940fe61     	ldr	x1, [x19, #0x1f8]
  e327e4: d63f0100     	blr	x8
  e327e8: b9400a68     	ldr	w8, [x19, #0x8]
  e327ec: 51000909     	sub	w9, w8, #0x2
  e327f0: 7100093f     	cmp	w9, #0x2
  e327f4: 540000c8     	b.hi	0xe3280c <ma_device_uninit+0x12c>
  e327f8: f9400268     	ldr	x8, [x19]
  e327fc: 91304260     	add	x0, x19, #0xc10
  e32800: 91048101     	add	x1, x8, #0x120
  e32804: 9403b83b     	bl	0xf208f0 <ma_data_converter_uninit@plt>
  e32808: b9400a68     	ldr	w8, [x19, #0x8]
  e3280c: 321f0108     	orr	w8, w8, #0x2
  e32810: 71000d1f     	cmp	w8, #0x3
  e32814: 540000a1     	b.ne	0xe32828 <ma_device_uninit+0x148>
  e32818: f9400268     	ldr	x8, [x19]
  e3281c: 9119e260     	add	x0, x19, #0x678
  e32820: 91048101     	add	x1, x8, #0x120
  e32824: 9403b833     	bl	0xf208f0 <ma_data_converter_uninit@plt>
  e32828: f943e260     	ldr	x0, [x19, #0x7c0]
  e3282c: b40000c0     	cbz	x0, 0xe32844 <ma_device_uninit+0x164>
  e32830: f9400269     	ldr	x9, [x19]
  e32834: f9409d28     	ldr	x8, [x9, #0x138]
  e32838: b4000068     	cbz	x8, 0xe32844 <ma_device_uninit+0x164>
  e3283c: f9409121     	ldr	x1, [x9, #0x120]
  e32840: d63f0100     	blr	x8
  e32844: f946a660     	ldr	x0, [x19, #0xd48]
  e32848: b40000c0     	cbz	x0, 0xe32860 <ma_device_uninit+0x180>
  e3284c: f9400269     	ldr	x9, [x19]
  e32850: f9409d28     	ldr	x8, [x9, #0x138]
  e32854: b4000068     	cbz	x8, 0xe32860 <ma_device_uninit+0x180>
  e32858: f9409121     	ldr	x1, [x9, #0x120]
  e3285c: d63f0100     	blr	x8
  e32860: f943da60     	ldr	x0, [x19, #0x7b0]
  e32864: b40000c0     	cbz	x0, 0xe3287c <ma_device_uninit+0x19c>
  e32868: f9400269     	ldr	x9, [x19]
  e3286c: f9409d28     	ldr	x8, [x9, #0x138]
  e32870: b4000068     	cbz	x8, 0xe3287c <ma_device_uninit+0x19c>
  e32874: f9409121     	ldr	x1, [x9, #0x120]
  e32878: d63f0100     	blr	x8
  e3287c: 39461268     	ldrb	w8, [x19, #0x184]
  e32880: 340003a8     	cbz	w8, 0xe328f4 <ma_device_uninit+0x214>
  e32884: f9400275     	ldr	x21, [x19]
  e32888: f94006a8     	ldr	x8, [x21, #0x8]
  e3288c: f94092b4     	ldr	x20, [x21, #0x120]
  e32890: f9409eb6     	ldr	x22, [x21, #0x138]
  e32894: b4000068     	cbz	x8, 0xe328a0 <ma_device_uninit+0x1c0>
  e32898: aa1503e0     	mov	x0, x21
  e3289c: d63f0100     	blr	x8
  e328a0: 910502a0     	add	x0, x21, #0x140
  e328a4: 9403b7b7     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e328a8: 9105a2a0     	add	x0, x21, #0x168
  e328ac: 9403b7b5     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e328b0: f940d2a0     	ldr	x0, [x21, #0x1a0]
  e328b4: b40000a0     	cbz	x0, 0xe328c8 <ma_device_uninit+0x1e8>
  e328b8: f9409ea8     	ldr	x8, [x21, #0x138]
  e328bc: b4000068     	cbz	x8, 0xe328c8 <ma_device_uninit+0x1e8>
  e328c0: f94092a1     	ldr	x1, [x21, #0x120]
  e328c4: d63f0100     	blr	x8
  e328c8: f9403aa8     	ldr	x8, [x21, #0x70]
  e328cc: 9101e2a9     	add	x9, x21, #0x78
  e328d0: eb09011f     	cmp	x8, x9
  e328d4: 54000061     	b.ne	0xe328e0 <ma_device_uninit+0x200>
  e328d8: 910382a0     	add	x0, x21, #0xe0
  e328dc: 9403b7a9     	bl	0xf20780 <pthread_mutex_destroy@plt>
  e328e0: f9400260     	ldr	x0, [x19]
  e328e4: b4000080     	cbz	x0, 0xe328f4 <ma_device_uninit+0x214>
  e328e8: b4000076     	cbz	x22, 0xe328f4 <ma_device_uninit+0x214>
  e328ec: aa1403e1     	mov	x1, x20
  e328f0: d63f02c0     	blr	x22
  e328f4: aa1303e0     	mov	x0, x19
  e328f8: 2a1f03e1     	mov	w1, wzr
  e328fc: 5281d602     	mov	w2, #0xeb0              // =3760
  e32900: a9424ff4     	ldp	x20, x19, [sp, #0x20]
  e32904: a94157f6     	ldp	x22, x21, [sp, #0x10]
  e32908: a8c37bfd     	ldp	x29, x30, [sp], #0x30
  e3290c: 1403a5ed     	b	0xf1c0c0 <memset@plt>
  e32910: a9424ff4     	ldp	x20, x19, [sp, #0x20]
  e32914: a94157f6     	ldp	x22, x21, [sp, #0x10]
  e32918: a8c37bfd     	ldp	x29, x30, [sp], #0x30
  e3291c: d65f03c0     	ret

@znakeeye
Copy link
Author

Or maybe the crash is in the onDeviceUninit callback? Which seems to be set to ma_device_uninit__aaudio ... where the type is usually set to ma_device_type_playback.

@znakeeye
Copy link
Author

Looks similar to this oboe issue which appeared on Android 8.1:
google/oboe#262

@znakeeye
Copy link
Author

Indeed, this might be AAudioStream_close related? I investigated some crash dumps on my device, and found this:

#00 pc 000000000005da84  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: 32a42812c629603f9b8fb44bd931166b)
#01 pc 000000000008a278  /system/lib64/libc++.so (abort_message+256) (BuildId: 53e0091d25a788802d2d3a5324f79b527df4913f)
#02 pc 0000000000089ccc  /system/lib64/libc++.so (__cxa_pure_virtual+20) (BuildId: 53e0091d25a788802d2d3a5324f79b527df4913f)
#03 pc 00000000000396c8  /system/lib64/libaaudio_internal.so (aaudio::AudioStreamInternal::release_l()+600) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
#04 pc 00000000000471f0  /system/lib64/libaaudio_internal.so (aaudio::AudioStream::safeReleaseCloseInternal()+176) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
#05 pc 00000000000470f8  /system/lib64/libaaudio_internal.so (aaudio::AudioStream::safeReleaseClose()+184) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
#06 pc 0000000000005be8  /system/lib64/libaaudio.so (AAudioStream_close.cfi+120) (BuildId: 93220d8fdd72e9c8173dfa517c08704d)
#07 pc 0000000000e90120  /data/app/com.foo.mygame/lib/arm64/libgame.so
#08 pc 0000000000e2aaf4  /data/app/com.foo.mygame/lib/arm64/libgame.so (ma_device_uninit+128)

@mackron
Copy link
Owner

mackron commented Dec 16, 2024

Thanks. I wonder if this might be related to this this one: #833?

Unfortunately I don't have a solution for this. I tried to replicate it without success so I'm at a bit of a loss. It feels like an AAudio bug, but it's hard to know for sure, and I don't want to prematurely brush it off either just in case it might be something on the miniaudio side. I'm not sure what to do about this.

A workaround for you might be to force the OpenSL backend by either disabling the AAudio backend at compile time with MA_NO_AAUDIO, or disable it at runtime by passing in your own list of backends in ma_context_init().

@znakeeye
Copy link
Author

znakeeye commented Dec 16, 2024

I'm still trying to replicate it in Debug mode (with sample apps etc). I even tried Release mode with some additional logging, but that too made the error go away 😮‍💨

Let me list the clues I have so far.

  1. I usually have my phone set to silent. So when I first start my game it's silent.
  2. I then swipe away from the game.
  3. And wait... many, many hours.
  4. Then I open my game again, and it crashes. (I think I usually have volume > 0 this time.)
  5. Re-opening the game and it works.

The crash itself may be related to an incomplete Exit when the system presumably kills my game. Then when resumed again (I'm guessing) it performs Exit and then Init again. This sounds familiar to that other soloud issue, where a plugin is deinited immediately after being inited.

Also, I'm not able to reproduce this using any of the Developer Options that allow you to simulate low memory etc. Since I have to wait so many hours, could it be that AAudio itself is at some point being killed, causing our resumed AAudio state to be corrupt? In this issue they are able to recover from a similar crash (non-native it seems) using the ma_device_reinit__aaudio() function.

@znakeeye
Copy link
Author

So let's say we don't handle ma_device_notification_type_rerouted and keep the old pDevice pointer... Could that be the culprit?

@znakeeye
Copy link
Author

Ok, I think I found it. You seem to have a double-free here. Please correct me if I'm wrong 😃

  1. ma_device_reinit__aaudio frees the streams using ma_close_stream__aaudio which in turn calls AAudioStream_close.
  2. Then, if ma_device_post_init fails (probably rare), you call ma_device_uninit__aaudio which will free those streams again!

@mackron
Copy link
Owner

mackron commented Dec 16, 2024

Thanks for looking into that. I'll investigate that in a bit and report back.

@znakeeye
Copy link
Author

Another clue. When leaving my game for several hours, that means I will have planty of time to connect my phone to the audio system of my car (bluetooth). Thus, it's very likely that my audio device changes while my game is "paused".

@znakeeye
Copy link
Author

Some progress. The bug is indeed reproducible with the orxDemo app, in debug mode (unfortunately, no debugger attached). Crash dump below. Please note that it's still very hard to reproduce the bug...

19:29:45.357 I DisplayDeviceRepository: Display device changed: DisplayDeviceInfo{"Built-in Screen": ...
--------- beginning of crash
19:29:45.820 F libc    : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 15282 (Thread-2), pid 15242 (org.orx.demo)
19:29:45.971 W ActivityManager: pid 1664 system sent binder code 1 with flags 1 to frozen apps and got error -2147483646
19:29:45.971 D ActivityManager: Too many transaction errors, throttling freezer binder callback.
19:29:46.229 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
19:29:46.229 F DEBUG   : Build fingerprint: 'google/caiman/caiman:15/AP3A.241105.008/12485168:user/release-keys'
19:29:46.229 F DEBUG   : Revision: 'MP1.0'
19:29:46.229 F DEBUG   : ABI: 'arm64'
19:29:46.229 F DEBUG   : Timestamp: 2024-12-17 19:29:45.882631398+0100
19:29:46.229 F DEBUG   : Process uptime: 5579s
19:29:46.229 F DEBUG   : Cmdline: org.orx.demo
19:29:46.229 F DEBUG   : pid: 15242, tid: 15282, name: Thread-2  >>> org.orx.demo <<<
19:29:46.229 F DEBUG   : uid: 10363
19:29:46.229 F DEBUG   : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
19:29:46.229 F DEBUG   : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
19:29:46.229 F DEBUG   : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
19:29:46.229 F DEBUG   : Abort message: 'Pure virtual function called!'
19:29:46.229 F DEBUG   :     x0  0000000000000000  x1  0000000000003bb2  x2  0000000000000006  x3  00000071e40e5f60
19:29:46.229 F DEBUG   :     x4  0000000000000010  x5  0000000000000010  x6  0000000000000010  x7  7f7f7f7f7f7f7f7f
19:29:46.229 F DEBUG   :     x8  00000000000000f0  x9  00000074b26a53a8  x10 0000000000000001  x11 00000074b26f66f0
19:29:46.229 F DEBUG   :     x12 000000006761c319  x13 000000007fffffff  x14 00000000002ce068  x15 0000002b7c8b857c
19:29:46.229 F DEBUG   :     x16 00000074b2760fd0  x17 00000074b274a040  x18 00000070bdc18000  x19 0000000000003b8a
19:29:46.229 F DEBUG   :     x20 0000000000003bb2  x21 00000000ffffffff  x22 00000071e40e6180  x23 ffffff80ffffffc8
19:29:46.229 F DEBUG   :     x24 00000071e40e6090  x25 00000071e40e6730  x26 00000071e40e6a70  x27 00000074bb6cda80
19:29:46.229 F DEBUG   :     x28 00000000000fe000  x29 00000071e40e5fe0
19:29:46.229 F DEBUG   :     lr  00000074b26dfa58  sp  00000071e40e5f40  pc  00000074b26dfa84  pst 0000000000001000
19:29:46.229 F DEBUG   : 24 total frames
19:29:46.229 F DEBUG   : backtrace:
19:29:46.229 F DEBUG   :       #00 pc 000000000005da84  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: 32a42812c629603f9b8fb44bd931166b)
19:29:46.229 F DEBUG   :       #01 pc 000000000008a278  /system/lib64/libc++.so (abort_message+256) (BuildId: 53e0091d25a788802d2d3a5324f79b527df4913f)
19:29:46.229 F DEBUG   :       #02 pc 0000000000089ccc  /system/lib64/libc++.so (__cxa_pure_virtual+20) (BuildId: 53e0091d25a788802d2d3a5324f79b527df4913f)
19:29:46.229 F DEBUG   :       #03 pc 00000000000396c8  /system/lib64/libaaudio_internal.so (aaudio::AudioStreamInternal::release_l()+600) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
19:29:46.229 F DEBUG   :       #04 pc 00000000000471f0  /system/lib64/libaaudio_internal.so (aaudio::AudioStream::safeReleaseCloseInternal()+176) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
19:29:46.229 F DEBUG   :       #05 pc 00000000000470f8  /system/lib64/libaaudio_internal.so (aaudio::AudioStream::safeReleaseClose()+184) (BuildId: b2ede71b351fb88eaeab44cef6ac89b7)
19:29:46.229 F DEBUG   :       #06 pc 0000000000005be8  /system/lib64/libaaudio.so (AAudioStream_close.cfi+120) (BuildId: 93220d8fdd72e9c8173dfa517c08704d)
19:29:46.229 F DEBUG   :       #07 pc 0000000000340dd4  ~/arm64/liborxDemo.so
19:29:46.229 F DEBUG   :       #08 pc 0000000000341044  ~/arm64/liborxDemo.so
19:29:46.229 F DEBUG   :       #09 pc 000000000029e62c  ~/arm64/liborxDemo.so (ma_device_uninit+184)
19:29:46.229 F DEBUG   :       #10 pc 00000000002eeb04  ~/arm64/liborxDemo.so (ma_engine_uninit+56)
19:29:46.229 F DEBUG   :       #11 pc 000000000030fa14  ~/arm64/liborxDemo.so (orxSoundSystem_MiniAudio_Exit()+168)
19:29:46.229 F DEBUG   :       #12 pc 0000000000259b74  ~/arm64/liborxDemo.so (orxSoundSystem_Exit+20)
19:29:46.229 F DEBUG   :       #13 pc 000000000014b1ac  ~/arm64/liborxDemo.so (orxModule_Exit+580)
19:29:46.229 F DEBUG   :       #14 pc 000000000014b298  ~/arm64/liborxDemo.so (orxModule_Exit+816)
19:29:46.229 F DEBUG   :       #15 pc 000000000014b298  ~/arm64/liborxDemo.so (orxModule_Exit+816)
19:29:46.229 F DEBUG   :       #16 pc 000000000014b298  ~/arm64/liborxDemo.so (orxModule_Exit+816)
19:29:46.229 F DEBUG   :       #17 pc 000000000014b298  ~/arm64/liborxDemo.so (orxModule_Exit+816)
19:29:46.229 F DEBUG   :       #18 pc 0000000000149500  ~/arm64/liborxDemo.so
19:29:46.229 F DEBUG   :       #19 pc 00000000001491cc  ~/arm64/liborxDemo.so (main+76)
19:29:46.229 F DEBUG   :       #20 pc 00000000002621c0  ~/arm64/liborxDemo.so (android_main+440)
19:29:46.229 F DEBUG   :       #21 pc 0000000000413f78  ~/arm64/liborxDemo.so
19:29:46.229 F DEBUG   :       #22 pc 000000000006f718  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+200) (BuildId: 32a42812c629603f9b8fb44bd931166b)
19:29:46.229 F DEBUG   :       #23 pc 0000000000060e00  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 32a42812c629603f9b8fb44bd931166b)

@znakeeye
Copy link
Author

My latest theory is that a UI thread assert (our code) caused a __builtin_trap() to crash the game, which resulted in the Aaudio thread to get killed, and hence the crash above. So far the fix seems to have resolved this issue (still testing).

However, since we fixed our crash bug, the same repro steps sometimes cause the game to go silent. Incredibly hard to debug. So I'm wondering if you have any pointers. Are there any parts of miniaudio.h which would risk getting in a muted state? Maybe reading the volume from device X when switching to device Y?

I'm grasping at straws here 😛

@znakeeye
Copy link
Author

znakeeye commented Dec 20, 2024

Finally got a log. Error AAUDIO_ERROR_DISCONNECTED is involved.

D AAudioStream: setDisconnected setting disconnected, current disconnected: 0, current state: 4
D orxDebug: [17:03:28] [SOUND] [orxSoundSystem.c:orxSoundSystem_MiniAudio_Log(1814)] [INFO] [AAudio] ERROR CALLBACK: error=-899, AAudioStream_getState()=13
D orxDebug: 

The error is interesting indeed. But what happened on that last line? The log string sent to orx was empty. Shouldn't be possible, right?

Also, found a similar SDL issue: libsdl-org/SDL#4985

@znakeeye
Copy link
Author

I'm closing in on the issue! When the app is forever silenced as described above, the last thing that happens is this:
ma_device_reinit__aaudio -> ma_device_start__aaudio

I'm adding additional logging to figure out what happens when the device is started during re-routing. I'll keep you posted!

However, my theory is that this statement fails:

ma_aaudio_result_t resultAA = ((MA_PFN_AAudioStream_waitForStateChange)pContext->aaudio.AAudioStream_waitForStateChange)(pStream, oldState, &actualNewState, 5000000000); /* 5 second timeout. */```

@znakeeye
Copy link
Author

I think I have identified the bug! 🙌

When error AAUDIO_ERROR_DISCONNECTED occurs, the error callback schedules a re-routing. When re-routing, the device is started again. Finally, Miniaudio will wait for the state to become MA_AAUDIO_STREAM_STATE_STARTED using the AAudioStream_waitForStateChange API, with a timeout of 5 seconds. This is the key!

The AAudioStream_waitForStateChange function may crash if it is waiting on a stream which is closed from another thread. The question is which threads are involved here. In orx, we have the UI thread where we tear down miniaudio on exit. I.e. calling ma_device_uninit where it closes the stream and causes the waiting thread to crash.

Not sure which threads need synchronization here (miniaudio has a worker, right?) but I'm quite sure this is not enough:

/* Putting the device into an uninitialized state will make the worker thread return. */
ma_device__set_state(pDevice, ma_device_state_uninitialized);

The ma_wait_for_simple_state_transition__aaudio function doesn't care about device state.

You might want to have a look at Oboe solution: https://github.com/rodydavis/oboe/blob/00e03d1a2ef1ef3c4111729375a263a55a10186a/src/aaudio/AudioStreamAAudio.cpp#L381C1-L387C70

@znakeeye
Copy link
Author

I tried the algorithm in PR #916. Unfortunately, I'm still able to (quite easily) reproduce the problem of muted audio (the original crash is no more, thanks to orx/orx#115).

Steps to reproduce:

  1. Run the Android game. E.g. orx Demo
  2. Swipe away from the game.
  3. Turn on bluetooth earpods and wait until they become active.
  4. Turn off bluetooth earpods.
  5. Turn off the screen.
  6. Wait 10 minutes.
  7. Resume the game.

It's like 50% chance that audio will be muted at this point, impossible to recover without a hard reset of the game. And no trace of a crash, just silence. It's like the MA thread was stopped and never restarted. Note that (7) above usually means that onDestroy is called, resulting in ma_engine_uninit

I think the original theory about AAudioStream_waitForStateChange still holds true. But maybe we need some additional recover logic.

@znakeeye
Copy link
Author

I'm wondering if it could be as simple as ma_engine_uninit + ma_engine_init being called while audio is still delivered to some sample callback...

Not sure what to try next. It would certainly help if you could reproduce it.

@znakeeye
Copy link
Author

Grasping at straws here, but... if I call the function below, the audio is forever muted. Is that expected?

I'm trying to isolate the problem of re-initing miniaudio. I think the "~AudioStream(s#3) mPlayerBase strongCount = 1" log is interesting. It only appears after successful init, it seems.

void MiniAudio_ReInit_Bug()
{
    ma_result hResult;

    /* Waits for all pending operations */
    while(orxThread_GetTaskCount() != 0)
      ;

    ma_engine_uninit(&(sstSoundSystem.stEngine));

    ma_engine_config stEngineConfig;
    stEngineConfig = ma_engine_config_init();
    /* Here, re-init config properties as per original init logic */

    hResult = ma_engine_init(&stEngineConfig, &(sstSoundSystem.stEngine)); // MA_SUCCESS
}

Logcat after successful loading of miniaudio (with some custom logging):

D  [00:55:34] [SOUND] [DEBUG] WASAPI backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] DirectSound backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] WinMM backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] Core Audio backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] sndio backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] audio(4) backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] OSS backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] PulseAudio backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] ALSA backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] JACK backend is disabled.
D  [00:55:34] [SOUND] [DEBUG] Attempting to initialize AAudio backend...
D  [00:55:34] [SOUND] [DEBUG] Loading library: libaaudio.so
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudio_createStreamBuilder
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_delete
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setDeviceId
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setDirection
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setSharingMode
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setFormat
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setChannelCount
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setSampleRate
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setBufferCapacityInFrames
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setFramesPerDataCallback
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setDataCallback
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setErrorCallback
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setPerformanceMode
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setUsage
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setContentType
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setInputPreset
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_setAllowedCapturePolicy
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStreamBuilder_openStream
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_close
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getState
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_waitForStateChange
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getFormat
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getChannelCount
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getSampleRate
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getBufferCapacityInFrames
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getFramesPerDataCallback
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_getFramesPerBurst
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_requestStart
D  [00:55:34] [SOUND] [DEBUG] Loading symbol: AAudioStream_requestStop
D  [00:55:34] [SOUND] [DEBUG] System Architecture:
D  [00:55:34] [SOUND] [DEBUG]   Endian: LE
D  [00:55:34] [SOUND] [DEBUG]   SSE2:   NO
D  [00:55:34] [SOUND] [DEBUG]   AVX2:   NO
D  [00:55:34] [SOUND] [DEBUG]   NEON:   YES
I  AAudioStreamBuilder_openStream() called ----------------------------------------
I  rate   =      0, channels  = 0, channelMask = 0, format   = 0, sharing = SH, dir = OUTPUT
I  device =      0, sessionId = -1, perfMode = 10, callback: OFF with frames = 0
I  usage  =      0, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
I  privacy sensitive = false, opPackageName = (null), attributionTag = (null)
D  build, global mmap policy is 0
D  build, system mmap policy is 2
D  build, final mmap policy is 2
D  build, system mmap exclusive policy is 2
D  build, final mmap exclusive policy is 2
D  build() MMAP not used because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not requested.
D  PlayerBase::PlayerBase()
D  open(), request notificationFrames = 0, frameCount = 0
D  setState(s#1) from 0 to 2
I  AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#1 ----------------
D  AAudioStream_close(s#1) called ---------------
D  setState(s#1) from 2 to 11
D  setState(s#1) from 11 to 12
D  ~AudioStream(s#1) mPlayerBase strongCount = 2
D  AAudioStream_close(s#1) returned 0 ---------
I  AAudioStreamBuilder_openStream() called ----------------------------------------
I  rate   =      0, channels  = 0, channelMask = 0, format   = 0, sharing = SH, dir = INPUT
I  device =      0, sessionId = -1, perfMode = 10, callback: OFF with frames = 0
I  usage  =      0, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
I  privacy sensitive = false, opPackageName = (null), attributionTag = (null)
D  build, global mmap policy is 0
D  build, system mmap policy is 2
D  build, final mmap policy is 2
D  build, system mmap exclusive policy is 2
D  build, final mmap exclusive policy is 2
D  build() MMAP not used because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not requested.
D  PlayerBase::PlayerBase()
E  getInputForAttr permission denied: recording not allowed for AttributionSourceState{pid: 23057, uid: 10391, deviceId: 0, packageName: org.orx.demo, attributionTag: (null), token: binder:0xb4000076474a1c80, renouncedPermissions: (null), next: []}
E  createRecord_l(0): AudioFlinger could not create record track, status: -1
W  removeAudioDeviceCallback(0): removing different callback!
D  setState(s#2) from 0 to 11
D  setState(s#2) from 11 to 12
E  open(), initCheck() returned -1
D  ~AudioStream(s#2) mPlayerBase strongCount = 1
D  PlayerBase::~PlayerBase()
I  AAudioStreamBuilder_openStream() returns -896 = AAUDIO_ERROR_INTERNAL for s#0 ----------------
I  AAudioStreamBuilder_openStream() called ----------------------------------------
I  rate   =      0, channels  = 0, channelMask = 0, format   = 5, sharing = SH, dir = OUTPUT
I  device =      0, sessionId = -1, perfMode = 12, callback: ON with frames = 480
I  usage  =      0, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
I  privacy sensitive = false, opPackageName = (null), attributionTag = (null)
D  build, global mmap policy is 0
D  build, system mmap policy is 2
D  build, final mmap policy is 2
D  build, system mmap exclusive policy is 2
D  build, final mmap exclusive policy is 2
D  PlayerBase::PlayerBase()
D  configureDataInformation() original HW burst = 96, minMicros = 2000 => SW burst = 96
D  setFramesPerBurst() - mFramesPerBurst = 96 - mBurstPeriodNanos = 2000000
D  setState(s#3) from 0 to 2
D  configure() source format = 0x00000005, channels = 2, sample rate = 48000, sink format = 0x00000005, channels = 2, sample rate = 48000, useMonoBlend = 0, audioBalance = 0.000000, useVolumeRamps 0
I  AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#3 ----------------
D  [00:55:34] [SOUND] [INFO] [AAudio]
D  [00:55:34] [SOUND] [INFO]   Default Playback Device (Playback)
D  [00:55:34] [SOUND] [INFO]     Format:      32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
D  [00:55:34] [SOUND] [INFO]     Channels:    2 -> 2
D  [00:55:34] [SOUND] [INFO]     Sample Rate: 48000 -> 48000
D  [00:55:34] [SOUND] [INFO]     Buffer Size: 480*3 (1440)
D  [00:55:34] [SOUND] [INFO]     Conversion:
D  [00:55:34] [SOUND] [INFO]       Pre Format Conversion:  NO
D  [00:55:34] [SOUND] [INFO]       Post Format Conversion: NO
D  [00:55:34] [SOUND] [INFO]       Channel Routing:        NO
D  [00:55:34] [SOUND] [INFO]       Resampling:             NO
D  [00:55:34] [SOUND] [INFO]       Passthrough:            YES
D  [00:55:34] [SOUND] [INFO]       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
D  [00:55:34] [SOUND] [INFO]       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
D  [00:55:34] [SOUND] [INFO] ma_device_start__aaudio ma_device_start_stream__aaudio, deviceType=1 ...
D  AAudioStream_requestStart(s#3) called --------------
D  setState(s#3) from 2 to 3
I  Compiler allocated 5281KB to compile void android.view.ViewRootImpl.performTraversals()
D  AAudioStream_requestStart(s#3) returned 0 ---------
D  [00:55:34] [SOUND] [INFO] ma_device_start_stream__aaudio: AAudioStream_getState currentState=3 ...
D  [00:55:34] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: newState 4 != actualNewState 3
D  callbackLoop() entering >>>>>>>>>>>>>>>
D  onEventFromServer - got AAUDIO_SERVICE_EVENT_STARTED
D  setState(s#3) from 3 to 4
D  [00:55:34] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: slept for 10 ms #1...
D  [00:55:34] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: newState == actualNewState (4)
W  type=1400 audit(0.0:6514): avc:  denied  { read } for  name="/" dev="configfs" ino=13536 scontext=u:r:untrusted_app:s0:c135,c257,c512,c768 tcontext=u:object_r:configfs:s0 tclass=dir permissive=0 app=org.orx.demo
W  type=1400 audit(0.0:6515): avc:  denied  { read } for  name="/" dev="dm-12" ino=2 scontext=u:r:untrusted_app:s0:c135,c257,c512,c768 tcontext=u:object_r:rootfs:s0 tclass=dir permissive=0 app=org.orx.demo
I  [00:55:34] [LOG] Application save directory: /data/user/0/org.orx.demo/files
I  APP_CMD_GAINED_FOCUS
W  Initializing without READ_DEVICE_CONFIG permission. enabled=false, interval=1, missedFrameThreshold=3, frameTimeThreshold=64, package=org.orx.demo
D  Installing profile for org.orx.demo

Logcat after calling MiniAudio_ReInit_Bug():

D  [00:55:47] [SOUND] [INFO] ma_device_uninit: onDeviceUninit...
D  [00:55:47] [SOUND] [INFO] ma_device_uninit__aaudio: (second) ma_close_stream__aaudio
D  AAudioStream_close(s#3) called ---------------
D  release_l(): mServiceStreamHandle = 0x0000005E
D  callbackLoop() exiting, result = 480, isActive() = 1 <<<<<<<<<<<<<<
D  joinThread_l() pthread_join succeeded
D  stop(nanos = 18210571183736) max lateness = 616 micros, DSP stalled 0 times
D  setState(s#3) from 4 to 9
D  setState(s#3) from 9 to 11
D  setState(s#3) from 11 to 11
D  setState(s#3) from 11 to 12
D  ~AudioStreamInternal() 0xb4000075cd628b20 called
D  ~AudioStream(s#3) mPlayerBase strongCount = 2
D  AAudioStream_close(s#3) returned 0 ---------
D  [00:55:48] [SOUND] [INFO] ma_device_uninit: onDeviceUninit... DONE!
I  AAudioStreamBuilder_openStream() called ----------------------------------------
I  rate   =      0, channels  = 0, channelMask = 0, format   = 5, sharing = SH, dir = OUTPUT
I  device =      0, sessionId = -1, perfMode = 12, callback: ON with frames = 480
I  usage  =      0, contentType = 0, inputPreset = 0, allowedCapturePolicy = 0
I  privacy sensitive = false, opPackageName = (null), attributionTag = (null)
D  build, global mmap policy is 0
D  build, system mmap policy is 2
D  build, final mmap policy is 2
D  build, system mmap exclusive policy is 2
D  build, final mmap exclusive policy is 2
D  PlayerBase::PlayerBase()
D  configureDataInformation() original HW burst = 96, minMicros = 2000 => SW burst = 96
D  setFramesPerBurst() - mFramesPerBurst = 96 - mBurstPeriodNanos = 2000000
D  setState(s#4) from 0 to 2
D  configure() source format = 0x00000005, channels = 2, sample rate = 48000, sink format = 0x00000005, channels = 2, sample rate = 48000, useMonoBlend = 0, audioBalance = 0.000000, useVolumeRamps 0
I  AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#4 ----------------
D  [00:55:48] [SOUND] [INFO] [AAudio]
D  [00:55:48] [SOUND] [INFO]   Default Playback Device (Playback)
D  [00:55:48] [SOUND] [INFO]     Format:      32-bit IEEE Floating Point -> 32-bit IEEE Floating Point
D  [00:55:48] [SOUND] [INFO]     Channels:    2 -> 2
D  [00:55:48] [SOUND] [INFO]     Sample Rate: 48000 -> 48000
D  [00:55:48] [SOUND] [INFO]     Buffer Size: 480*3 (1440)
D  [00:55:48] [SOUND] [INFO]     Conversion:
D  [00:55:48] [SOUND] [INFO]       Pre Format Conversion:  NO
D  [00:55:48] [SOUND] [INFO]       Post Format Conversion: NO
D  [00:55:48] [SOUND] [INFO]       Channel Routing:        NO
D  [00:55:48] [SOUND] [INFO]       Resampling:             NO
D  [00:55:48] [SOUND] [INFO]       Passthrough:            YES
D  [00:55:48] [SOUND] [INFO]       Channel Map In:         {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
D  [00:55:48] [SOUND] [INFO]       Channel Map Out:        {CHANNEL_FRONT_LEFT CHANNEL_FRONT_RIGHT}
D  [00:55:48] [SOUND] [INFO] ma_device_start__aaudio ma_device_start_stream__aaudio, deviceType=1 ...
D  AAudioStream_requestStart(s#4) called --------------
D  setState(s#4) from 2 to 3
D  AAudioStream_requestStart(s#4) returned 0 ---------
D  [00:55:48] [SOUND] [INFO] ma_device_start_stream__aaudio: AAudioStream_getState currentState=3 ...
D  [00:55:48] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: newState 4 != actualNewState 3
D  callbackLoop() entering >>>>>>>>>>>>>>>
D  onEventFromServer - got AAUDIO_SERVICE_EVENT_STARTED
D  setState(s#4) from 3 to 4
D  [00:55:48] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: slept for 10 ms #1...
D  [00:55:48] [SOUND] [INFO] ma_wait_for_simple_state_transition__aaudio: newState == actualNewState (4)

@znakeeye
Copy link
Author

Calling my MiniAudio_ReInit_Bug() above does nothing if called when my app starts. However, if called at any other point (e.g. during game play), it silently fails to re-initialize AAudio and I never hear any sound again...

The logs are identical.

@znakeeye
Copy link
Author

znakeeye commented Dec 27, 2024

So there is a difference. After re-init, no samples are read. We end up here:

pFirst = ma_node_input_bus_first(pInputBus);
if (pFirst == NULL) {
    return MA_SUCCESS;  /* No attachments. Read nothing. */
}

When calling ma_engine_uninit all nodes are detached. Re-initing with ma_engine_init will not re-attach them. That's why no samples are read. Is this a bug?

I'm seeing this log when audio disappears:

restartIfDisabled(7030): releaseBuffer() track 0xb40000750d6240d0 disabled due to previous underrun, restarting

https://android.googlesource.com/platform/frameworks/av/+/master/media/libaudioclient/AudioTrack.cpp#2320

@znakeeye
Copy link
Author

So I tried OpenSL instead of AAudio, and I got the same problem. It's easily reproducible in my app.

  1. Put a breakpoint in ma_sound_set_volume. (Any UI thread called function will probably do, but this is what I tested.)
  2. Toggle audio device. E.g. enable and disable BlueTooth earpods.
  3. Now set the volume for a sound to play, and continue debugging (from that breakpoint).
  4. Repeat 3 a few times. Audio will soon disappear.

And you will indeed see this in the logcat:

restartIfDisabled(7071): releaseBuffer() track 0xb40000750d625420 disabled due to previous underrun, restarting

@znakeeye
Copy link
Author

znakeeye commented Dec 28, 2024

From what I can tell, as soon as you get restartIfDisabled in logcat, all callbacks are dead. No audio is processed etc. The pStreamPlayback stream reports status == 4 (started).

It certainly looks like the underlying AudioStreamLegacy implementation is broken. Or maybe we somehow make it unhappy.

@znakeeye
Copy link
Author

This line is part of the problem! First of all, I don't understand the logic. The condition is always true... can't be right?

if (!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30)

Analysis
When re-routing on a phone with AAUDIO_POLICY_AUTO (probably the most common value), the new device seems to switch to legacy path (no longer low latency mode). Then, when you see the restartIfDisabled log, it's game over. No sound.

I figured it makes no sense that my device switches to legacy path just because I attach and detach my earplugs, so I tried enforcing AAUDIO_POLICY_ALWAYS using AAudio_setMMapPolicy(). Sure enough, that changes the behavior. Now rerouting always fails at this line:

result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture);

Error is AAUDIO_ERROR_OUT_OF_RANGE due to the buffer size logic in AudioStreamInternal.cpp(295).

The fix
Disabling the workaround fixes the problem (verified on Android 35). Also, I don't see any logs indicating the legacy path (which I observed for both AAudio and OpenSL).

So the question is if we need this workaround at all, or if we should remove it altogether. If you want to keep it, I suggest we change the logic to this:

#ifdef MA_AAUDIO_ENABLE_COMPATIBILITY_WORKAROUNDS
if (ma_android_sdk_version() == 31)
{
   // ...
}
#endif

I suggest targeting Api level 31 since I have seen this audio issue for at least two years now, so the workaround is probably not needed for e.g. 33 and 34... What is your take @mackron?

Feature request
Finally, I think that there should be a ma_device_notification_type_error notification which should be sent when device initialization fails in re-route:

result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture);
if (result != MA_SUCCESS) {
  // Notify "ma_device_notification_type_error" so that consumer gets to know about this critical error.
  // MA will never recover! Consumer must explicitly re-initialize MA when ready (e.g. some retry logic).
  ma_device__on_notification_error(pDevice);
  ma_device__set_state(pDevice, ma_device_state_stopped);
  return result;
}

@znakeeye
Copy link
Author

znakeeye commented Jan 1, 2025

PR #920 fixes at least two issues in this problem area, where audio usually gets muted. However, I did find the crash too.

ma_device_uninit will crash if the streams were closed (NULL) and never re-initialized during rerouting. Certainly an edge case, but it happens fairly often. Will update the PR with a proper fix.

It crashes here due to pStream == NULL:

return ma_result_from_aaudio(((MA_PFN_AAudioStream_close)pContext->aaudio.AAudioStream_close)(pStream));

@mackron
Copy link
Owner

mackron commented Jan 2, 2025

Thanks for all your work on investigating this. I haven't had a chance to look at your PR's just yet, but on that compatibility workaround stuff, are you saying that when AAudioStreamBuilder_setBufferCapacityInFrames/setFramesPerDataCallback are not called, the muting issue goes away? That branch will be evaluating to true because your ma_android_sdk_version() will always be returning >30. The idea behind that logic was that newer versions of Android would have fixed any issues related to those functions and would therefore not need any compatibility workarounds... 😒

I haven't put much thought into it yet, but I wonder if maybe the AAudioStreamBuilder_setBufferCapacityInFrames/setFramesPerDataCallback branch could be opt-in with a config option in ma_device_config? Will consider some options. It just doesn't feel right having those functions there and then not using them.

A quick clarification, is the crashing issue still a thing? I saw your comment about this fixing the issue: orx/orx#115, but just wanted to double check that there was no underlying miniaudio issue causing that crash (assuming the muting issue is a separate thing).

@znakeeye
Copy link
Author

znakeeye commented Jan 4, 2025

I encountered four bugs in total, of which one was indeed fixed by orx/orx#115. The other three are miniaudio bugs which are all fixed in #920.

  1. ma_device_init__aaudio may fail due to workaround. Fixed by removing workaround.
  2. ma_device_start__aaudio may fail if device just got disconnected. Fixed using retry logic.
  3. ma_close_stream__aaudio may fail if stream was already closed. The retry logic likely increased the chance for this to occur. Fixed using NULL pointer check.

With all these changes, I haven't seen any crashes or "mutes" 👍 A few days of testing now.

As for the workaround that I removed, it's related to AudioStreamInternal.cpp#295 where the set framesPerCallback value is somehow altered and disliked by AAudio internals:

AudioStrea...nal_Client org.orx.demo W  configureDataInformation - framesPerCallback too big = 480, capacity = 480
AudioStrea...nal_Client org.orx.demo D  release_l(): mServiceStreamHandle = 0x0000033A
AAudioStream            org.orx.demo D  setState(s#4) from 0 to 11
AAudioStream            org.orx.demo D  setState(s#4) from 11 to 11
AAudioStream            org.orx.demo D  setState(s#4) from 11 to 12

I remember it was hard to analyze, but with aformentioned retry logic I ended up decreasing the buffer (until nothing was left). My guess is that AAudio divided it by 2 after each run. So I removed it, and then everything worked. If you pay attention to line 340, you'll see that something happened in Android Q (API 29):

// Default buffer size to match Q
setBufferSize(mBufferCapacityInFrames / 2);

@mackron
Copy link
Owner

mackron commented Jan 5, 2025

#920 has been merged into the dev branch. I think this issue can be closed now, but feel free to reopen if necessary.

@mackron mackron closed this as completed Jan 5, 2025
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

Successfully merging a pull request may close this issue.

2 participants