fix(sync): preserve startup timing across client uplinks
This commit is contained in:
parent
51f6934318
commit
7793f1eb41
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.16"
|
version = "0.14.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1676,7 +1676,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.16"
|
version = "0.14.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.16"
|
version = "0.14.17"
|
||||||
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.16"
|
version = "0.14.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -13,28 +13,6 @@ impl LesavkaClientApp {
|
|||||||
telemetry.record_reconnect_attempt();
|
telemetry.record_reconnect_attempt();
|
||||||
let mut cli = RelayClient::new(ep.clone());
|
let mut cli = RelayClient::new(ep.clone());
|
||||||
let queue = crate::uplink_fresh_queue::FreshPacketQueue::new(AUDIO_UPLINK_QUEUE);
|
let queue = crate::uplink_fresh_queue::FreshPacketQueue::new(AUDIO_UPLINK_QUEUE);
|
||||||
let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
|
|
||||||
|
|
||||||
let mic_clone = mic.clone();
|
|
||||||
let telemetry_thread = telemetry.clone();
|
|
||||||
let queue_thread = queue.clone();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
while stop_rx.try_recv().is_err() {
|
|
||||||
if let Some(pkt) = mic_clone.pull() {
|
|
||||||
trace!("🎤📤 cli {} bytes → gRPC", pkt.data.len());
|
|
||||||
let enqueue_age = crate::live_capture_clock::packet_age(pkt.pts);
|
|
||||||
let stats = queue_thread.push(pkt, enqueue_age);
|
|
||||||
if stats.dropped_queue_full > 0 {
|
|
||||||
telemetry_thread.record_queue_full_drop(stats.dropped_queue_full);
|
|
||||||
}
|
|
||||||
telemetry_thread.record_enqueue(
|
|
||||||
queue_depth_u32(stats.queue_depth),
|
|
||||||
duration_ms(enqueue_age),
|
|
||||||
0.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let queue_stream = queue.clone();
|
let queue_stream = queue.clone();
|
||||||
let telemetry_stream = telemetry.clone();
|
let telemetry_stream = telemetry.clone();
|
||||||
@ -58,10 +36,34 @@ impl LesavkaClientApp {
|
|||||||
|
|
||||||
match cli.stream_microphone(Request::new(outbound)).await {
|
match cli.stream_microphone(Request::new(outbound)).await {
|
||||||
Ok(mut resp) => {
|
Ok(mut resp) => {
|
||||||
|
let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
|
||||||
|
let mic_clone = mic.clone();
|
||||||
|
let telemetry_thread = telemetry.clone();
|
||||||
|
let queue_thread = queue.clone();
|
||||||
|
let mic_worker = std::thread::spawn(move || {
|
||||||
|
while stop_rx.try_recv().is_err() {
|
||||||
|
if let Some(pkt) = mic_clone.pull() {
|
||||||
|
trace!("🎤📤 cli {} bytes → gRPC", pkt.data.len());
|
||||||
|
let enqueue_age = crate::live_capture_clock::packet_age(pkt.pts);
|
||||||
|
let stats = queue_thread.push(pkt, enqueue_age);
|
||||||
|
if stats.dropped_queue_full > 0 {
|
||||||
|
telemetry_thread.record_queue_full_drop(stats.dropped_queue_full);
|
||||||
|
}
|
||||||
|
telemetry_thread.record_enqueue(
|
||||||
|
queue_depth_u32(stats.queue_depth),
|
||||||
|
duration_ms(enqueue_age),
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
delay = Duration::from_secs(1);
|
delay = Duration::from_secs(1);
|
||||||
telemetry.record_connected();
|
telemetry.record_connected();
|
||||||
while resp.get_mut().message().await.transpose().is_some() {}
|
while resp.get_mut().message().await.transpose().is_some() {}
|
||||||
telemetry.record_disconnect("microphone uplink stream ended");
|
telemetry.record_disconnect("microphone uplink stream ended");
|
||||||
|
queue.close();
|
||||||
|
let _ = stop_tx.send(());
|
||||||
|
let _ = mic_worker.join();
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
telemetry.record_disconnect(format!("microphone uplink connect failed: {e}"));
|
telemetry.record_disconnect(format!("microphone uplink connect failed: {e}"));
|
||||||
@ -76,7 +78,6 @@ impl LesavkaClientApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queue.close();
|
queue.close();
|
||||||
let _ = stop_tx.send(());
|
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,41 +95,6 @@ impl LesavkaClientApp {
|
|||||||
telemetry.record_reconnect_attempt();
|
telemetry.record_reconnect_attempt();
|
||||||
let mut cli = RelayClient::new(ep.clone());
|
let mut cli = RelayClient::new(ep.clone());
|
||||||
let queue = crate::uplink_fresh_queue::FreshPacketQueue::new(VIDEO_UPLINK_QUEUE);
|
let queue = crate::uplink_fresh_queue::FreshPacketQueue::new(VIDEO_UPLINK_QUEUE);
|
||||||
let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
|
|
||||||
|
|
||||||
let cam_worker = std::thread::spawn({
|
|
||||||
let cam = cam.clone();
|
|
||||||
let telemetry = telemetry.clone();
|
|
||||||
let queue = queue.clone();
|
|
||||||
move || {
|
|
||||||
loop {
|
|
||||||
if stop_rx.try_recv().is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Some(pkt) = cam.pull() else {
|
|
||||||
std::thread::sleep(Duration::from_millis(5));
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
static CNT: std::sync::atomic::AtomicU64 =
|
|
||||||
std::sync::atomic::AtomicU64::new(0);
|
|
||||||
let n = CNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
if n < 10 || n.is_multiple_of(120) {
|
|
||||||
tracing::trace!("📸 cli frame#{n} {} B", pkt.data.len());
|
|
||||||
}
|
|
||||||
tracing::trace!("📸⬆️ sent webcam AU pts={} {} B", pkt.pts, pkt.data.len());
|
|
||||||
let enqueue_age = crate::live_capture_clock::packet_age(pkt.pts);
|
|
||||||
let stats = queue.push(pkt, enqueue_age);
|
|
||||||
if stats.dropped_queue_full > 0 {
|
|
||||||
telemetry.record_queue_full_drop(stats.dropped_queue_full);
|
|
||||||
}
|
|
||||||
telemetry.record_enqueue(
|
|
||||||
queue_depth_u32(stats.queue_depth),
|
|
||||||
duration_ms(enqueue_age),
|
|
||||||
0.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let queue_stream = queue.clone();
|
let queue_stream = queue.clone();
|
||||||
let telemetry_stream = telemetry.clone();
|
let telemetry_stream = telemetry.clone();
|
||||||
@ -152,17 +118,50 @@ impl LesavkaClientApp {
|
|||||||
|
|
||||||
match cli.stream_camera(Request::new(outbound)).await {
|
match cli.stream_camera(Request::new(outbound)).await {
|
||||||
Ok(mut resp) => {
|
Ok(mut resp) => {
|
||||||
|
let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
|
||||||
|
let cam_worker = std::thread::spawn({
|
||||||
|
let cam = cam.clone();
|
||||||
|
let telemetry = telemetry.clone();
|
||||||
|
let queue = queue.clone();
|
||||||
|
move || loop {
|
||||||
|
if stop_rx.try_recv().is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let Some(pkt) = cam.pull() else {
|
||||||
|
std::thread::sleep(Duration::from_millis(5));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
static CNT: std::sync::atomic::AtomicU64 =
|
||||||
|
std::sync::atomic::AtomicU64::new(0);
|
||||||
|
let n = CNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if n < 10 || n.is_multiple_of(120) {
|
||||||
|
tracing::trace!("📸 cli frame#{n} {} B", pkt.data.len());
|
||||||
|
}
|
||||||
|
tracing::trace!("📸⬆️ sent webcam AU pts={} {} B", pkt.pts, pkt.data.len());
|
||||||
|
let enqueue_age = crate::live_capture_clock::packet_age(pkt.pts);
|
||||||
|
let stats = queue.push(pkt, enqueue_age);
|
||||||
|
if stats.dropped_queue_full > 0 {
|
||||||
|
telemetry.record_queue_full_drop(stats.dropped_queue_full);
|
||||||
|
}
|
||||||
|
telemetry.record_enqueue(
|
||||||
|
queue_depth_u32(stats.queue_depth),
|
||||||
|
duration_ms(enqueue_age),
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
delay = Duration::from_secs(1);
|
delay = Duration::from_secs(1);
|
||||||
telemetry.record_connected();
|
telemetry.record_connected();
|
||||||
while resp.get_mut().message().await.transpose().is_some() {}
|
while resp.get_mut().message().await.transpose().is_some() {}
|
||||||
telemetry.record_disconnect("camera uplink stream ended");
|
telemetry.record_disconnect("camera uplink stream ended");
|
||||||
|
queue.close();
|
||||||
|
let _ = stop_tx.send(());
|
||||||
|
let _ = cam_worker.join();
|
||||||
}
|
}
|
||||||
Err(e) if e.code() == tonic::Code::Unimplemented => {
|
Err(e) if e.code() == tonic::Code::Unimplemented => {
|
||||||
tracing::warn!("📸 server does not support StreamCamera – giving up");
|
tracing::warn!("📸 server does not support StreamCamera – giving up");
|
||||||
telemetry.record_disconnect("camera uplink unavailable on server");
|
telemetry.record_disconnect("camera uplink unavailable on server");
|
||||||
queue.close();
|
queue.close();
|
||||||
let _ = stop_tx.send(());
|
|
||||||
let _ = cam_worker.join();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -173,8 +172,6 @@ impl LesavkaClientApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queue.close();
|
queue.close();
|
||||||
let _ = stop_tx.send(());
|
|
||||||
let _ = cam_worker.join();
|
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,8 +180,8 @@ impl LesavkaClientApp {
|
|||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
|
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
|
||||||
crate::uplink_fresh_queue::FreshQueueConfig {
|
crate::uplink_fresh_queue::FreshQueueConfig {
|
||||||
capacity: 8,
|
capacity: 32,
|
||||||
max_age: Duration::from_millis(350),
|
max_age: Duration::from_secs(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
|
|||||||
@ -4,12 +4,17 @@ use std::sync::{Mutex, OnceLock};
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
static CAPTURE_ORIGIN: OnceLock<Instant> = OnceLock::new();
|
static CAPTURE_ORIGIN: OnceLock<Instant> = OnceLock::new();
|
||||||
|
static SHARED_SOURCE_CAPTURE_BASE_US: OnceLock<Mutex<Option<u64>>> = OnceLock::new();
|
||||||
const DEFAULT_SOURCE_LAG_CAP_MS: u64 = 250;
|
const DEFAULT_SOURCE_LAG_CAP_MS: u64 = 250;
|
||||||
|
|
||||||
fn origin() -> Instant {
|
fn origin() -> Instant {
|
||||||
*CAPTURE_ORIGIN.get_or_init(Instant::now)
|
*CAPTURE_ORIGIN.get_or_init(Instant::now)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn shared_source_capture_base_slot() -> &'static Mutex<Option<u64>> {
|
||||||
|
SHARED_SOURCE_CAPTURE_BASE_US.get_or_init(|| Mutex::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the shared live-capture timestamp for upstream camera/mic packets.
|
/// Return the shared live-capture timestamp for upstream camera/mic packets.
|
||||||
///
|
///
|
||||||
/// Inputs: none.
|
/// Inputs: none.
|
||||||
@ -164,7 +169,13 @@ impl SourcePtsRebaser {
|
|||||||
|
|
||||||
if let Some(source_pts_us) = source_pts_us {
|
if let Some(source_pts_us) = source_pts_us {
|
||||||
let source_base_us = *state.source_base_us.get_or_insert(source_pts_us);
|
let source_base_us = *state.source_base_us.get_or_insert(source_pts_us);
|
||||||
let capture_base_us = *state.capture_base_us.get_or_insert(capture_now_us);
|
let capture_base_us = {
|
||||||
|
let mut shared_capture_base_us = shared_source_capture_base_slot()
|
||||||
|
.lock()
|
||||||
|
.expect("shared source capture base mutex poisoned");
|
||||||
|
*shared_capture_base_us.get_or_insert(capture_now_us)
|
||||||
|
};
|
||||||
|
state.capture_base_us = Some(capture_base_us);
|
||||||
packet_pts_us =
|
packet_pts_us =
|
||||||
capture_base_us.saturating_add(source_pts_us.saturating_sub(source_base_us));
|
capture_base_us.saturating_add(source_pts_us.saturating_sub(source_base_us));
|
||||||
used_source_pts = true;
|
used_source_pts = true;
|
||||||
@ -242,10 +253,19 @@ mod tests {
|
|||||||
DurationPacedSourcePtsRebaser, SourcePtsRebaser, capture_pts_us, packet_age,
|
DurationPacedSourcePtsRebaser, SourcePtsRebaser, capture_pts_us, packet_age,
|
||||||
upstream_source_lag_cap, upstream_timing_trace_enabled,
|
upstream_source_lag_cap, upstream_timing_trace_enabled,
|
||||||
};
|
};
|
||||||
|
use serial_test::serial;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn reset_shared_source_capture_base_for_tests() {
|
||||||
|
*super::shared_source_capture_base_slot()
|
||||||
|
.lock()
|
||||||
|
.expect("shared source capture base mutex poisoned") = None;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn capture_pts_us_monotonically_advances() {
|
fn capture_pts_us_monotonically_advances() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let first = capture_pts_us();
|
let first = capture_pts_us();
|
||||||
std::thread::sleep(Duration::from_millis(2));
|
std::thread::sleep(Duration::from_millis(2));
|
||||||
let second = capture_pts_us();
|
let second = capture_pts_us();
|
||||||
@ -253,7 +273,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn packet_age_is_small_for_recent_packets() {
|
fn packet_age_is_small_for_recent_packets() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let pts = capture_pts_us();
|
let pts = capture_pts_us();
|
||||||
std::thread::sleep(Duration::from_millis(2));
|
std::thread::sleep(Duration::from_millis(2));
|
||||||
let age = packet_age(pts);
|
let age = packet_age(pts);
|
||||||
@ -262,7 +284,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn source_pts_rebaser_preserves_source_delta_on_shared_capture_clock() {
|
fn source_pts_rebaser_preserves_source_delta_on_shared_capture_clock() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = SourcePtsRebaser::default();
|
let rebased = SourcePtsRebaser::default();
|
||||||
let first = rebased.rebase_or_now(Some(1_000_000), 1);
|
let first = rebased.rebase_or_now(Some(1_000_000), 1);
|
||||||
let second = rebased.rebase_or_now(Some(1_033_333), 1);
|
let second = rebased.rebase_or_now(Some(1_033_333), 1);
|
||||||
@ -278,7 +302,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn source_pts_rebaser_stays_monotonic_when_source_pts_repeat() {
|
fn source_pts_rebaser_stays_monotonic_when_source_pts_repeat() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = SourcePtsRebaser::default();
|
let rebased = SourcePtsRebaser::default();
|
||||||
let first = rebased.rebase_or_now(Some(50_000), 1);
|
let first = rebased.rebase_or_now(Some(50_000), 1);
|
||||||
let second = rebased.rebase_or_now(Some(50_000), 1);
|
let second = rebased.rebase_or_now(Some(50_000), 1);
|
||||||
@ -287,7 +313,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn source_pts_rebaser_falls_back_to_capture_clock_without_source_pts() {
|
fn source_pts_rebaser_falls_back_to_capture_clock_without_source_pts() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = SourcePtsRebaser::default();
|
let rebased = SourcePtsRebaser::default();
|
||||||
let first = rebased.rebase_or_now(None, 1);
|
let first = rebased.rebase_or_now(None, 1);
|
||||||
std::thread::sleep(Duration::from_millis(2));
|
std::thread::sleep(Duration::from_millis(2));
|
||||||
@ -301,7 +329,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn source_pts_rebaser_clamps_source_lag_when_it_falls_too_far_behind_now() {
|
fn source_pts_rebaser_clamps_source_lag_when_it_falls_too_far_behind_now() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = SourcePtsRebaser::default();
|
let rebased = SourcePtsRebaser::default();
|
||||||
let _first = rebased.rebase_with_lag_cap(Some(1_000_000), 1, None);
|
let _first = rebased.rebase_with_lag_cap(Some(1_000_000), 1, None);
|
||||||
std::thread::sleep(Duration::from_millis(8));
|
std::thread::sleep(Duration::from_millis(8));
|
||||||
@ -315,7 +345,26 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn source_pts_rebasers_share_one_capture_base_across_streams() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
|
let microphone = SourcePtsRebaser::default();
|
||||||
|
let camera = SourcePtsRebaser::default();
|
||||||
|
|
||||||
|
let first_microphone = microphone.rebase_or_now(Some(80_000), 1);
|
||||||
|
std::thread::sleep(Duration::from_millis(5));
|
||||||
|
let first_camera = camera.rebase_or_now(Some(435_000), 1);
|
||||||
|
|
||||||
|
assert_eq!(first_microphone.capture_base_us, first_camera.capture_base_us);
|
||||||
|
assert_eq!(first_microphone.packet_pts_us, first_camera.packet_pts_us);
|
||||||
|
assert_eq!(first_microphone.source_base_us, Some(80_000));
|
||||||
|
assert_eq!(first_camera.source_base_us, Some(435_000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn upstream_timing_trace_flag_defaults_off_and_accepts_true_values() {
|
fn upstream_timing_trace_flag_defaults_off_and_accepts_true_values() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
temp_env::with_var_unset("LESAVKA_UPSTREAM_TIMING_TRACE", || {
|
temp_env::with_var_unset("LESAVKA_UPSTREAM_TIMING_TRACE", || {
|
||||||
assert!(!upstream_timing_trace_enabled());
|
assert!(!upstream_timing_trace_enabled());
|
||||||
});
|
});
|
||||||
@ -330,7 +379,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn upstream_source_lag_cap_defaults_and_accepts_override() {
|
fn upstream_source_lag_cap_defaults_and_accepts_override() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
temp_env::with_var_unset("LESAVKA_UPSTREAM_SOURCE_LAG_CAP_MS", || {
|
temp_env::with_var_unset("LESAVKA_UPSTREAM_SOURCE_LAG_CAP_MS", || {
|
||||||
assert_eq!(upstream_source_lag_cap(), Duration::from_millis(250));
|
assert_eq!(upstream_source_lag_cap(), Duration::from_millis(250));
|
||||||
});
|
});
|
||||||
@ -341,7 +392,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn duration_paced_rebaser_advances_by_packet_duration_when_source_pts_stretch() {
|
fn duration_paced_rebaser_advances_by_packet_duration_when_source_pts_stretch() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = DurationPacedSourcePtsRebaser::default();
|
let rebased = DurationPacedSourcePtsRebaser::default();
|
||||||
let first =
|
let first =
|
||||||
rebased.rebase_with_packet_duration(Some(0), 21_333, Duration::from_millis(250));
|
rebased.rebase_with_packet_duration(Some(0), 21_333, Duration::from_millis(250));
|
||||||
@ -355,7 +408,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial]
|
||||||
fn duration_paced_rebaser_clamps_when_duration_pacing_falls_stale() {
|
fn duration_paced_rebaser_clamps_when_duration_pacing_falls_stale() {
|
||||||
|
reset_shared_source_capture_base_for_tests();
|
||||||
let rebased = DurationPacedSourcePtsRebaser::default();
|
let rebased = DurationPacedSourcePtsRebaser::default();
|
||||||
let _first = rebased.rebase_with_packet_duration(Some(0), 10_000, Duration::from_millis(2));
|
let _first = rebased.rebase_with_packet_duration(Some(0), 10_000, Duration::from_millis(2));
|
||||||
std::thread::sleep(Duration::from_millis(8));
|
std::thread::sleep(Duration::from_millis(8));
|
||||||
|
|||||||
@ -43,8 +43,8 @@ pub use runtime::SyncProbeCapture;
|
|||||||
|
|
||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
const PROBE_VIDEO_QUEUE: FreshQueueConfig = FreshQueueConfig {
|
const PROBE_VIDEO_QUEUE: FreshQueueConfig = FreshQueueConfig {
|
||||||
capacity: 8,
|
capacity: 32,
|
||||||
max_age: Duration::from_millis(350),
|
max_age: Duration::from_secs(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
|
|||||||
@ -48,7 +48,6 @@ async fn run_sync_probe(config: ProbeConfig) -> Result<()> {
|
|||||||
config.pulse_width,
|
config.pulse_width,
|
||||||
config.marker_tick_period,
|
config.marker_tick_period,
|
||||||
);
|
);
|
||||||
let capture = SyncProbeCapture::new(camera, schedule, config.duration)?;
|
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
server = %config.server,
|
server = %config.server,
|
||||||
@ -62,6 +61,7 @@ async fn run_sync_probe(config: ProbeConfig) -> Result<()> {
|
|||||||
|
|
||||||
let video_channel = connect(config.server.as_str()).await?;
|
let video_channel = connect(config.server.as_str()).await?;
|
||||||
let audio_channel = connect(config.server.as_str()).await?;
|
let audio_channel = connect(config.server.as_str()).await?;
|
||||||
|
let capture = SyncProbeCapture::new(camera, schedule, config.duration)?;
|
||||||
let video_queue = capture.video_queue();
|
let video_queue = capture.video_queue();
|
||||||
let audio_queue = capture.audio_queue();
|
let audio_queue = capture.audio_queue();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.16"
|
version = "0.14.17"
|
||||||
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.16"
|
version = "0.14.17"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user