#[cfg(any(feature = "alloc", feature = "std"))]
mod audio;
#[cfg(any(feature = "alloc", feature = "std"))]
mod extended_blocks;
#[cfg(any(feature = "alloc", feature = "std"))]
mod hdmi_vsdb;
#[cfg(any(feature = "alloc", feature = "std"))]
pub use audio::{AudioFormat, AudioFormatInfo, AudioSampleRates, ShortAudioDescriptor};
pub use display_types::cea861::Cea861Flags;
#[cfg(any(feature = "alloc", feature = "std"))]
pub use display_types::cea861::{Cea861Capabilities, HdmiAudioBlock};
#[cfg(any(feature = "alloc", feature = "std"))]
pub use extended_blocks::{
ColorimetryBlock, ColorimetryFlags, DtcPointEncoding, HdmiDscMaxSlices, HdmiForumDsc,
HdmiForumFrl, HdmiForumSinkCap, HdrDynamicMetadataDescriptor, HdrEotf, HdrStaticMetadata,
InfoFrameDescriptor, RoomConfigurationBlock, SpeakerAllocation, SpeakerAllocationFlags,
SpeakerAllocationFlags2, SpeakerAllocationFlags3, SpeakerLocationEntry, T7VtdbBlock,
T8VtdbBlock, T10VtdbBlock, T10VtdbEntry, VendorSpecificBlock, VesaDisplayDeviceBlock,
VesaTransferCharacteristic, VideoCapability, VideoCapabilityFlags, VtbExtBlock, infoframe_type,
};
#[cfg(any(feature = "alloc", feature = "std"))]
pub use hdmi_vsdb::{HdmiVsdb, HdmiVsdbFlags};
pub(crate) use display_types::cea861::{dmt_to_mode, vic_to_mode};
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::capabilities::base::timings::decode_dtd_slot;
use crate::capabilities::base::timings::decode_dtd_slot_into_sink;
use crate::model::capabilities::DisplayCapabilities;
use crate::model::capabilities::ModeSink;
use crate::model::capabilities::StaticContext;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::model::capabilities::VideoMode;
use crate::model::diagnostics::EdidWarning;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::model::diagnostics::ParseWarning;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::model::extension::ExtensionHandler;
use crate::model::extension::StaticExtensionHandler;
#[cfg(any(feature = "alloc", feature = "std"))]
use crate::model::prelude::{Arc, Vec};
#[cfg(any(feature = "alloc", feature = "std"))]
use audio::parse_audio_data_block;
#[cfg(any(feature = "alloc", feature = "std"))]
use extended_blocks::{
EXT_TAG_COLORIMETRY, EXT_TAG_HDMI_AUDIO, EXT_TAG_HDR_DYNAMIC_METADATA,
EXT_TAG_HDR_STATIC_METADATA, EXT_TAG_HF_EEODB, EXT_TAG_HF_SCDB, EXT_TAG_INFOFRAME,
EXT_TAG_ROOM_CONFIGURATION, EXT_TAG_SPEAKER_LOCATION, EXT_TAG_T7VTDB, EXT_TAG_T8VTDB,
EXT_TAG_T10VTDB, EXT_TAG_VESA_DDDB, EXT_TAG_VIDEO_CAPABILITY, EXT_TAG_VIDEO_FORMAT_PREFERENCE,
EXT_TAG_VSADB, EXT_TAG_VSVDB, EXT_TAG_VTB_EXT, EXT_TAG_Y420_CAPABILITY_MAP, EXT_TAG_Y420_VIDEO,
parse_colorimetry, parse_hdr_dynamic_metadata, parse_hdr_static_metadata, parse_hf_eeodb,
parse_hf_scdb, parse_hf_vsdb, parse_infoframe_db, parse_room_configuration,
parse_speaker_allocation, parse_speaker_location, parse_t7vtdb, parse_t8vtdb, parse_t10vtdb,
parse_vendor_specific_block, parse_vesa_display_device, parse_vesa_transfer_characteristic,
parse_video_capability, parse_video_format_preferences, parse_vtb_ext,
parse_y420_capability_map, parse_y420_vdb,
};
#[cfg(any(feature = "alloc", feature = "std"))]
use hdmi_vsdb::parse_hdmi_vsdb;
#[derive(Debug)]
pub struct Cea861Handler;
#[cfg(any(feature = "alloc", feature = "std"))]
impl ExtensionHandler for Cea861Handler {
fn process(
&self,
blocks: &[&[u8; 128]],
caps: &mut DisplayCapabilities,
warnings: &mut Vec<ParseWarning>,
) {
let Some(first) = blocks.first() else { return };
let flags = Cea861Flags::from_bits_truncate(first[3]);
let mut cea_caps = caps
.take_extension_data::<Cea861Capabilities>(0x02)
.unwrap_or_else(|| Cea861Capabilities::new(flags));
cea_caps.flags |= flags;
for ext in blocks {
let dtd_offset = ext[2] as usize;
let collection_end = if dtd_offset == 0 {
128
} else {
dtd_offset.min(128)
};
if collection_end > 4 {
parse_cea861_data_block_collection(
&ext[4..collection_end],
caps,
&mut cea_caps,
warnings,
);
}
if (4..=110).contains(&dtd_offset) {
let mut offset = dtd_offset;
let mut dtd_index: u8 = 0;
while offset + 18 <= 127 {
decode_dtd_slot(&ext[offset..offset + 18], caps, dtd_index);
offset += 18;
dtd_index = dtd_index.saturating_add(1);
}
}
}
caps.set_extension_data(0x02, cea_caps);
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
pub(crate) fn parse_cea861_data_block_collection(
collection: &[u8],
caps: &mut DisplayCapabilities,
cea_caps: &mut Cea861Capabilities,
warnings: &mut Vec<ParseWarning>,
) {
let mut i = 0;
while i < collection.len() {
let header = collection[i];
if header == 0 {
break;
}
let tag = (header >> 5) & 0x07;
let length = (header & 0x1F) as usize;
i += 1;
if i + length > collection.len() {
warnings.push(Arc::new(EdidWarning::MalformedDataBlock));
break;
}
let block_data = &collection[i..i + length];
match tag {
0x01 => {
cea_caps
.audio_descriptors
.extend(parse_audio_data_block(block_data));
}
0x02 => handle_video_data_block(block_data, caps, cea_caps),
0x03 => {
if cea_caps.hdmi_vsdb.is_none() {
cea_caps.hdmi_vsdb = parse_hdmi_vsdb(block_data);
}
if cea_caps.hf_vsdb.is_none() {
cea_caps.hf_vsdb = parse_hf_vsdb(block_data);
}
}
0x04
if cea_caps.speaker_allocation.is_none() => {
cea_caps.speaker_allocation = parse_speaker_allocation(block_data);
}
0x05
if cea_caps.vesa_transfer_characteristic.is_none() => {
cea_caps.vesa_transfer_characteristic =
parse_vesa_transfer_characteristic(block_data);
}
0x07 => handle_extended_tag_block(block_data, caps, cea_caps),
_ => {}
}
i += length;
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
fn push_mode_dedup(modes: &mut Vec<VideoMode>, mode: VideoMode) {
let already_present = modes.iter().any(|m| {
m.width == mode.width
&& m.height == mode.height
&& m.refresh_rate == mode.refresh_rate
&& m.interlaced == mode.interlaced
});
if !already_present {
modes.push(mode);
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
fn handle_video_data_block(
block_data: &[u8],
caps: &mut DisplayCapabilities,
cea_caps: &mut Cea861Capabilities,
) {
let mut j = 0;
while j < block_data.len() {
let b = block_data[j];
let native = (b & 0x80) != 0;
let vic_low = b & 0x7F;
let vic = if vic_low == 0 {
j += 1;
match block_data.get(j).copied() {
Some(0) | None => {
j += 1;
continue; }
Some(v) => v,
}
} else {
vic_low
};
j += 1;
cea_caps.vics.push((vic, native));
if let Some(mode) = vic_to_mode(vic) {
push_mode_dedup(&mut caps.supported_modes, mode);
}
}
}
#[cfg(any(feature = "alloc", feature = "std"))]
fn handle_extended_tag_block(
block_data: &[u8],
caps: &mut DisplayCapabilities,
cea_caps: &mut Cea861Capabilities,
) {
match block_data.first().copied() {
Some(EXT_TAG_VSVDB) => {
if let Some(b) = parse_vendor_specific_block(&block_data[1..]) {
cea_caps.vendor_specific_video.push(b);
}
}
Some(EXT_TAG_VSADB) => {
if let Some(b) = parse_vendor_specific_block(&block_data[1..]) {
cea_caps.vendor_specific_audio.push(b);
}
}
Some(EXT_TAG_VESA_DDDB) if cea_caps.vesa_display_device.is_none() => {
cea_caps.vesa_display_device = parse_vesa_display_device(&block_data[1..]);
}
Some(EXT_TAG_VTB_EXT) => {
if let Some(vtb) = parse_vtb_ext(&block_data[1..]) {
for timing in &vtb.timings {
push_mode_dedup(&mut caps.supported_modes, timing.clone());
}
cea_caps.vtb_ext.push(vtb);
}
}
Some(EXT_TAG_VIDEO_CAPABILITY) if cea_caps.video_capability.is_none() => {
cea_caps.video_capability = parse_video_capability(block_data);
}
Some(EXT_TAG_COLORIMETRY) if cea_caps.colorimetry.is_none() => {
cea_caps.colorimetry = parse_colorimetry(block_data);
}
Some(EXT_TAG_HDR_STATIC_METADATA) if cea_caps.hdr_static_metadata.is_none() => {
cea_caps.hdr_static_metadata = parse_hdr_static_metadata(block_data);
}
Some(EXT_TAG_HDR_DYNAMIC_METADATA) => {
cea_caps
.hdr_dynamic_metadata
.extend(parse_hdr_dynamic_metadata(block_data));
}
Some(EXT_TAG_VIDEO_FORMAT_PREFERENCE) if cea_caps.video_format_preferences.is_empty() => {
cea_caps.video_format_preferences = parse_video_format_preferences(block_data);
}
Some(EXT_TAG_Y420_VIDEO) => {
let vics = parse_y420_vdb(block_data);
for &vic in &vics {
if let Some(mode) = vic_to_mode(vic) {
push_mode_dedup(&mut caps.supported_modes, mode);
}
}
cea_caps.y420_vics.extend(vics);
}
Some(EXT_TAG_Y420_CAPABILITY_MAP) if cea_caps.y420_capability_map.is_empty() => {
cea_caps.y420_capability_map = parse_y420_capability_map(block_data);
}
Some(EXT_TAG_INFOFRAME) => {
cea_caps
.infoframe_descriptors
.extend(parse_infoframe_db(block_data));
}
Some(EXT_TAG_ROOM_CONFIGURATION) if cea_caps.room_configuration.is_none() => {
cea_caps.room_configuration = parse_room_configuration(block_data);
}
Some(EXT_TAG_SPEAKER_LOCATION) => {
cea_caps
.speaker_locations
.extend(parse_speaker_location(block_data));
}
Some(EXT_TAG_T7VTDB) => {
if let Some(t7) = parse_t7vtdb(&block_data[1..]) {
push_mode_dedup(&mut caps.supported_modes, t7.mode.clone());
cea_caps.t7_vtdb.push(t7);
}
}
Some(EXT_TAG_T8VTDB) => {
if let Some(t8) = parse_t8vtdb(&block_data[1..]) {
for mode in &t8.timings {
push_mode_dedup(&mut caps.supported_modes, mode.clone());
}
cea_caps.t8_vtdb.push(t8);
}
}
Some(EXT_TAG_T10VTDB) => {
if let Some(t10) = parse_t10vtdb(&block_data[1..]) {
for entry in &t10.entries {
let mode = VideoMode::new(entry.width, entry.height, entry.refresh_hz, false);
push_mode_dedup(&mut caps.supported_modes, mode);
}
cea_caps.t10_vtdb.push(t10);
}
}
Some(EXT_TAG_HF_EEODB) if cea_caps.hf_eeodb_extension_count.is_none() => {
cea_caps.hf_eeodb_extension_count = parse_hf_eeodb(&block_data[1..]);
}
Some(EXT_TAG_HF_SCDB) if cea_caps.hf_scdb.is_none() => {
cea_caps.hf_scdb = parse_hf_scdb(&block_data[1..]);
}
Some(EXT_TAG_HDMI_AUDIO) if cea_caps.hdmi_audio.is_none() => {
let multi_stream_audio = block_data.get(1).is_some_and(|&b| b & 0x08 != 0);
let audio_descriptors = block_data
.get(2..)
.map(parse_audio_data_block)
.unwrap_or_default();
cea_caps.hdmi_audio = Some(HdmiAudioBlock::new(multi_stream_audio, audio_descriptors));
}
_ => {}
}
}
pub(crate) fn cea861_collection_into_sink(collection: &[u8], sink: &mut dyn ModeSink) {
let mut i = 0;
while i < collection.len() {
let header = collection[i];
if header == 0 {
break;
}
let tag = (header >> 5) & 0x07;
let length = (header & 0x1F) as usize;
i += 1;
if i + length > collection.len() {
sink.push_warning(EdidWarning::MalformedDataBlock);
break;
}
let block_data = &collection[i..i + length];
if tag == 0x02 {
let mut j = 0;
while j < block_data.len() {
let b = block_data[j];
let vic_low = b & 0x7F;
let vic = if vic_low == 0 {
j += 1;
match block_data.get(j).copied() {
Some(0) | None => {
j += 1;
continue;
}
Some(v) => v,
}
} else {
vic_low
};
j += 1;
if let Some(mode) = vic_to_mode(vic) {
sink.push_mode(mode);
}
}
} else if tag == 0x07 {
#[cfg(any(feature = "alloc", feature = "std"))]
match block_data.first().copied() {
Some(EXT_TAG_VTB_EXT) => {
if let Some(vtb) = parse_vtb_ext(&block_data[1..]) {
for timing in vtb.timings {
sink.push_mode(timing);
}
}
}
Some(EXT_TAG_Y420_VIDEO) => {
for &vic in &parse_y420_vdb(block_data) {
if let Some(mode) = vic_to_mode(vic) {
sink.push_mode(mode);
}
}
}
Some(EXT_TAG_T7VTDB) => {
if let Some(t7) = parse_t7vtdb(&block_data[1..]) {
sink.push_mode(t7.mode);
}
}
Some(EXT_TAG_T8VTDB) => {
if let Some(t8) = parse_t8vtdb(&block_data[1..]) {
for mode in t8.timings {
sink.push_mode(mode);
}
}
}
Some(EXT_TAG_T10VTDB) => {
if let Some(t10) = parse_t10vtdb(&block_data[1..]) {
for entry in &t10.entries {
sink.push_mode(VideoMode::new(
entry.width,
entry.height,
entry.refresh_hz,
false,
));
}
}
}
_ => {}
}
}
i += length;
}
}
pub(crate) fn cea861_process_into_sink(ext: &[u8; 128], sink: &mut dyn ModeSink) {
let dtd_offset = ext[2] as usize;
let collection_end = if dtd_offset == 0 {
128
} else {
dtd_offset.min(128)
};
if collection_end > 4 {
cea861_collection_into_sink(&ext[4..collection_end], sink);
}
if (4..=110).contains(&dtd_offset) {
let mut offset = dtd_offset;
let mut dtd_index: u8 = 0;
while offset + 18 <= 127 {
decode_dtd_slot_into_sink(&ext[offset..offset + 18], sink, dtd_index);
offset += 18;
dtd_index = dtd_index.saturating_add(1);
}
}
}
impl StaticExtensionHandler for Cea861Handler {
fn tag(&self) -> u8 {
0x02
}
fn process(&self, blocks: &[&[u8; 128]], ctx: &mut StaticContext<'_>) {
for block in blocks {
cea861_process_into_sink(block, ctx);
}
}
}
pub static CEA861_HANDLER: &dyn StaticExtensionHandler = &Cea861Handler;
#[cfg(test)]
mod test_static {
use super::*;
use crate::model::extension::StaticExtensionHandler;
#[test]
fn test_static_handler_tag() {
assert_eq!(StaticExtensionHandler::tag(&Cea861Handler), 0x02);
}
}
#[cfg(test)]
#[cfg(any(feature = "alloc", feature = "std"))]
mod tests {
use super::*;
use crate::model::capabilities::{DisplayCapabilities, RefreshRate};
#[test]
fn test_audio_flag() {
let mut ext = [0u8; 128];
ext[3] = Cea861Flags::BASIC_AUDIO.bits();
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert!(cea.flags.contains(Cea861Flags::BASIC_AUDIO));
}
#[test]
fn test_no_audio_flag() {
let ext = [0u8; 128];
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert!(!cea.flags.contains(Cea861Flags::BASIC_AUDIO));
}
#[test]
fn test_flags_parsing() {
let flags = Cea861Flags::from_bits_truncate(0xF0);
assert!(flags.contains(Cea861Flags::UNDERSCAN));
assert!(flags.contains(Cea861Flags::BASIC_AUDIO));
assert!(flags.contains(Cea861Flags::YCBCR_444));
assert!(flags.contains(Cea861Flags::YCBCR_422));
}
#[test]
fn test_malformed_data_block_warns() {
let mut ext = [0u8; 128];
ext[2] = 10;
ext[4] = (1 << 5) | 8;
let mut caps = DisplayCapabilities::default();
let mut warnings = Vec::new();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut warnings);
assert!(
warnings
.iter()
.any(|w| (**w).downcast_ref::<EdidWarning>()
== Some(&EdidWarning::MalformedDataBlock))
);
}
#[test]
fn test_svd_parsing() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[3] = Cea861Flags::BASIC_AUDIO.bits();
ext[4] = (2 << 5) | 3;
ext[5] = 0x90; ext[6] = 0x04; ext[7] = 0x01;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.vics, vec![(16, true), (4, false), (1, false)]);
assert_eq!(caps.supported_modes.len(), 3);
assert!(caps.supported_modes.iter().any(|m| m.width == 1920
&& m.height == 1080
&& m.refresh_rate == Some(RefreshRate::integral(60))
&& !m.interlaced));
assert!(caps.supported_modes.iter().any(|m| m.width == 1280
&& m.height == 720
&& m.refresh_rate == Some(RefreshRate::integral(60))));
assert!(caps.supported_modes.iter().any(|m| m.width == 640
&& m.height == 480
&& m.refresh_rate == Some(RefreshRate::integral(60))));
}
#[test]
fn test_svd_dedup() {
let mut ext = [0u8; 128];
ext[2] = 7;
ext[4] = (2 << 5) | 2; ext[5] = 0x10; ext[6] = 0x90;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.vics.len(), 2);
assert_eq!(
caps.supported_modes
.iter()
.filter(|m| m.width == 1920
&& m.height == 1080
&& m.refresh_rate == Some(RefreshRate::integral(60)))
.count(),
1
);
}
#[test]
fn test_svd_byte_0x80_is_skipped() {
let mut ext = [0u8; 128];
ext[2] = 6;
ext[4] = (2 << 5) | 1; ext[5] = 0x80;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert!(cea.vics.is_empty());
assert!(caps.supported_modes.is_empty());
}
fn write_dtd_2560x1440_144(ext: &mut [u8; 128], offset: usize) {
let clk: u16 = 58392; ext[offset] = (clk & 0xFF) as u8;
ext[offset + 1] = (clk >> 8) as u8;
ext[offset + 2] = (2560 & 0xFF) as u8; ext[offset + 3] = (160 & 0xFF) as u8; ext[offset + 4] = ((2560 >> 4) & 0xF0) as u8; ext[offset + 5] = (1440 & 0xFF) as u8; ext[offset + 6] = (41 & 0xFF) as u8; ext[offset + 7] = ((1440 >> 4) & 0xF0) as u8; ext[offset + 17] = 0x1E; }
#[test]
fn test_cea_dtd_parsed() {
let mut ext = [0u8; 128];
ext[2] = 4; write_dtd_2560x1440_144(&mut ext, 4);
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
assert!(
caps.supported_modes.iter().any(|m| m.width == 2560
&& m.height == 1440
&& m.refresh_rate == Some(RefreshRate::integral(144))),
"2560×1440@144 not found in supported_modes"
);
}
#[test]
fn test_cea_dtd_zero_clock_skipped() {
let mut ext = [0u8; 128];
ext[2] = 4;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
assert!(caps.supported_modes.is_empty());
}
#[test]
fn test_speaker_allocation_parsed() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[4] = (4 << 5) | 3;
ext[5] = 0x07; ext[6] = 0x00;
ext[7] = 0x00;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
let sa = cea.speaker_allocation.unwrap();
assert!(sa.channels.contains(SpeakerAllocationFlags::FL_FR));
assert!(sa.channels.contains(SpeakerAllocationFlags::LFE1));
assert!(sa.channels.contains(SpeakerAllocationFlags::FC));
}
#[test]
fn test_y420_vdb_adds_modes_and_vics() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[4] = (7 << 5) | 2;
ext[5] = 0x0E; ext[6] = 96;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.y420_vics, vec![96]);
assert!(caps.supported_modes.iter().any(|m| m.width == 3840
&& m.height == 2160
&& m.refresh_rate == Some(RefreshRate::integral(50))));
}
#[test]
fn test_extended_svd_vic193() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[4] = (2 << 5) | 2;
ext[5] = 0x00; ext[6] = 193;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.vics, vec![(193, false)]);
assert!(caps.supported_modes.iter().any(|m| m.width == 5120
&& m.height == 2160
&& m.refresh_rate == Some(RefreshRate::integral(120))));
}
#[test]
fn test_extended_svd_native_flag() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[4] = (2 << 5) | 2;
ext[5] = 0x80; ext[6] = 196;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.vics, vec![(196, true)]);
}
#[test]
fn test_extended_svd_truncated_is_skipped() {
let mut ext = [0u8; 128];
ext[2] = 6;
ext[4] = (2 << 5) | 1; ext[5] = 0x00;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert!(cea.vics.is_empty());
}
#[test]
fn test_y420_capability_map_stored() {
let mut ext = [0u8; 128];
ext[2] = 8;
ext[4] = (7 << 5) | 2;
ext[5] = 0x0F; ext[6] = 0b0000_0101;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
assert_eq!(cea.y420_capability_map, vec![0b0000_0101]);
}
#[test]
fn test_hdmi_audio_block_with_msa_and_sads() {
let mut ext = [0u8; 128];
ext[2] = 13;
ext[4] = (7 << 5) | 8; ext[5] = 0x12; ext[6] = 0x08; ext[7] = (1 << 3) | 1; ext[8] = 0x06; ext[9] = 0x01; ext[10] = (2 << 3) | 5; ext[11] = 0x04; ext[12] = 0x50;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
let hdmi_audio = cea.hdmi_audio.as_ref().unwrap();
assert!(hdmi_audio.multi_stream_audio);
assert_eq!(hdmi_audio.audio_descriptors.len(), 2);
assert!(matches!(
hdmi_audio.audio_descriptors[0].format,
AudioFormat::Lpcm
));
assert_eq!(hdmi_audio.audio_descriptors[0].max_channels, 2);
assert!(matches!(
hdmi_audio.audio_descriptors[1].format,
AudioFormat::Ac3
));
assert_eq!(hdmi_audio.audio_descriptors[1].max_channels, 6);
}
#[test]
fn test_hdmi_audio_block_no_msa() {
let mut ext = [0u8; 128];
ext[2] = 10;
ext[4] = (7 << 5) | 5; ext[5] = 0x12; ext[6] = 0x00; ext[7] = (1 << 3) | 1;
ext[8] = 0x04;
ext[9] = 0x01;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
let hdmi_audio = cea.hdmi_audio.as_ref().unwrap();
assert!(!hdmi_audio.multi_stream_audio);
assert_eq!(hdmi_audio.audio_descriptors.len(), 1);
}
#[test]
fn test_hdmi_audio_block_flags_only() {
let mut ext = [0u8; 128];
ext[2] = 7;
ext[4] = (7 << 5) | 2; ext[5] = 0x12;
ext[6] = 0x08;
let mut caps = DisplayCapabilities::default();
ExtensionHandler::process(&Cea861Handler, &[&ext], &mut caps, &mut Vec::new());
let cea = caps.get_extension_data::<Cea861Capabilities>(0x02).unwrap();
let hdmi_audio = cea.hdmi_audio.as_ref().unwrap();
assert!(hdmi_audio.multi_stream_audio);
assert!(hdmi_audio.audio_descriptors.is_empty());
}
}