use core::fmt;
use std::collections::HashMap;
use std::sync::OnceLock;
use crate::event::Event;
use crate::state::State;
use crate::types::BaySignalDetails;
use crate::wire::{BayStatus, BayUid, MxrSignalType};
use super::handlers::{u16_at, u32_at};
use super::Rx;
const SVD_TABLE: &str = include_str!("../svd.csv");
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Svd {
pub id: u16,
pub picture_aspect: u16,
pub pixel_aspect: u16,
pub horizontal_active: u16,
pub horizontal_total: u16,
pub vertical_active: u16,
pub vertical_total: u16,
pub refresh: u16,
pub interlaced: bool,
pub multiplier: u16,
}
impl fmt::Display for Svd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}x{}@{}Hz",
self.horizontal_active, self.vertical_active, self.refresh
)
}
}
fn table() -> &'static HashMap<u16, Svd> {
static TABLE: OnceLock<HashMap<u16, Svd>> = OnceLock::new();
TABLE.get_or_init(|| {
let mut map = HashMap::new();
for line in SVD_TABLE.lines() {
let fields: Vec<u16> = line
.trim()
.split(';')
.filter_map(|f| f.trim().parse().ok())
.collect();
let Ok(n) = <[u16; 10]>::try_from(fields.as_slice()) else {
continue;
};
map.insert(
n[0],
Svd {
id: n[0],
picture_aspect: n[1],
pixel_aspect: n[2],
horizontal_active: n[3],
horizontal_total: n[4],
vertical_active: n[5],
vertical_total: n[6],
refresh: n[7],
interlaced: n[8] == 1,
multiplier: n[9],
},
);
}
map
})
}
pub fn lookup_svd(id: u16) -> Option<Svd> {
table().get(&id).copied()
}
fn colour_space(v: u8) -> &'static str {
match v {
0 => "RGB",
1 => "4:4:4",
2 => "4:2:2",
3 => "4:2:0",
_ => "unknown",
}
}
const AV_DETAILS_SIZE: usize = 112;
const STREAM_INTERLACED: u8 = 1 << 1;
const STREAM_NON_INTEGER_CLOCK: u8 = 1 << 3;
const STREAM_HDR: u8 = 1 << 4;
const SUPPORT_STREAM_VALID: u8 = 1 << 1;
pub(super) fn signal_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
if p.len() < AV_DETAILS_SIZE {
return;
}
let support_flags = p[2];
let stream_flags = p[3];
let stream_valid = support_flags & SUPPORT_STREAM_VALID != 0;
let bay_block = &p[100..112];
let port = u16_at(bay_block, 0);
let bay = BayUid::new(rx.sender(), port);
if state.bay(bay).is_none() {
return;
}
let video = &p[40..56];
let svd_id = u16::from(video[0]);
let mut frame_rate = f64::from(u16_at(video, 8));
if stream_flags & STREAM_NON_INTEGER_CLOCK != 0 {
frame_rate = (frame_rate * 1000.0 / 1001.0 * 100.0).round() / 100.0;
}
let signal_type = match lookup_svd(svd_id) {
Some(svd) if stream_valid && svd_id != 0 => {
let mut description = format!(
"{}x{} / {} / {}bpp",
svd.horizontal_active,
svd.vertical_active,
colour_space(video[1]),
video[2]
);
if stream_flags & STREAM_INTERLACED != 0 {
description.push_str(" interlaced");
}
if stream_flags & STREAM_HDR != 0 {
description.push_str(" HDR");
}
description.push_str(&format!(" / {frame_rate}Hz"));
description
}
_ => "No Signal".to_owned(),
};
let details = BaySignalDetails {
frame_rate,
tmds_clock: u32_at(video, 10),
status: BayStatus::from_bits(u32_at(bay_block, 2)),
scaling: MxrSignalType::from_wire(u16_at(bay_block, 6)),
clock_rate: u32_at(bay_block, 8),
};
if let Some(bay) = state.bay_mut(bay) {
bay.set_signal_details(details);
bay.apply_signal_status(stream_valid, Some(signal_type), ev);
}
}