fix(sync): honor configurable uvc codec
This commit is contained in:
parent
f477332834
commit
ab00babf99
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.19"
|
version = "0.14.20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1676,7 +1676,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.19"
|
version = "0.14.20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1688,7 +1688,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.19"
|
version = "0.14.20"
|
||||||
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.19"
|
version = "0.14.20"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -7,6 +7,12 @@ use crate::sync_probe::schedule::PulseSchedule;
|
|||||||
use lesavka_common::lesavka::{AudioPacket, VideoPacket};
|
use lesavka_common::lesavka::{AudioPacket, VideoPacket};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
use gstreamer as gst;
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
use gstreamer::prelude::*;
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
use gstreamer_app as gst_app;
|
||||||
|
|
||||||
fn stub_camera() -> CameraConfig {
|
fn stub_camera() -> CameraConfig {
|
||||||
CameraConfig {
|
CameraConfig {
|
||||||
@ -141,6 +147,49 @@ fn probe_video_frames_render_distinct_idle_regular_and_marker_patterns() {
|
|||||||
assert_ne!(regular, marker);
|
assert_ne!(regular, marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
fn decode_mjpeg_packet_mean_luma(packet: &VideoPacket) -> u8 {
|
||||||
|
gst::init().expect("gst init");
|
||||||
|
let pipeline = gst::parse::launch(
|
||||||
|
"appsrc name=src is-live=false format=time do-timestamp=false \
|
||||||
|
caps=image/jpeg,parsed=true ! jpegdec ! videoconvert ! \
|
||||||
|
video/x-raw,format=GRAY8 ! \
|
||||||
|
appsink name=sink emit-signals=false sync=false max-buffers=1 drop=false",
|
||||||
|
)
|
||||||
|
.expect("decode pipeline")
|
||||||
|
.downcast::<gst::Pipeline>()
|
||||||
|
.expect("pipeline");
|
||||||
|
let src = pipeline
|
||||||
|
.by_name("src")
|
||||||
|
.expect("src")
|
||||||
|
.downcast::<gst_app::AppSrc>()
|
||||||
|
.expect("appsrc");
|
||||||
|
let sink = pipeline
|
||||||
|
.by_name("sink")
|
||||||
|
.expect("sink")
|
||||||
|
.downcast::<gst_app::AppSink>()
|
||||||
|
.expect("appsink");
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Playing)
|
||||||
|
.expect("pipeline playing");
|
||||||
|
let mut buffer = gst::Buffer::from_slice(packet.data.clone());
|
||||||
|
if let Some(meta) = buffer.get_mut() {
|
||||||
|
meta.set_pts(Some(gst::ClockTime::from_useconds(packet.pts)));
|
||||||
|
}
|
||||||
|
src.push_buffer(buffer).expect("push buffer");
|
||||||
|
src.end_of_stream().expect("end of stream");
|
||||||
|
let sample = sink.pull_sample().expect("decoded sample");
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("pipeline null");
|
||||||
|
let buffer = sample.buffer().expect("sample buffer");
|
||||||
|
let map = buffer.map_readable().expect("buffer readable");
|
||||||
|
let bytes = map.as_slice();
|
||||||
|
let mean = bytes.iter().map(|value| u64::from(*value)).sum::<u64>() / bytes.len().max(1) as u64;
|
||||||
|
mean.min(u64::from(u8::MAX)) as u8
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn probe_video_pts_are_lag_capped_like_audio() {
|
fn probe_video_pts_are_lag_capped_like_audio() {
|
||||||
let rebaser = crate::live_capture_clock::DurationPacedSourcePtsRebaser::default();
|
let rebaser = crate::live_capture_clock::DurationPacedSourcePtsRebaser::default();
|
||||||
@ -415,4 +464,11 @@ async fn runtime_probe_video_packets_change_across_a_pulse_boundary() {
|
|||||||
assert_ne!(dark_packet.data, pulse_packet.data);
|
assert_ne!(dark_packet.data, pulse_packet.data);
|
||||||
assert!(!dark_packet.data.is_empty());
|
assert!(!dark_packet.data.is_empty());
|
||||||
assert!(!pulse_packet.data.is_empty());
|
assert!(!pulse_packet.data.is_empty());
|
||||||
|
|
||||||
|
let dark_mean = decode_mjpeg_packet_mean_luma(&dark_packet);
|
||||||
|
let pulse_mean = decode_mjpeg_packet_mean_luma(&pulse_packet);
|
||||||
|
assert!(
|
||||||
|
pulse_mean > dark_mean.saturating_add(100),
|
||||||
|
"expected decoded pulse frame to be much brighter than decoded dark frame, got dark={dark_mean} pulse={pulse_mean}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.14.19"
|
version = "0.14.20"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -499,6 +499,7 @@ fi
|
|||||||
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-0}"
|
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US:-0}"
|
||||||
printf 'LESAVKA_UPSTREAM_PAIR_SLACK_US=%s\n' "${LESAVKA_UPSTREAM_PAIR_SLACK_US:-20000}"
|
printf 'LESAVKA_UPSTREAM_PAIR_SLACK_US=%s\n' "${LESAVKA_UPSTREAM_PAIR_SLACK_US:-20000}"
|
||||||
printf 'LESAVKA_UPSTREAM_STALE_DROP_MS=%s\n' "${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"
|
printf 'LESAVKA_UPSTREAM_STALE_DROP_MS=%s\n' "${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"
|
||||||
|
printf 'LESAVKA_UVC_CODEC=%s\n' "${LESAVKA_UVC_CODEC:-mjpeg}"
|
||||||
} | sudo tee /etc/lesavka/server.env >/dev/null
|
} | sudo tee /etc/lesavka/server.env >/dev/null
|
||||||
|
|
||||||
echo "==> 6a. Systemd units - lesavka-core"
|
echo "==> 6a. Systemd units - lesavka-core"
|
||||||
@ -514,6 +515,7 @@ ExecStart=/usr/local/bin/lesavka-core.sh
|
|||||||
RemainAfterExit=yes
|
RemainAfterExit=yes
|
||||||
Environment=LESAVKA_UVC_FALLBACK=0
|
Environment=LESAVKA_UVC_FALLBACK=0
|
||||||
Environment=LESAVKA_UVC_CODEC=mjpeg
|
Environment=LESAVKA_UVC_CODEC=mjpeg
|
||||||
|
EnvironmentFile=-/etc/lesavka/server.env
|
||||||
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_SYS_MODULE
|
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_SYS_MODULE
|
||||||
AmbientCapabilities=CAP_SYS_MODULE
|
AmbientCapabilities=CAP_SYS_MODULE
|
||||||
MountFlags=slave
|
MountFlags=slave
|
||||||
@ -572,7 +574,7 @@ if [[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || ! is_attached_state "$UDC_STATE";
|
|||||||
LESAVKA_DETACH_CLEAR_UDC=1 \
|
LESAVKA_DETACH_CLEAR_UDC=1 \
|
||||||
LESAVKA_RELOAD_UVCVIDEO=1 \
|
LESAVKA_RELOAD_UVCVIDEO=1 \
|
||||||
LESAVKA_UVC_FALLBACK=1 \
|
LESAVKA_UVC_FALLBACK=1 \
|
||||||
LESAVKA_UVC_CODEC=mjpeg \
|
LESAVKA_UVC_CODEC="${LESAVKA_UVC_CODEC:-mjpeg}" \
|
||||||
/usr/local/bin/lesavka-core.sh
|
/usr/local/bin/lesavka-core.sh
|
||||||
sudo systemctl restart lesavka-core
|
sudo systemctl restart lesavka-core
|
||||||
echo "✅ lesavka-core installed and restarted..."
|
echo "✅ lesavka-core installed and restarted..."
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.14.19"
|
version = "0.14.20"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -90,6 +90,17 @@ fn select_hdmi_codec(hw_decode: bool) -> CameraCodec {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_uvc_codec(uvc_env: Option<&HashMap<String, String>>) -> CameraCodec {
|
||||||
|
std::env::var("LESAVKA_UVC_CODEC")
|
||||||
|
.ok()
|
||||||
|
.or_else(|| std::env::var("LESAVKA_CAM_CODEC").ok())
|
||||||
|
.or_else(|| uvc_env.and_then(|env| env.get("LESAVKA_UVC_CODEC").cloned()))
|
||||||
|
.or_else(|| uvc_env.and_then(|env| env.get("LESAVKA_CAM_CODEC").cloned()))
|
||||||
|
.as_deref()
|
||||||
|
.and_then(parse_camera_codec)
|
||||||
|
.unwrap_or(CameraCodec::Mjpeg)
|
||||||
|
}
|
||||||
|
|
||||||
fn select_hdmi_config(hdmi: Option<HdmiConnector>) -> CameraConfig {
|
fn select_hdmi_config(hdmi: Option<HdmiConnector>) -> CameraConfig {
|
||||||
let hw_decode = has_hw_h264_decode();
|
let hw_decode = has_hw_h264_decode();
|
||||||
let (default_width, default_height) = if hw_decode { (1920, 1080) } else { (1280, 720) };
|
let (default_width, default_height) = if hw_decode { (1920, 1080) } else { (1280, 720) };
|
||||||
@ -144,10 +155,11 @@ fn select_uvc_config() -> CameraConfig {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(25);
|
.unwrap_or(25);
|
||||||
|
let codec = select_uvc_codec(None);
|
||||||
|
|
||||||
CameraConfig {
|
CameraConfig {
|
||||||
output: CameraOutput::Uvc,
|
output: CameraOutput::Uvc,
|
||||||
codec: CameraCodec::Mjpeg,
|
codec,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fps,
|
fps,
|
||||||
@ -182,10 +194,11 @@ fn select_uvc_config() -> CameraConfig {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(25);
|
.unwrap_or(25);
|
||||||
|
let codec = select_uvc_codec(Some(&uvc_env));
|
||||||
|
|
||||||
CameraConfig {
|
CameraConfig {
|
||||||
output: CameraOutput::Uvc,
|
output: CameraOutput::Uvc,
|
||||||
codec: CameraCodec::Mjpeg,
|
codec,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fps,
|
fps,
|
||||||
|
|||||||
@ -32,6 +32,18 @@ fn camera_config_env_override_prefers_uvc_values() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn camera_config_env_override_honors_uvc_codec() {
|
||||||
|
with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || {
|
||||||
|
with_var("LESAVKA_UVC_CODEC", Some("h264"), || {
|
||||||
|
let cfg = update_camera_config();
|
||||||
|
assert_eq!(cfg.output, CameraOutput::Uvc);
|
||||||
|
assert_eq!(cfg.codec, CameraCodec::H264);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn hdmi_camera_profile_honors_installed_1080p_override() {
|
fn hdmi_camera_profile_honors_installed_1080p_override() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user