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}; use serde::Serialize; use std::f64::consts::TAU; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; 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]; 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; 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] = [ 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, ]; #[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, audio_delay: Duration, video_delay: Duration, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct OutputDelayProbeSummary { pub video_frames: u64, pub audio_packets: u64, pub event_count: u64, 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, 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, pulse_period_ms: u64, pulse_width_ms: u64, warmup_us: u64, duration_us: u64, events: Vec, } #[derive(Clone, Debug, Serialize)] struct OutputDelayProbeEventTimeline { event_id: usize, code: u32, planned_start_us: u64, planned_end_us: u64, video_seq: Option, audio_seq: Option, video_feed_monotonic_us: Option, audio_push_monotonic_us: Option, video_feed_unix_ns: Option, audio_push_unix_ns: Option, server_feed_delta_ms: Option, } include!("output_delay_probe/timeline_config.rs"); include!("output_delay_probe/probe_runtime.rs"); include!("output_delay_probe/media_encoding.rs"); #[cfg(test)] #[path = "output_delay_probe/tests/mod.rs"] mod tests;