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