Skip to main content

dvb_si/descriptors/extension/
video_depth_range.rs

1//! Video Depth Range Descriptor — ETSI EN 300 468 §6.4.16.1 (tag_extension 0x10).
2use super::*;
3use alloc::vec::Vec;
4
5/// Range type — ETSI EN 300 468 §6.4.16.1 Table 161
6/// (`docs/en_300_468.md`, Table 161 — Range type coding).
7///
8/// 8-bit field. Selects the interpretation of the `range_selector` bytes.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[non_exhaustive]
12pub enum RangeType {
13    /// `0x00` — production disparity hint (`production_disparity_hint_info()`).
14    ProductionDisparityHint,
15    /// `0x01` — multi-region disparity Supplemental Enhancement Information (SEI) present.
16    MultiRegionSei,
17    /// `0x02`–`0xFF` — reserved for future use.
18    Reserved(u8),
19}
20
21impl RangeType {
22    #[must_use]
23    /// Creates a value from a wire byte, preserving every possible
24    /// byte value for lossless round-trip.
25    pub fn from_u8(v: u8) -> Self {
26        match v {
27            0x00 => Self::ProductionDisparityHint,
28            0x01 => Self::MultiRegionSei,
29            v => Self::Reserved(v),
30        }
31    }
32
33    #[must_use]
34    /// Returns the wire byte for this value.
35    pub fn to_u8(self) -> u8 {
36        match self {
37            Self::ProductionDisparityHint => 0x00,
38            Self::MultiRegionSei => 0x01,
39            Self::Reserved(v) => v,
40        }
41    }
42
43    #[must_use]
44    /// Returns the spec token for this value.
45    pub fn name(self) -> &'static str {
46        match self {
47            Self::ProductionDisparityHint => "production disparity hint",
48            Self::MultiRegionSei => "multi-region SEI",
49            Self::Reserved(_) => "reserved",
50        }
51    }
52}
53dvb_common::impl_spec_display!(RangeType, Reserved);
54
55impl<'a> ExtensionBodyDef<'a> for VideoDepthRange<'a> {
56    const TAG_EXTENSION: u8 = 0x10;
57    const NAME: &'static str = "VIDEO_DEPTH_RANGE";
58}
59
60/// One depth range entry (Table 160 inner loop).
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
64pub struct DepthRange<'a> {
65    /// range_type(8) — Table 161 — [`RangeType`].
66    pub range_type: RangeType,
67    /// Body interpreted by `range_type`.
68    pub body: DepthRangeBody<'a>,
69}
70
71/// Body of a [`DepthRange`], keyed on `range_type` (Table 161).
72#[derive(Debug, Clone, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
75#[non_exhaustive]
76pub enum DepthRangeBody<'a> {
77    /// `0x00` — production_disparity_hint_info() (Table 162).
78    /// Two 12-bit two's-complement signed values packed into 3 bytes.
79    ProductionDisparityHint {
80        /// video_max_disparity_hint (12 tcimsbf).
81        max: i16,
82        /// video_min_disparity_hint (12 tcimsbf).
83        min: i16,
84    },
85    /// `0x01` — multi-region SEI present (empty body).
86    MultiRegionSei,
87    /// Any other `range_type`: raw `range_selector` bytes.
88    #[cfg_attr(feature = "serde", serde(borrow))]
89    Other(&'a [u8]),
90}
91
92/// video_depth_range body (Table 160) — fully typed loop.
93#[derive(Debug, Clone, PartialEq, Eq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize))]
95#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
96pub struct VideoDepthRange<'a> {
97    /// Depth range entries in wire order.
98    pub ranges: Vec<DepthRange<'a>>,
99}
100
101/// Sign-extend a 12-bit two's-complement value to `i16` (bit 11 is the sign).
102fn sext12(v: u16) -> i16 {
103    if v & 0x800 != 0 {
104        (v | 0xF000) as i16
105    } else {
106        v as i16
107    }
108}
109
110impl<'a> Parse<'a> for VideoDepthRange<'a> {
111    type Error = crate::error::Error;
112    fn parse(sel: &'a [u8]) -> Result<Self> {
113        let mut pos = 0;
114        let mut ranges = Vec::new();
115        loop {
116            if pos == sel.len() {
117                break;
118            }
119            // Need at least range_type + range_length (Table 160).
120            if sel.len() - pos < VD_RANGE_HDR_LEN {
121                return Err(Error::BufferTooShort {
122                    need: pos + VD_RANGE_HDR_LEN,
123                    have: sel.len(),
124                    what: "video_depth_range body",
125                });
126            }
127            let range_type = RangeType::from_u8(sel[pos]);
128            let range_length = sel[pos + 1] as usize;
129            pos += VD_RANGE_HDR_LEN;
130            if sel.len() < pos + range_length {
131                return Err(Error::BufferTooShort {
132                    need: pos + range_length,
133                    have: sel.len(),
134                    what: "video_depth_range body",
135                });
136            }
137            let body = match range_type {
138                // Table 161: production_disparity_hint_info() — Table 162.
139                RangeType::ProductionDisparityHint => {
140                    if range_length < VD_DISPARITY_LEN {
141                        return Err(invalid(
142                            "video_depth_range: production_disparity_hint requires 3+ bytes",
143                        ));
144                    }
145                    // Two 12-bit tcimsbf values packed into 3 bytes (b0..b2):
146                    let b0 = sel[pos];
147                    let b1 = sel[pos + 1];
148                    let b2 = sel[pos + 2];
149                    let max = sext12((u16::from(b0) << 4) | (u16::from(b1) >> 4));
150                    let min = sext12(((u16::from(b1) & 0x0F) << 8) | u16::from(b2));
151                    DepthRangeBody::ProductionDisparityHint { max, min }
152                }
153                // Table 161: multi-region SEI present (no body).
154                RangeType::MultiRegionSei => DepthRangeBody::MultiRegionSei,
155                // Any other range_type: raw range_selector bytes.
156                _ => DepthRangeBody::Other(&sel[pos..pos + range_length]),
157            };
158            ranges.push(DepthRange { range_type, body });
159            pos += range_length;
160        }
161        Ok(VideoDepthRange { ranges })
162    }
163}
164
165impl Serialize for VideoDepthRange<'_> {
166    type Error = crate::error::Error;
167    fn serialized_len(&self) -> usize {
168        self.ranges
169            .iter()
170            .map(|r| {
171                VD_RANGE_HDR_LEN
172                    + match &r.body {
173                        DepthRangeBody::ProductionDisparityHint { .. } => VD_DISPARITY_LEN,
174                        DepthRangeBody::MultiRegionSei => 0,
175                        DepthRangeBody::Other(s) => s.len(),
176                    }
177            })
178            .sum()
179    }
180    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
181        let len = self.serialized_len();
182        if buf.len() < len {
183            return Err(Error::OutputBufferTooSmall {
184                need: len,
185                have: buf.len(),
186            });
187        }
188        let mut p = 0;
189        for r in &self.ranges {
190            buf[p] = r.range_type.to_u8();
191            match &r.body {
192                DepthRangeBody::ProductionDisparityHint { max, min } => {
193                    // Table 162: two 12-bit tcimsbf values packed into 3 bytes.
194                    buf[p + 1] = VD_DISPARITY_LEN as u8;
195                    let max_bits = *max as u16 & 0x0FFF;
196                    let min_bits = *min as u16 & 0x0FFF;
197                    buf[p + 2] = (max_bits >> 4) as u8;
198                    buf[p + 3] = (((max_bits & 0x0F) << 4) | ((min_bits >> 8) & 0x0F)) as u8;
199                    buf[p + 4] = min_bits as u8;
200                    p += VD_RANGE_HDR_LEN + VD_DISPARITY_LEN;
201                }
202                DepthRangeBody::MultiRegionSei => {
203                    buf[p + 1] = 0;
204                    p += VD_RANGE_HDR_LEN;
205                }
206                DepthRangeBody::Other(s) => {
207                    buf[p + 1] = s.len() as u8;
208                    buf[p + 2..p + 2 + s.len()].copy_from_slice(s);
209                    p += VD_RANGE_HDR_LEN + s.len();
210                }
211            }
212        }
213        Ok(len)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::descriptors::extension::test_support::*;
221    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
222
223    #[test]
224    fn parse_video_depth_range_two_entries_round_trip() {
225        // Two depth ranges in one selector:
226        //   entry 1: range_type 0x00, range_length 3, disparity max=100 min=-50
227        //   entry 2: range_type 0x05, range_length 2, raw [0xAA, 0xBB]
228        // max=100 -> 0x0074 bits -> sext12=100; min=-50 -> 0x0032 bits,
229        // two's complement of 50 is 0x0FCE, sext12 -> -50.
230        let max_val: i16 = 100;
231        let min_val: i16 = -50;
232        let max_b = max_val as u16 & 0x0FFF; // 0x0064
233        let min_b = min_val as u16 & 0x0FFF; // 0x0FCE
234        let sel = [
235            0x00,
236            0x03,
237            (max_b >> 4) as u8,
238            (((max_b & 0x0F) << 4) | ((min_b >> 8) & 0x0F)) as u8,
239            min_b as u8,
240            0x05,
241            0x02,
242            0xAA,
243            0xBB,
244        ];
245        let bytes = wrap(0x10, &sel);
246        let d = ExtensionDescriptor::parse(&bytes).unwrap();
247        assert_eq!(d.kind(), Some(ExtensionTag::VideoDepthRange));
248        match &d.body {
249            ExtensionBody::VideoDepthRange(b) => {
250                assert_eq!(b.ranges.len(), 2);
251                assert_eq!(b.ranges[0].range_type, RangeType::ProductionDisparityHint);
252                match &b.ranges[0].body {
253                    DepthRangeBody::ProductionDisparityHint { max, min } => {
254                        assert_eq!(*max, 100);
255                        assert_eq!(*min, -50);
256                    }
257                    _ => panic!("expected ProductionDisparityHint"),
258                }
259                assert_eq!(b.ranges[1].range_type, RangeType::Reserved(0x05));
260                match &b.ranges[1].body {
261                    DepthRangeBody::Other(s) => assert_eq!(s, &[0xAA, 0xBB]),
262                    _ => panic!("expected Other"),
263                }
264            }
265            other => panic!("expected VideoDepthRange, got {other:?}"),
266        }
267        // parse → serialize → parse round-trip (byte-identical)
268        round_trip(&d);
269    }
270
271    #[test]
272    fn parse_video_depth_range_negative_edge_round_trip() {
273        // ProductionDisparityHint with max=-1 (0x0FFF), min=0
274        let max_val: i16 = -1;
275        let min_val: i16 = 0;
276        let max_b = max_val as u16 & 0x0FFF;
277        let min_b = min_val as u16 & 0x0FFF;
278        let sel = [
279            0x00,
280            0x03,
281            (max_b >> 4) as u8,
282            (((max_b & 0x0F) << 4) | ((min_b >> 8) & 0x0F)) as u8,
283            min_b as u8,
284        ];
285        let bytes = wrap(0x10, &sel);
286        let d = ExtensionDescriptor::parse(&bytes).unwrap();
287        match &d.body {
288            ExtensionBody::VideoDepthRange(b) => {
289                assert_eq!(b.ranges.len(), 1);
290                match &b.ranges[0].body {
291                    DepthRangeBody::ProductionDisparityHint { max, min } => {
292                        assert_eq!(*max, -1);
293                        assert_eq!(*min, 0);
294                    }
295                    _ => panic!("expected ProductionDisparityHint"),
296                }
297            }
298            other => panic!("expected VideoDepthRange, got {other:?}"),
299        }
300        round_trip(&d);
301    }
302
303    #[test]
304    fn parse_video_depth_range_multi_region_sei_round_trip() {
305        // range_type 0x01 with empty body
306        let sel = [0x01, 0x00, 0x01, 0x00];
307        let bytes = wrap(0x10, &sel);
308        let d = ExtensionDescriptor::parse(&bytes).unwrap();
309        match &d.body {
310            ExtensionBody::VideoDepthRange(b) => {
311                assert_eq!(b.ranges.len(), 2);
312                assert!(matches!(b.ranges[0].body, DepthRangeBody::MultiRegionSei));
313                assert!(matches!(b.ranges[1].body, DepthRangeBody::MultiRegionSei));
314            }
315            other => panic!("expected VideoDepthRange, got {other:?}"),
316        }
317        round_trip(&d);
318    }
319
320    #[test]
321    fn parse_video_depth_range_empty_selector() {
322        let bytes = wrap(0x10, &[]);
323        let d = ExtensionDescriptor::parse(&bytes).unwrap();
324        match &d.body {
325            ExtensionBody::VideoDepthRange(b) => {
326                assert!(b.ranges.is_empty());
327            }
328            other => panic!("expected VideoDepthRange, got {other:?}"),
329        }
330        round_trip(&d);
331    }
332
333    #[test]
334    fn parse_video_depth_range_rejects_truncated() {
335        // Only range_type byte, no range_length
336        let sel = [0x00];
337        let bytes = wrap(0x10, &sel);
338        assert!(matches!(
339            ExtensionDescriptor::parse(&bytes).unwrap_err(),
340            crate::error::Error::BufferTooShort { .. }
341        ));
342    }
343
344    #[test]
345    fn parse_video_depth_range_rejects_overrun() {
346        // range_length=5 but only 2 bytes follow
347        let sel = [0x00, 0x05, 0xAA, 0xBB];
348        let bytes = wrap(0x10, &sel);
349        assert!(matches!(
350            ExtensionDescriptor::parse(&bytes).unwrap_err(),
351            crate::error::Error::BufferTooShort { .. }
352        ));
353    }
354
355    #[test]
356    fn range_type_full_range_round_trip() {
357        for v in 0u8..=0xFF {
358            let rt = RangeType::from_u8(v);
359            assert_eq!(rt.to_u8(), v, "RangeType round-trip failed for 0x{v:02X}");
360        }
361    }
362
363    #[test]
364    fn range_type_known_values() {
365        assert_eq!(RangeType::from_u8(0x00), RangeType::ProductionDisparityHint);
366        assert_eq!(RangeType::from_u8(0x01), RangeType::MultiRegionSei);
367        assert_eq!(RangeType::from_u8(0x02), RangeType::Reserved(0x02));
368        assert_eq!(
369            RangeType::ProductionDisparityHint.name(),
370            "production disparity hint"
371        );
372        assert_eq!(RangeType::MultiRegionSei.name(), "multi-region SEI");
373        assert_eq!(RangeType::Reserved(0xFF).name(), "reserved");
374    }
375}