DirectAudio v1.2.1 - recover from a dead AAudio data callback
Fixes permanent audio loss — the game keeps running, but sound never returns until you relaunch it.
Reported by JT against GameNative, reproduced on Bannerlator with v1.2, so it is driver-side, not app-specific.
The trigger is rapid background/foreground of the app — not pause/resume. The in-drawer Pause/Resume does not reproduce it.
What was happening
Backgrounding freezes the guest, so the in-process AAudio stream starves. AudioTrack disables itself after the underruns and auto-restarts, but the data callback never resumes — and this raises no error, so the driver's only stream-rebuild path never ran. It kept reporting playing=1 while nothing reached the speaker.
Fixed
- Stall watchdog — if the AAudio callback goes silent for 1 s with audio playing, the stream is rebuilt. A disabled AAudio stream can't be restarted, so recreate is the only recovery.
- Liveness counter —
cb_countwas only incremented inside aTRACE_ON()test, so with tracing off a dead callback looked identical to a live one. - Adaptive buffer — no longer treats a frozen guest as timing pressure. It was inflating the buffer permanently (192 → 5568 frames, 4 ms → 116 ms).
- Rebuilds are now logged in the release build —
DirectAudio: reopen: <reason>. A few lines per session, no diagnostics build needed.
Verified on device (DiRT 3)
| before | after | |
|---|---|---|
| audio after bg/fg abuse | lost permanently | no dropout at all |
| buffer | 192 → 5568 fr, never shrinks | back to 192 fr each rebuild |
| recovery | relaunch required | 5 rebuilds, none audible |
| 5 min normal play | — | 0 false rebuilds, buffer flat at 8 ms |
Recovery is inaudible because the new stream is started before the old one is destroyed — a ~77 ms swap the guest's ring buffer covers.
Downloads
directaudio-v1.2.1-sdk28/35.zip— release build. Use this one.directaudio-v1.2.1-diagnostics-sdk28/35.zip— same fix plus 23 logcat probes, for debugging a specific title.
Each contains winedirectaudio.drv and winedirectaudio.so for lib/wine/aarch64-windows/ and lib/wine/aarch64-unix/ of a Proton 11.0 layer.
Includes everything from v1.2 (the mmdevapi MIDI thread that spun a CPU core all session). Wine 11 only — the vtable differs on Wine 10, so one build cannot span Proton 10 and 11.
Tested on one device (Adreno 750, Android 14). Mali untested.
Defaults in this release
With no host configuration, v1.2.1 runs:
| v1.2.1 default | total latency | |
|---|---|---|
| buffer | 62.5 ms (3000 frames) | 83 ms |
| growth ceiling | 250 ms (device capacity) | |
| performance mode | LOW_LATENCY |
|
| adaptive growth | on | |
| device period | 10 ms (min 5 ms) | |
| sharing mode | SHARED |
|
| callback watchdog | on, 1 s |
Latency figures are totals — the driver's buffer plus Android's fixed 21 ms
mixer/HAL cost. A 4 ms buffer is 25 ms total, and that 25 ms is the floor no
application on Android can go below. Hosts that select a preset (Bannerlator
does) override the buffer default; hosts that bundle the driver and set nothing
get the values above.
Environment variables recognised by this release:
| variable | meaning |
|---|---|
BANNER_AUDIO_DIRECT_PERF |
0 NONE · 1 LOW_LATENCY (default) · 2 POWER_SAVING |
BANNER_AUDIO_DIRECT_ADAPTIVE |
1 grow on underrun (default) · 0 fixed buffer |
BANNER_AUDIO_DIRECT_BF |
initial buffer in frames (0 = default) |
BANNER_AUDIO_DIRECT_MBF |
cap for adaptive growth (0 = device capacity) |
Buffer values here are in frames: 48 frames = 1 ms. Millisecond knobs
(_MS / _MAXMS), buffer decay, the tuning and logging knobs, and a lower
12 ms / 33 ms default are not in this release — they are in development.