dvb_si/descriptors/extension/
video_depth_range.rs1use super::*;
3use alloc::vec::Vec;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[non_exhaustive]
12pub enum RangeType {
13 ProductionDisparityHint,
15 MultiRegionSei,
17 Reserved(u8),
19}
20
21impl RangeType {
22 #[must_use]
23 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 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 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#[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 pub range_type: RangeType,
67 pub body: DepthRangeBody<'a>,
69}
70
71#[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 ProductionDisparityHint {
80 max: i16,
82 min: i16,
84 },
85 MultiRegionSei,
87 #[cfg_attr(feature = "serde", serde(borrow))]
89 Other(&'a [u8]),
90}
91
92#[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 pub ranges: Vec<DepthRange<'a>>,
99}
100
101fn 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 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 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 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 RangeType::MultiRegionSei => DepthRangeBody::MultiRegionSei,
155 _ => 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 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 let max_val: i16 = 100;
231 let min_val: i16 = -50;
232 let max_b = max_val as u16 & 0x0FFF; let min_b = min_val as u16 & 0x0FFF; 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 round_trip(&d);
269 }
270
271 #[test]
272 fn parse_video_depth_range_negative_edge_round_trip() {
273 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 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 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 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}