Skip to content

fix(ios): avoid installTap sample-rate assertion crash when starting stream recording - #623

Open
striver2006 wants to merge 1 commit into
llfbandit:mainfrom
striver2006:fix/ios-stream-start-sample-rate-race
Open

fix(ios): avoid installTap sample-rate assertion crash when starting stream recording#623
striver2006 wants to merge 1 commit into
llfbandit:mainfrom
striver2006:fix/ios-stream-start-sample-rate-race

Conversation

@striver2006

Copy link
Copy Markdown

Addresses #622 (full root-cause analysis there).

RecorderStreamDelegate.start() can abort the whole app with an uncatchable NSException (required condition is false: format.sampleRate == hwFormat.sampleRate) when installTap runs while an asynchronous hardware sample-rate switch (session reconfiguration, Bluetooth HFP/A2DP negotiation, route change) is still in progress and inputFormat(forBus:) returned a stale format.

Changes (pure Swift, no API change)

  1. Configure the session before creating the engine. Upstream order is engine → initAVAudioSession; since session configuration (category/activation/preferred sample rate) is what triggers the async hardware switch, creating the engine afterwards removes the structural half of the race.
  2. Wait for the input format to settle before installTap. Poll until the format is non-zero and (unless voice processing is enabled, where the input node format may legitimately differ) matches AVAudioSession.sampleRate. The engine is re-created per retry because the input node caches the first format it observed. Bounded at 10×25 ms on the plugin's background serial queue — in normal operation it takes 0 rounds, so the happy path is unchanged. If no valid format shows up, a RecorderError is thrown instead of tripping the AVFAudio assertion.

Testing

  • Device-matrix regression of our voice-interaction flow (repeated stream starts with Bluetooth headset connect/disconnect and wired-headphone plug/unplug loops): the assertion crash no longer reproduces; the format-settle loop measures 0 wait rounds in normal operation.
  • This change (as part of a vendored fork) is rolling out in our production app for children's voice exercises; the crash signature came from real production crash reports against record_ios 2.1.1.

Note: a third, defense-in-depth layer (ObjC @try/@catch around installTap/prepare/start so any residual race becomes a catchable PlatformException instead of SIGABRT) is deliberately not included — it needs an ObjC file and a single SPM target can't mix languages. See #622 for discussion.

🤖 Generated with Claude Code

…stream recording

AVFAudio aborts the process with an uncatchable NSException
('required condition is false: format.sampleRate == hwFormat.sampleRate')
when installTap is called with a stale input format while an asynchronous
hardware sample-rate switch (session reconfiguration, Bluetooth HFP/A2DP
negotiation, route change) is still in progress.

Two changes in RecorderStreamDelegate.start():
- Configure the audio session before creating the AVAudioEngine, so the
  engine cannot observe a pre-switch input format.
- Wait (bounded, 10 x 25ms on the plugin's background serial queue) for
  the input format to settle before installing the tap, re-creating the
  engine per retry since the input node caches the first observed format.
  If no valid format shows up, throw a RecorderError instead of tripping
  the AVFAudio assertion.

Edit by CZB

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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 this pull request may close these issues.

1 participant