Skip to main content
The LiveKit Swift SDK provides the AudioManager singleton to configure the audio session, manage audio devices, and control voice processing.

AudioManager Overview

Access the shared AudioManager instance:
let audioManager = AudioManager.shared

AVAudioSession Configuration (iOS/tvOS/visionOS)

Automatic Configuration

By default, the SDK automatically configures the AVAudioSession based on whether you’re publishing or subscribing to audio tracks.

Disable Automatic Configuration

If you need to manage the audio session yourself (e.g., for CallKit integration):
AudioManager.shared.audioSession.isAutomaticConfigurationEnabled = false
Set this before connecting to a room. When disabled, you’re responsible for configuring the audio session category (e.g., .playAndRecord) before publishing audio.

Disable Automatic Deactivation

By default, the SDK deactivates the audio session when both recording and playback stop:
AudioManager.shared.audioSession.isAutomaticDeactivationEnabled = false
When set to false, the audio session remains active after disconnecting, preserving your app’s audio state.

Speaker Output Preference

Control whether audio routes to the speaker or receiver:
// Prefer speaker output (default)
AudioManager.shared.isSpeakerOutputPreferred = true

// Prefer receiver output
AudioManager.shared.isSpeakerOutputPreferred = false

Voice Processing

Apple’s voice processing provides echo cancellation and auto-gain control.

Enable/Disable Voice Processing

Disable voice processing entirely (requires audio engine restart):
try AudioManager.shared.setVoiceProcessingEnabled(false)
Set this before connecting to a room to avoid audio glitches.

Bypass Voice Processing at Runtime

Toggle voice processing without restarting the audio engine:
// Bypass voice processing
AudioManager.shared.isVoiceProcessingBypassed = true

// Re-enable voice processing
AudioManager.shared.isVoiceProcessingBypassed = false

Auto Gain Control

Control the automatic gain control independently:
// Enable AGC (default)
AudioManager.shared.isVoiceProcessingAGCEnabled = true

// Disable AGC
AudioManager.shared.isVoiceProcessingAGCEnabled = false

Audio Ducking

Audio ducking reduces the volume of other audio (music, podcasts) during voice calls.

Advanced Ducking

Enable dynamic ducking based on voice activity:
// Enable dynamic ducking (FaceTime/SharePlay-like behavior)
AudioManager.shared.isAdvancedDuckingEnabled = true

Ducking Level

Control how much other audio is reduced:
if #available(iOS 17, macOS 14.0, visionOS 1.0, *) {
    // Maximize voice intelligibility
    AudioManager.shared.duckingLevel = .max
    
    // Keep other audio as loud as possible (SDK default)
    AudioManager.shared.duckingLevel = .min
    
    // Apple's historical default
    AudioManager.shared.duckingLevel = .default
}
Options:
  • .min: Minimal ducking (SDK default)
  • .default: Apple’s historical fixed ducking amount
  • .mid: Medium ducking
  • .max: Maximum ducking for best voice clarity

Microphone Mute Modes

Control how microphone mute/unmute works:
try AudioManager.shared.set(microphoneMuteMode: .voiceProcessing)
try AudioManager.shared.set(microphoneMuteMode: .voiceProcessing)
  • Uses AVAudioEngine.isVoiceProcessingInputMuted
  • Fast mute/unmute
  • iOS plays a system sound when muting/unmuting
  • Mic indicator turns off when muted
ModeiOS BeepMic IndicatorSpeed
.voiceProcessingYesTurns offFast
.restartNoTurns offSlow
.inputMixerNoRemains onFast

Low-Latency Microphone Publishing

Prepare the audio engine for instant microphone publishing:
Task.detached {
    try? await AudioManager.shared.setRecordingAlwaysPreparedMode(true)
}
Behavior:
  • Starts audio engine in muted state
  • Mic indicator typically stays off while muted
  • Requires microphone permission
  • Persists across room connections
To disable:
try await AudioManager.shared.setRecordingAlwaysPreparedMode(false)

Manual Rendering Mode

Provide custom audio buffers without accessing the device microphone:
// Enable manual rendering
try AudioManager.shared.setManualRenderingMode(true)

// Enable microphone track (doesn't access physical mic)
try await room.localParticipant.setMicrophone(enabled: true)

// Provide audio buffers continuously
AudioManager.shared.mixer.capture(appAudio: yourAudioBuffer)
In manual rendering mode:
  • The audio engine doesn’t access audio devices
  • Remote audio will not play automatically
  • You must handle audio playback yourself

Capturing Audio Buffers

With Microphone

Capture custom audio while using the microphone:
// Enable microphone
try await room.localParticipant.setMicrophone(enabled: true)

// Capture additional audio (e.g., app audio)
AudioManager.shared.mixer.capture(appAudio: yourAudioBuffer)

Volume Control

Adjust volume levels for different sources:
// Microphone volume (0.0 to 1.0)
AudioManager.shared.mixer.micVolume = 0.8

// Custom audio buffer volume (0.0 to 1.0)
AudioManager.shared.mixer.appVolume = 0.5

Audio Devices (macOS)

On macOS, select specific input/output devices:
// List available devices
let outputDevices = AudioManager.shared.outputDevices
let inputDevices = AudioManager.shared.inputDevices

// Get current devices
let currentOutput = AudioManager.shared.outputDevice
let currentInput = AudioManager.shared.inputDevice

// Set output device
if let device = outputDevices.first {
    AudioManager.shared.outputDevice = device
}

// Set input device
if let device = inputDevices.first {
    AudioManager.shared.inputDevice = device
}

Monitor Device Changes

AudioManager.shared.onDeviceUpdate = { audioManager in
    print("Audio devices changed")
    print("Output devices: \(audioManager.outputDevices)")
    print("Input devices: \(audioManager.inputDevices)")
}

Muted Speech Activity Detection

Detect voice activity even when the microphone is muted:
AudioManager.shared.onMutedSpeechActivity = { audioManager, event in
    switch event {
    case .started:
        print("User started speaking while muted")
        // Show "You're muted" indicator
    case .ended:
        print("User stopped speaking")
    }
}
The audio engine must be initialized (by connecting to a room or calling setRecordingAlwaysPreparedMode) for this to work.

Audio Processing Delegates

Capture Post-Processing

Modify local audio before sending to the network:
class MyAudioProcessor: AudioCustomProcessingDelegate {
    func audioProcessingInitialize(sampleRate: Int, channels: Int) {
        // Initialize your audio processor
    }
    
    func audioProcessingProcess(audioBuffer: LKRTCAudioBuffer) {
        // Modify audio buffer (e.g., apply effects)
    }
    
    func audioProcessingRelease() {
        // Clean up
    }
}

AudioManager.shared.capturePostProcessingDelegate = MyAudioProcessor()

Render Pre-Processing

Modify combined remote audio before playback:
AudioManager.shared.renderPreProcessingDelegate = MyAudioProcessor()

Engine Availability

Control when the audio engine is allowed to run:
// Prevent engine from starting (useful for CallKit flows)
try AudioManager.shared.setEngineAvailability(.notAvailable)

// Allow engine to start
try AudioManager.shared.setEngineAvailability(.available)

Check Engine State

// Check if engine is running
let isRunning = AudioManager.shared.isEngineRunning

// Check engine availability
let availability = AudioManager.shared.engineAvailability

See Also

  • Audio/Manager/AudioManager.swift
  • Docs/audio.md

Build docs developers (and LLMs) love