pub use display_types::cea861::hdmi_vsdb::{HdmiVsdb, HdmiVsdbFlags};
pub(super) const HDMI_OUI: [u8; 3] = {
let v = display_types::cea861::oui::HDMI_LICENSING;
[
(v & 0xFF) as u8,
((v >> 8) & 0xFF) as u8,
((v >> 16) & 0xFF) as u8,
]
};
fn decode_latency(raw: u8) -> Option<u16> {
match raw {
1..=251 => Some((raw as u16 - 1) * 2),
_ => None,
}
}
pub(super) fn parse_hdmi_vsdb(block_data: &[u8]) -> Option<HdmiVsdb> {
if block_data.len() < 5 {
return None;
}
if block_data[0..3] != HDMI_OUI {
return None;
}
let source_physical_address = ((block_data[3] as u16) << 8) | (block_data[4] as u16);
let flags = block_data
.get(5)
.map(|&b| HdmiVsdbFlags::from_bits_truncate(b))
.unwrap_or_else(HdmiVsdbFlags::empty);
let max_tmds_clock_mhz = block_data.get(6).map(|&b| (b as u16) * 5);
let latency_present = block_data.get(7).is_some_and(|&b| b & 0x80 != 0);
let i_latency_present = block_data.get(7).is_some_and(|&b| b & 0x40 != 0);
let (video_latency_ms, audio_latency_ms) = if latency_present && block_data.len() >= 10 {
(decode_latency(block_data[8]), decode_latency(block_data[9]))
} else {
(None, None)
};
let latency_offset = if latency_present { 2 } else { 0 };
let (interlaced_video_latency_ms, interlaced_audio_latency_ms) =
if i_latency_present && block_data.len() >= 10 + latency_offset {
(
decode_latency(block_data[8 + latency_offset]),
decode_latency(block_data[9 + latency_offset]),
)
} else {
(None, None)
};
Some(HdmiVsdb::new(
source_physical_address,
flags,
max_tmds_clock_mhz,
video_latency_ms,
audio_latency_ms,
interlaced_video_latency_ms,
interlaced_audio_latency_ms,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimal_vsdb() {
let data = [0x03, 0x0C, 0x00, 0x10, 0x00];
let vsdb = parse_hdmi_vsdb(&data).unwrap();
assert_eq!(vsdb.source_physical_address, 0x1000);
assert_eq!(vsdb.flags, HdmiVsdbFlags::empty());
assert_eq!(vsdb.max_tmds_clock_mhz, None);
assert_eq!(vsdb.video_latency_ms, None);
}
#[test]
fn test_full_vsdb_with_deep_color() {
let data = [0x03, 0x0C, 0x00, 0x10, 0x00, 0x38, 74, 0x00];
let vsdb = parse_hdmi_vsdb(&data).unwrap();
assert!(vsdb.flags.contains(HdmiVsdbFlags::DC_36BIT));
assert!(vsdb.flags.contains(HdmiVsdbFlags::DC_30BIT));
assert!(vsdb.flags.contains(HdmiVsdbFlags::DC_Y444));
assert!(!vsdb.flags.contains(HdmiVsdbFlags::DC_48BIT));
assert_eq!(vsdb.max_tmds_clock_mhz, Some(370));
}
#[test]
fn test_vsdb_with_latency() {
let data = [0x03, 0x0C, 0x00, 0x10, 0x00, 0x00, 0x00, 0x80, 51, 1];
let vsdb = parse_hdmi_vsdb(&data).unwrap();
assert_eq!(vsdb.video_latency_ms, Some(100));
assert_eq!(vsdb.audio_latency_ms, Some(0));
assert_eq!(vsdb.interlaced_video_latency_ms, None);
}
#[test]
fn test_vsdb_with_interlaced_latency() {
let data = [
0x03, 0x0C, 0x00, 0x10, 0x00, 0x00, 0x00, 0xC0, 11, 6, 21, 16, ];
let vsdb = parse_hdmi_vsdb(&data).unwrap();
assert_eq!(vsdb.video_latency_ms, Some(20));
assert_eq!(vsdb.audio_latency_ms, Some(10));
assert_eq!(vsdb.interlaced_video_latency_ms, Some(40));
assert_eq!(vsdb.interlaced_audio_latency_ms, Some(30));
}
#[test]
fn test_wrong_oui_returns_none() {
let data = [0x01, 0x02, 0x03, 0x10, 0x00];
assert!(parse_hdmi_vsdb(&data).is_none());
}
#[test]
fn test_too_short_returns_none() {
let data = [0x03, 0x0C, 0x00, 0x10]; assert!(parse_hdmi_vsdb(&data).is_none());
}
#[test]
fn test_latency_decode() {
assert_eq!(decode_latency(0), None);
assert_eq!(decode_latency(255), None);
assert_eq!(decode_latency(1), Some(0));
assert_eq!(decode_latency(2), Some(2));
assert_eq!(decode_latency(251), Some(500));
}
}