media: bake mjpeg opus calibration defaults
This commit is contained in:
parent
ef9701f6e9
commit
0e5de9d21b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const AUDIO_SAMPLE_RATE: i32 = 48_000;
|
|||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
const AUDIO_CHANNELS: usize = 2;
|
const AUDIO_CHANNELS: usize = 2;
|
||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
const AUDIO_CHUNK_MS: u64 = 10;
|
const AUDIO_CHUNK_MS: u64 = 20;
|
||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
const AUDIO_PULSE_FREQUENCY_HZ: f64 = 1_800.0;
|
const AUDIO_PULSE_FREQUENCY_HZ: f64 = 1_800.0;
|
||||||
#[cfg(any(not(coverage), test))]
|
#[cfg(any(not(coverage), test))]
|
||||||
|
|||||||
@ -271,6 +271,7 @@ fn spawn_audio_thread(
|
|||||||
let packet = AudioPacket {
|
let packet = AudioPacket {
|
||||||
id: 0,
|
id: 0,
|
||||||
pts: source_pts_us,
|
pts: source_pts_us,
|
||||||
|
frame_duration_us: AUDIO_CHUNK_MS.saturating_mul(1_000) as u32,
|
||||||
data: chunk,
|
data: chunk,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,12 +19,17 @@ async fn runtime_audio_probe_emits_nontrivial_pcm_packets() {
|
|||||||
let mut packet_count = 0usize;
|
let mut packet_count = 0usize;
|
||||||
let mut total_bytes = 0usize;
|
let mut total_bytes = 0usize;
|
||||||
let mut largest_packet = 0usize;
|
let mut largest_packet = 0usize;
|
||||||
|
let expected_duration_us = 20_000;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next = audio_queue.pop_fresh().await;
|
let next = audio_queue.pop_fresh().await;
|
||||||
let Some(packet) = next.packet else {
|
let Some(packet) = next.packet else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
assert_eq!(
|
||||||
|
packet.frame_duration_us, expected_duration_us,
|
||||||
|
"synthetic audio metadata should match its PCM payload duration"
|
||||||
|
);
|
||||||
packet_count += 1;
|
packet_count += 1;
|
||||||
total_bytes += packet.data.len();
|
total_bytes += packet.data.len();
|
||||||
largest_packet = largest_packet.max(packet.data.len());
|
largest_packet = largest_packet.max(packet.data.len());
|
||||||
@ -334,7 +339,7 @@ async fn runtime_probe_hevc_video_and_audio_can_form_one_local_bundle() {
|
|||||||
videos.len()
|
videos.len()
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
audios.len() >= 120,
|
audios.len() >= 90,
|
||||||
"expected local PCM audio packets, got {}",
|
"expected local PCM audio packets, got {}",
|
||||||
audios.len()
|
audios.len()
|
||||||
);
|
);
|
||||||
@ -357,8 +362,8 @@ async fn runtime_probe_hevc_video_and_audio_can_form_one_local_bundle() {
|
|||||||
video.pts
|
video.pts
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
paired_audio.iter().any(|packet| packet.data.len() >= 1_920),
|
paired_audio.iter().any(|packet| packet.data.len() >= 3_840),
|
||||||
"expected paired audio to carry full 10ms stereo PCM packets"
|
"expected paired audio to carry full 20ms stereo PCM packets"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,9 @@
|
|||||||
//! streaming `UpstreamMediaBundle` messages to the server.
|
//! streaming `UpstreamMediaBundle` messages to the server.
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use lesavka_common::lesavka::{
|
use lesavka_common::{
|
||||||
AudioPacket, UpstreamMediaBundle, VideoPacket, relay_client::RelayClient,
|
audio_transport::{self, AudioTransportProfile, UpstreamAudioCodec},
|
||||||
|
lesavka::{AudioPacket, UpstreamMediaBundle, VideoPacket, relay_client::RelayClient},
|
||||||
};
|
};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -43,6 +44,7 @@ pub async fn run_bundled_probe_stream(
|
|||||||
.context("opening sync probe audio dump")?;
|
.context("opening sync probe audio dump")?;
|
||||||
let mut send_log = open_debug_dump("LESAVKA_SYNC_PROBE_SEND_LOG")
|
let mut send_log = open_debug_dump("LESAVKA_SYNC_PROBE_SEND_LOG")
|
||||||
.context("opening sync probe send log")?;
|
.context("opening sync probe send log")?;
|
||||||
|
let mut audio_transport = ProbeAudioTransport::from_env();
|
||||||
let outbound = async_stream::stream! {
|
let outbound = async_stream::stream! {
|
||||||
let mut pending_audio = Vec::<AudioPacket>::new();
|
let mut pending_audio = Vec::<AudioPacket>::new();
|
||||||
let mut audio_done = false;
|
let mut audio_done = false;
|
||||||
@ -79,6 +81,7 @@ pub async fn run_bundled_probe_stream(
|
|||||||
&mut pending_audio,
|
&mut pending_audio,
|
||||||
&mut audio_done,
|
&mut audio_done,
|
||||||
&mut audio_seq,
|
&mut audio_seq,
|
||||||
|
&mut audio_transport,
|
||||||
audio_dump.as_mut(),
|
audio_dump.as_mut(),
|
||||||
probe_start,
|
probe_start,
|
||||||
).await;
|
).await;
|
||||||
@ -129,8 +132,10 @@ pub async fn run_bundled_probe_stream(
|
|||||||
next.queue_depth,
|
next.queue_depth,
|
||||||
probe_start,
|
probe_start,
|
||||||
);
|
);
|
||||||
write_probe_audio_dump(audio_dump.as_mut(), &packet);
|
if let Some(packet) = audio_transport.encode_packet(packet) {
|
||||||
pending_audio.push(packet);
|
write_probe_audio_dump(audio_dump.as_mut(), &packet);
|
||||||
|
pending_audio.push(packet);
|
||||||
|
}
|
||||||
retain_newest_probe_audio(&mut pending_audio);
|
retain_newest_probe_audio(&mut pending_audio);
|
||||||
} else if next.closed {
|
} else if next.closed {
|
||||||
audio_done = true;
|
audio_done = true;
|
||||||
@ -176,20 +181,26 @@ fn build_probe_bundle(
|
|||||||
capture_start_us: u64,
|
capture_start_us: u64,
|
||||||
capture_end_us: u64,
|
capture_end_us: u64,
|
||||||
) -> UpstreamMediaBundle {
|
) -> UpstreamMediaBundle {
|
||||||
UpstreamMediaBundle {
|
let profile = audio
|
||||||
|
.first()
|
||||||
|
.map(audio_transport::packet_audio_profile)
|
||||||
|
.unwrap_or_else(AudioTransportProfile::pcm_s16le);
|
||||||
|
let mut bundle = UpstreamMediaBundle {
|
||||||
session_id,
|
session_id,
|
||||||
seq,
|
seq,
|
||||||
capture_start_us,
|
capture_start_us,
|
||||||
capture_end_us,
|
capture_end_us,
|
||||||
video,
|
video,
|
||||||
audio,
|
audio,
|
||||||
audio_sample_rate: 48_000,
|
audio_sample_rate: profile.sample_rate,
|
||||||
audio_channels: 2,
|
audio_channels: profile.channels,
|
||||||
audio_encoding: lesavka_common::lesavka::AudioEncoding::PcmS16le as i32,
|
audio_encoding: profile.encoding as i32,
|
||||||
video_width: camera.width,
|
video_width: camera.width,
|
||||||
video_height: camera.height,
|
video_height: camera.height,
|
||||||
video_fps: camera.fps,
|
video_fps: camera.fps,
|
||||||
}
|
};
|
||||||
|
audio_transport::mark_bundle_audio_profile(&mut bundle, profile);
|
||||||
|
bundle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drain one short audio grace window when a video packet arrives first.
|
/// Drain one short audio grace window when a video packet arrives first.
|
||||||
@ -204,6 +215,7 @@ async fn collect_probe_audio_grace(
|
|||||||
pending_audio: &mut Vec<AudioPacket>,
|
pending_audio: &mut Vec<AudioPacket>,
|
||||||
audio_done: &mut bool,
|
audio_done: &mut bool,
|
||||||
audio_seq: &mut u64,
|
audio_seq: &mut u64,
|
||||||
|
audio_transport: &mut ProbeAudioTransport,
|
||||||
audio_dump: Option<&mut File>,
|
audio_dump: Option<&mut File>,
|
||||||
probe_start: Instant,
|
probe_start: Instant,
|
||||||
) {
|
) {
|
||||||
@ -216,14 +228,65 @@ async fn collect_probe_audio_grace(
|
|||||||
};
|
};
|
||||||
if let Some(mut packet) = next.packet {
|
if let Some(mut packet) = next.packet {
|
||||||
stamp_probe_audio_packet(&mut packet, audio_seq, next.queue_depth, probe_start);
|
stamp_probe_audio_packet(&mut packet, audio_seq, next.queue_depth, probe_start);
|
||||||
write_probe_audio_dump(audio_dump, &packet);
|
if let Some(packet) = audio_transport.encode_packet(packet) {
|
||||||
pending_audio.push(packet);
|
write_probe_audio_dump(audio_dump, &packet);
|
||||||
|
pending_audio.push(packet);
|
||||||
|
}
|
||||||
retain_newest_probe_audio(pending_audio);
|
retain_newest_probe_audio(pending_audio);
|
||||||
} else if next.closed {
|
} else if next.closed {
|
||||||
*audio_done = true;
|
*audio_done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runtime audio transport for synthetic probe packets.
|
||||||
|
///
|
||||||
|
/// Inputs: `LESAVKA_UPLINK_AUDIO_CODEC` and installed GStreamer plugins.
|
||||||
|
/// Outputs: packet bytes and metadata matching the selected upstream audio
|
||||||
|
/// route. Why: the blind client->server->RCT test must exercise the same Opus
|
||||||
|
/// compression path as the live microphone, not silently measure PCM.
|
||||||
|
struct ProbeAudioTransport {
|
||||||
|
encoder: Option<crate::input::audio_codec::OpusPacketEncoder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProbeAudioTransport {
|
||||||
|
fn from_env() -> Self {
|
||||||
|
match crate::input::audio_codec::requested_upstream_audio_codec_from_env() {
|
||||||
|
UpstreamAudioCodec::PcmS16le => Self { encoder: None },
|
||||||
|
UpstreamAudioCodec::Opus => match crate::input::audio_codec::OpusPacketEncoder::new() {
|
||||||
|
Ok(encoder) => {
|
||||||
|
tracing::info!("🧪 sync probe Opus audio transport enabled");
|
||||||
|
Self {
|
||||||
|
encoder: Some(encoder),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"🧪⚠️ sync probe Opus requested but unavailable ({err:#}); falling back to PCM"
|
||||||
|
);
|
||||||
|
Self { encoder: None }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_packet(&mut self, mut packet: AudioPacket) -> Option<AudioPacket> {
|
||||||
|
audio_transport::mark_packet_pcm_s16le(&mut packet);
|
||||||
|
let Some(encoder) = self.encoder.as_mut() else {
|
||||||
|
return Some(packet);
|
||||||
|
};
|
||||||
|
match encoder.encode_packet(packet.clone()) {
|
||||||
|
Ok(Some(encoded)) => Some(encoded),
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"🧪⚠️ sync probe Opus encode failed; sending PCM fallback for this packet: {err:#}"
|
||||||
|
);
|
||||||
|
Some(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stamp one synthetic audio packet with client-side transport telemetry.
|
/// Stamp one synthetic audio packet with client-side transport telemetry.
|
||||||
///
|
///
|
||||||
/// Inputs: mutable packet, sequence counter, queue depth, and probe clock.
|
/// Inputs: mutable packet, sequence counter, queue depth, and probe clock.
|
||||||
|
|||||||
@ -88,6 +88,59 @@ fn hevc_probe_bundle_preserves_paired_media_and_server_metadata() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Verifies the blind sync probe can describe compressed Opus audio bundles.
|
||||||
|
///
|
||||||
|
/// Inputs: one synthetic video packet plus a pre-compressed Opus-like audio
|
||||||
|
/// packet. Outputs: assertions over bundle metadata. Why: the client-to-RCT
|
||||||
|
/// hardware probe must not silently report PCM evidence when the operator asked
|
||||||
|
/// to validate the Opus upstream route.
|
||||||
|
fn probe_bundle_uses_opus_metadata_when_audio_packets_are_opus() {
|
||||||
|
let camera = CameraConfig {
|
||||||
|
codec: CameraCodec::Hevc,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
fps: 30,
|
||||||
|
};
|
||||||
|
let probe_start = Instant::now();
|
||||||
|
let mut video = VideoPacket {
|
||||||
|
pts: 1_000_000,
|
||||||
|
data: vec![0, 0, 0, 1, 0x26, 0xaa, 0xbb],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut video_seq = 0;
|
||||||
|
stamp_probe_video_packet(&mut video, &mut video_seq, 1, camera.fps, probe_start);
|
||||||
|
|
||||||
|
let mut audio = AudioPacket {
|
||||||
|
pts: 1_000_000,
|
||||||
|
data: vec![0x7f; 160],
|
||||||
|
frame_duration_us: 20_000,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
lesavka_common::audio_transport::mark_packet_opus(&mut audio);
|
||||||
|
let mut audio_seq = 0;
|
||||||
|
stamp_probe_audio_packet(&mut audio, &mut audio_seq, 1, probe_start);
|
||||||
|
|
||||||
|
let bundle = build_probe_bundle(
|
||||||
|
PROBE_BUNDLE_SESSION_ID,
|
||||||
|
42,
|
||||||
|
&camera,
|
||||||
|
Some(video),
|
||||||
|
vec![audio],
|
||||||
|
1_000_000,
|
||||||
|
1_000_000,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
bundle.audio_encoding,
|
||||||
|
lesavka_common::lesavka::AudioEncoding::Opus as i32
|
||||||
|
);
|
||||||
|
assert_eq!(bundle.audio_sample_rate, 48_000);
|
||||||
|
assert_eq!(bundle.audio_channels, 2);
|
||||||
|
assert_eq!(bundle.audio[0].frame_duration_us, 20_000);
|
||||||
|
assert_eq!(bundle.audio[0].data.len(), 160);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Verifies a synthetic HEVC event train leaves as paired A/V bundles.
|
/// Verifies a synthetic HEVC event train leaves as paired A/V bundles.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -13,6 +13,10 @@ use std::time::Duration;
|
|||||||
use crate::input::camera::{CameraCodec, CameraConfig};
|
use crate::input::camera::{CameraCodec, CameraConfig};
|
||||||
use crate::sync_probe::schedule::PulseSchedule;
|
use crate::sync_probe::schedule::PulseSchedule;
|
||||||
|
|
||||||
|
const AUDIO_SAMPLE_RATE_HZ: u32 = 48_000;
|
||||||
|
const AUDIO_CHANNEL_COUNT: u32 = 2;
|
||||||
|
const AUDIO_CHUNK_MS: u32 = 20;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ProbeTimeline {
|
pub struct ProbeTimeline {
|
||||||
schema: &'static str,
|
schema: &'static str,
|
||||||
@ -72,9 +76,9 @@ impl ProbeTimeline {
|
|||||||
camera_height: camera.height,
|
camera_height: camera.height,
|
||||||
camera_fps: camera.fps,
|
camera_fps: camera.fps,
|
||||||
camera_codec: codec_label(camera.codec),
|
camera_codec: codec_label(camera.codec),
|
||||||
audio_sample_rate: 48_000,
|
audio_sample_rate: AUDIO_SAMPLE_RATE_HZ,
|
||||||
audio_channels: 2,
|
audio_channels: AUDIO_CHANNEL_COUNT,
|
||||||
audio_chunk_ms: 10,
|
audio_chunk_ms: AUDIO_CHUNK_MS,
|
||||||
warmup_us: micros(schedule.warmup_boundary()),
|
warmup_us: micros(schedule.warmup_boundary()),
|
||||||
duration_us: micros(duration),
|
duration_us: micros(duration),
|
||||||
pulse_period_ms: millis(schedule.pulse_period()),
|
pulse_period_ms: millis(schedule.pulse_period()),
|
||||||
@ -188,6 +192,9 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(timeline.warmup_us, 4_000_000);
|
assert_eq!(timeline.warmup_us, 4_000_000);
|
||||||
assert_eq!(timeline.camera_codec, "mjpeg");
|
assert_eq!(timeline.camera_codec, "mjpeg");
|
||||||
|
assert_eq!(timeline.audio_sample_rate, 48_000);
|
||||||
|
assert_eq!(timeline.audio_channels, 2);
|
||||||
|
assert_eq!(timeline.audio_chunk_ms, 20);
|
||||||
assert!(timeline.event_width_codes.is_empty());
|
assert!(timeline.event_width_codes.is_empty());
|
||||||
assert_eq!(timeline.events.len(), 4);
|
assert_eq!(timeline.events.len(), 4);
|
||||||
assert_eq!(timeline.events[0].event_id, 0);
|
assert_eq!(timeline.events[0].event_id, 0);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -72,18 +72,26 @@ DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
|||||||
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
|
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
|
||||||
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
|
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
|
||||||
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952
|
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952
|
||||||
|
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-34199
|
||||||
|
DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=513850
|
||||||
|
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199
|
||||||
|
DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712
|
||||||
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
||||||
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000
|
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000
|
||||||
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
|
DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0
|
||||||
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952
|
DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952
|
||||||
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
||||||
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
||||||
|
DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
|
||||||
|
DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
|
||||||
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
||||||
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
||||||
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
|
||||||
DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
|
||||||
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US
|
||||||
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US
|
||||||
|
DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US
|
||||||
|
DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US
|
||||||
LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000
|
LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000
|
||||||
PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=720000
|
PREVIOUS_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=720000
|
||||||
PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000
|
PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000
|
||||||
@ -1516,12 +1524,20 @@ SERVER_ENV_TMP=$(mktemp)
|
|||||||
printf 'LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
printf 'LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_PCM_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
printf 'LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_PCM_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_PCM_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_PCM_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
|
printf 'LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_OFFSET_US=%s\n' "${LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_OFFSET_US:-$DEFAULT_HEVC_OPUS_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
printf 'LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s\n' "${LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US:-$DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US}"
|
||||||
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_audio_playout_offset_us)"
|
printf 'LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s\n' "$(resolve_upstream_audio_playout_offset_us)"
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.17"
|
version = "0.22.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,8 @@ const V4L2_MEMORY_MMAP: u32 = 1;
|
|||||||
const V4L2_FIELD_NONE: u32 = 1;
|
const V4L2_FIELD_NONE: u32 = 1;
|
||||||
const V4L2_PIX_FMT_MJPEG: u32 = u32::from_le_bytes(*b"MJPG");
|
const V4L2_PIX_FMT_MJPEG: u32 = u32::from_le_bytes(*b"MJPG");
|
||||||
const MAX_MJPEG_FRAME_BYTES: usize = 8 * 1024 * 1024;
|
const MAX_MJPEG_FRAME_BYTES: usize = 8 * 1024 * 1024;
|
||||||
const EMPTY_MJPEG_FRAME: &[u8] = &[0xff, 0xd8, 0xff, 0xd9];
|
const MINIMAL_MJPEG_FRAME: &[u8] = &[0xff, 0xd8, 0xff, 0xd9];
|
||||||
|
const IDLE_MJPEG_FRAME: &[u8] = include_bytes!("lesavka_uvc/idle_1280x720_black.jpg");
|
||||||
const DEFAULT_UVC_BUFFER_COUNT: u32 = 2;
|
const DEFAULT_UVC_BUFFER_COUNT: u32 = 2;
|
||||||
const DEFAULT_UVC_IDLE_PUMP_MS: u64 = 2;
|
const DEFAULT_UVC_IDLE_PUMP_MS: u64 = 2;
|
||||||
const DEFAULT_UVC_FRAME_MAX_AGE_MS: u64 = 1_000;
|
const DEFAULT_UVC_FRAME_MAX_AGE_MS: u64 = 1_000;
|
||||||
@ -250,7 +251,7 @@ impl UvcVideoStream {
|
|||||||
fd,
|
fd,
|
||||||
buffers: Vec::new(),
|
buffers: Vec::new(),
|
||||||
frame_path: frame_spool_path(),
|
frame_path: frame_spool_path(),
|
||||||
latest_frame: EMPTY_MJPEG_FRAME.to_vec(),
|
latest_frame: IDLE_MJPEG_FRAME.to_vec(),
|
||||||
frame_max_bytes: MAX_MJPEG_FRAME_BYTES,
|
frame_max_bytes: MAX_MJPEG_FRAME_BYTES,
|
||||||
streaming: false,
|
streaming: false,
|
||||||
}
|
}
|
||||||
@ -445,7 +446,7 @@ impl UvcVideoStream {
|
|||||||
{
|
{
|
||||||
self.latest_frame = frame;
|
self.latest_frame = frame;
|
||||||
} else if !looks_like_mjpeg_frame(&self.latest_frame) {
|
} else if !looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
self.latest_frame = EMPTY_MJPEG_FRAME.to_vec();
|
self.latest_frame = IDLE_MJPEG_FRAME.to_vec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,8 +462,10 @@ impl UvcVideoStream {
|
|||||||
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
||||||
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
&self.latest_frame
|
&self.latest_frame
|
||||||
|
} else if IDLE_MJPEG_FRAME.len() <= buffer_len {
|
||||||
|
IDLE_MJPEG_FRAME
|
||||||
} else {
|
} else {
|
||||||
EMPTY_MJPEG_FRAME
|
MINIMAL_MJPEG_FRAME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,7 +512,7 @@ fn frame_spool_path() -> std::path::PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn looks_like_mjpeg_frame(frame: &[u8]) -> bool {
|
fn looks_like_mjpeg_frame(frame: &[u8]) -> bool {
|
||||||
frame.len() > EMPTY_MJPEG_FRAME.len()
|
frame.len() > MINIMAL_MJPEG_FRAME.len()
|
||||||
&& frame.starts_with(&[0xff, 0xd8])
|
&& frame.starts_with(&[0xff, 0xd8])
|
||||||
&& frame.ends_with(&[0xff, 0xd9])
|
&& frame.ends_with(&[0xff, 0xd9])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,9 @@ const DEFAULT_UVC_MJPEG_BUDGET_BYTES_PER_SEC: u32 = 10_000_000;
|
|||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
const MAX_MJPEG_FRAME_BYTES: usize = 8 * 1024 * 1024;
|
const MAX_MJPEG_FRAME_BYTES: usize = 8 * 1024 * 1024;
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
const EMPTY_MJPEG_FRAME: &[u8] = &[0xff, 0xd8, 0xff, 0xd9];
|
const MINIMAL_MJPEG_FRAME: &[u8] = &[0xff, 0xd8, 0xff, 0xd9];
|
||||||
|
#[cfg(coverage)]
|
||||||
|
const IDLE_MJPEG_FRAME: &[u8] = include_bytes!("idle_1280x720_black.jpg");
|
||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -162,7 +164,7 @@ impl UvcVideoStream {
|
|||||||
Self {
|
Self {
|
||||||
buffers: Vec::new(),
|
buffers: Vec::new(),
|
||||||
frame_path: frame_spool_path(),
|
frame_path: frame_spool_path(),
|
||||||
latest_frame: EMPTY_MJPEG_FRAME.to_vec(),
|
latest_frame: IDLE_MJPEG_FRAME.to_vec(),
|
||||||
frame_max_bytes: MAX_MJPEG_FRAME_BYTES,
|
frame_max_bytes: MAX_MJPEG_FRAME_BYTES,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,7 +181,7 @@ impl UvcVideoStream {
|
|||||||
{
|
{
|
||||||
self.latest_frame = frame;
|
self.latest_frame = frame;
|
||||||
} else if !looks_like_mjpeg_frame(&self.latest_frame) {
|
} else if !looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
self.latest_frame = EMPTY_MJPEG_FRAME.to_vec();
|
self.latest_frame = IDLE_MJPEG_FRAME.to_vec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,8 +197,10 @@ impl UvcVideoStream {
|
|||||||
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] {
|
||||||
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) {
|
||||||
&self.latest_frame
|
&self.latest_frame
|
||||||
|
} else if IDLE_MJPEG_FRAME.len() <= buffer_len {
|
||||||
|
IDLE_MJPEG_FRAME
|
||||||
} else {
|
} else {
|
||||||
EMPTY_MJPEG_FRAME
|
MINIMAL_MJPEG_FRAME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,7 +214,7 @@ fn frame_spool_path() -> std::path::PathBuf {
|
|||||||
|
|
||||||
#[cfg(coverage)]
|
#[cfg(coverage)]
|
||||||
fn looks_like_mjpeg_frame(frame: &[u8]) -> bool {
|
fn looks_like_mjpeg_frame(frame: &[u8]) -> bool {
|
||||||
frame.len() > EMPTY_MJPEG_FRAME.len()
|
frame.len() > MINIMAL_MJPEG_FRAME.len()
|
||||||
&& frame.starts_with(&[0xff, 0xd8])
|
&& frame.starts_with(&[0xff, 0xd8])
|
||||||
&& frame.ends_with(&[0xff, 0xd9])
|
&& frame.ends_with(&[0xff, 0xd9])
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
server/src/bin/lesavka_uvc/idle_1280x720_black.jpg
Normal file
BIN
server/src/bin/lesavka_uvc/idle_1280x720_black.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@ -16,13 +16,20 @@ use mode_env::{current_uvc_mode, lookup_mode_offset_us};
|
|||||||
pub use profile_offsets::{
|
pub use profile_offsets::{
|
||||||
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_AUDIO_OFFSET_US,
|
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_AUDIO_OFFSET_US,
|
||||||
FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_AUDIO_OFFSET_US,
|
FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_AUDIO_OFFSET_US,
|
||||||
|
FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_VIDEO_OFFSET_US,
|
||||||
FACTORY_HEVC_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_PCM_AUDIO_OFFSET_US,
|
FACTORY_HEVC_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_PCM_AUDIO_OFFSET_US,
|
||||||
|
FACTORY_HEVC_PCM_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_PCM_VIDEO_OFFSET_US,
|
||||||
FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_VIDEO_OFFSET_1280X720_20_US,
|
FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_VIDEO_OFFSET_1280X720_20_US,
|
||||||
FACTORY_HEVC_VIDEO_OFFSET_1280X720_30_US, FACTORY_HEVC_VIDEO_OFFSET_1920X1080_20_US,
|
FACTORY_HEVC_VIDEO_OFFSET_1280X720_30_US, FACTORY_HEVC_VIDEO_OFFSET_1920X1080_20_US,
|
||||||
FACTORY_HEVC_VIDEO_OFFSET_1920X1080_30_US, FACTORY_HEVC_VIDEO_OFFSET_US,
|
FACTORY_HEVC_VIDEO_OFFSET_1920X1080_30_US, FACTORY_HEVC_VIDEO_OFFSET_US,
|
||||||
FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_OFFSET_US,
|
FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_OFFSET_US,
|
||||||
FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_AUDIO_OFFSET_US,
|
FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_AUDIO_OFFSET_US,
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_20_US,
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_30_US,
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_20_US,
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_30_US, FACTORY_MJPEG_OPUS_VIDEO_OFFSET_US,
|
||||||
FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_AUDIO_OFFSET_US,
|
FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_AUDIO_OFFSET_US,
|
||||||
|
FACTORY_MJPEG_PCM_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_VIDEO_OFFSET_US,
|
||||||
FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_OFFSET_1280X720_20_US,
|
FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_OFFSET_1280X720_20_US,
|
||||||
FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US, FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US,
|
FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US, FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US,
|
||||||
FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US, FACTORY_MJPEG_VIDEO_OFFSET_US,
|
FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US, FACTORY_MJPEG_VIDEO_OFFSET_US,
|
||||||
@ -30,6 +37,7 @@ pub use profile_offsets::{
|
|||||||
use profile_offsets::{
|
use profile_offsets::{
|
||||||
configured_profile_offset_us, current_profile, factory_audio_mode_offsets_us,
|
configured_profile_offset_us, current_profile, factory_audio_mode_offsets_us,
|
||||||
factory_audio_scalar_offset_us, factory_video_mode_offsets_us, factory_video_scalar_offset_us,
|
factory_audio_scalar_offset_us, factory_video_mode_offsets_us, factory_video_scalar_offset_us,
|
||||||
|
normalize_calibration_profile,
|
||||||
};
|
};
|
||||||
|
|
||||||
const LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = -45_000;
|
const LEGACY_FACTORY_MJPEG_AUDIO_OFFSET_US: i64 = -45_000;
|
||||||
@ -358,24 +366,25 @@ fn parse_snapshot(raw: &str) -> CalibrationSnapshot {
|
|||||||
/// Inputs are the typed parameters; output is the return value or side effect.
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
||||||
fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapshot {
|
fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapshot {
|
||||||
let current_profile = current_profile();
|
let current_profile = current_profile();
|
||||||
|
let stored_profile = normalize_calibration_profile(&state.profile);
|
||||||
let source_allows_migration = matches!(state.source.as_str(), "factory" | "env");
|
let source_allows_migration = matches!(state.source.as_str(), "factory" | "env");
|
||||||
let confidence_allows_migration = matches!(state.confidence.as_str(), "factory" | "configured");
|
let confidence_allows_migration = matches!(state.confidence.as_str(), "factory" | "configured");
|
||||||
let detail_allows_profile_migration = state
|
let detail_allows_profile_migration = state
|
||||||
.detail
|
.detail
|
||||||
.contains("loaded upstream A/V calibration defaults")
|
.contains("loaded upstream A/V calibration defaults")
|
||||||
|| state.detail.contains("restored release-shipped");
|
|| state.detail.contains("restored release-shipped");
|
||||||
if state.profile != current_profile
|
if stored_profile != current_profile
|
||||||
&& source_allows_migration
|
&& source_allows_migration
|
||||||
&& confidence_allows_migration
|
&& confidence_allows_migration
|
||||||
&& detail_allows_profile_migration
|
&& detail_allows_profile_migration
|
||||||
{
|
{
|
||||||
let mut replacement = factory_snapshot_from_env(format!(
|
let mut replacement = factory_snapshot_from_env(format!(
|
||||||
"migrated factory upstream A/V calibration profile from {} to {}",
|
"migrated factory upstream A/V calibration profile from {} to {}",
|
||||||
state.profile, current_profile
|
stored_profile, current_profile
|
||||||
));
|
));
|
||||||
replacement.detail = format!(
|
replacement.detail = format!(
|
||||||
"migrated factory upstream A/V calibration profile from {} to {}",
|
"migrated factory upstream A/V calibration profile from {} to {}",
|
||||||
state.profile, replacement.profile
|
stored_profile, replacement.profile
|
||||||
);
|
);
|
||||||
return replacement;
|
return replacement;
|
||||||
}
|
}
|
||||||
@ -391,7 +400,7 @@ fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapsho
|
|||||||
&& state.active_audio_offset_us == state.default_audio_offset_us;
|
&& state.active_audio_offset_us == state.default_audio_offset_us;
|
||||||
let untouched_legacy_video = is_stale_video_offset_us(state.default_video_offset_us)
|
let untouched_legacy_video = is_stale_video_offset_us(state.default_video_offset_us)
|
||||||
&& state.active_video_offset_us == state.default_video_offset_us;
|
&& state.active_video_offset_us == state.default_video_offset_us;
|
||||||
if state.profile == current_profile
|
if stored_profile == current_profile
|
||||||
&& source_allows_migration
|
&& source_allows_migration
|
||||||
&& confidence_allows_migration
|
&& confidence_allows_migration
|
||||||
&& untouched_legacy_audio
|
&& untouched_legacy_audio
|
||||||
@ -403,6 +412,7 @@ fn migrate_legacy_snapshot(mut state: CalibrationSnapshot) -> CalibrationSnapsho
|
|||||||
state.active_audio_offset_us = state.factory_audio_offset_us;
|
state.active_audio_offset_us = state.factory_audio_offset_us;
|
||||||
state.default_video_offset_us = state.factory_video_offset_us;
|
state.default_video_offset_us = state.factory_video_offset_us;
|
||||||
state.active_video_offset_us = state.factory_video_offset_us;
|
state.active_video_offset_us = state.factory_video_offset_us;
|
||||||
|
state.profile = current_profile;
|
||||||
state.source = "factory".to_string();
|
state.source = "factory".to_string();
|
||||||
state.confidence = FACTORY_CONFIDENCE.to_string();
|
state.confidence = FACTORY_CONFIDENCE.to_string();
|
||||||
state.detail = format!(
|
state.detail = format!(
|
||||||
|
|||||||
@ -3,10 +3,22 @@ pub const FACTORY_MJPEG_VIDEO_OFFSET_1280X720_20_US: i64 = 162_659;
|
|||||||
pub const FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US: i64 = 135_090;
|
pub const FACTORY_MJPEG_VIDEO_OFFSET_1280X720_30_US: i64 = 135_090;
|
||||||
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US: i64 = 160_045;
|
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_20_US: i64 = 160_045;
|
||||||
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US: i64 = 127_952;
|
pub const FACTORY_MJPEG_VIDEO_OFFSET_1920X1080_30_US: i64 = 127_952;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_AUDIO_OFFSET_US: i64 = -34_199;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_20_US: i64 = 541_419;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_30_US: i64 = 513_850;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_20_US: i64 = 538_805;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1920X1080_30_US: i64 = 506_712;
|
||||||
pub const FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US: &str =
|
pub const FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US: &str =
|
||||||
"1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0";
|
"1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0";
|
||||||
pub const FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US: &str =
|
pub const FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US: &str =
|
||||||
"1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952";
|
"1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952";
|
||||||
|
pub const FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US: &str =
|
||||||
|
"1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199";
|
||||||
|
// 1280x720@30 was measured through the blind client->server->RCT Opus path.
|
||||||
|
// The sibling MJPEG modes carry the same Opus-vs-PCM video delta until the next
|
||||||
|
// full mode matrix replaces them with direct hardware measurements.
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US: &str =
|
||||||
|
"1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712";
|
||||||
pub const FACTORY_HEVC_AUDIO_OFFSET_US: i64 = 0;
|
pub const FACTORY_HEVC_AUDIO_OFFSET_US: i64 = 0;
|
||||||
pub const FACTORY_HEVC_VIDEO_OFFSET_1280X720_20_US: i64 = 173_852;
|
pub const FACTORY_HEVC_VIDEO_OFFSET_1280X720_20_US: i64 = 173_852;
|
||||||
pub const FACTORY_HEVC_VIDEO_OFFSET_1280X720_30_US: i64 = 110_000;
|
pub const FACTORY_HEVC_VIDEO_OFFSET_1280X720_30_US: i64 = 110_000;
|
||||||
@ -18,12 +30,17 @@ pub const FACTORY_HEVC_VIDEO_MODE_OFFSETS_US: &str =
|
|||||||
"1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952";
|
"1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952";
|
||||||
pub const FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US: &str = FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US;
|
pub const FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US: &str = FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US;
|
||||||
pub const FACTORY_HEVC_PCM_AUDIO_MODE_OFFSETS_US: &str = FACTORY_HEVC_AUDIO_MODE_OFFSETS_US;
|
pub const FACTORY_HEVC_PCM_AUDIO_MODE_OFFSETS_US: &str = FACTORY_HEVC_AUDIO_MODE_OFFSETS_US;
|
||||||
pub const FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US: &str = FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US;
|
|
||||||
pub const FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US: &str = FACTORY_HEVC_AUDIO_MODE_OFFSETS_US;
|
pub const FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US: &str = FACTORY_HEVC_AUDIO_MODE_OFFSETS_US;
|
||||||
|
pub const FACTORY_MJPEG_PCM_VIDEO_MODE_OFFSETS_US: &str = FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US;
|
||||||
|
pub const FACTORY_HEVC_PCM_VIDEO_MODE_OFFSETS_US: &str = FACTORY_HEVC_VIDEO_MODE_OFFSETS_US;
|
||||||
|
pub const FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US: &str = FACTORY_HEVC_VIDEO_MODE_OFFSETS_US;
|
||||||
pub const FACTORY_MJPEG_PCM_AUDIO_OFFSET_US: i64 = FACTORY_MJPEG_AUDIO_OFFSET_US;
|
pub const FACTORY_MJPEG_PCM_AUDIO_OFFSET_US: i64 = FACTORY_MJPEG_AUDIO_OFFSET_US;
|
||||||
pub const FACTORY_HEVC_PCM_AUDIO_OFFSET_US: i64 = FACTORY_HEVC_AUDIO_OFFSET_US;
|
pub const FACTORY_HEVC_PCM_AUDIO_OFFSET_US: i64 = FACTORY_HEVC_AUDIO_OFFSET_US;
|
||||||
pub const FACTORY_MJPEG_OPUS_AUDIO_OFFSET_US: i64 = FACTORY_MJPEG_AUDIO_OFFSET_US;
|
|
||||||
pub const FACTORY_HEVC_OPUS_AUDIO_OFFSET_US: i64 = FACTORY_HEVC_AUDIO_OFFSET_US;
|
pub const FACTORY_HEVC_OPUS_AUDIO_OFFSET_US: i64 = FACTORY_HEVC_AUDIO_OFFSET_US;
|
||||||
|
pub const FACTORY_MJPEG_PCM_VIDEO_OFFSET_US: i64 = FACTORY_MJPEG_VIDEO_OFFSET_US;
|
||||||
|
pub const FACTORY_HEVC_PCM_VIDEO_OFFSET_US: i64 = FACTORY_HEVC_VIDEO_OFFSET_US;
|
||||||
|
pub const FACTORY_MJPEG_OPUS_VIDEO_OFFSET_US: i64 = FACTORY_MJPEG_OPUS_VIDEO_OFFSET_1280X720_30_US;
|
||||||
|
pub const FACTORY_HEVC_OPUS_VIDEO_OFFSET_US: i64 = FACTORY_HEVC_VIDEO_OFFSET_US;
|
||||||
// Direct UVC/UAC output-delay probes against the lab RC target showed a
|
// Direct UVC/UAC output-delay probes against the lab RC target showed a
|
||||||
// per-mode sync center for MJPEG/UVC video. This is output-path compensation,
|
// per-mode sync center for MJPEG/UVC video. This is output-path compensation,
|
||||||
// not a freshness buffer. The scalar fallback follows the default UVC mode.
|
// not a freshness buffer. The scalar fallback follows the default UVC mode.
|
||||||
@ -103,6 +120,15 @@ pub(super) fn current_profile() -> String {
|
|||||||
format!("{camera_profile}+{audio_profile}")
|
format!("{camera_profile}+{audio_profile}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize a persisted calibration profile into the current `camera+audio`
|
||||||
|
/// shape. Why: pre-Opus snapshots stored only `mjpeg` or `hevc`, and those
|
||||||
|
/// should compare as the PCM profiles they always represented.
|
||||||
|
pub(super) fn normalize_calibration_profile(value: &str) -> String {
|
||||||
|
let camera_profile = camera_profile_from_calibration_profile(value);
|
||||||
|
let audio_profile = audio_profile_from_calibration_profile(value);
|
||||||
|
format!("{camera_profile}+{audio_profile}")
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the camera side of the active calibration profile.
|
/// Resolve the camera side of the active calibration profile.
|
||||||
///
|
///
|
||||||
/// Inputs: process environment. Output: `mjpeg` or `hevc`. Why: the camera
|
/// Inputs: process environment. Output: `mjpeg` or `hevc`. Why: the camera
|
||||||
@ -227,9 +253,13 @@ pub(super) fn factory_audio_mode_offsets_us(profile: &str) -> &'static str {
|
|||||||
/// where HEVC decode and MJPEG re-emission can shift sync, so each ingress
|
/// where HEVC decode and MJPEG re-emission can shift sync, so each ingress
|
||||||
/// profile needs its own baked server-to-RCT center points.
|
/// profile needs its own baked server-to-RCT center points.
|
||||||
pub(super) fn factory_video_mode_offsets_us(profile: &str) -> &'static str {
|
pub(super) fn factory_video_mode_offsets_us(profile: &str) -> &'static str {
|
||||||
match camera_profile_from_calibration_profile(profile).as_str() {
|
let camera = camera_profile_from_calibration_profile(profile);
|
||||||
HEVC_PROFILE => FACTORY_HEVC_VIDEO_MODE_OFFSETS_US,
|
let audio = audio_profile_from_calibration_profile(profile);
|
||||||
_ => FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US,
|
match (camera.as_str(), audio.as_str()) {
|
||||||
|
(HEVC_PROFILE, OPUS_AUDIO_PROFILE) => FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US,
|
||||||
|
(HEVC_PROFILE, _) => FACTORY_HEVC_PCM_VIDEO_MODE_OFFSETS_US,
|
||||||
|
(MJPEG_PROFILE, OPUS_AUDIO_PROFILE) => FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US,
|
||||||
|
_ => FACTORY_MJPEG_PCM_VIDEO_MODE_OFFSETS_US,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,8 +286,12 @@ pub(super) fn factory_audio_scalar_offset_us(profile: &str) -> i64 {
|
|||||||
/// most common calibrated profile instead of silently borrowing stale MJPEG
|
/// most common calibrated profile instead of silently borrowing stale MJPEG
|
||||||
/// values for HEVC.
|
/// values for HEVC.
|
||||||
pub(super) fn factory_video_scalar_offset_us(profile: &str) -> i64 {
|
pub(super) fn factory_video_scalar_offset_us(profile: &str) -> i64 {
|
||||||
match camera_profile_from_calibration_profile(profile).as_str() {
|
let camera = camera_profile_from_calibration_profile(profile);
|
||||||
HEVC_PROFILE => FACTORY_HEVC_VIDEO_OFFSET_US,
|
let audio = audio_profile_from_calibration_profile(profile);
|
||||||
_ => FACTORY_MJPEG_VIDEO_OFFSET_US,
|
match (camera.as_str(), audio.as_str()) {
|
||||||
|
(HEVC_PROFILE, OPUS_AUDIO_PROFILE) => FACTORY_HEVC_OPUS_VIDEO_OFFSET_US,
|
||||||
|
(HEVC_PROFILE, _) => FACTORY_HEVC_PCM_VIDEO_OFFSET_US,
|
||||||
|
(MJPEG_PROFILE, OPUS_AUDIO_PROFILE) => FACTORY_MJPEG_OPUS_VIDEO_OFFSET_US,
|
||||||
|
_ => FACTORY_MJPEG_PCM_VIDEO_OFFSET_US,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,14 @@ fn with_clean_offset_env(test: impl FnOnce()) {
|
|||||||
"LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
"LESAVKA_UPSTREAM_HEVC_PCM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
@ -54,6 +62,14 @@ fn with_clean_offset_env(test: impl FnOnce()) {
|
|||||||
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_HEVC_OPUS_VIDEO_PLAYOUT_OFFSET_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US",
|
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_OFFSET_US",
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
@ -253,6 +269,29 @@ fn opus_audio_profile_gets_its_own_factory_and_env_namespace() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mjpeg_opus_profile_uses_blind_client_rct_factory_offsets() {
|
||||||
|
with_clean_offset_env(|| {
|
||||||
|
temp_env::with_vars(
|
||||||
|
[
|
||||||
|
("LESAVKA_UPLINK_CAMERA_CODEC", Some("mjpeg")),
|
||||||
|
("LESAVKA_UPLINK_AUDIO_CODEC", Some("opus")),
|
||||||
|
("LESAVKA_UVC_WIDTH", Some("1280")),
|
||||||
|
("LESAVKA_UVC_HEIGHT", Some("720")),
|
||||||
|
("LESAVKA_UVC_FPS", Some("30")),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let state = snapshot_from_env();
|
||||||
|
assert_eq!(state.profile, "mjpeg+opus");
|
||||||
|
assert_eq!(state.factory_audio_offset_us, -34_199);
|
||||||
|
assert_eq!(state.factory_video_offset_us, 513_850);
|
||||||
|
assert_eq!(state.default_audio_offset_us, -34_199);
|
||||||
|
assert_eq!(state.default_video_offset_us, 513_850);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn opus_audio_profile_specific_map_does_not_overwrite_pcm_baseline() {
|
fn opus_audio_profile_specific_map_does_not_overwrite_pcm_baseline() {
|
||||||
with_clean_offset_env(|| {
|
with_clean_offset_env(|| {
|
||||||
@ -546,20 +585,22 @@ fn load_migrates_untouched_legacy_factory_mjpeg_baseline() {
|
|||||||
)
|
)
|
||||||
.expect("legacy calibration seed");
|
.expect("legacy calibration seed");
|
||||||
let path = file.path().to_string_lossy().to_string();
|
let path = file.path().to_string_lossy().to_string();
|
||||||
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
with_clean_offset_env(|| {
|
||||||
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
||||||
let store = CalibrationStore::load(runtime.clone());
|
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
||||||
let state = store.current();
|
let store = CalibrationStore::load(runtime.clone());
|
||||||
assert_eq!(state.active_audio_offset_us, 0);
|
let state = store.current();
|
||||||
assert_eq!(state.default_audio_offset_us, 0);
|
assert_eq!(state.active_audio_offset_us, 0);
|
||||||
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.default_audio_offset_us, 0);
|
||||||
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(state.source, "factory");
|
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(
|
assert_eq!(state.source, "factory");
|
||||||
runtime.playout_offsets(),
|
assert_eq!(
|
||||||
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
runtime.playout_offsets(),
|
||||||
);
|
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
||||||
assert!(state.detail.contains("migrated legacy MJPEG"));
|
);
|
||||||
|
assert!(state.detail.contains("migrated legacy MJPEG"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,20 +624,22 @@ fn load_migrates_untouched_previous_factory_mjpeg_baseline() {
|
|||||||
)
|
)
|
||||||
.expect("previous calibration seed");
|
.expect("previous calibration seed");
|
||||||
let path = file.path().to_string_lossy().to_string();
|
let path = file.path().to_string_lossy().to_string();
|
||||||
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
with_clean_offset_env(|| {
|
||||||
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
||||||
let store = CalibrationStore::load(runtime.clone());
|
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
||||||
let state = store.current();
|
let store = CalibrationStore::load(runtime.clone());
|
||||||
assert_eq!(state.active_audio_offset_us, 0);
|
let state = store.current();
|
||||||
assert_eq!(state.default_audio_offset_us, 0);
|
assert_eq!(state.active_audio_offset_us, 0);
|
||||||
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.default_audio_offset_us, 0);
|
||||||
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(state.source, "factory");
|
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(
|
assert_eq!(state.source, "factory");
|
||||||
runtime.playout_offsets(),
|
assert_eq!(
|
||||||
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
runtime.playout_offsets(),
|
||||||
);
|
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
||||||
assert!(state.detail.contains("to audio +0.0ms/video +135.1ms"));
|
);
|
||||||
|
assert!(state.detail.contains("to audio +0.0ms/video +135.1ms"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,20 +663,22 @@ fn load_migrates_overshot_video_factory_mjpeg_baseline() {
|
|||||||
)
|
)
|
||||||
.expect("overshot video calibration seed");
|
.expect("overshot video calibration seed");
|
||||||
let path = file.path().to_string_lossy().to_string();
|
let path = file.path().to_string_lossy().to_string();
|
||||||
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
with_clean_offset_env(|| {
|
||||||
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
temp_env::with_var("LESAVKA_CALIBRATION_PATH", Some(path.as_str()), || {
|
||||||
let store = CalibrationStore::load(runtime.clone());
|
let runtime = Arc::new(UpstreamMediaRuntime::new());
|
||||||
let state = store.current();
|
let store = CalibrationStore::load(runtime.clone());
|
||||||
assert_eq!(state.active_audio_offset_us, 0);
|
let state = store.current();
|
||||||
assert_eq!(state.default_audio_offset_us, 0);
|
assert_eq!(state.active_audio_offset_us, 0);
|
||||||
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.default_audio_offset_us, 0);
|
||||||
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
assert_eq!(state.active_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(state.source, "factory");
|
assert_eq!(state.default_video_offset_us, FACTORY_MJPEG_VIDEO_OFFSET_US);
|
||||||
assert_eq!(
|
assert_eq!(state.source, "factory");
|
||||||
runtime.playout_offsets(),
|
assert_eq!(
|
||||||
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
runtime.playout_offsets(),
|
||||||
);
|
(FACTORY_MJPEG_VIDEO_OFFSET_US, 0)
|
||||||
assert!(state.detail.contains("from audio +0.0ms/video +1090.0ms"));
|
);
|
||||||
|
assert!(state.detail.contains("from audio +0.0ms/video +1090.0ms"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,18 @@ fn with_clean_offset_env(test: impl FnOnce()) {
|
|||||||
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_OFFSET_US",
|
||||||
|
None::<&str>,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
test,
|
test,
|
||||||
);
|
);
|
||||||
@ -110,6 +122,25 @@ fn runtime_can_start_with_opus_specific_audio_calibration_profile() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runtime_starts_mjpeg_opus_with_blind_client_rct_offsets() {
|
||||||
|
with_clean_offset_env(|| {
|
||||||
|
temp_env::with_vars(
|
||||||
|
[
|
||||||
|
("LESAVKA_UVC_WIDTH", Some("1280")),
|
||||||
|
("LESAVKA_UVC_HEIGHT", Some("720")),
|
||||||
|
("LESAVKA_UVC_FPS", Some("30")),
|
||||||
|
("LESAVKA_UPLINK_CAMERA_CODEC", Some("mjpeg")),
|
||||||
|
("LESAVKA_UPLINK_AUDIO_CODEC", Some("opus")),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let runtime = UpstreamMediaRuntime::new();
|
||||||
|
assert_eq!(runtime.playout_offsets(), (513_850, -34_199));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Keeps `runtime_records_client_and_sink_timing_for_upstream_snapshots` explicit because the blind client-to-RCT probe depends on this telemetry to explain freshness losses.
|
/// Keeps `runtime_records_client_and_sink_timing_for_upstream_snapshots` explicit because the blind client-to-RCT probe depends on this telemetry to explain freshness losses.
|
||||||
/// Inputs are paired camera/microphone timing samples plus sink handoff marks; output is a live snapshot with skew, queue, late, and freeze fields populated.
|
/// Inputs are paired camera/microphone timing samples plus sink handoff marks; output is a live snapshot with skew, queue, late, and freeze fields populated.
|
||||||
|
|||||||
@ -9,8 +9,10 @@
|
|||||||
|
|
||||||
use lesavka_server::calibration::{
|
use lesavka_server::calibration::{
|
||||||
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US,
|
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US,
|
||||||
FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US,
|
FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_HEVC_VIDEO_MODE_OFFSETS_US,
|
||||||
FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US,
|
FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US,
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US,
|
||||||
|
FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MATRIX_SCRIPT: &str = include_str!(concat!(
|
const MATRIX_SCRIPT: &str = include_str!(concat!(
|
||||||
@ -30,8 +32,20 @@ fn hevc_and_mjpeg_factory_maps_cover_all_supported_uvc_modes() {
|
|||||||
for mode in SUPPORTED_MODES {
|
for mode in SUPPORTED_MODES {
|
||||||
assert!(map_contains_mode(FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, mode));
|
assert!(map_contains_mode(FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US, mode));
|
||||||
assert!(map_contains_mode(FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, mode));
|
assert!(map_contains_mode(FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US, mode));
|
||||||
|
assert!(map_contains_mode(
|
||||||
|
FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US,
|
||||||
|
mode
|
||||||
|
));
|
||||||
|
assert!(map_contains_mode(
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US,
|
||||||
|
mode
|
||||||
|
));
|
||||||
assert!(map_contains_mode(FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, mode));
|
assert!(map_contains_mode(FACTORY_HEVC_AUDIO_MODE_OFFSETS_US, mode));
|
||||||
assert!(map_contains_mode(FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, mode));
|
assert!(map_contains_mode(FACTORY_HEVC_VIDEO_MODE_OFFSETS_US, mode));
|
||||||
|
assert!(map_contains_mode(
|
||||||
|
FACTORY_HEVC_OPUS_VIDEO_MODE_OFFSETS_US,
|
||||||
|
mode
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,9 +59,17 @@ fn hevc_profile_defaults_are_separate_from_mjpeg_profile_defaults() {
|
|||||||
FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US,
|
FACTORY_MJPEG_PCM_AUDIO_MODE_OFFSETS_US,
|
||||||
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US
|
FACTORY_HEVC_AUDIO_MODE_OFFSETS_US
|
||||||
);
|
);
|
||||||
|
assert_ne!(
|
||||||
|
FACTORY_MJPEG_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_MJPEG_AUDIO_MODE_OFFSETS_US,
|
||||||
|
"MJPEG+Opus should preserve the blind client-to-RCT audio calibration"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
FACTORY_MJPEG_OPUS_VIDEO_MODE_OFFSETS_US, FACTORY_MJPEG_VIDEO_MODE_OFFSETS_US,
|
||||||
|
"MJPEG+Opus should not silently borrow the PCM video timing map"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_AUDIO_MODE_OFFSETS_US,
|
FACTORY_HEVC_OPUS_AUDIO_MODE_OFFSETS_US, FACTORY_HEVC_AUDIO_MODE_OFFSETS_US,
|
||||||
"Opus has a separate audio map that currently inherits PCM until lab calibration lands"
|
"HEVC+Opus keeps the PCM audio baseline until a dedicated lab pass lands"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -420,7 +420,12 @@ mod uvc_binary_extra {
|
|||||||
|
|
||||||
assert_eq!(stream.latest_frame, vec![0xff, 0xd8, 0x11, 0xff, 0xd9]);
|
assert_eq!(stream.latest_frame, vec![0xff, 0xd8, 0x11, 0xff, 0xd9]);
|
||||||
assert_eq!(stream.frame_payload_limit(), 8);
|
assert_eq!(stream.frame_payload_limit(), 8);
|
||||||
assert_eq!(stream.frame_for_buffer(4), EMPTY_MJPEG_FRAME);
|
assert_eq!(stream.frame_for_buffer(4), MINIMAL_MJPEG_FRAME);
|
||||||
|
assert_eq!(
|
||||||
|
UvcVideoStream::new(-1).frame_for_buffer(64 * 1024),
|
||||||
|
IDLE_MJPEG_FRAME
|
||||||
|
);
|
||||||
|
assert!(IDLE_MJPEG_FRAME.len() > 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -96,6 +96,12 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
|||||||
assert!(SERVER_INSTALL.contains(
|
assert!(SERVER_INSTALL.contains(
|
||||||
"DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952"
|
"DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952"
|
||||||
));
|
));
|
||||||
|
assert!(SERVER_INSTALL.contains(
|
||||||
|
"DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199"
|
||||||
|
));
|
||||||
|
assert!(SERVER_INSTALL.contains(
|
||||||
|
"DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712"
|
||||||
|
));
|
||||||
assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0"));
|
assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0"));
|
||||||
assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000"));
|
assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000"));
|
||||||
assert!(SERVER_INSTALL.contains(
|
assert!(SERVER_INSTALL.contains(
|
||||||
@ -105,6 +111,12 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
|||||||
"DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US"
|
"DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US"
|
||||||
));
|
));
|
||||||
assert!(SERVER_INSTALL.contains("LESAVKA_UPLINK_AUDIO_CODEC=%s"));
|
assert!(SERVER_INSTALL.contains("LESAVKA_UPLINK_AUDIO_CODEC=%s"));
|
||||||
|
assert!(
|
||||||
|
SERVER_INSTALL.contains("LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
SERVER_INSTALL.contains("LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s")
|
||||||
|
);
|
||||||
assert!(SERVER_INSTALL.contains("LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s"));
|
assert!(SERVER_INSTALL.contains("LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s"));
|
||||||
assert!(
|
assert!(
|
||||||
SERVER_INSTALL.contains("resolve_upstream_video_playout_offset_us"),
|
SERVER_INSTALL.contains("resolve_upstream_video_playout_offset_us"),
|
||||||
|
|||||||
@ -23,9 +23,12 @@ const PROFILE_OFFSETS: &str = include_str!(concat!(
|
|||||||
fn installer_persists_both_mjpeg_and_hevc_factory_offset_maps() {
|
fn installer_persists_both_mjpeg_and_hevc_factory_offset_maps() {
|
||||||
for marker in [
|
for marker in [
|
||||||
"DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952",
|
"DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952",
|
||||||
|
"DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199",
|
||||||
|
"DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712",
|
||||||
"DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952",
|
"DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952",
|
||||||
"DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
"DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US",
|
||||||
"LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
"LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
||||||
|
"LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
||||||
"LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
"LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
||||||
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s",
|
"LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s",
|
||||||
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user