The mic tap's format snapshot could abort the whole app #387

Merged
enricobuehler merged 1 commits from worktree-fix-mic-tap-format-abort into main 2026-08-24 09:32:28 +00:00
1 Commits
Author SHA1 Message Date
enricobuehler f60b6e30e2 fix(clients/apple): the mic tap's format snapshot could abort the whole app
ci / bun-nix (pull_request) Successful in 27s
ci / docs-drift (pull_request) Successful in 1m4s
ci / docs-site (pull_request) Successful in 1m8s
ci / web (pull_request) Successful in 1m17s
ci / rust-arm64 (pull_request) Successful in 1m59s
apple / swift (pull_request) Successful in 2m16s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Successful in 9m38s
`installTap(onBus:bufferSize:format:)` validates a non-nil format against the
bus and raises an Objective-C exception on ANY mismatch. Swift cannot catch
that, so it reached the terminate handler and aborted the process — SIGABRT in
`AVAudioEngineGraph::InstallTapOnNode`, crashing macOS 0.31.0 at session start,
not at launch.

The format handed to the tap is necessarily read a moment earlier
(`input.outputFormat(forBus: 0)`), and on macOS the input can move underneath
it in that window: a device switch, a clock/rate change, or the
`kAudioOutputUnitProperty_CurrentDevice` swap `startCapture` itself performs two
lines before. The existing guard only rejected the 0 Hz / 0-channel case, which
is a different failure — a device that is absent, not one that changed.

Install with `format: nil` instead, which is the documented "use the bus's own
format" and makes the mismatch unrepresentable rather than merely unlikely.
The tap then has to follow the real format, so the rate-dependent pieces (mono
bus, resampler, both scratch buffers) move into a `MicChain` the tap rebuilds
when `buffer.format.sampleRate` differs — a chain pinned to a stale rate would
resample by the wrong ratio and pitch-shift the mic. That rebuild subsumes the
old grow-on-larger-quantum branch; the steady state still allocates nothing.

Tests cover the sizing arithmetic the rebuild rests on — including that
`staging` fits the UPWARD ratio, which silently truncates every packet on any
device below 48 kHz if it is sized for the input rate instead.
2026-08-24 11:16:07 +02:00