fix: preserve UVC format compatibility
This commit is contained in:
parent
4dea0407b8
commit
ffd6a08749
13
AGENTS.md
13
AGENTS.md
@ -674,3 +674,16 @@ downstream appsrc dropping.
|
|||||||
- [x] Stop default downstream appsrc leaking on the UAC speech path; shredded chunks are worse than modest added latency for calls.
|
- [x] Stop default downstream appsrc leaking on the UAC speech path; shredded chunks are worse than modest added latency for calls.
|
||||||
- [ ] Reinstall/restart Theia services so `/etc/lesavka/uvc.env` is refreshed from `640x480 @ 20fps` to `1280x720 @ 30fps`.
|
- [ ] Reinstall/restart Theia services so `/etc/lesavka/uvc.env` is refreshed from `640x480 @ 20fps` to `1280x720 @ 30fps`.
|
||||||
- [ ] Re-run manual Google Meet before trusting mirrored probe calibration; verify speech is intelligible and video cadence is stable by eye.
|
- [ ] Re-run manual Google Meet before trusting mirrored probe calibration; verify speech is intelligible and video cadence is stable by eye.
|
||||||
|
|
||||||
|
## 0.17.37 UVC Format Compatibility Checklist
|
||||||
|
|
||||||
|
Context: after 0.17.36, Google Meet showed `Video Format Not Supported`. The client correctly
|
||||||
|
captured the UI-selected `720p@30` profile, but it emitted those frames into a server UVC gadget still
|
||||||
|
advertising `640x480 @ 20fps`. USB camera consumers require advertised caps and frame payloads to
|
||||||
|
match; otherwise the feed is rejected before we can evaluate smoothness or sync.
|
||||||
|
|
||||||
|
- [x] Preserve UI-selected capture quality as the source capture profile.
|
||||||
|
- [x] Restore safe default UVC emission to the negotiated server gadget profile so browsers see frames matching the camera format they negotiated.
|
||||||
|
- [x] Keep `LESAVKA_CAM_EMIT_UI_PROFILE=1` as an explicit lab-only opt-in until the server can reconfigure the UVC gadget from the UI/session profile.
|
||||||
|
- [x] Keep `LESAVKA_CAM_LOCK_TO_SERVER_PROFILE=1` as a safety override that wins over experimental UI-profile emission.
|
||||||
|
- [ ] Add a real server-side UVC profile reconfigure path before making UI-selected quality drive the gadget-advertised output format.
|
||||||
|
|||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.17.36"
|
version = "0.17.37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.17.36"
|
version = "0.17.37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.17.36"
|
version = "0.17.37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.17.36"
|
version = "0.17.37"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -237,7 +237,7 @@ impl LesavkaClientApp {
|
|||||||
width = cfg.width,
|
width = cfg.width,
|
||||||
height = cfg.height,
|
height = cfg.height,
|
||||||
fps = cfg.fps,
|
fps = cfg.fps,
|
||||||
"📸 using server camera caps as codec/fallback; launcher camera quality remains authoritative"
|
"📸 using negotiated server UVC caps for emitted format; launcher quality still controls local capture"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let ep = vid_ep.clone();
|
let ep = vid_ep.clone();
|
||||||
|
|||||||
@ -83,6 +83,7 @@ mod tests {
|
|||||||
("LESAVKA_CAM_HEIGHT", Some("720")),
|
("LESAVKA_CAM_HEIGHT", Some("720")),
|
||||||
("LESAVKA_CAM_FPS", Some("30")),
|
("LESAVKA_CAM_FPS", Some("30")),
|
||||||
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", None),
|
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", None),
|
||||||
|
("LESAVKA_CAM_EMIT_UI_PROFILE", None),
|
||||||
],
|
],
|
||||||
|| assert_eq!(resolved_capture_profile(Some(cfg)), (1280, 720, 30)),
|
|| assert_eq!(resolved_capture_profile(Some(cfg)), (1280, 720, 30)),
|
||||||
);
|
);
|
||||||
@ -90,8 +91,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
/// UI-selected launcher quality is the source of truth for the camera uplink.
|
/// UVC output must match the gadget profile that browsers negotiate.
|
||||||
fn negotiated_output_profile_follows_launcher_quality_by_default() {
|
fn negotiated_output_profile_matches_server_uvc_contract_by_default() {
|
||||||
let cfg = CameraConfig {
|
let cfg = CameraConfig {
|
||||||
codec: CameraCodec::Mjpeg,
|
codec: CameraCodec::Mjpeg,
|
||||||
width: 640,
|
width: 640,
|
||||||
@ -104,6 +105,36 @@ mod tests {
|
|||||||
("LESAVKA_CAM_HEIGHT", Some("720")),
|
("LESAVKA_CAM_HEIGHT", Some("720")),
|
||||||
("LESAVKA_CAM_FPS", Some("30")),
|
("LESAVKA_CAM_FPS", Some("30")),
|
||||||
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", None),
|
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", None),
|
||||||
|
("LESAVKA_CAM_EMIT_UI_PROFILE", None),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let capture_profile = resolved_capture_profile(Some(cfg));
|
||||||
|
assert_eq!(capture_profile, (1280, 720, 30));
|
||||||
|
assert_eq!(
|
||||||
|
resolved_output_profile(Some(cfg), capture_profile),
|
||||||
|
(640, 480, 20)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
/// Keeps UI-profile emission explicit until the server can reconfigure UVC.
|
||||||
|
fn explicit_ui_profile_emission_keeps_lab_mode_available() {
|
||||||
|
let cfg = CameraConfig {
|
||||||
|
codec: CameraCodec::Mjpeg,
|
||||||
|
width: 640,
|
||||||
|
height: 480,
|
||||||
|
fps: 20,
|
||||||
|
};
|
||||||
|
temp_env::with_vars(
|
||||||
|
[
|
||||||
|
("LESAVKA_CAM_WIDTH", Some("1280")),
|
||||||
|
("LESAVKA_CAM_HEIGHT", Some("720")),
|
||||||
|
("LESAVKA_CAM_FPS", Some("30")),
|
||||||
|
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", None),
|
||||||
|
("LESAVKA_CAM_EMIT_UI_PROFILE", Some("1")),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
let capture_profile = resolved_capture_profile(Some(cfg));
|
let capture_profile = resolved_capture_profile(Some(cfg));
|
||||||
@ -118,8 +149,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
/// Keeps the explicit lab lock available for controlled gadget debugging.
|
/// The safety lock wins if both experimental flags are set.
|
||||||
fn explicit_server_profile_lock_keeps_lab_mode_available() {
|
fn explicit_server_profile_lock_wins_over_ui_emission() {
|
||||||
let cfg = CameraConfig {
|
let cfg = CameraConfig {
|
||||||
codec: CameraCodec::Mjpeg,
|
codec: CameraCodec::Mjpeg,
|
||||||
width: 640,
|
width: 640,
|
||||||
@ -132,6 +163,7 @@ mod tests {
|
|||||||
("LESAVKA_CAM_HEIGHT", Some("720")),
|
("LESAVKA_CAM_HEIGHT", Some("720")),
|
||||||
("LESAVKA_CAM_FPS", Some("30")),
|
("LESAVKA_CAM_FPS", Some("30")),
|
||||||
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", Some("1")),
|
("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE", Some("1")),
|
||||||
|
("LESAVKA_CAM_EMIT_UI_PROFILE", Some("1")),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
let capture_profile = resolved_capture_profile(Some(cfg));
|
let capture_profile = resolved_capture_profile(Some(cfg));
|
||||||
|
|||||||
@ -291,7 +291,10 @@ fn resolved_output_profile(
|
|||||||
capture_profile: (u32, u32, u32),
|
capture_profile: (u32, u32, u32),
|
||||||
) -> (u32, u32, u32) {
|
) -> (u32, u32, u32) {
|
||||||
match cfg {
|
match cfg {
|
||||||
Some(cfg) if env_flag_enabled("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE") => {
|
Some(cfg)
|
||||||
|
if env_flag_enabled("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE")
|
||||||
|
|| !env_flag_enabled("LESAVKA_CAM_EMIT_UI_PROFILE") =>
|
||||||
|
{
|
||||||
(cfg.width, cfg.height, cfg.fps.max(1))
|
(cfg.width, cfg.height, cfg.fps.max(1))
|
||||||
}
|
}
|
||||||
_ => capture_profile,
|
_ => capture_profile,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.17.36"
|
version = "0.17.37"
|
||||||
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.17.36"
|
version = "0.17.37"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -171,6 +171,7 @@ fn launcher_webcam_quality_selection_reaches_preview_and_relay_env() {
|
|||||||
assert!(DEVICE_TEST_SRC.contains("capsfilter caps=\\\"video/x-raw"));
|
assert!(DEVICE_TEST_SRC.contains("capsfilter caps=\\\"video/x-raw"));
|
||||||
assert!(CAMERA_SRC.contains("fn resolved_capture_profile"));
|
assert!(CAMERA_SRC.contains("fn resolved_capture_profile"));
|
||||||
assert!(CAMERA_SRC.contains("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE"));
|
assert!(CAMERA_SRC.contains("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE"));
|
||||||
|
assert!(CAMERA_SRC.contains("LESAVKA_CAM_EMIT_UI_PROFILE"));
|
||||||
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_WIDTH\""));
|
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_WIDTH\""));
|
||||||
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_H264_KBIT\""));
|
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_H264_KBIT\""));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user