Skip to content

iOS: second voice-processing audio engine instance per process captures silence while reporting healthy (isRecording() == true) #98

Description

@Smartsoil-Media

Environment

  • @livekit/react-native-webrtc 144.1.1, @livekit/react-native 2.11.1
  • Consumed via @elevenlabs/react-native 1.2.15 (WebRTC conversation transport)
  • iOS 26.3 (23D127), iPhone 17 Pro (iPhone17,3), React Native 0.83.6 (Fabric/Hermes)

Summary

Within a single app process, the first WebRTC voice session captures microphone
audio normally. Every subsequent session connects, publishes its local audio
track, plays remote audio — but the captured local audio is pure silence. Only
killing the process restores capture. AudioDeviceModule.setVoiceProcessingEnabled(false)
fully resolves it (raw audio unit capture works on every instantiation), which
isolates the fault to the voice-processing IO unit path.

Evidence (Sentry breadcrumb trails from a release build)

Working call 1 and deaf call 2 are indistinguishable at every inspectable layer:

  • Identical JS sequences: AudioSession.startAudioSession() → local track publish
    (muted: false) → room connect.
  • Identical engine snapshots at connect, read via AudioDeviceModule:
    { micMuted: false, recording: true, playing: true, engineRunning: true, vpEnabled: true, vpBypassed: false }.
  • The engine naturally stops between calls (engineRunning: false before call 2)
    and restarts for call 2 — the restart "succeeds" and captures silence.

Ruled out by on-device falsification across separate builds:

  • Audio session refcounting / stale setActive(false): reference-counting
    start/stop so only the first start / last stop reach native → still deaf.
  • Deactivation timing: deferring setActive(false) until 2.6 s after the mic
    track stopped (fully clean teardown) → still deaf.
  • Re-activation after deactivation: keeping the session active continuously
    (never calling setActive(false) between calls) → still deaf. Backgrounding
    and returning does not recover; only process death does.
  • Latched VPIO mute: setMicrophoneMuted(false) before every session, and
    isMicrophoneMuted() confirmed false during the deaf call → still deaf.

Workaround

// Before each session (engine stopped at this point):
await AudioDeviceModule.setVoiceProcessingEnabled(false)

Capture then works on every session. Software AEC handles echo acceptably on
speakerphone, but losing the hardware voice-processing path (AEC/AGC/NS quality,
power) is a real cost — hence this report.

Repro sketch

  1. Start a WebRTC session that publishes a mic track (any LiveKit room works;
    observed via the ElevenLabs conversation transport). Confirm remote side hears
    audio.
  2. Disconnect the room, stop tracks.
  3. Start a second session in the same process.
  4. Remote side receives silence; AudioDeviceModule.isRecording() returns true.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions