Skip to content

Hang on audio-device change in 1.6.5 — AVAudioEngine start/stop stalls in _GetHWFormat (distinct from #665 crash) #682

Description

@poltorrr

Summary

Since the #665 fix, FluidVoice no longer crashes on audio-device changes — it hangs instead. The window stays responsive, but dictation is dead: pressing the hotkey does nothing, because the audio pipeline is frozen. Only Force Quit clears it. I'm doing that ~10 times a day.

This is a different defect from #665 (that SIGABRT is fixed). Here the app spins/deadlocks inside AVAudioEngine start and stop, both stuck in Apple's hardware-format query _GetHWFormat.

Environment

  • FluidVoice 1.6.5 (build 16)
  • macOS 26.3.1 (25D2128), Apple Silicon, MacBook Pro (MacBookPro18,3), 16 GB
  • Speech model: parakeet-tdt
  • Mic: built-in — PreferredInputDeviceUID = BuiltInMicrophoneDevice (nothing exotic selected)
  • ExperimentalDirectAudioCaptureEnabled = true, ExperimentalDirectAudioCaptureForcedOff = true — the app auto-disabled its direct-capture fast path after repeated failures, so it now always uses the AVAudioEngine ("compatibility") path, which is the one that hangs
  • Audio devices that come and go here: iPhone Continuity mic, LG UltraFine Display Audio, Hue Sync Audio (4-ch), Microsoft Teams Audio, ZoomAudioDevice

Symptoms

  • UI responds; dictation does not. Hotkey press does nothing.
  • One thread pinned ~11% CPU; process otherwise idle.
  • Never recovers on its own — requires Force Quit.

Evidence — two live sample captures

Both show the audio engine wedged in _GetHWFormat.

1) Hang while STARTING the engine (most recent):

ASRService audio-capture path
  ASRService.startCompatibilityAudioCapture(reason:)
    ASRService.startEngine()
      AVAudioEngineImpl::Initialize(NSError**)        <- ~85% of samples stuck here
AVAudioIOUnit queue (~100% CPU)
  AVAudioIOUnit::IOUnitPropertyListener(...)
    AVAudioIOUnit_OSX::_GetHWFormat(...)              <- spinning

2) Hang while STOPPING/retiring the engine (earlier):

DispatchQueue app.fluidvoice.audio-engine-retirement (serial)
  AudioEngineRetirementDrain.enqueueAndWait(_:)       <- synchronous wait
    -[AVAudioEngine dealloc] -> ~AVAudioIOUnit_OSX()
      blocked on _dispatch_event_loop_wait_for_ownership
AVAudioIOUnit queue (~100% CPU)
  AVAudioIOUnit::IOUnitPropertyListener(...)
    AVAudioIOUnit_OSX::_GetHWFormat(...)              <- spinning

In (2), the engine dealloc waits on the AVAudioIOUnit queue via enqueueAndWait, but that queue never yields because it's stuck in the hardware-format property listener. The process sat wedged like this for ~30 minutes.

Also observed (same subsystem)

Reproduction

  1. Run 1.6.5 on a machine with audio devices that appear/disappear.
  2. Trigger an audio-device change while using the app. Reliable triggers for me:
    • Physically plugging or unplugging headphones or an external amplifier / audio interface.
    • Virtual/multi-channel devices appearing or disappearing (Microsoft Teams, Zoom, Hue Sync).
    • The iPhone Continuity mic or a display's audio waking up.
  3. FluidVoice hangs — either failing to start capture (hotkey does nothing) or freezing during engine teardown. Force Quit required. Happens even with the plain built-in mic selected.

Suggested directions

  • The hang sits in AVAudioEngine start/stop, both blocked on _GetHWFormat. Consider removing the IOUnit property listener before engine teardown, and not driving engine init/dealloc synchronously (enqueueAndWait) on a queue that the hardware-format listener can block.
  • Add a timeout/watchdog around engine start and retirement so a stuck _GetHWFormat degrades gracefully instead of freezing the whole capture path.
  • Debounce device/format-change events; a burst currently triggers repeated teardown/rebuild.
  • Direct-capture got force-disabled on this machine, so the app is permanently on the hanging path — worth revisiting that fallback so a machine that fails direct capture isn't stranded on a compatibility path that also hangs.

Full .ips crash reports and .hang spindumps available on request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions