Skip to content

Commit 57a7f74

Browse files
committed
fix parsing. Explicitly set simulcast fps to 30.
1 parent f20a93e commit 57a7f74

8 files changed

Lines changed: 311 additions & 28 deletions

File tree

examples/local_video/src/publisher.rs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Result;
22
use clap::Parser;
33
use livekit::e2ee::{key_provider::*, E2eeOptions, EncryptionType};
4-
use livekit::options::{TrackPublishOptions, VideoCodec, VideoEncoding};
4+
use livekit::options::{self, TrackPublishOptions, VideoCodec, VideoEncoding, VideoPreset, video as video_presets};
55
use livekit::prelude::*;
66
use livekit::webrtc::video_frame::{I420Buffer, VideoFrame, VideoRotation};
77
use livekit::webrtc::video_source::native::NativeVideoSource;
@@ -146,6 +146,7 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
146146
info!("Connecting to LiveKit room '{}' as '{}'...", args.room_name, args.identity);
147147
let mut room_options = RoomOptions::default();
148148
room_options.auto_subscribe = true;
149+
room_options.dynacast = true;
149150

150151
// Configure E2EE if an encryption key is provided
151152
if let Some(ref e2ee_key) = args.e2ee_key {
@@ -220,19 +221,40 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
220221
let requested_codec = if args.h265 { VideoCodec::H265 } else { VideoCodec::H264 };
221222
info!("Attempting publish with codec: {}", requested_codec.as_str());
222223

224+
// Compute an explicit video encoding so all simulcast layers use 30 fps.
225+
// The SDK defaults reduce lower layers to 15/20 fps; we override that here.
226+
let target_fps = args.fps as f64;
227+
let main_encoding = {
228+
let base = options::compute_appropriate_encoding(false, width, height, VideoCodec::H264);
229+
VideoEncoding {
230+
max_bitrate: args.max_bitrate.unwrap_or(base.max_bitrate),
231+
max_framerate: target_fps,
232+
}
233+
};
234+
let simulcast_presets = compute_simulcast_presets_30fps(width, height, target_fps);
235+
info!(
236+
"Video encoding: {}x{} @ {:.0} fps, {} bps (simulcast layers: {})",
237+
width,
238+
height,
239+
target_fps,
240+
main_encoding.max_bitrate,
241+
simulcast_presets
242+
.iter()
243+
.map(|p| format!("{}x{}@{:.0}fps/{}bps", p.width, p.height, p.encoding.max_framerate, p.encoding.max_bitrate))
244+
.collect::<Vec<_>>()
245+
.join(", "),
246+
);
247+
223248
let publish_opts = |codec: VideoCodec| {
224-
let mut opts = TrackPublishOptions {
249+
TrackPublishOptions {
225250
source: TrackSource::Camera,
226251
simulcast: args.simulcast,
227252
video_codec: codec,
228253
user_timestamp: args.attach_timestamp,
254+
video_encoding: Some(main_encoding.clone()),
255+
simulcast_layers: Some(simulcast_presets.clone()),
229256
..Default::default()
230-
};
231-
if let Some(bitrate) = args.max_bitrate {
232-
opts.video_encoding =
233-
Some(VideoEncoding { max_bitrate: bitrate, max_framerate: args.fps as f64 });
234257
}
235-
opts
236258
};
237259

238260
let publish_result = room
@@ -486,3 +508,19 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
486508

487509
Ok(())
488510
}
511+
512+
/// Build simulcast presets that match the SDK defaults but with a uniform frame rate.
513+
/// The SDK's built-in `DEFAULT_SIMULCAST_PRESETS` use 15/20 fps for lower layers;
514+
/// this keeps the same resolutions and bitrates but overrides fps to `target_fps`.
515+
fn compute_simulcast_presets_30fps(width: u32, height: u32, target_fps: f64) -> Vec<VideoPreset> {
516+
let ar = width as f32 / height as f32;
517+
let defaults: &[VideoPreset] = if f32::abs(ar - 16.0 / 9.0) < f32::abs(ar - 4.0 / 3.0) {
518+
video_presets::DEFAULT_SIMULCAST_PRESETS
519+
} else {
520+
livekit::options::video43::DEFAULT_SIMULCAST_PRESETS
521+
};
522+
defaults
523+
.iter()
524+
.map(|p| VideoPreset::new(p.width, p.height, p.encoding.max_bitrate, target_fps))
525+
.collect()
526+
}

examples/local_video/src/subscriber.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,26 @@ async fn handle_track_subscribed(
334334
std::mem::swap(&mut s.u, &mut u_buf);
335335
std::mem::swap(&mut s.v, &mut v_buf);
336336
s.dirty = true;
337+
338+
if let Some(ts) = frame.user_timestamp_us {
339+
let now_us = current_timestamp_us();
340+
let delta_ms = (now_us - ts) as f64 / 1000.0;
341+
if ts < 0 || ts > 2_000_000_000_000_000 || delta_ms < -60_000.0 {
342+
log::warn!(
343+
"[Subscriber] BAD TIMESTAMP: frame_id={:?} user_ts={} \
344+
timestamp_us={} now_us={} delta_ms={:.1} \
345+
prev_user_ts={:?} prev_frame_id={:?}",
346+
frame.frame_id,
347+
ts,
348+
frame.timestamp_us,
349+
now_us,
350+
delta_ms,
351+
s.user_timestamp_us,
352+
s.frame_id,
353+
);
354+
}
355+
}
356+
337357
s.user_timestamp_us = frame.user_timestamp_us;
338358
s.frame_id = frame.frame_id;
339359

@@ -674,6 +694,8 @@ async fn run(args: Args, ctrl_c_received: Arc<AtomicBool>) -> Result<()> {
674694
info!("Connecting to LiveKit room '{}' as '{}'...", args.room_name, args.identity);
675695
let mut room_options = RoomOptions::default();
676696
room_options.auto_subscribe = true;
697+
room_options.dynacast = true;
698+
room_options.adaptive_stream = true;
677699

678700
// Configure E2EE if an encryption key is provided
679701
if let Some(ref e2ee_key) = args.e2ee_key {

libwebrtc/src/native/user_timestamp.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ impl UserTimestampHandler {
6666
let ts = self.sys_handle.lookup_user_timestamp(rtp_timestamp);
6767
if ts >= 0 {
6868
let frame_id = self.sys_handle.last_lookup_frame_id();
69+
if ts > 2_000_000_000_000_000 || ts < 0 {
70+
log::warn!(
71+
"[UserTS-FFI] C++ returned bad ts={} (0x{:016x}) fid={} rtp_ts={}",
72+
ts, ts, frame_id, rtp_timestamp
73+
);
74+
}
6975
Some((ts, frame_id))
7076
} else {
7177
None

libwebrtc/src/native/video_stream.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ impl sys_vt::VideoSink for VideoTrackObserver {
113113
.and_then(|h| h.lookup_frame_metadata(rtp_timestamp));
114114

115115
let (user_timestamp_us, frame_id) = match meta {
116-
Some((ts, fid)) => (Some(ts), Some(fid)),
116+
Some((ts, fid)) => {
117+
if ts < 0 || ts > 2_000_000_000_000_000 {
118+
log::warn!(
119+
"[on_frame] SUSPICIOUS user_ts={} fid={} rtp_ts={}",
120+
ts, fid, rtp_timestamp
121+
);
122+
}
123+
(Some(ts), Some(fid))
124+
}
117125
None => (None, None),
118126
};
119127

livekit-ffi/src/conversion/room.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl From<proto::TrackPublishOptions> for TrackPublishOptions {
253253
red: opts.red.unwrap_or(default_publish_options.red),
254254
simulcast: opts.simulcast.unwrap_or(default_publish_options.simulcast),
255255
stream: opts.stream.unwrap_or(default_publish_options.stream),
256+
simulcast_layers: default_publish_options.simulcast_layers,
256257
preconnect_buffer: opts
257258
.preconnect_buffer
258259
.unwrap_or(default_publish_options.preconnect_buffer),

livekit/src/room/options.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ pub struct TrackPublishOptions {
8484
pub dtx: bool,
8585
pub red: bool,
8686
pub simulcast: bool,
87+
/// Custom simulcast layer presets (low, mid). When set, these override the
88+
/// SDK's built-in defaults which reduce fps on lower layers.
89+
pub simulcast_layers: Option<Vec<VideoPreset>>,
8790
// pub name: String,
8891
pub source: TrackSource,
8992
pub stream: String,
@@ -100,6 +103,7 @@ impl Default for TrackPublishOptions {
100103
dtx: true,
101104
red: true,
102105
simulcast: true,
106+
simulcast_layers: None,
103107
source: TrackSource::Unknown,
104108
stream: "".to_string(),
105109
preconnect_buffer: false,
@@ -149,7 +153,10 @@ pub fn compute_video_encodings(
149153
return into_rtp_encodings(width, height, &[initial_preset]);
150154
}
151155

152-
let mut simulcast_presets = compute_default_simulcast_presets(screenshare, &initial_preset);
156+
let mut simulcast_presets = match options.simulcast_layers {
157+
Some(ref custom) => custom.clone(),
158+
None => compute_default_simulcast_presets(screenshare, &initial_preset),
159+
};
153160

154161
let mid_preset = simulcast_presets.pop();
155162
let low_preset = simulcast_presets.pop();

webrtc-sys/include/livekit/user_timestamp.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ constexpr size_t kUserTimestampTrailerSize =
5353
struct FrameMetadata {
5454
int64_t user_timestamp_us;
5555
uint32_t frame_id;
56+
uint32_t ssrc; // SSRC that produced this entry (for simulcast tracking)
5657
};
5758

5859
/// Frame transformer that appends/extracts user timestamp trailers.
@@ -132,13 +133,38 @@ class UserTimestampTransformer : public webrtc::FrameTransformerInterface {
132133
mutable std::deque<int64_t> send_map_order_;
133134
static constexpr size_t kMaxSendMapEntries = 300;
134135

136+
// Send-side per-SSRC stats for diagnosing simulcast encoding delay.
137+
struct SendSsrcStats {
138+
uint64_t frame_count{0};
139+
int64_t last_user_ts{0};
140+
int64_t sum_encode_delay_us{0};
141+
uint64_t encode_delay_samples{0};
142+
};
143+
mutable std::unordered_map<uint32_t, SendSsrcStats> send_ssrc_stats_;
144+
135145
// Receive-side map: RTP timestamp -> frame metadata.
136146
// Keyed by RTP timestamp so decoded frames can look up their
137147
// metadata regardless of frame drops or reordering.
138148
mutable webrtc::Mutex recv_map_mutex_;
139149
mutable std::unordered_map<uint32_t, FrameMetadata> recv_map_;
140150
mutable std::deque<uint32_t> recv_map_order_;
141151
static constexpr size_t kMaxRecvMapEntries = 300;
152+
153+
// Simulcast tracking: detect layer switches and flush stale entries.
154+
mutable uint32_t recv_active_ssrc_{0};
155+
mutable int64_t recv_last_user_ts_{0};
156+
mutable uint64_t recv_frame_count_{0};
157+
mutable uint64_t recv_lookup_hits_{0};
158+
mutable uint64_t recv_lookup_misses_{0};
159+
160+
// Receive-side per-SSRC latency tracking.
161+
struct RecvSsrcStats {
162+
uint64_t frame_count{0};
163+
int64_t sum_latency_us{0};
164+
uint64_t latency_samples{0};
165+
int64_t max_latency_us{0};
166+
};
167+
mutable std::unordered_map<uint32_t, RecvSsrcStats> recv_ssrc_stats_;
142168
};
143169

144170
/// Wrapper class for Rust FFI that manages user timestamp transformers.

0 commit comments

Comments
 (0)