fix(sync): keep microphone paired through camera warmup

This commit is contained in:
Brad Stein 2026-04-27 01:08:06 -03:00
parent 7224ae399d
commit 56c475f742
8 changed files with 52 additions and 51 deletions

6
Cargo.lock generated
View File

@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"anyhow",
"async-stream",
@ -1676,7 +1676,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"anyhow",
"base64",
@ -1688,7 +1688,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.14.10"
version = "0.14.11"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.14.10"
version = "0.14.11"
edition = "2024"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.14.10"
version = "0.14.11"
edition = "2024"
build = "build.rs"

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.14.10"
version = "0.14.11"
edition = "2024"
autobins = false

View File

@ -8,15 +8,6 @@ fn upstream_stale_drop_budget() -> Duration {
Duration::from_millis(drop_ms)
}
#[cfg(not(coverage))]
fn upstream_camera_startup_grace() -> Duration {
let grace_ms = std::env::var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS")
.ok()
.and_then(|value| value.trim().parse::<u64>().ok())
.unwrap_or(250);
Duration::from_millis(grace_ms)
}
#[cfg(not(coverage))]
fn retain_freshest_video_packet(
pending: &mut std::collections::VecDeque<VideoPacket>,
@ -289,10 +280,6 @@ impl Relay for Handler {
let mut inbound_closed = false;
let stale_drop_budget = upstream_stale_drop_budget();
let mut startup_video_settled = false;
let startup_grace_us = upstream_camera_startup_grace()
.as_micros()
.min(u64::MAX as u128) as u64;
let mut cold_startup_grace_pending = startup_grace_us > 0;
loop {
if !camera_rt.is_active(session_id)
|| !upstream_media_rt.is_camera_active(upstream_lease.generation)
@ -339,20 +326,6 @@ impl Relay for Handler {
}
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(plan) => plan,
};
if cold_startup_grace_pending && plan.local_pts_us < startup_grace_us {
let coalesced = retain_freshest_video_packet(&mut pending);
tracing::debug!(
rpc_id,
session_id,
remote_pts_us = pkt.pts,
local_pts_us = plan.local_pts_us,
startup_grace_us,
dropped_pending = coalesced,
"🎥 dropping startup video until the shared-session warm-up grace is spent"
);
continue;
}
cold_startup_grace_pending = false;
if !upstream_media_rt
.wait_for_audio_master(plan.local_pts_us, plan.due_at)
.await

View File

@ -7,15 +7,6 @@ fn upstream_stale_drop_budget() -> Duration {
Duration::from_millis(drop_ms)
}
#[cfg(coverage)]
fn upstream_camera_startup_grace() -> Duration {
let grace_ms = std::env::var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS")
.ok()
.and_then(|value| value.trim().parse::<u64>().ok())
.unwrap_or(250);
Duration::from_millis(grace_ms)
}
#[cfg(coverage)]
fn retain_freshest_video_packet(
pending: &mut std::collections::VecDeque<VideoPacket>,
@ -184,10 +175,6 @@ impl Relay for Handler {
let mut pending = std::collections::VecDeque::new();
let mut inbound_closed = false;
let stale_drop_budget = upstream_stale_drop_budget();
let startup_grace_us = upstream_camera_startup_grace()
.as_micros()
.min(u64::MAX as u128) as u64;
let mut cold_startup_grace_pending = startup_grace_us > 0;
loop {
if !camera_rt.is_active(session_id)
|| !upstream_media_rt.is_camera_active(upstream_lease.generation)
@ -225,11 +212,6 @@ impl Relay for Handler {
}
lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(plan) => plan,
};
if cold_startup_grace_pending && plan.local_pts_us < startup_grace_us {
let _ = retain_freshest_video_packet(&mut pending);
continue;
}
cold_startup_grace_pending = false;
if !upstream_media_rt
.wait_for_audio_master(plan.local_pts_us, plan.due_at)
.await

View File

@ -397,6 +397,19 @@ impl UpstreamMediaRuntime {
);
}
return UpstreamPlanDecision::AwaitingPair;
} else if state.first_camera_remote_pts_us.is_some() && !state.camera_startup_ready {
if upstream_timing_trace_enabled()
&& (packet_count <= 10 || packet_count.is_multiple_of(300))
{
info!(
session_id,
?kind,
packet_count,
remote_pts_us,
"upstream media packet buffered while camera startup warm-up is still in progress"
);
}
return UpstreamPlanDecision::AwaitingPair;
} else {
let single_stream_base_remote_pts_us = match kind {
UpstreamMediaKind::Camera => {

View File

@ -91,6 +91,39 @@ fn overlap_waits_for_camera_startup_grace_before_establishing_the_shared_base()
});
}
#[test]
fn pairing_window_does_not_expire_into_one_sided_playout_while_camera_warms_up() {
temp_env::with_var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS", Some("250"), || {
temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("20"), || {
let runtime = UpstreamMediaRuntime::new();
let _camera = runtime.activate_camera();
let _microphone = runtime.activate_microphone();
assert!(matches!(
runtime.plan_video_pts(1_000_000, 16_666),
super::UpstreamPlanDecision::AwaitingPair
));
assert!(matches!(
runtime.plan_audio_pts(1_000_000),
super::UpstreamPlanDecision::AwaitingPair
));
std::thread::sleep(Duration::from_millis(30));
assert!(matches!(
runtime.plan_audio_pts(1_010_000),
super::UpstreamPlanDecision::AwaitingPair
));
let video_ready = play(runtime.plan_video_pts(1_250_000, 16_666));
let audio_ready = play(runtime.plan_audio_pts(1_260_000));
assert_eq!(video_ready.local_pts_us, 0);
assert_eq!(audio_ready.local_pts_us, 10_000);
});
});
}
#[test]
fn overlap_pairing_drops_leading_packets_before_the_shared_base() {
let runtime = UpstreamMediaRuntime::new();