sync: keep uvc flowing with delayed audio
This commit is contained in:
parent
488d70f809
commit
e73e7f0a0f
@ -140,6 +140,7 @@ Context: the mirrored browser probe finally reproduced the real failure class on
|
||||
- [x] Raise calibration offset limits to cover one-second healing without rejecting measured probe corrections.
|
||||
- [x] Update the MJPEG/UVC factory audio baseline from `-45ms` to `+720ms` based on the first trustworthy mirrored browser probe artifact.
|
||||
- [x] Migrate untouched legacy `-45ms` factory/env calibration files on load so old installs actually receive the new baseline.
|
||||
- [x] Make the video/audio-master wait offset-aware so a positive audio playout delay does not freeze UVC video while UAC sleeps before emission.
|
||||
- [ ] Flush/stop UAC cleanly on session close, replacement, and recovery.
|
||||
- [x] Add tests or contract coverage for bounded UAC settings where practical.
|
||||
|
||||
@ -165,5 +166,7 @@ Context: the mirrored browser probe finally reproduced the real failure class on
|
||||
- 0.16.18 captured real colored/audio-coded events but the analyzer still bailed with `need at least 3 matching coded pulse pairs; saw 1`. Replaying that artifact after analyzer hardening now reports `gross_failure`: 16/16 coded pairs, p95 `775.7 ms`, activity start `-766.4 ms`, and drift `-2.8 ms`; the failure is stable audio-ahead/video-late skew, not random detector noise.
|
||||
- 0.16.19 changes the shipped MJPEG/UVC audio playout baseline to `+720ms`; the next mirrored browser probe should move the measured median from about `-766ms` toward roughly `-46ms` before fine calibration.
|
||||
- 0.16.19 mirrored browser probe did not move the measured skew: p95 `885.7 ms`, median `-788.4 ms`, activity start `-659.1 ms`, drift `-81.2 ms`. SSH inspection showed Theia was on commit `c348597`, but `/etc/lesavka/server.env` still contained `LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000`; the new `+720ms` baseline was not actually installed. Patch the installer to migrate leaked legacy ambient `-45000` to `+720000` unless `LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US` explicitly asks for the legacy value.
|
||||
- 0.16.20 installed the `+720ms` offset (`/etc/lesavka/server.env` had `LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=720000`), but the mirrored browser capture contained no recognizable color pulses. Theia server logs showed repeated `upstream video frame dropped because the audio master never caught up inside the pairing window`; UVC was effectively starved by the positive audio delay instead of flowing delayed-but-fresh frames.
|
||||
- 0.16.21 makes that wait offset-aware and adds a regression test proving a configured positive audio delay does not freeze UVC video while UAC sleeps before playout.
|
||||
- [ ] Re-run the mirrored browser probe after the pre-start false-positive fix.
|
||||
- [ ] Run Google Meet manual validation.
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.16.20"
|
||||
version = "0.16.21"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -87,6 +87,14 @@ impl UpstreamMediaRuntime {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn positive_audio_delay_allowance_us(&self) -> u64 {
|
||||
let camera_offset_us = self.camera_playout_offset_us.load(Ordering::Relaxed);
|
||||
let microphone_offset_us = self.microphone_playout_offset_us.load(Ordering::Relaxed);
|
||||
microphone_offset_us
|
||||
.saturating_sub(camera_offset_us)
|
||||
.max(0) as u64
|
||||
}
|
||||
}
|
||||
|
||||
include!("upstream_media_runtime/lease_lifecycle.rs");
|
||||
@ -132,6 +140,7 @@ impl UpstreamMediaRuntime {
|
||||
let slack_us = upstream_pairing_master_slack()
|
||||
.as_micros()
|
||||
.min(u64::MAX as u128) as u64;
|
||||
let audio_delay_allowance_us = self.positive_audio_delay_allowance_us();
|
||||
loop {
|
||||
let notified = self.audio_progress_notify.notified();
|
||||
{
|
||||
@ -143,7 +152,10 @@ impl UpstreamMediaRuntime {
|
||||
return true;
|
||||
}
|
||||
if state.last_audio_local_pts_us.is_some_and(|audio_pts_us| {
|
||||
audio_pts_us.saturating_add(slack_us) >= video_local_pts_us
|
||||
audio_pts_us
|
||||
.saturating_add(slack_us)
|
||||
.saturating_add(audio_delay_allowance_us)
|
||||
>= video_local_pts_us
|
||||
}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use super::{UpstreamMediaRuntime, play};
|
||||
use super::{UpstreamMediaRuntime, play, runtime_without_offsets};
|
||||
use serial_test::serial;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@ -6,7 +6,7 @@ use std::time::Duration;
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[serial(upstream_media_runtime)]
|
||||
async fn wait_for_audio_master_releases_video_once_audio_catches_up() {
|
||||
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
||||
let runtime = Arc::new(runtime_without_offsets());
|
||||
let _camera = runtime.activate_camera();
|
||||
let _microphone = runtime.activate_microphone();
|
||||
|
||||
@ -34,8 +34,36 @@ async fn wait_for_audio_master_releases_video_once_audio_catches_up() {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[serial(upstream_media_runtime)]
|
||||
async fn wait_for_audio_master_times_out_when_audio_never_catches_up() {
|
||||
async fn wait_for_audio_master_allows_configured_positive_audio_delay() {
|
||||
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
||||
runtime.set_playout_offsets(0, 720_000);
|
||||
let _camera = runtime.activate_camera();
|
||||
let _microphone = runtime.activate_microphone();
|
||||
|
||||
assert!(matches!(
|
||||
runtime.plan_video_pts(1_000_000, 16_666),
|
||||
super::UpstreamPlanDecision::AwaitingPair
|
||||
));
|
||||
let _audio_first = play(runtime.plan_audio_pts(1_000_000));
|
||||
let _video_first = play(runtime.plan_video_pts(1_000_000, 16_666));
|
||||
let delayed_video = play(runtime.plan_video_pts(1_700_000, 16_666));
|
||||
|
||||
assert_eq!(delayed_video.local_pts_us, 700_000);
|
||||
assert!(
|
||||
runtime
|
||||
.wait_for_audio_master(
|
||||
delayed_video.local_pts_us,
|
||||
tokio::time::Instant::now() + Duration::from_millis(5)
|
||||
)
|
||||
.await,
|
||||
"a positive audio playout offset should not freeze UVC video while the audio sink sleeps"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[serial(upstream_media_runtime)]
|
||||
async fn wait_for_audio_master_times_out_when_audio_never_catches_up() {
|
||||
let runtime = Arc::new(runtime_without_offsets());
|
||||
let _camera = runtime.activate_camera();
|
||||
let _microphone = runtime.activate_microphone();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user