fix(sync): gate overlap startup on camera warmup
This commit is contained in:
parent
f8c48cd89c
commit
7224ae399d
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1676,7 +1676,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.9"
|
version = "0.14.10"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,14 @@ use config::{
|
|||||||
};
|
};
|
||||||
use state::UpstreamClockState;
|
use state::UpstreamClockState;
|
||||||
|
|
||||||
|
fn upstream_camera_startup_grace_us() -> u64 {
|
||||||
|
std::env::var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.trim().parse::<u64>().ok())
|
||||||
|
.unwrap_or(if cfg!(test) { 0 } else { 250 })
|
||||||
|
.saturating_mul(1_000)
|
||||||
|
}
|
||||||
|
|
||||||
/// Logical upstream media kinds that share one live-call session timeline.
|
/// Logical upstream media kinds that share one live-call session timeline.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum UpstreamMediaKind {
|
pub enum UpstreamMediaKind {
|
||||||
@ -143,6 +151,7 @@ impl UpstreamMediaRuntime {
|
|||||||
state.session_id = self.next_session_id.fetch_add(1, Ordering::SeqCst) + 1;
|
state.session_id = self.next_session_id.fetch_add(1, Ordering::SeqCst) + 1;
|
||||||
state.first_camera_remote_pts_us = None;
|
state.first_camera_remote_pts_us = None;
|
||||||
state.first_microphone_remote_pts_us = None;
|
state.first_microphone_remote_pts_us = None;
|
||||||
|
state.camera_startup_ready = false;
|
||||||
state.session_base_remote_pts_us = None;
|
state.session_base_remote_pts_us = None;
|
||||||
state.last_video_local_pts_us = None;
|
state.last_video_local_pts_us = None;
|
||||||
state.last_audio_local_pts_us = None;
|
state.last_audio_local_pts_us = None;
|
||||||
@ -216,6 +225,7 @@ impl UpstreamMediaRuntime {
|
|||||||
{
|
{
|
||||||
state.first_camera_remote_pts_us = None;
|
state.first_camera_remote_pts_us = None;
|
||||||
state.first_microphone_remote_pts_us = None;
|
state.first_microphone_remote_pts_us = None;
|
||||||
|
state.camera_startup_ready = false;
|
||||||
state.session_base_remote_pts_us = None;
|
state.session_base_remote_pts_us = None;
|
||||||
state.last_video_local_pts_us = None;
|
state.last_video_local_pts_us = None;
|
||||||
state.last_audio_local_pts_us = None;
|
state.last_audio_local_pts_us = None;
|
||||||
@ -317,11 +327,27 @@ impl UpstreamMediaRuntime {
|
|||||||
state.microphone_packet_count
|
state.microphone_packet_count
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let first_slot = match kind {
|
let mut first_remote_for_kind = match kind {
|
||||||
UpstreamMediaKind::Camera => &mut state.first_camera_remote_pts_us,
|
UpstreamMediaKind::Camera => {
|
||||||
UpstreamMediaKind::Microphone => &mut state.first_microphone_remote_pts_us,
|
let first_slot = &mut state.first_camera_remote_pts_us;
|
||||||
|
*first_slot.get_or_insert(remote_pts_us)
|
||||||
|
}
|
||||||
|
UpstreamMediaKind::Microphone => {
|
||||||
|
let first_slot = &mut state.first_microphone_remote_pts_us;
|
||||||
|
*first_slot.get_or_insert(remote_pts_us)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let first_remote_for_kind = *first_slot.get_or_insert(remote_pts_us);
|
if kind == UpstreamMediaKind::Camera {
|
||||||
|
let startup_grace_us = upstream_camera_startup_grace_us();
|
||||||
|
if !state.camera_startup_ready
|
||||||
|
&& (startup_grace_us == 0
|
||||||
|
|| remote_pts_us.saturating_sub(first_remote_for_kind) >= startup_grace_us)
|
||||||
|
{
|
||||||
|
state.camera_startup_ready = true;
|
||||||
|
state.first_camera_remote_pts_us = Some(remote_pts_us);
|
||||||
|
first_remote_for_kind = remote_pts_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let pairing_deadline = *state
|
let pairing_deadline = *state
|
||||||
.pairing_anchor_deadline
|
.pairing_anchor_deadline
|
||||||
@ -331,6 +357,7 @@ impl UpstreamMediaRuntime {
|
|||||||
if state.session_base_remote_pts_us.is_none() {
|
if state.session_base_remote_pts_us.is_none() {
|
||||||
if state.first_camera_remote_pts_us.is_some()
|
if state.first_camera_remote_pts_us.is_some()
|
||||||
&& state.first_microphone_remote_pts_us.is_some()
|
&& state.first_microphone_remote_pts_us.is_some()
|
||||||
|
&& state.camera_startup_ready
|
||||||
{
|
{
|
||||||
let first_camera_remote_pts_us =
|
let first_camera_remote_pts_us =
|
||||||
state.first_camera_remote_pts_us.unwrap_or_default();
|
state.first_camera_remote_pts_us.unwrap_or_default();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ pub(super) struct UpstreamClockState {
|
|||||||
pub active_microphone_generation: Option<u64>,
|
pub active_microphone_generation: Option<u64>,
|
||||||
pub first_camera_remote_pts_us: Option<u64>,
|
pub first_camera_remote_pts_us: Option<u64>,
|
||||||
pub first_microphone_remote_pts_us: Option<u64>,
|
pub first_microphone_remote_pts_us: Option<u64>,
|
||||||
|
pub camera_startup_ready: bool,
|
||||||
pub session_base_remote_pts_us: Option<u64>,
|
pub session_base_remote_pts_us: Option<u64>,
|
||||||
pub last_video_local_pts_us: Option<u64>,
|
pub last_video_local_pts_us: Option<u64>,
|
||||||
pub last_audio_local_pts_us: Option<u64>,
|
pub last_audio_local_pts_us: Option<u64>,
|
||||||
|
|||||||
@ -63,6 +63,34 @@ fn first_packets_wait_for_the_counterpart_before_pairing() {
|
|||||||
assert_eq!(audio_first.due_at, video_first.due_at);
|
assert_eq!(audio_first.due_at, video_first.due_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlap_waits_for_camera_startup_grace_before_establishing_the_shared_base() {
|
||||||
|
temp_env::with_var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS", Some("250"), || {
|
||||||
|
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
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
runtime.plan_video_pts(1_200_000, 16_666),
|
||||||
|
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]
|
#[test]
|
||||||
fn overlap_pairing_drops_leading_packets_before_the_shared_base() {
|
fn overlap_pairing_drops_leading_packets_before_the_shared_base() {
|
||||||
let runtime = UpstreamMediaRuntime::new();
|
let runtime = UpstreamMediaRuntime::new();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user