Summary
The #223 invalid-hardware-format fallback in installTapWithHardwareFormat() installs a tap with a synthetic format on the live input bus, which throws an uncatchable Objective-C NSException ("Failed to create tap due to format mismatch, AVAudioFormat 16000 Hz Float32") and crashes the process. The guard correctly detects the bad state, then does the one thing guaranteed to crash in it.
This is still present on main (and @siteed/audio-studio@3.2.0):
packages/audio-studio/ios/AudioStreamManager.swift L944–959.
// Validate hardware format before installing tap (#223)
if inputHardwareFormat.channelCount == 0 || inputHardwareFormat.sampleRate == 0 {
let fallbackSampleRate = Double(recordingSettings?.sampleRate ?? 16000)
let fallbackChannels = AVAudioChannelCount(recordingSettings?.numberOfChannels ?? 1)
guard let fallbackFormat = AVAudioFormat(standardFormatWithSampleRate: fallbackSampleRate, channels: fallbackChannels) else { ... }
inputNode.installTap(onBus: 0, bufferSize: bufferSize, format: fallbackFormat, block: tapBlock) // ← throws when fallbackFormat ≠ live bus format
return fallbackFormat
}
AVAudioNode.installTap requires the tap format to match the input bus's format. When the bus reports channelCount == 0 || sampleRate == 0, installing a tap with any concrete format (the synthetic 16 kHz fallback) mismatches the live bus and AVAudioEngineImpl::InstallTapOnNode raises an ObjC NSException. Swift try/catch cannot catch it → fatal/unhandled.
When it fires
The bus reads 0 Hz / 0 ch only transiently — the AVAudioSession isn't active yet. We hit it when prepareRecording() is called in the window right after a fresh requestRecordingPermissions grant, or during a contended route (Bluetooth handoff / incoming call / another app holding the mic via MixWithOthers). Rare per-user but fatal when it lands; it's a crash, not a degraded recording.
Environment
@siteed/audio-studio@3.0.2 (confirmed identical on main / 3.2.0)
- iOS, config:
sampleRate: 16000, channels: 1, PlayAndRecord + DefaultToSpeaker + AllowBluetooth + MixWithOthers
- Sentry pins the culprit symbol to
checkMicrophonePermission (nearest symbol on the prepare/permission thread); the real frame is the tap install.
Related
Same area as #192 / #218 / #223 — the #223 fix (this fallback) traded the original IsFormatSampleRateAndChannelCountValid crash for a different fatal in the same spot.
Suggested fix
When the hardware format is invalid, don't install a tap at all — skip it and return. The engine stays valid without an input tap (it just delivers no buffers), so the caller's recovery/retry runs once a real route exists, instead of the process dying:
if inputHardwareFormat.channelCount == 0 || inputHardwareFormat.sampleRate == 0 {
Logger.debug("AudioStreamManager", "Invalid hardware format (\(inputHardwareFormat.channelCount)ch/\(inputHardwareFormat.sampleRate)Hz); skipping tap to avoid fatal format mismatch.")
if prepareEngine { audioEngine.prepare() }
return inputHardwareFormat
}
App-side, activating the session (setAudioMode allowsRecording: true) before prepareRecording avoids the trigger for the common case, but the library installing a mismatched tap on an invalid bus is still a latent fatal for any contended-route path.
Summary
The
#223invalid-hardware-format fallback ininstallTapWithHardwareFormat()installs a tap with a synthetic format on the live input bus, which throws an uncatchable Objective-CNSException("Failed to create tap due to format mismatch, AVAudioFormat 16000 Hz Float32") and crashes the process. The guard correctly detects the bad state, then does the one thing guaranteed to crash in it.This is still present on
main(and@siteed/audio-studio@3.2.0):packages/audio-studio/ios/AudioStreamManager.swiftL944–959.AVAudioNode.installTaprequires the tap format to match the input bus's format. When the bus reportschannelCount == 0 || sampleRate == 0, installing a tap with any concrete format (the synthetic 16 kHz fallback) mismatches the live bus andAVAudioEngineImpl::InstallTapOnNoderaises an ObjCNSException. Swifttry/catchcannot catch it → fatal/unhandled.When it fires
The bus reads
0 Hz / 0 chonly transiently — the AVAudioSession isn't active yet. We hit it whenprepareRecording()is called in the window right after a freshrequestRecordingPermissionsgrant, or during a contended route (Bluetooth handoff / incoming call / another app holding the mic viaMixWithOthers). Rare per-user but fatal when it lands; it's a crash, not a degraded recording.Environment
@siteed/audio-studio@3.0.2(confirmed identical onmain/3.2.0)sampleRate: 16000, channels: 1, PlayAndRecord + DefaultToSpeaker + AllowBluetooth + MixWithOtherscheckMicrophonePermission(nearest symbol on the prepare/permission thread); the real frame is the tap install.Related
Same area as #192 / #218 / #223 — the
#223fix (this fallback) traded the originalIsFormatSampleRateAndChannelCountValidcrash for a different fatal in the same spot.Suggested fix
When the hardware format is invalid, don't install a tap at all — skip it and return. The engine stays valid without an input tap (it just delivers no buffers), so the caller's recovery/retry runs once a real route exists, instead of the process dying:
App-side, activating the session (
setAudioMode allowsRecording: true) beforeprepareRecordingavoids the trigger for the common case, but the library installing a mismatched tap on an invalid bus is still a latent fatal for any contended-route path.