From 33a5f23a8930fd9c9b5360dc440e40c1dea05b8a Mon Sep 17 00:00:00 2001 From: Ahmet Eren Bilgili Date: Sat, 15 Aug 2026 17:28:56 +0300 Subject: [PATCH] feat(livekit): add a stereo option to TrackPublishOptions The server only adds stereo=1;maxaveragebitrate=510000 to the publisher's SDP answer (and sprop-stereo=1 for subscribers) when the published track carries the TF_STEREO audio feature. The Rust SDK never sets it, so a stereo AudioSource is downmixed to mono by the Opus encoder and encoded in speech mode regardless of channel count or max_bitrate. Add TrackPublishOptions::stereo (default false, unchanged behaviour) and forward it as both AddTrackRequest::stereo and the TF_STEREO audio feature for audio tracks. Expose the same flag through the FFI TrackPublishOptions message so the Node and Python SDKs can opt in as well. --- livekit-ffi/protocol/room.proto | 3 +++ livekit-ffi/src/conversion/room.rs | 1 + livekit/src/room/options.rs | 5 +++++ livekit/src/room/participant/local_participant.rs | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/livekit-ffi/protocol/room.proto b/livekit-ffi/protocol/room.proto index df08ed5be..77a5f1516 100644 --- a/livekit-ffi/protocol/room.proto +++ b/livekit-ffi/protocol/room.proto @@ -326,6 +326,9 @@ message TrackPublishOptions { // Controls how the encoder trades off between resolution and framerate // when bandwidth is constrained. Default is MAINTAIN_RESOLUTION. optional DegradationPreference degradation_preference = 13; + // Publish an audio track as stereo. Required for the server to signal + // stereo=1 to the publisher and sprop-stereo=1 to subscribers. + optional bool stereo = 14; } enum VideoEncoderBackend { diff --git a/livekit-ffi/src/conversion/room.rs b/livekit-ffi/src/conversion/room.rs index 4922add55..bbcce55c3 100644 --- a/livekit-ffi/src/conversion/room.rs +++ b/livekit-ffi/src/conversion/room.rs @@ -359,6 +359,7 @@ impl From for TrackPublishOptions { preconnect_buffer: opts .preconnect_buffer .unwrap_or(default_publish_options.preconnect_buffer), + stereo: opts.stereo.unwrap_or(default_publish_options.stereo), frame_metadata_features: frame_metadata_features_from_proto( opts.frame_metadata_features, ), diff --git a/livekit/src/room/options.rs b/livekit/src/room/options.rs index f443f6087..6ffb5a9d3 100644 --- a/livekit/src/room/options.rs +++ b/livekit/src/room/options.rs @@ -121,6 +121,10 @@ pub struct TrackPublishOptions { pub video_codec: VideoCodec, pub dtx: bool, pub red: bool, + /// Publish the audio track as stereo. The server only signals `stereo=1;maxaveragebitrate=510000` + /// to the publisher (and `sprop-stereo=1` to subscribers) when the track carries the + /// `TF_STEREO` audio feature, so stereo sources are otherwise downmixed to mono by the encoder. + pub stereo: bool, pub simulcast: bool, /// Custom simulcast layer presets (low, mid). When set, these override the /// SDK's built-in defaults which reduce fps on lower layers. @@ -161,6 +165,7 @@ impl Default for TrackPublishOptions { video_codec: VideoCodec::VP8, dtx: true, red: true, + stereo: false, simulcast: true, simulcast_layers: None, source: TrackSource::Unknown, diff --git a/livekit/src/room/participant/local_participant.rs b/livekit/src/room/participant/local_participant.rs index f01c754c4..09345cd7e 100644 --- a/livekit/src/room/participant/local_participant.rs +++ b/livekit/src/room/participant/local_participant.rs @@ -399,6 +399,11 @@ impl LocalParticipant { req.audio_features.push(proto::AudioTrackFeature::TfPreconnectBuffer as i32); } + if options.stereo && matches!(track, LocalTrack::Audio(_)) { + req.stereo = true; + req.audio_features.push(proto::AudioTrackFeature::TfStereo as i32); + } + req.packet_trailer_features = options.frame_metadata_features.to_proto().into_iter().map(|f| f as i32).collect();