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
try AudioManager.shared.set(microphoneMuteMode: .restart)
- Stops audio engine on mute, restarts on unmute
- Slower (reconfigures audio session)
- No system sound
- Mic indicator turns off when muted
try AudioManager.shared.set(microphoneMuteMode: .inputMixer)
- Mutes the input mixer only
- Fast mute/unmute
- No system sound
- Mic indicator remains on
| Mode | iOS Beep | Mic Indicator | Speed |
|---|
.voiceProcessing | Yes | Turns off | Fast |
.restart | No | Turns off | Slow |
.inputMixer | No | Remains on | Fast |
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