use super::*;
impl<'a> ExtensionBodyDef<'a> for VideoDepthRange<'a> {
const TAG_EXTENSION: u8 = 0x10;
const NAME: &'static str = "VIDEO_DEPTH_RANGE";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct DepthRange<'a> {
pub range_type: u8,
pub body: DepthRangeBody<'a>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[non_exhaustive]
pub enum DepthRangeBody<'a> {
ProductionDisparityHint {
max: i16,
min: i16,
},
MultiRegionSei,
#[cfg_attr(feature = "serde", serde(borrow))]
Other(&'a [u8]),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct VideoDepthRange<'a> {
pub ranges: Vec<DepthRange<'a>>,
}
fn sext12(v: u16) -> i16 {
if v & 0x800 != 0 {
(v | 0xF000) as i16
} else {
v as i16
}
}
impl<'a> Parse<'a> for VideoDepthRange<'a> {
type Error = crate::error::Error;
fn parse(sel: &'a [u8]) -> Result<Self> {
let mut pos = 0;
let mut ranges = Vec::new();
loop {
if pos == sel.len() {
break;
}
if sel.len() - pos < VD_RANGE_HDR_LEN {
return Err(Error::BufferTooShort {
need: pos + VD_RANGE_HDR_LEN,
have: sel.len(),
what: "video_depth_range body",
});
}
let range_type = sel[pos];
let range_length = sel[pos + 1] as usize;
pos += VD_RANGE_HDR_LEN;
if sel.len() < pos + range_length {
return Err(Error::BufferTooShort {
need: pos + range_length,
have: sel.len(),
what: "video_depth_range body",
});
}
let body = match range_type {
0x00 => {
if range_length < VD_DISPARITY_LEN {
return Err(invalid(
"video_depth_range: production_disparity_hint requires 3+ bytes",
));
}
let b0 = sel[pos];
let b1 = sel[pos + 1];
let b2 = sel[pos + 2];
let max = sext12((u16::from(b0) << 4) | (u16::from(b1) >> 4));
let min = sext12(((u16::from(b1) & 0x0F) << 8) | u16::from(b2));
DepthRangeBody::ProductionDisparityHint { max, min }
}
0x01 => DepthRangeBody::MultiRegionSei,
_ => DepthRangeBody::Other(&sel[pos..pos + range_length]),
};
ranges.push(DepthRange { range_type, body });
pos += range_length;
}
Ok(VideoDepthRange { ranges })
}
}
impl Serialize for VideoDepthRange<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
self.ranges
.iter()
.map(|r| {
VD_RANGE_HDR_LEN
+ match &r.body {
DepthRangeBody::ProductionDisparityHint { .. } => VD_DISPARITY_LEN,
DepthRangeBody::MultiRegionSei => 0,
DepthRangeBody::Other(s) => s.len(),
}
})
.sum()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let mut p = 0;
for r in &self.ranges {
buf[p] = r.range_type;
match &r.body {
DepthRangeBody::ProductionDisparityHint { max, min } => {
buf[p + 1] = VD_DISPARITY_LEN as u8;
let max_bits = *max as u16 & 0x0FFF;
let min_bits = *min as u16 & 0x0FFF;
buf[p + 2] = (max_bits >> 4) as u8;
buf[p + 3] = (((max_bits & 0x0F) << 4) | ((min_bits >> 8) & 0x0F)) as u8;
buf[p + 4] = min_bits as u8;
p += VD_RANGE_HDR_LEN + VD_DISPARITY_LEN;
}
DepthRangeBody::MultiRegionSei => {
buf[p + 1] = 0;
p += VD_RANGE_HDR_LEN;
}
DepthRangeBody::Other(s) => {
buf[p + 1] = s.len() as u8;
buf[p + 2..p + 2 + s.len()].copy_from_slice(s);
p += VD_RANGE_HDR_LEN + s.len();
}
}
}
Ok(len)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptors::extension::test_support::*;
use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
#[test]
fn parse_video_depth_range_two_entries_round_trip() {
let max_val: i16 = 100;
let min_val: i16 = -50;
let max_b = max_val as u16 & 0x0FFF; let min_b = min_val as u16 & 0x0FFF; let sel = [
0x00,
0x03,
(max_b >> 4) as u8,
(((max_b & 0x0F) << 4) | ((min_b >> 8) & 0x0F)) as u8,
min_b as u8,
0x05,
0x02,
0xAA,
0xBB,
];
let bytes = wrap(0x10, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
assert_eq!(d.kind(), Some(ExtensionTag::VideoDepthRange));
match &d.body {
ExtensionBody::VideoDepthRange(b) => {
assert_eq!(b.ranges.len(), 2);
assert_eq!(b.ranges[0].range_type, 0x00);
match &b.ranges[0].body {
DepthRangeBody::ProductionDisparityHint { max, min } => {
assert_eq!(*max, 100);
assert_eq!(*min, -50);
}
_ => panic!("expected ProductionDisparityHint"),
}
assert_eq!(b.ranges[1].range_type, 0x05);
match &b.ranges[1].body {
DepthRangeBody::Other(s) => assert_eq!(s, &[0xAA, 0xBB]),
_ => panic!("expected Other"),
}
}
other => panic!("expected VideoDepthRange, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_video_depth_range_negative_edge_round_trip() {
let max_val: i16 = -1;
let min_val: i16 = 0;
let max_b = max_val as u16 & 0x0FFF;
let min_b = min_val as u16 & 0x0FFF;
let sel = [
0x00,
0x03,
(max_b >> 4) as u8,
(((max_b & 0x0F) << 4) | ((min_b >> 8) & 0x0F)) as u8,
min_b as u8,
];
let bytes = wrap(0x10, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::VideoDepthRange(b) => {
assert_eq!(b.ranges.len(), 1);
match &b.ranges[0].body {
DepthRangeBody::ProductionDisparityHint { max, min } => {
assert_eq!(*max, -1);
assert_eq!(*min, 0);
}
_ => panic!("expected ProductionDisparityHint"),
}
}
other => panic!("expected VideoDepthRange, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_video_depth_range_multi_region_sei_round_trip() {
let sel = [0x01, 0x00, 0x01, 0x00];
let bytes = wrap(0x10, &sel);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::VideoDepthRange(b) => {
assert_eq!(b.ranges.len(), 2);
assert!(matches!(b.ranges[0].body, DepthRangeBody::MultiRegionSei));
assert!(matches!(b.ranges[1].body, DepthRangeBody::MultiRegionSei));
}
other => panic!("expected VideoDepthRange, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_video_depth_range_empty_selector() {
let bytes = wrap(0x10, &[]);
let d = ExtensionDescriptor::parse(&bytes).unwrap();
match &d.body {
ExtensionBody::VideoDepthRange(b) => {
assert!(b.ranges.is_empty());
}
other => panic!("expected VideoDepthRange, got {other:?}"),
}
round_trip(&d);
}
#[test]
fn parse_video_depth_range_rejects_truncated() {
let sel = [0x00];
let bytes = wrap(0x10, &sel);
assert!(matches!(
ExtensionDescriptor::parse(&bytes).unwrap_err(),
crate::error::Error::BufferTooShort { .. }
));
}
#[test]
fn parse_video_depth_range_rejects_overrun() {
let sel = [0x00, 0x05, 0xAA, 0xBB];
let bytes = wrap(0x10, &sel);
assert!(matches!(
ExtensionDescriptor::parse(&bytes).unwrap_err(),
crate::error::Error::BufferTooShort { .. }
));
}
}