Skip to main content

dvb_subtitle/segments/
disparity_signalling.rs

1//! Disparity Signalling Segment — ETSI EN 300 743 §7.2.7, Table 29 (segment_type 0x15).
2//!
3//! Supports plano-stereoscopic 3DTV subtitling by allowing disparity values
4//! to be ascribed to regions or subregions.
5
6use crate::error::{Error, Result};
7use broadcast_common::{Parse, Serialize};
8
9/// The disparity_signalling_segment segment_type.
10pub const SEGMENT_TYPE: u8 = 0x15;
11/// Header: 6 bytes.
12pub const HEADER_LEN: usize = 6;
13/// Fixed body: dss_version_number(4b)+page_flag(1b)+reserved(3b) + page_default_disparity_shift(1) = 2 bytes.
14pub const FIXED_LEN: usize = 2;
15
16/// A disparity shift update sequence as defined in Table 30.
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize))]
19pub struct DisparityShiftUpdateSequence {
20    /// Interval duration in 90kHz STC units (24-bit).
21    pub interval_duration: u32,
22    /// Number of division periods (≥1).
23    pub division_period_count: u8,
24    /// Interval count + disparity shift integer pairs.
25    pub intervals: alloc::vec::Vec<DisparityShiftInterval>,
26}
27
28/// An interval within a disparity shift update sequence.
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub struct DisparityShiftInterval {
32    /// Interval count multiplier.
33    pub interval_count: u8,
34    /// Disparity shift integer part (signed).
35    pub disparity_shift_integer: i8,
36}
37
38impl DisparityShiftUpdateSequence {
39    fn serialized_len(&self) -> usize {
40        1 + 3 + 1 + self.intervals.len() * 2
41    }
42
43    fn serialize_into(&self, buf: &mut [u8]) {
44        let body_len = 3 + 1 + self.intervals.len() * 2;
45        buf[0] = body_len as u8;
46        buf[1] = (self.interval_duration >> 16) as u8;
47        buf[2] = (self.interval_duration >> 8) as u8;
48        buf[3] = self.interval_duration as u8;
49        buf[4] = self.division_period_count;
50        for (i, interval) in self.intervals.iter().enumerate() {
51            let off = 5 + i * 2;
52            buf[off] = interval.interval_count;
53            buf[off + 1] = interval.disparity_shift_integer as u8;
54        }
55    }
56
57    fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
58        if bytes.len() < 5 {
59            return Err(Error::BufferTooShort {
60                need: 5,
61                have: bytes.len(),
62                what: "disparity_shift_update_sequence",
63            });
64        }
65        let seq_len = bytes[0] as usize;
66        if bytes.len() < 1 + seq_len {
67            return Err(Error::BufferTooShort {
68                need: 1 + seq_len,
69                have: bytes.len(),
70                what: "disparity_shift_update_sequence data",
71            });
72        }
73        let dur = ((bytes[1] as u32) << 16) | ((bytes[2] as u32) << 8) | (bytes[3] as u32);
74        let count = bytes[4];
75        let total = 1 + seq_len;
76        let mut intervals = alloc::vec::Vec::new();
77        let mut ipos = 5;
78        for _ in 0..count {
79            if ipos + 2 > total {
80                return Err(Error::BufferTooShort {
81                    need: ipos + 2,
82                    have: total,
83                    what: "disparity_shift_update_sequence interval",
84                });
85            }
86            intervals.push(DisparityShiftInterval {
87                interval_count: bytes[ipos],
88                disparity_shift_integer: bytes[ipos + 1] as i8,
89            });
90            ipos += 2;
91        }
92        Ok((
93            DisparityShiftUpdateSequence {
94                interval_duration: dur,
95                division_period_count: count,
96                intervals,
97            },
98            total,
99        ))
100    }
101}
102
103/// A subregion within a region for disparity signalling.
104#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize))]
106pub struct Subregion {
107    /// Horizontal position relative to page (only if multiple subregions).
108    pub subregion_horizontal_position: Option<u16>,
109    /// Width in pixels (only if multiple subregions).
110    pub subregion_width: Option<u16>,
111    /// Disparity shift integer part (signed).
112    pub subregion_disparity_shift_integer: i8,
113    /// Disparity shift fractional part (unsigned, in 1/16 pixel).
114    pub subregion_disparity_shift_fractional: u8,
115    /// Reserved bits in the subregion shift/frac byte (bits `[3:0]`).
116    pub reserved: u8,
117    /// Update sequence if flag is set.
118    pub update_sequence: Option<DisparityShiftUpdateSequence>,
119}
120
121impl Subregion {
122    fn serialized_len(&self) -> usize {
123        let base = if self.subregion_horizontal_position.is_some() {
124            4 + 2
125        } else {
126            2
127        };
128        base + self
129            .update_sequence
130            .as_ref()
131            .map_or(0, |u| u.serialized_len())
132    }
133
134    fn serialize_into(&self, buf: &mut [u8]) {
135        let mut off = 0;
136        if let (Some(h), Some(w)) = (self.subregion_horizontal_position, self.subregion_width) {
137            buf[0..2].copy_from_slice(&h.to_be_bytes());
138            buf[2..4].copy_from_slice(&w.to_be_bytes());
139            off = 4;
140        }
141        buf[off] = self.subregion_disparity_shift_integer as u8;
142        buf[off + 1] = (self.subregion_disparity_shift_fractional << 4) | (self.reserved & 0x0F);
143        off += 2;
144        if let Some(ref seq) = self.update_sequence {
145            seq.serialize_into(&mut buf[off..]);
146        }
147    }
148}
149
150/// A region entry in the disparity signalling segment.
151#[derive(Debug, Clone, PartialEq, Eq)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize))]
153pub struct DisparityRegion {
154    /// Region identifier.
155    pub region_id: u8,
156    /// Whether an update sequence follows for this region's subregions.
157    pub update_sequence_region_flag: bool,
158    /// Reserved bits in the region flags byte (bits `[6:2]`).
159    pub reserved_flags: u8,
160    /// Subregions within this region.
161    pub subregions: alloc::vec::Vec<Subregion>,
162}
163
164impl DisparityRegion {
165    fn serialized_len(&self) -> usize {
166        2 + self
167            .subregions
168            .iter()
169            .map(|s| s.serialized_len())
170            .sum::<usize>()
171    }
172
173    fn serialize_into(&self, buf: &mut [u8]) {
174        let num = self.subregions.len().saturating_sub(1) as u8;
175        buf[0] = self.region_id;
176        buf[1] = (u8::from(self.update_sequence_region_flag) << 7)
177            | (self.reserved_flags & 0x7C)
178            | (num & 0x03);
179        let mut off = 2;
180        for sub in &self.subregions {
181            sub.serialize_into(&mut buf[off..]);
182            off += sub.serialized_len();
183        }
184    }
185}
186
187/// Disparity Signalling Segment.
188#[derive(Debug, Clone, PartialEq, Eq)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190pub struct DisparitySignallingSegment {
191    /// The page_id from the segment header.
192    pub page_id: u16,
193    /// DSS version number (modulo 16).
194    pub dss_version_number: u8,
195    /// Whether a page-level update sequence follows.
196    pub update_sequence_page_flag: bool,
197    /// Reserved bits in body byte 0 (bits `[2:0]`).
198    pub reserved: u8,
199    /// Default disparity shift for the whole page (signed).
200    pub page_default_disparity_shift: i8,
201    /// Page-level update sequence if flag is set.
202    pub page_update_sequence: Option<DisparityShiftUpdateSequence>,
203    /// Region entries.
204    pub regions: alloc::vec::Vec<DisparityRegion>,
205}
206
207impl<'a> Parse<'a> for DisparitySignallingSegment {
208    type Error = Error;
209
210    fn parse(bytes: &'a [u8]) -> Result<Self> {
211        if bytes.len() < HEADER_LEN + FIXED_LEN {
212            return Err(Error::BufferTooShort {
213                need: HEADER_LEN + FIXED_LEN,
214                have: bytes.len(),
215                what: "disparity_signalling_segment",
216            });
217        }
218        if bytes[1] != SEGMENT_TYPE {
219            return Err(Error::UnknownSegmentType(bytes[1]));
220        }
221        let page_id = u16::from_be_bytes([bytes[2], bytes[3]]);
222        let segment_length = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
223        let total = HEADER_LEN + segment_length;
224        if bytes.len() < total {
225            return Err(Error::BufferTooShort {
226                need: total,
227                have: bytes.len(),
228                what: "disparity_signalling_segment data",
229            });
230        }
231        let body = &bytes[HEADER_LEN..HEADER_LEN + segment_length];
232        if body.len() < FIXED_LEN {
233            return Err(Error::BufferTooShort {
234                need: FIXED_LEN,
235                have: body.len(),
236                what: "disparity_signalling_segment body",
237            });
238        }
239        let dss_version_number = body[0] >> 4;
240        let update_sequence_page_flag = (body[0] & 0x08) != 0;
241        let reserved = body[0] & 0x07;
242        let page_default_disparity_shift = body[1] as i8;
243
244        let mut pos = FIXED_LEN;
245        let page_update_sequence = if update_sequence_page_flag {
246            let (seq, seq_len) = DisparityShiftUpdateSequence::parse(&body[pos..])?;
247            pos += seq_len;
248            Some(seq)
249        } else {
250            None
251        };
252
253        let mut regions = alloc::vec::Vec::new();
254        while pos < body.len() {
255            if pos + 2 > body.len() {
256                break;
257            }
258            let region_id = body[pos];
259            pos += 1;
260            let update_flag = (body[pos] & 0x80) != 0;
261            let reserved_flags = body[pos] & 0x7C;
262            let num_subregions_minus_1 = body[pos] & 0x03;
263            pos += 1;
264
265            let mut subregions = alloc::vec::Vec::new();
266            for _n in 0..=num_subregions_minus_1 {
267                let has_pos = num_subregions_minus_1 > 0;
268                let entry_len = if has_pos { 4 + 2 } else { 2 };
269                if pos + entry_len > body.len() {
270                    return Err(Error::BufferTooShort {
271                        need: pos + entry_len,
272                        have: body.len(),
273                        what: "subregion entry",
274                    });
275                }
276
277                let (hpos, width) = if has_pos {
278                    let h = u16::from_be_bytes([body[pos], body[pos + 1]]);
279                    let w = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
280                    pos += 4;
281                    (Some(h), Some(w))
282                } else {
283                    (None, None)
284                };
285
286                let shift_int = body[pos] as i8;
287                let shift_frac = body[pos + 1] >> 4;
288                let reserved = body[pos + 1] & 0x0F;
289                pos += 2;
290
291                let sub_update = if update_flag {
292                    let (seq, seq_len) = DisparityShiftUpdateSequence::parse(&body[pos..])?;
293                    pos += seq_len;
294                    Some(seq)
295                } else {
296                    None
297                };
298
299                subregions.push(Subregion {
300                    subregion_horizontal_position: hpos,
301                    subregion_width: width,
302                    subregion_disparity_shift_integer: shift_int,
303                    subregion_disparity_shift_fractional: shift_frac,
304                    reserved,
305                    update_sequence: sub_update,
306                });
307            }
308
309            regions.push(DisparityRegion {
310                region_id,
311                update_sequence_region_flag: update_flag,
312                reserved_flags,
313                subregions,
314            });
315        }
316
317        Ok(DisparitySignallingSegment {
318            page_id,
319            dss_version_number,
320            update_sequence_page_flag,
321            reserved,
322            page_default_disparity_shift,
323            page_update_sequence,
324            regions,
325        })
326    }
327}
328
329impl Serialize for DisparitySignallingSegment {
330    type Error = Error;
331
332    fn serialized_len(&self) -> usize {
333        HEADER_LEN
334            + FIXED_LEN
335            + self
336                .page_update_sequence
337                .as_ref()
338                .map_or(0, |s| s.serialized_len())
339            + self
340                .regions
341                .iter()
342                .map(|r| r.serialized_len())
343                .sum::<usize>()
344    }
345
346    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
347        let len = self.serialized_len();
348        if buf.len() < len {
349            return Err(Error::BufferTooShort {
350                need: len,
351                have: buf.len(),
352                what: "disparity_signalling_segment serialize",
353            });
354        }
355        buf[0] = 0x0F;
356        buf[1] = SEGMENT_TYPE;
357        buf[2..4].copy_from_slice(&self.page_id.to_be_bytes());
358        let seg_len = (len - HEADER_LEN) as u16;
359        buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
360
361        buf[6] = (self.dss_version_number << 4)
362            | (u8::from(self.update_sequence_page_flag) << 3)
363            | (self.reserved & 0x07);
364        buf[7] = self.page_default_disparity_shift as u8;
365
366        let mut off = HEADER_LEN + FIXED_LEN;
367        if let Some(ref seq) = self.page_update_sequence {
368            seq.serialize_into(&mut buf[off..]);
369            off += seq.serialized_len();
370        }
371        for region in &self.regions {
372            region.serialize_into(&mut buf[off..]);
373            off += region.serialized_len();
374        }
375        Ok(len)
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382    use broadcast_common::{Parse, Serialize};
383
384    #[test]
385    fn round_trip_simple() {
386        let bytes = [
387            0x0F, 0x15, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
388        ];
389        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
390        assert_eq!(seg.dss_version_number, 0);
391        assert!(!seg.update_sequence_page_flag);
392        assert_eq!(seg.regions.len(), 1);
393        assert_eq!(seg.regions[0].region_id, 1);
394        assert_eq!(seg.regions[0].subregions.len(), 1);
395        let out = seg.to_bytes();
396        assert_eq!(out, bytes);
397
398        // Biting test
399        let mut seg2 = seg.clone();
400        seg2.regions[0].region_id = 5;
401        let out2 = seg2.to_bytes();
402        assert_ne!(out2, bytes);
403        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
404        assert_eq!(reparse.regions[0].region_id, 5);
405    }
406
407    #[test]
408    fn round_trip_with_page_update_seq() {
409        let bytes = [
410            0x0F, 0x15, 0x00, 0x01, 0x00, 0x0B, 0x88, 0x05, 0x08, 0x00, 0x00, 0x0A, 0x02, 0x01,
411            0x10, 0x03, 0x20,
412        ];
413        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
414        assert_eq!(seg.dss_version_number, 8);
415        assert!(seg.update_sequence_page_flag);
416        assert_eq!(seg.regions.len(), 0);
417        let out = seg.to_bytes();
418        assert_eq!(out, bytes);
419
420        // Biting test
421        let mut seg2 = seg.clone();
422        seg2.page_default_disparity_shift = 10;
423        let out2 = seg2.to_bytes();
424        assert_ne!(out2, bytes);
425        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
426        assert_eq!(reparse.page_default_disparity_shift, 10);
427    }
428
429    #[test]
430    fn round_trip_with_update_sequence() {
431        let bytes = [
432            0x0F, 0x15, 0x00, 0x01, 0x00, 0x16, 0x88, 0x05, 0x08, 0x00, 0x00, 0x0A, 0x02, 0x01,
433            0x10, 0x03, 0x20, 0x01, 0x80, 0x10, 0x00, 0x06, 0x00, 0x00, 0x14, 0x01, 0x01, 0x08,
434        ];
435        let seg = DisparitySignallingSegment::parse(&bytes).unwrap();
436        assert_eq!(seg.dss_version_number, 8);
437        assert!(seg.update_sequence_page_flag);
438        assert_eq!(seg.page_default_disparity_shift, 5);
439        let seq = seg.page_update_sequence.as_ref().unwrap();
440        assert_eq!(seq.division_period_count, 2);
441        assert_eq!(seg.regions.len(), 1);
442        assert_eq!(seg.regions[0].region_id, 1);
443        assert!(seg.regions[0].update_sequence_region_flag);
444        assert_eq!(seg.regions[0].subregions.len(), 1);
445        let out = seg.to_bytes();
446        assert_eq!(out, bytes);
447
448        // Biting test
449        let mut seg2 = seg.clone();
450        seg2.dss_version_number = 3;
451        let out2 = seg2.to_bytes();
452        assert_ne!(out2, bytes);
453        let reparse = DisparitySignallingSegment::parse(&out2).unwrap();
454        assert_eq!(reparse.dss_version_number, 3);
455    }
456}