lesavka/server/src/output_delay_probe.rs

197 lines
4.2 KiB
Rust
Raw Normal View History

2026-05-03 14:00:58 -03:00
use anyhow::{Context, Result, bail};
use gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer_app as gst_app;
use lesavka_common::lesavka::{AudioPacket, OutputDelayProbeRequest, VideoPacket};
2026-05-03 14:45:16 -03:00
use serde::Serialize;
2026-05-03 14:00:58 -03:00
use std::f64::consts::TAU;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
2026-05-03 14:00:58 -03:00
use crate::audio::Voice;
use crate::camera::{CameraCodec, CameraConfig};
use crate::video::CameraRelay;
const DEFAULT_DURATION_SECONDS: u32 = 20;
const DEFAULT_WARMUP_SECONDS: u32 = 4;
const DEFAULT_PULSE_PERIOD_MS: u32 = 1_000;
const DEFAULT_PULSE_WIDTH_MS: u32 = 120;
const DEFAULT_EVENT_WIDTH_CODES: &[u32] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
2026-05-03 14:00:58 -03:00
const AUDIO_SAMPLE_RATE: u32 = 48_000;
const AUDIO_CHANNELS: usize = 2;
const AUDIO_CHUNK_MS: u64 = 10;
const AUDIO_AMPLITUDE: f64 = 24_000.0;
const AUDIO_PILOT_AMPLITUDE: f64 = 700.0;
const AUDIO_PILOT_FREQUENCY_HZ: f64 = 180.0;
2026-05-03 14:00:58 -03:00
const DARK_FRAME_RGB: Rgb = Rgb { r: 4, g: 8, b: 12 };
const VIDEO_CONTINUITY_BLOCKS: usize = 20;
const VIDEO_CONTINUITY_DATA_BITS: usize = 16;
const EVENT_COLORS: [Rgb; 16] = [
2026-05-03 14:00:58 -03:00
Rgb {
r: 255,
g: 45,
b: 45,
},
Rgb {
r: 0,
g: 230,
b: 118,
},
Rgb {
r: 41,
g: 121,
b: 255,
},
Rgb {
r: 255,
g: 179,
b: 0,
},
Rgb {
r: 216,
g: 27,
b: 96,
},
Rgb {
r: 0,
g: 188,
b: 212,
},
Rgb {
r: 205,
g: 220,
b: 57,
},
Rgb {
r: 126,
g: 87,
b: 194,
},
Rgb {
r: 255,
g: 112,
b: 67,
},
Rgb {
r: 38,
g: 166,
b: 154,
},
Rgb {
r: 255,
g: 64,
b: 129,
},
Rgb {
r: 92,
g: 107,
b: 192,
},
Rgb {
r: 255,
g: 235,
b: 59,
},
Rgb {
r: 105,
g: 240,
b: 174,
},
Rgb {
r: 171,
g: 71,
b: 188,
},
Rgb {
r: 3,
g: 169,
b: 244,
},
];
const EVENT_FREQUENCIES_HZ: [f64; 16] = [
620.0, 780.0, 940.0, 1_120.0, 1_320.0, 1_540.0, 1_780.0, 2_040.0, 2_320.0, 2_620.0, 2_960.0,
3_340.0, 3_760.0, 4_220.0, 4_740.0, 5_320.0,
2026-05-03 14:00:58 -03:00
];
#[derive(Clone, Copy, Debug)]
struct Rgb {
r: u8,
g: u8,
b: u8,
}
#[derive(Clone, Debug)]
struct ProbeConfig {
duration: Duration,
warmup: Duration,
pulse_period: Duration,
pulse_width: Duration,
event_width_codes: Vec<u32>,
audio_delay: Duration,
video_delay: Duration,
2026-05-03 14:00:58 -03:00
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OutputDelayProbeSummary {
pub video_frames: u64,
pub audio_packets: u64,
pub event_count: u64,
2026-05-03 14:45:16 -03:00
pub timeline_json: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ProbeEventSlot {
event_id: usize,
code: u32,
planned_start_us: u64,
planned_end_us: u64,
}
#[derive(Clone, Debug, Serialize)]
struct OutputDelayProbeTimeline {
schema: &'static str,
origin: &'static str,
media_path: &'static str,
injection_scope: &'static str,
server_pipeline_reference: &'static str,
sink_handoff_path: &'static str,
client_uplink_included: bool,
2026-05-03 14:45:16 -03:00
camera_width: u32,
camera_height: u32,
camera_fps: u32,
audio_sample_rate: u32,
audio_channels: usize,
audio_chunk_ms: u64,
audio_delay_us: u64,
video_delay_us: u64,
server_start_unix_ns: u128,
2026-05-03 14:45:16 -03:00
pulse_period_ms: u64,
pulse_width_ms: u64,
warmup_us: u64,
duration_us: u64,
events: Vec<OutputDelayProbeEventTimeline>,
}
#[derive(Clone, Debug, Serialize)]
struct OutputDelayProbeEventTimeline {
event_id: usize,
code: u32,
planned_start_us: u64,
planned_end_us: u64,
video_seq: Option<u64>,
audio_seq: Option<u64>,
video_feed_monotonic_us: Option<u64>,
audio_push_monotonic_us: Option<u64>,
video_feed_unix_ns: Option<u128>,
audio_push_unix_ns: Option<u128>,
2026-05-03 14:45:16 -03:00
server_feed_delta_ms: Option<f64>,
}
include!("output_delay_probe/timeline_config.rs");
include!("output_delay_probe/probe_runtime.rs");
include!("output_delay_probe/media_encoding.rs");
2026-05-03 14:00:58 -03:00
#[cfg(test)]
#[path = "output_delay_probe/tests/mod.rs"]
mod tests;