fix: prefer clean UVC upstream handoff

This commit is contained in:
Brad Stein 2026-05-19 12:34:49 -03:00
parent 31b828808c
commit 2d79a3144a
12 changed files with 95 additions and 23 deletions

6
Cargo.lock generated
View File

@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "lesavka_client" name = "lesavka_client"
version = "0.25.5" version = "0.26.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1692,7 +1692,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_common" name = "lesavka_common"
version = "0.25.5" version = "0.26.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@ -1704,7 +1704,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_server" name = "lesavka_server"
version = "0.25.5" version = "0.26.0"
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.25.5" version = "0.26.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

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

View File

@ -104,7 +104,12 @@ if [[ "${REQUESTED_UVC_CODEC,,}" != "${INSTALL_UVC_CODEC}" ]]; then
echo "⚠️ UVC gadget output codec '${REQUESTED_UVC_CODEC}' is not supported by the MJPEG UVC helper; using '${INSTALL_UVC_CODEC}' for the host-facing gadget." echo "⚠️ UVC gadget output codec '${REQUESTED_UVC_CODEC}' is not supported by the MJPEG UVC helper; using '${INSTALL_UVC_CODEC}' for the host-facing gadget."
echo " Use LESAVKA_INSTALL_CAM_CODEC=hevc to choose HEVC for the client-to-server upstream transport." echo " Use LESAVKA_INSTALL_CAM_CODEC=hevc to choose HEVC for the client-to-server upstream transport."
fi fi
INSTALL_CAM_CODEC=$(normalize_cam_codec "${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-mjpeg}}") INSTALL_CAM_CODEC_EXPLICIT=0
if [[ -n "${LESAVKA_INSTALL_CAM_CODEC+x}" || -n "${LESAVKA_CAM_CODEC+x}" ]]; then
INSTALL_CAM_CODEC_EXPLICIT=1
fi
REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-hevc}}
INSTALL_CAM_CODEC=$(normalize_cam_codec "${REQUESTED_CAM_CODEC}")
INSTALL_UPLINK_AUDIO_CODEC=${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}} INSTALL_UPLINK_AUDIO_CODEC=${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}}
INSTALL_UVC_FRAME_META=${LESAVKA_INSTALL_UVC_FRAME_META:-${LESAVKA_UVC_FRAME_META:-0}} INSTALL_UVC_FRAME_META=${LESAVKA_INSTALL_UVC_FRAME_META:-${LESAVKA_UVC_FRAME_META:-0}}
INSTALL_UVC_FRAME_META_LOG_PATH=${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LESAVKA_UVC_FRAME_META_LOG_PATH:-/tmp/lesavka-uvc-frame-meta.jsonl}} INSTALL_UVC_FRAME_META_LOG_PATH=${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LESAVKA_UVC_FRAME_META_LOG_PATH:-/tmp/lesavka-uvc-frame-meta.jsonl}}
@ -259,6 +264,11 @@ ensure_hevc_decode_support() {
else else
echo "❌ no hardware HEVC decoder exposed to GStreamer; Lesavka will not fall back to avdec_h265 in production." >&2 echo "❌ no hardware HEVC decoder exposed to GStreamer; Lesavka will not fall back to avdec_h265 in production." >&2
if [[ "$INSTALL_UVC_CODEC" == "hevc" || "$INSTALL_CAM_CODEC" == "hevc" ]]; then if [[ "$INSTALL_UVC_CODEC" == "hevc" || "$INSTALL_CAM_CODEC" == "hevc" ]]; then
if [[ "$INSTALL_CAM_CODEC" == "hevc" && "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then
echo " Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress." >&2
INSTALL_CAM_CODEC=mjpeg
return 0
fi
echo " Install/repair v4l2slh265dec or set LESAVKA_INSTALL_CAM_CODEC=mjpeg before running the server installer." >&2 echo " Install/repair v4l2slh265dec or set LESAVKA_INSTALL_CAM_CODEC=mjpeg before running the server installer." >&2
exit 1 exit 1
fi fi
@ -278,6 +288,14 @@ ensure_hevc_decode_support() {
echo "✅ hardware HEVC decoder passed a real 1280x720 decode smoke: $hevc_decoder" echo "✅ hardware HEVC decoder passed a real 1280x720 decode smoke: $hevc_decoder"
else else
if [[ "$INSTALL_CAM_CODEC" == "hevc" ]]; then if [[ "$INSTALL_CAM_CODEC" == "hevc" ]]; then
if [[ "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then
echo "⚠️ hardware HEVC decoder is exposed but the synthetic 1280x720 decode smoke failed: $hevc_decoder" >&2
echo " smoke log: $hevc_smoke_log" >&2
sed -n '1,120p' "$hevc_smoke_log" >&2 || true
echo " Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress." >&2
INSTALL_CAM_CODEC=mjpeg
return 0
fi
echo "❌ hardware HEVC decoder is exposed but failed a real 1280x720 decode smoke: $hevc_decoder" >&2 echo "❌ hardware HEVC decoder is exposed but failed a real 1280x720 decode smoke: $hevc_decoder" >&2
echo " smoke log: $hevc_smoke_log" >&2 echo " smoke log: $hevc_smoke_log" >&2
sed -n '1,120p' "$hevc_smoke_log" >&2 || true sed -n '1,120p' "$hevc_smoke_log" >&2 || true
@ -1647,7 +1665,7 @@ SERVER_ENV_TMP=$(mktemp)
printf 'LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}" printf 'LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}" printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}" printf 'LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-25}" printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-50}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}" printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}" printf 'LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"
printf 'LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD:-1}" printf 'LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD=%s\n' "${LESAVKA_UVC_DIRECT_MJPEG_VISUAL_GUARD:-1}"

View File

@ -16,7 +16,7 @@ bench = false
[package] [package]
name = "lesavka_server" name = "lesavka_server"
version = "0.25.5" version = "0.26.0"
edition = "2024" edition = "2024"
autobins = false autobins = false

View File

@ -18,7 +18,7 @@ const DEFAULT_DIRECT_MJPEG_MIN_REFERENCE_BYTES: u32 = 48 * 1024;
const DEFAULT_DIRECT_MJPEG_PROFILE_MISMATCH_REJECT: bool = false; const DEFAULT_DIRECT_MJPEG_PROFILE_MISMATCH_REJECT: bool = false;
const DEFAULT_DIRECT_MJPEG_NORMALIZE: bool = true; const DEFAULT_DIRECT_MJPEG_NORMALIZE: bool = true;
const DEFAULT_DIRECT_MJPEG_JPEG_QUALITY: u32 = 60; const DEFAULT_DIRECT_MJPEG_JPEG_QUALITY: u32 = 60;
const DEFAULT_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS: u32 = 25; const DEFAULT_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS: u32 = 50;
const DEFAULT_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT: u32 = 30; const DEFAULT_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT: u32 = 30;
const DEFAULT_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB: u32 = 384; const DEFAULT_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB: u32 = 384;

View File

@ -35,7 +35,7 @@ fn direct_mjpeg_normalization_defaults_on_and_clamps_tuning() {
|| { || {
assert!(super::direct_mjpeg_normalize_enabled()); assert!(super::direct_mjpeg_normalize_enabled());
assert_eq!(super::direct_mjpeg_jpeg_quality(), 60); assert_eq!(super::direct_mjpeg_jpeg_quality(), 60);
assert_eq!(super::direct_mjpeg_normalize_pull_timeout_ms(), 25); assert_eq!(super::direct_mjpeg_normalize_pull_timeout_ms(), 50);
assert_eq!(super::direct_mjpeg_normalize_miss_limit(), 30); assert_eq!(super::direct_mjpeg_normalize_miss_limit(), 30);
assert_eq!( assert_eq!(
super::direct_mjpeg_normalize_rss_limit_kb(), super::direct_mjpeg_normalize_rss_limit_kb(),

View File

@ -229,7 +229,13 @@ impl WebcamSink {
return; return;
} }
let buf = gst::Buffer::from_slice(pkt.data.clone()); let mut buf = gst::Buffer::from_slice(pkt.data.clone());
if let Some(meta) = buf.get_mut() {
let ts = gst::ClockTime::from_useconds(pkt.pts);
meta.set_pts(Some(ts));
meta.set_dts(Some(ts));
meta.set_duration(Some(gst::ClockTime::from_useconds(self.frame_step_us)));
}
if let Err(err) = src.push_buffer(buf) { if let Err(err) = src.push_buffer(buf) {
tracing::warn!( tracing::warn!(
target:"lesavka_server::video", target:"lesavka_server::video",

View File

@ -176,3 +176,30 @@ fn webcam_bus_watch_stops_promptly_on_drop() {
"webcam bus watcher should not outlive dropped webcam sinks" "webcam bus watcher should not outlive dropped webcam sinks"
); );
} }
#[cfg(not(coverage))]
#[test]
fn direct_mjpeg_normalizer_branch_reencodes_a_valid_frame() {
use gstreamer as gst;
use gstreamer::prelude::ElementExt;
const FIXTURE: &[u8] = include_bytes!("../../bin/lesavka_uvc/idle_1280x720_black.jpg");
gst::init().expect("gstreamer init");
let pipeline = gst::Pipeline::new();
let (src, sink) = super::build_direct_mjpeg_normalize_branch(&pipeline, 1280, 720, 30)
.expect("normalizer branch");
pipeline
.set_state(gst::State::Playing)
.expect("normalizer pipeline playing");
src.push_buffer(gst::Buffer::from_slice(FIXTURE))
.expect("push fixture");
let sample = super::freshest_direct_mjpeg_sample(&sink).expect("normalized sample");
let buffer = sample.buffer().expect("sample buffer");
let map = buffer.map_readable().expect("readable normalized buffer");
assert!(map.as_slice().starts_with(&[0xff, 0xd8, 0xff]));
assert!(map.as_slice().ends_with(&[0xff, 0xd9]));
pipeline.set_state(gst::State::Null).ok();
}

View File

@ -84,9 +84,16 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
assert!(SERVER_INSTALL.contains("${LESAVKA_CAM_FPS:-30}")); assert!(SERVER_INSTALL.contains("${LESAVKA_CAM_FPS:-30}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}")); assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}"));
assert!(SERVER_INSTALL.contains("normalize_cam_codec()")); assert!(SERVER_INSTALL.contains("normalize_cam_codec()"));
assert!(SERVER_INSTALL.contains( assert!(
"INSTALL_CAM_CODEC=$(normalize_cam_codec \"${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-mjpeg}}\")" SERVER_INSTALL.contains(
)); "REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-hevc}}"
)
);
assert!(
SERVER_INSTALL
.contains("INSTALL_CAM_CODEC=$(normalize_cam_codec \"${REQUESTED_CAM_CODEC}\")")
);
assert!(SERVER_INSTALL.contains("INSTALL_CAM_CODEC_EXPLICIT=0"));
assert!( assert!(
SERVER_INSTALL SERVER_INSTALL
.contains("${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}}") .contains("${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}}")
@ -194,7 +201,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-25}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-50}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"));
assert!( assert!(
@ -279,8 +286,9 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
); );
assert!( assert!(
SERVER_INSTALL.contains("Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven") SERVER_INSTALL.contains("Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven")
&& SERVER_INSTALL.contains("Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired"), && SERVER_INSTALL.contains("Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired")
"explicit HEVC installs should fail loud instead of producing a black UVC webcam feed" && SERVER_INSTALL.contains("Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress."),
"explicit HEVC installs should fail loud while default HEVC installs can safely fall back"
); );
assert!( assert!(
!SERVER_INSTALL !SERVER_INSTALL

View File

@ -1,11 +1,11 @@
// Regression contract for preserving codec settings across upgrades. // Regression contract for preserving codec settings across upgrades.
// //
// Scope: keep safe MJPEG ingress and MJPEG UVC output defaults explicit, while still // Scope: keep safe HEVC/MJPEG ingress selection and MJPEG UVC output defaults
// allowing operator-provided install overrides. // explicit, while still allowing operator-provided install overrides.
// Targets: server/client install scripts and client camera capture defaults. // Targets: server/client install scripts and client camera capture defaults.
// Why: Lesavka now supports both MJPEG and HEVC upstream media, and installer // Why: Lesavka now supports both MJPEG and HEVC upstream media, and installer
// reruns must not silently select a HEVC profile that produces black frames // reruns may prefer HEVC only when hardware decode is proven, and must fall
// when hardware decode is not proven. // back to MJPEG instead of producing black frames when it is not.
const SERVER_INSTALL: &str = include_str!(concat!( const SERVER_INSTALL: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"), env!("CARGO_MANIFEST_DIR"),
@ -17,14 +17,17 @@ const CLIENT_CAMERA: &str = include_str!(concat!(
)); ));
#[test] #[test]
fn server_install_defaults_to_mjpeg_ingress_and_mjpeg_uvc_output() { fn server_install_defaults_to_hevc_ingress_with_mjpeg_fallback_and_mjpeg_uvc_output() {
for marker in [ for marker in [
"PERSISTED_UVC_CODEC=$(persisted_uvc_value LESAVKA_UVC_CODEC || true)", "PERSISTED_UVC_CODEC=$(persisted_uvc_value LESAVKA_UVC_CODEC || true)",
"normalize_uvc_codec()", "normalize_uvc_codec()",
"normalize_cam_codec()", "normalize_cam_codec()",
"REQUESTED_UVC_CODEC=${LESAVKA_INSTALL_UVC_CODEC:-${PERSISTED_UVC_CODEC:-mjpeg}}", "REQUESTED_UVC_CODEC=${LESAVKA_INSTALL_UVC_CODEC:-${PERSISTED_UVC_CODEC:-mjpeg}}",
"INSTALL_UVC_CODEC=$(normalize_uvc_codec \"$REQUESTED_UVC_CODEC\")", "INSTALL_UVC_CODEC=$(normalize_uvc_codec \"$REQUESTED_UVC_CODEC\")",
"INSTALL_CAM_CODEC=$(normalize_cam_codec \"${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-mjpeg}}\")", "REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-hevc}}",
"INSTALL_CAM_CODEC=$(normalize_cam_codec \"${REQUESTED_CAM_CODEC}\")",
"INSTALL_CAM_CODEC_EXPLICIT=0",
"Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress.",
"printf 'LESAVKA_CAM_CODEC=%s\\n' \"${INSTALL_CAM_CODEC}\"", "printf 'LESAVKA_CAM_CODEC=%s\\n' \"${INSTALL_CAM_CODEC}\"",
"printf 'LESAVKA_UVC_CODEC=%s\\n' \"${INSTALL_UVC_CODEC}\"", "printf 'LESAVKA_UVC_CODEC=%s\\n' \"${INSTALL_UVC_CODEC}\"",
"\"LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}\"", "\"LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}\"",

View File

@ -115,6 +115,7 @@ fn installer_keeps_the_native_normalizer_memory_bounded_by_default() {
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"));
assert!(!SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-0}")); assert!(!SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-0}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}")); assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"));
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-50}"));
} }
#[test] #[test]
@ -161,6 +162,15 @@ fn opt_in_normalizer_has_rss_fuse_before_per_frame_gstreamer_allocation() {
"direct_mjpeg_normalize_rss_limit_kb()", "direct_mjpeg_normalize_rss_limit_kb()",
"gst::Buffer::from_slice(pkt.data.clone())", "gst::Buffer::from_slice(pkt.data.clone())",
); );
let normalizer_push = WEBCAM_FRAME_HANDOFF
.split("gst::Buffer::from_slice(pkt.data.clone())")
.nth(1)
.expect("direct normalizer buffer allocation block");
assert_ordered(
normalizer_push,
"meta.set_duration(Some(gst::ClockTime::from_useconds(self.frame_step_us)))",
"src.push_buffer(buf)",
);
} }
#[test] #[test]