use crate::usb::descriptors::video;
use super::*;
const CAM_CTRL_NAMES: [&str; 22] = [
"Scanning Mode",
"Auto-Exposure Mode",
"Auto-Exposure Priority",
"Exposure Time (Absolute)",
"Exposure Time (Relative)",
"Focus (Absolute)",
"Focus (Relative)",
"Iris (Absolute)",
"Iris (Relative)",
"Zoom (Absolute)",
"Zoom (Relative)",
"PanTilt (Absolute)",
"PanTilt (Relative)",
"Roll (Absolute)",
"Roll (Relative)",
"Reserved",
"Reserved",
"Focus, Auto",
"Privacy",
"Focus, Simple",
"Window",
"Region of Interest",
];
const CTRL_NAMES: [&str; 19] = [
"Brightness",
"Contrast",
"Hue",
"Saturation",
"Sharpness",
"Gamma",
"White Balance Temperature",
"White Balance Component",
"Backlight Compensation",
"Gain",
"Power Line Frequency",
"Hue, Auto",
"White Balance Temperature, Auto",
"White Balance Component, Auto",
"Digital Multiplier",
"Digital Multiplier Limit",
"Analog Video Standard",
"Analog Video Lock Status",
"Contrast, Auto",
];
const EN_CTRL_NAMES: [&str; 22] = [
"Scanning Mode",
"Auto-Exposure Mode",
"Auto-Exposure Priority",
"Exposure Time (Absolute)",
"Exposure Time (Relative)",
"Focus (Absolute)",
"Focus (Relative)",
"Iris (Absolute)",
"Iris (Relative)",
"Zoom (Absolute)",
"Zoom (Relative)",
"PanTilt (Absolute)",
"PanTilt (Relative)",
"Roll (Absolute)",
"Roll (Relative)",
"Reserved",
"Reserved",
"Focus, Auto",
"Privacy",
"Focus, Simple",
"Window",
"Region of Interest",
];
const STD_NAMES: [&str; 6] = [
"None",
"NTSC - 525/60",
"PAL - 625/50",
"SECAM - 625/50",
"NTSC - 625/50",
"PAL - 525/60",
];
fn dump_processing_unit(pu: &video::ProcessingUnit, protocol: u8, indent: usize, width: usize) {
dump_value(pu.unit_id, "bUnitID", indent + 2, width);
dump_value(pu.source_id, "bSourceID", indent + 2, width);
dump_value(pu.max_multiplier, "wMaxMultiplier", indent + 2, width);
dump_value(pu.control_size, "bControlSize", indent + 2, width);
dump_hex(pu.controls, "bmControls", indent + 2, width);
if protocol == 0x01 {
for (i, n) in CTRL_NAMES.iter().enumerate().take(19) {
if (pu.controls >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
} else {
for (i, n) in CTRL_NAMES.iter().enumerate().take(18) {
if (pu.controls >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
}
dump_value_string(
pu.processing_index,
"iProcessing",
pu.processing_index.to_string(),
indent + 2,
width,
);
dump_hex(pu.video_standards, "bmVideoStandards", indent + 2, width);
for (i, n) in STD_NAMES.iter().enumerate().take(6) {
if (pu.video_standards >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
}
fn dump_extension_unit(eu: &video::ExtensionUnit, indent: usize, width: usize) {
dump_value(eu.unit_id, "bUnitID", indent + 2, width);
dump_guid(
&eu.guid_extension_code,
"guidExtensionCode",
indent + 2,
width,
);
dump_value(eu.num_controls, "bNumControls", indent + 2, width);
dump_value(eu.num_input_pins, "bNrInPins", indent + 2, width);
for (i, source_id) in eu.source_ids.iter().enumerate() {
dump_value(*source_id, &format!("baSourceID({i:2})"), indent + 2, width);
}
dump_value(eu.control_size, "bControlSize", indent + 2, width);
for (i, control) in eu.controls.iter().enumerate() {
dump_hex(*control, &format!("bmControls({i:2})"), indent + 2, width);
}
dump_value_string(
eu.extension_index,
"iExtension",
eu.extension.as_ref().unwrap_or(&String::new()),
indent + 2,
width,
);
}
fn dump_encoding_unit(eu: &video::EncodingUnit, indent: usize, width: usize) {
dump_value(eu.unit_id, "bUnitID", indent + 2, width);
dump_value(eu.source_id, "bSourceID", indent + 2, width);
dump_value_string(
eu.encoding_index,
"iEncoding",
eu.encoding.as_ref().unwrap_or(&String::new()),
indent + 2,
width,
);
dump_value(eu.control_size, "bControlSize", indent + 2, width);
dump_hex(eu.controls, "bmControls", indent + 2, width);
for (i, n) in EN_CTRL_NAMES.iter().enumerate().take(20) {
if (eu.controls >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
dump_hex(eu.controls_runtime, "bmControlsRuntime", indent + 2, width);
for (i, n) in EN_CTRL_NAMES.iter().enumerate().take(20) {
if (eu.controls_runtime >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
}
pub(crate) fn dump_videocontrol_interface(
vcd: &video::UvcDescriptor,
vct: &video::ControlSubtype,
protocol: u8,
indent: usize,
) {
const DUMP_WIDTH: usize = 36; dump_string("VideoControl Interface Descriptor:", indent);
dump_value(vcd.length, "bLength", indent + 2, DUMP_WIDTH);
dump_value(
vcd.descriptor_type,
"bDescriptorType",
indent + 2,
DUMP_WIDTH,
);
dump_value_string(
vct.to_owned() as u8,
"bDescriptorSubtype",
format!("({vct:#})"),
indent + 2,
DUMP_WIDTH,
);
match &vcd.interface {
video::UvcInterfaceDescriptor::Invalid(_) => {
println!(
"{:indent$}Warning: {:#} descriptors are illegal for {}",
"",
vct,
protocol.to_owned(),
indent = indent
);
}
uvcid => dump_video_subtype(uvcid, protocol, indent, DUMP_WIDTH),
}
}
pub(crate) fn dump_videocontrol_interrupt_endpoint(
epd: &video::UvcDescriptor,
indent: usize,
width: usize,
) {
let subtype_byte = u8::from(epd.descriptor_subtype.to_owned());
let subtype_string = match subtype_byte {
0x03 => "EP_INTERRUPT",
_ => "invalid",
};
let bytes = Vec::<u8>::from(epd.interface.to_owned());
if epd.length < 5 {
println!("{indent:indent$}Warning: Descriptor too short");
}
if epd.descriptor_type != 0x01 {
println!("{indent:indent$}Warning: Invalid descriptor type");
}
dump_string("VideoControl Endpoint Descriptor:", indent);
dump_value(epd.length, "bLength", indent + 2, width);
dump_value(epd.descriptor_type, "bDescriptorType", indent + 2, width);
dump_value_string(
subtype_byte,
"bDescriptorSubtype",
format!("({subtype_string})"),
indent + 2,
width,
);
if let (Some(first), Some(second)) = (bytes.first(), bytes.get(1)) {
let wmax = u16::from_le_bytes([*first, *second]);
dump_value(wmax, "wMaxTransferSize", indent + 2, width);
}
dump_junk(&bytes, indent + 2, epd.length as usize - 3, 2);
}
fn dump_video_input_header(ih: &video::InputHeader, indent: usize, width: usize) {
dump_value(ih.num_formats, "bNumFormats", indent + 2, width);
dump_hex(ih.total_length, "wTotalLength", indent + 2, width);
dump_value_string(
format!("0x{:02x}", ih.endpoint_address.address),
"bEndpointAddress",
ih.endpoint_address.to_string(),
indent + 2,
width,
);
dump_value(ih.info, "bmInfo", indent + 2, width);
dump_value(ih.terminal_link, "bTerminalLink", indent + 2, width);
dump_value(
ih.still_capture_method,
"bStillCaptureMethod",
indent + 2,
width,
);
dump_value(ih.trigger_support, "bTriggerSupport", indent + 2, width);
dump_value(ih.trigger_usage, "bTriggerUsage", indent + 2, width);
dump_value(ih.control_size, "bControlSize", indent + 2, width);
for (i, b) in ih.controls.chunks(ih.control_size as usize).enumerate() {
dump_value(b[0], &format!("bmaControls({i:2})"), indent + 2, width);
}
}
fn dump_video_output_header(oh: &video::OutputHeader, indent: usize, width: usize) {
dump_value(oh.num_formats, "bNumFormats", indent + 2, width);
dump_hex(oh.total_length, "wTotalLength", indent + 2, width);
dump_value_string(
format!("0x{:02x}", oh.endpoint_address.address),
"bEndpointAddress",
oh.endpoint_address.to_string(),
indent + 2,
width,
);
dump_value(oh.terminal_link, "bTerminalLink", indent + 2, width);
dump_value(oh.control_size, "bControlSize", indent + 2, width);
for (i, b) in oh.controls.chunks(oh.control_size as usize).enumerate() {
dump_value(b[0], &format!("bmaControls({i:2})"), indent + 2, width);
}
}
fn dump_video_color_format(cf: &video::ColorFormat, indent: usize, width: usize) {
let color_primitives = |c: u8| match c {
1 => "BT.709,sRGB",
2 => "BT.470-2 (M)",
3 => "BT.470-2 (B,G)",
4 => "SMPTE 170M",
5 => "SMPTE 240M",
_ => "Unspecified",
};
let transfer_characteristics = |c: u8| match c {
1 => "BT.709",
2 => "BT.470-2 (M)",
3 => "BT.470-2 (B,G)",
4 => "SMPTE 170M",
5 => "SMPTE 240M",
6 => "Linear",
7 => "sRGB",
_ => "Unspecified",
};
let matrix_coefficients = |c: u8| match c {
1 => "BT.709",
2 => "FCC",
3 => "BT.470-2 (B,G)",
4 => "SMPTE 170M (BT.601)",
5 => "SMPTE 240M",
_ => "Unspecified",
};
dump_value_string(
cf.color_primaries,
"bColorPrimaries",
format!("({})", color_primitives(cf.color_primaries)),
indent + 2,
width,
);
dump_value_string(
cf.transfer_characteristics,
"bTransferCharacteristics",
format!(
"({})",
transfer_characteristics(cf.transfer_characteristics)
),
indent + 2,
width,
);
dump_value_string(
cf.matrix_coefficients,
"bMatrixCoefficients",
format!("({})", matrix_coefficients(cf.matrix_coefficients)),
indent + 2,
width,
);
}
fn dump_format_stream_based(fs: &video::FormatStreamBased, indent: usize, width: usize) {
dump_value(fs.format_index, "bFormatIndex", indent + 2, width);
dump_guid(&fs.guid_format, "guidFormat", indent + 2, width);
dump_value(fs.packet_length, "dwPacketLength", indent + 2, width);
}
fn dump_format_mpeg2ts(fmts: &video::FormatMPEG2TS, indent: usize, width: usize) {
dump_value(fmts.format_index, "bFormatIndex", indent + 2, width);
dump_value(fmts.data_offset, "bDataOffset", indent + 2, width);
dump_value(fmts.packet_length, "bPacketLength", indent + 2, width);
dump_value(fmts.stride_length, "bStrideLength", indent + 2, width);
if let Some(guid) = &fmts.guid_stride_format {
dump_guid(guid, "guidStrideFormat", indent + 2, width);
}
}
fn dump_interlace_flags(interlace_flags: u8, indent: usize, width: usize) {
let field_pattern = |f: u8| -> &'static str {
match f {
0 => "Field 1 only",
1 => "Field 2 only",
2 => "Regular pattern of fields 1 and 2",
3 => "Random pattern of fields 1 and 2",
_ => "Invalid",
}
};
dump_hex(interlace_flags, "bmInterlaceFlags", indent, width);
dump_string(
&format!(
"Interlaced stream or variable: {}",
if interlace_flags & 0x01 != 0 {
"Yes"
} else {
"No"
}
),
indent + 2,
);
dump_string(
&format!(
"Fields per frame: {}",
if interlace_flags & 0x02 != 0 {
"1"
} else {
"2"
}
),
indent + 2,
);
dump_string(
&format!(
"Field 1 first: {}",
if interlace_flags & 0x04 != 0 {
"Yes"
} else {
"No"
}
),
indent + 2,
);
dump_string(
&format!(
"Field pattern: {}",
field_pattern((interlace_flags >> 4) & 0x03)
),
indent + 2,
);
}
fn dump_format_mjpeg(fmjpeg: &video::FormatMJPEG, indent: usize, width: usize) {
dump_value(fmjpeg.format_index, "bFormatIndex", indent + 2, width);
dump_value(
fmjpeg.num_frame_descriptors,
"bNumFrameDescriptors",
indent + 2,
width,
);
dump_value(fmjpeg.flags, "bmFlags", indent + 2, width);
dump_string(
&format!(
"Fixed-size samples: {}",
if fmjpeg.flags & 0x01 != 0 {
"Yes"
} else {
"No"
}
),
indent + 2,
);
dump_value(
fmjpeg.default_frame_index,
"bDefaultFrameIndex",
indent + 2,
width,
);
dump_value(fmjpeg.aspect_ratio_x, "bAspectRatioX", indent + 2, width);
dump_value(fmjpeg.aspect_ratio_y, "bAspectRatioY", indent + 2, width);
dump_interlace_flags(fmjpeg.interlace_flags, indent + 2, width);
dump_value(fmjpeg.copy_protect, "bCopyProtect", indent + 2, width);
}
fn dump_still_image_frame(sif: &video::StillImageFrame, indent: usize, width: usize) {
dump_value_string(
sif.endpoint_address.address,
"bEndpointAddress",
sif.endpoint_address.to_string(),
indent + 2,
width,
);
dump_value(
sif.num_image_size_patterns,
"bNumImageSizePatterns",
indent + 2,
width,
);
for (i, (w, h)) in sif.image_size_patterns.iter().enumerate() {
dump_value(*w, &format!("wWidth({i:2})"), indent + 2, width);
dump_value(*h, &format!("wHeight({i:2})"), indent + 2, width);
}
dump_value(
sif.num_compression_patterns,
"bNumCompressionPatterns",
indent + 2,
width,
);
for (i, b) in sif.compression_patterns.iter().enumerate() {
dump_value(*b, &format!("bCompression({i:2})"), indent + 2, width);
}
}
fn dump_format_frame(fufb: &video::FormatFrame, indent: usize, width: usize) {
dump_value(fufb.format_index, "bFormatIndex", indent + 2, width);
dump_value(
fufb.num_frame_descriptors,
"bNumFrameDescriptors",
indent + 2,
width,
);
dump_guid(&fufb.guid_format, "guidFormat", indent + 2, width);
dump_value(fufb.bits_per_pixel, "bBitsPerPixel", indent + 2, width);
dump_value(
fufb.default_frame_index,
"bDefaultFrameIndex",
indent + 2,
width,
);
dump_value(fufb.aspect_ratio_x, "bAspectRatioX", indent + 2, width);
dump_value(fufb.aspect_ratio_y, "bAspectRatioY", indent + 2, width);
dump_hex(fufb.interlace_flags, "bmInterlaceFlags", indent + 2, width);
dump_value(fufb.copy_protect, "bCopyProtect", indent + 2, width);
dump_interlace_flags(fufb.interlace_flags, indent + 2, width);
if let Some(variable_size) = fufb.variable_size {
dump_value(variable_size, "bVariableSize", indent + 2, width);
}
}
fn dump_frame(frame: &video::FrameCommon, indent: usize, dump_width: usize) {
dump_value(frame.frame_index, "bFrameIndex", indent + 2, dump_width);
dump_hex(frame.capabilities, "bmCapabilities", indent + 2, dump_width);
if frame.capabilities & 0x01 != 0 {
dump_string("Still image supported", indent + 4);
} else {
dump_string("Still image unsupported", indent + 4);
}
if frame.capabilities & 0x02 != 0 {
dump_string("Fixed frame-rate", indent + 4);
}
dump_value(frame.width, "wWidth", indent + 2, dump_width);
dump_value(frame.height, "wHeight", indent + 2, dump_width);
dump_value(frame.min_bit_rate, "dwMinBitRate", indent + 2, dump_width);
dump_value(frame.max_bit_rate, "dwMaxBitRate", indent + 2, dump_width);
}
fn dump_frame_uncompressed(frame: &video::FrameUncompressed, indent: usize, dump_width: usize) {
dump_frame(&frame.common, indent, dump_width);
dump_value(
frame.max_video_frame_buffer_size,
"dwMaxVideoFrameBufferSize",
indent + 2,
dump_width,
);
dump_value(
frame.default_frame_interval,
"dwDefaultFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_interval_type,
"bFrameIntervalType",
indent + 2,
dump_width,
);
if frame.frame_interval_type == 0 {
dump_value(
frame.frame_intervals[0],
"dwMinFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_intervals[1],
"dwMaxFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_intervals[2],
"dwFrameIntervalStep",
indent + 2,
dump_width,
);
} else {
for (i, interval) in frame.frame_intervals.iter().enumerate() {
dump_value(
*interval,
&format!("dwFrameInterval({i:2})"),
indent + 2,
dump_width,
);
}
}
}
fn dump_frame_framebased(frame: &video::FrameFrameBased, indent: usize, dump_width: usize) {
dump_frame(&frame.common, indent, dump_width);
dump_value(
frame.default_frame_interval,
"dwDefaultFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_interval_type,
"bFrameIntervalType",
indent + 2,
dump_width,
);
dump_value(
frame.bytes_per_line,
"dwBytesPerLine",
indent + 2,
dump_width,
);
if frame.frame_interval_type == 0 {
dump_value(
frame.frame_intervals[0],
"dwMinFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_intervals[1],
"dwMaxFrameInterval",
indent + 2,
dump_width,
);
dump_value(
frame.frame_intervals[2],
"dwFrameIntervalStep",
indent + 2,
dump_width,
);
} else {
for (i, interval) in frame.frame_intervals.iter().enumerate() {
dump_value(
*interval,
&format!("dwFrameInterval({i:2})"),
indent + 2,
dump_width,
);
}
}
}
fn dump_video_subtype(
uvcid: &video::UvcInterfaceDescriptor,
protocol: u8,
indent: usize,
width: usize,
) {
match uvcid {
video::UvcInterfaceDescriptor::Header(h) => {
dump_value(h.version, "bcdUVC", indent + 2, width);
dump_hex(h.total_length, "wTotalLength", indent + 2, width);
dump_value(
format!(
"{:5}.{:06}MHz",
h.clock_frequency / 1000000,
h.clock_frequency % 1000000
),
"dwClockFrequency",
indent + 2,
width + 10,
);
dump_value(h.collection_bytes, "bInCollection", indent + 2, width);
dump_array(&h.interfaces, "baInterfaceNr", indent + 2, width);
}
video::UvcInterfaceDescriptor::InputTerminal(d) => {
dump_value(d.terminal_id, "bTerminalID", indent + 2, width);
dump_name_hex(
d.terminal_type,
names::videoterminal,
"wTerminalType",
indent + 2,
width,
);
dump_value(d.associated_terminal, "bAssocTerminal", indent + 2, width);
dump_value_string(
d.terminal_index,
"iTerminal",
d.terminal.as_ref().unwrap_or(&String::new()),
indent + 2,
width,
);
if let Some(extra) = &d.extra {
dump_value(
extra.objective_focal_length_min,
"wObjectiveFocalLengthMin",
indent + 2,
width,
);
dump_value(
extra.objective_focal_length_max,
"wObjectiveFocalLengthMax",
indent + 2,
width,
);
dump_value(
extra.ocular_focal_length,
"wOcularFocalLength",
indent + 2,
width,
);
dump_value(extra.control_size, "bControlSize", indent + 2, width);
dump_hex(extra.controls, "bmControls", indent + 2, width);
if protocol == 0x01 {
for (i, n) in CAM_CTRL_NAMES.iter().enumerate().take(22) {
if (extra.controls >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
} else {
for (i, n) in CAM_CTRL_NAMES.iter().enumerate().take(19) {
if (extra.controls >> i) & 1 != 0 {
dump_string(n, indent + 4);
}
}
}
}
}
video::UvcInterfaceDescriptor::OutputTerminal(ot) => {
dump_audio_output_terminal1(ot, indent + 2, width);
}
video::UvcInterfaceDescriptor::SelectorUnit(su) => {
dump_audio_selector_unit1(su, indent, width);
}
video::UvcInterfaceDescriptor::ProcessingUnit(pu) => {
dump_processing_unit(pu, protocol, indent, width);
}
video::UvcInterfaceDescriptor::ExtensionUnit(eu) => {
dump_extension_unit(eu, indent, width);
}
video::UvcInterfaceDescriptor::EncodingUnit(eu) => {
dump_encoding_unit(eu, indent, width);
}
video::UvcInterfaceDescriptor::InputHeader(d) => {
dump_video_input_header(d, indent, width);
}
video::UvcInterfaceDescriptor::OutputHeader(d) => {
dump_video_output_header(d, indent, width);
}
video::UvcInterfaceDescriptor::StillImageFrame(d) => {
dump_still_image_frame(d, indent, width);
}
video::UvcInterfaceDescriptor::FormatFrameBased(d)
| video::UvcInterfaceDescriptor::FormatUncompressed(d) => {
dump_format_frame(d, indent, width);
}
video::UvcInterfaceDescriptor::FrameUncompressed(d)
| video::UvcInterfaceDescriptor::FrameMJPEG(d) => {
dump_frame_uncompressed(d, indent, width);
}
video::UvcInterfaceDescriptor::FrameFrameBased(d) => {
dump_frame_framebased(d, indent, width);
}
video::UvcInterfaceDescriptor::FormatMJPEG(d) => {
dump_format_mjpeg(d, indent, width);
}
video::UvcInterfaceDescriptor::FormatMPEG2TS(d) => {
dump_format_mpeg2ts(d, indent, width);
}
video::UvcInterfaceDescriptor::ColorFormat(d) => {
dump_video_color_format(d, indent, width);
}
video::UvcInterfaceDescriptor::FormatStreamBased(d) => {
dump_format_stream_based(d, indent, width);
}
video::UvcInterfaceDescriptor::Undefined(data)
| video::UvcInterfaceDescriptor::Invalid(data) => {
println!(
"{:indent$}Invalid desc subtype: {}",
"",
data.iter()
.map(|b| format!("{b:02x}"))
.collect::<Vec<String>>()
.join(" "),
indent = indent + 2,
);
}
_ => {
log::warn!("Unhandled UVC interface descriptor: {uvcid:?}");
}
}
}
pub(crate) fn dump_videostreaming_interface(
vsd: &video::UvcDescriptor,
vst: &video::StreamingSubtype,
protocol: u8,
indent: usize,
) {
const DUMP_WIDTH: usize = 36; dump_string("VideoStreaming Interface Descriptor:", indent);
dump_value(vsd.length, "bLength", indent + 2, DUMP_WIDTH);
dump_value(
vsd.descriptor_type,
"bDescriptorType",
indent + 2,
DUMP_WIDTH,
);
dump_value_string(
vst.to_owned() as u8,
"bDescriptorSubtype",
format!("({vst:#})"),
indent + 2,
DUMP_WIDTH,
);
match &vsd.interface {
video::UvcInterfaceDescriptor::Invalid(_) => {
println!(
"{:indent$}Warning: {:#} descriptors are illegal for {}",
"",
vst,
protocol.to_owned(),
indent = indent
);
}
uvcid => dump_video_subtype(uvcid, protocol, indent, DUMP_WIDTH),
}
}