Simulcast allows publishers to send multiple encodings of the same video track at different resolutions and bitrates. This enables the LiveKit SFU to dynamically select the best quality layer for each subscriber based on their network conditions and device capabilities.
Overview
When simulcast is enabled, the SDK publishes three quality layers:
- High quality: Full resolution
- Medium quality: Half resolution
- Low quality: Quarter resolution
The server automatically selects the appropriate layer for each subscriber based on:
- Available bandwidth
- CPU constraints
- Requested video dimensions
- Network conditions
Enabling Simulcast
Simulcast is enabled by default for video tracks. You can configure it using VideoPublishOptions:
// Simulcast enabled (default)
let options = VideoPublishOptions(
simulcast: true
)
await room.localParticipant.setCamera(enabled: true, options: options)
Disabling Simulcast
Disable simulcast for single-layer publishing:
let options = VideoPublishOptions(
simulcast: false
)
await room.localParticipant.setCamera(enabled: true, options: options)
Custom Simulcast Layers
Define custom quality layers with VideoParameters:
import LiveKit
let customLayers = [
VideoParameters(
dimensions: Dimensions(width: 1920, height: 1080),
encoding: VideoEncoding(
maxBitrate: 3_000_000,
maxFps: 30
)
),
VideoParameters(
dimensions: Dimensions(width: 1280, height: 720),
encoding: VideoEncoding(
maxBitrate: 1_500_000,
maxFps: 30
)
),
VideoParameters(
dimensions: Dimensions(width: 640, height: 360),
encoding: VideoEncoding(
maxBitrate: 500_000,
maxFps: 30
)
)
]
let options = VideoPublishOptions(
simulcast: true,
simulcastLayers: customLayers
)
Screen Share Simulcast
Screen sharing uses different default presets optimized for content:
let screenShareLayers = [
VideoParameters(
dimensions: Dimensions(width: 1920, height: 1080),
encoding: VideoEncoding(
maxBitrate: 3_000_000,
maxFps: 15
)
),
VideoParameters(
dimensions: Dimensions(width: 1280, height: 720),
encoding: VideoEncoding(
maxBitrate: 1_500_000,
maxFps: 15
)
)
]
let options = VideoPublishOptions(
simulcast: true,
screenShareSimulcastLayers: screenShareLayers
)
await room.localParticipant.setScreenShare(enabled: true, options: options)
Default Presets
The SDK includes built-in presets for common aspect ratios:
16:9 Aspect Ratio (Default)
// Default simulcast layers for 16:9
// High: 960×540 @ 1.7 Mbps
// Medium: 480×270 @ 600 Kbps
// Low: 240×135 @ 200 Kbps
4:3 Aspect Ratio
// Simulcast layers for 4:3
// High: 960×720 @ 1.7 Mbps
// Medium: 480×360 @ 600 Kbps
// Low: 240×180 @ 200 Kbps
Checking Simulcast Status
Check if a track publication is using simulcast:
let publication = room.localParticipant.videoTracks.first?.value
if publication?.isSimulcasted == true {
print("Track is using simulcast")
}
Simulcast Statistics
Monitor statistics for each simulcast layer:
class MyTrackDelegate: TrackDelegate {
func track(
_ track: Track,
didUpdateStatistics statistics: TrackStatistics,
simulcastStatistics: [VideoCodec: TrackStatistics]
) {
// Main track statistics
print("Main track stats: \(statistics)")
// Simulcast layer statistics by codec
for (codec, stats) in simulcastStatistics {
print("Codec \(codec): \(stats)")
// Access outbound stats for each layer
for stream in stats.outboundRtpStream {
if let rid = stream.rid {
print("Layer \(rid): \(stream.bytesSent ?? 0) bytes sent")
}
}
}
}
}
track.add(delegate: MyTrackDelegate())
Subscriber Quality Selection
Subscribers can request specific quality levels:
// Request high quality
try await remotePublication.set(videoQuality: .high)
// Request medium quality
try await remotePublication.set(videoQuality: .medium)
// Request low quality
try await remotePublication.set(videoQuality: .low)
The server will send the closest available layer based on:
- Requested quality
- Available bandwidth
- CPU constraints
- Current network conditions
Manually setting video quality requires adaptiveStream to be disabled in RoomOptions. With adaptive stream enabled, the SDK automatically manages quality based on view size.
RID (Restriction Identifier)
Each simulcast layer is identified by a RID:
f - Full resolution (high)
h - Half resolution (medium)
q - Quarter resolution (low)
for stream in statistics.outboundRtpStream {
switch stream.rid {
case "f":
print("High quality layer")
case "h":
print("Medium quality layer")
case "q":
print("Low quality layer")
default:
print("Unknown layer")
}
}
Room-Level Configuration
Set default simulcast options for all video tracks:
let videoOptions = VideoPublishOptions(
simulcast: true,
simulcastLayers: customLayers
)
let roomOptions = RoomOptions(
defaultVideoPublishOptions: videoOptions
)
let room = Room(roomOptions: roomOptions)
Best Practices
- Enable simulcast for camera tracks: Allows optimal quality for all subscribers
- Use lower layers for screen sharing: Content doesn’t benefit as much from high framerates
- Monitor quality limitation reasons: Check
qualityLimitationReason in statistics
- Adjust layers based on content: Use higher bitrates for motion-heavy content
Quality Limitation Tracking
func track(
_ track: Track,
didUpdateStatistics statistics: TrackStatistics,
simulcastStatistics: [VideoCodec: TrackStatistics]
) {
for stream in statistics.outboundRtpStream {
if let reason = stream.qualityLimitationReason {
switch reason {
case .bandwidth:
print("Quality limited by bandwidth")
case .cpu:
print("Quality limited by CPU")
case .none:
print("No quality limitation")
case .other:
print("Quality limited by other factors")
}
}
}
}
- CPU usage: Simulcast increases encoding CPU usage by ~2-3x
- Bandwidth: Upstream bandwidth requirement is higher (sum of all layers)
- Battery: Increased encoding work impacts battery life on mobile devices
- Benefits: Downstream bandwidth is optimized per subscriber
For bandwidth-constrained publishers, consider:
- Using fewer simulcast layers
- Reducing resolution of lower layers
- Disabling simulcast for certain tracks
See Also
- Dynacast - Dynamic broadcasting based on subscribers
- Adaptive Stream - Automatic quality management
- Source code:
Sources/LiveKit/Types/VideoParameters.swift