fix(sync): pace webcam timestamps by frame duration

This commit is contained in:
Brad Stein 2026-04-26 13:22:52 -03:00
parent 71b44521ef
commit 38ead8c1e9
7 changed files with 22 additions and 10 deletions

6
Cargo.lock generated
View File

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

View File

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

View File

@ -50,7 +50,8 @@ pub struct CameraCapture {
pipeline: gst::Pipeline, pipeline: gst::Pipeline,
sink: gst_app::AppSink, sink: gst_app::AppSink,
preview_tap_running: Option<Arc<AtomicBool>>, preview_tap_running: Option<Arc<AtomicBool>>,
pts_rebaser: crate::live_capture_clock::SourcePtsRebaser, pts_rebaser: crate::live_capture_clock::DurationPacedSourcePtsRebaser,
frame_duration_us: u64,
} }
include!("camera/capture_pipeline.rs"); include!("camera/capture_pipeline.rs");

View File

@ -227,7 +227,8 @@ impl CameraCapture {
pipeline, pipeline,
sink, sink,
preview_tap_running, preview_tap_running,
pts_rebaser: crate::live_capture_clock::SourcePtsRebaser::default(), pts_rebaser: crate::live_capture_clock::DurationPacedSourcePtsRebaser::default(),
frame_duration_us: (1_000_000u64 / u64::from(fps.max(1))).max(1),
}) })
} }
@ -236,7 +237,15 @@ impl CameraCapture {
let buf = sample.buffer()?; let buf = sample.buffer()?;
let map = buf.map_readable().ok()?; let map = buf.map_readable().ok()?;
let source_pts_us = buf.pts().map(|ts| ts.nseconds() / 1_000); let source_pts_us = buf.pts().map(|ts| ts.nseconds() / 1_000);
let timing = self.pts_rebaser.rebase_or_now(source_pts_us, 1); let packet_duration_us = buf
.duration()
.map(|ts| (ts.nseconds() / 1_000).max(1))
.unwrap_or(self.frame_duration_us);
let timing = self.pts_rebaser.rebase_with_packet_duration(
source_pts_us,
packet_duration_us,
crate::live_capture_clock::upstream_source_lag_cap(),
);
let pts = timing.packet_pts_us; let pts = timing.packet_pts_us;
static CAMERA_PACKET_COUNT: std::sync::atomic::AtomicU64 = static CAMERA_PACKET_COUNT: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0); std::sync::atomic::AtomicU64::new(0);
@ -278,6 +287,7 @@ fn log_camera_timing_sample(
packet_pts_us = timing.packet_pts_us, packet_pts_us = timing.packet_pts_us,
pull_path_delay_us = timing.capture_now_us as i128 - timing.packet_pts_us as i128, pull_path_delay_us = timing.capture_now_us as i128 - timing.packet_pts_us as i128,
used_source_pts = timing.used_source_pts, used_source_pts = timing.used_source_pts,
lag_clamped = timing.lag_clamped,
bytes, bytes,
"📸 upstream webcam timing sample" "📸 upstream webcam timing sample"
); );

View File

@ -94,7 +94,8 @@ impl CameraCapture {
pipeline, pipeline,
sink, sink,
preview_tap_running: None, preview_tap_running: None,
pts_rebaser: crate::live_capture_clock::SourcePtsRebaser::default(), pts_rebaser: crate::live_capture_clock::DurationPacedSourcePtsRebaser::default(),
frame_duration_us: 1,
} }
} }

View File

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

View File

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