Skip to main content

dvb_cc/
cc_data.rs

1//! cc_data() — ETSI TS 101 154 §B.5, Table B.9.
2
3use crate::error::{Error, Result};
4use alloc::vec::Vec;
5use dvb_common::{Parse, Serialize};
6
7/// Largest `cc_count` the 5-bit field can carry.
8const MAX_CC_COUNT: usize = 31;
9/// Fixed `0xFF` reserved byte after cc_count, and the trailing marker byte.
10const FF: u8 = 0xFF;
11
12/// `cc_type` — the type of the caption data byte pair (TS 101 154 Table B.9 /
13/// CEA-708-E). 2-bit field.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17pub enum CcType {
18    /// CEA-608 NTSC line-21 field 1 (cc_type 0).
19    Ntsc608Field1,
20    /// CEA-608 NTSC line-21 field 2 (cc_type 1).
21    Ntsc608Field2,
22    /// DTVCC (CEA-708) channel-packet data (cc_type 2).
23    Dtvcc708Data,
24    /// DTVCC (CEA-708) channel-packet start (cc_type 3).
25    Dtvcc708Start,
26}
27
28impl CcType {
29    /// From the 2-bit wire value.
30    #[must_use]
31    pub fn from_bits(v: u8) -> Self {
32        match v & 0x03 {
33            0 => Self::Ntsc608Field1,
34            1 => Self::Ntsc608Field2,
35            2 => Self::Dtvcc708Data,
36            _ => Self::Dtvcc708Start,
37        }
38    }
39    /// The 2-bit wire value.
40    #[must_use]
41    pub fn to_bits(self) -> u8 {
42        match self {
43            Self::Ntsc608Field1 => 0,
44            Self::Ntsc608Field2 => 1,
45            Self::Dtvcc708Data => 2,
46            Self::Dtvcc708Start => 3,
47        }
48    }
49    /// `true` for the CEA-608 (line-21) types.
50    #[must_use]
51    pub fn is_cea608(self) -> bool {
52        matches!(self, Self::Ntsc608Field1 | Self::Ntsc608Field2)
53    }
54    /// `true` for the CEA-708 (DTVCC) types.
55    #[must_use]
56    pub fn is_cea708(self) -> bool {
57        matches!(self, Self::Dtvcc708Data | Self::Dtvcc708Start)
58    }
59    /// Label per the project's `name()` convention.
60    #[must_use]
61    pub fn name(&self) -> &'static str {
62        match self {
63            Self::Ntsc608Field1 => "ntsc_608_field1",
64            Self::Ntsc608Field2 => "ntsc_608_field2",
65            Self::Dtvcc708Data => "dtvcc_708_data",
66            Self::Dtvcc708Start => "dtvcc_708_start",
67        }
68    }
69}
70
71dvb_common::impl_spec_display!(CcType);
72
73/// One closed-caption construct (the per-`cc_count` loop entry of Table B.9).
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76pub struct CcTriplet {
77    /// `cc_valid` — the two caption bytes are valid.
78    pub cc_valid: bool,
79    /// `cc_type` — type of the caption byte pair.
80    pub cc_type: CcType,
81    /// `cc_data_1` — first caption byte (contents per CEA-708-E).
82    pub cc_data_1: u8,
83    /// `cc_data_2` — second caption byte.
84    pub cc_data_2: u8,
85}
86
87/// `cc_data()` — the DVB closed-caption carriage structure (Table B.9).
88#[derive(Debug, Clone, PartialEq, Eq)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize))]
90pub struct CcData {
91    /// `process_cc_data_flag` — when `true`, the cc_data is to be processed.
92    pub process_cc_data_flag: bool,
93    /// The caption constructs (`cc_count` is `triplets.len()`).
94    pub triplets: Vec<CcTriplet>,
95}
96
97impl CcData {
98    /// Iterator over the CEA-608 (line-21) triplets.
99    pub fn cea608(&self) -> impl Iterator<Item = &CcTriplet> {
100        self.triplets.iter().filter(|t| t.cc_type.is_cea608())
101    }
102    /// Iterator over the CEA-708 (DTVCC) triplets.
103    pub fn cea708(&self) -> impl Iterator<Item = &CcTriplet> {
104        self.triplets.iter().filter(|t| t.cc_type.is_cea708())
105    }
106}
107
108impl<'a> Parse<'a> for CcData {
109    type Error = Error;
110    fn parse(b: &'a [u8]) -> Result<Self> {
111        // byte0: reserved(1) | process_cc_data_flag(1) | zero_bit(1) | cc_count(5)
112        // byte1: reserved 0xFF
113        if b.len() < 2 {
114            return Err(Error::BufferTooShort {
115                need: 2,
116                have: b.len(),
117                what: "cc_data header",
118            });
119        }
120        let process_cc_data_flag = (b[0] >> 6) & 0x01 != 0;
121        let cc_count = usize::from(b[0] & 0x1F);
122        let total = 2 + cc_count * 3 + 1; // header + triplets + marker
123        if b.len() < total {
124            return Err(Error::BufferTooShort {
125                need: total,
126                have: b.len(),
127                what: "cc_data triplets",
128            });
129        }
130        let mut triplets = Vec::with_capacity(cc_count);
131        let mut pos = 2;
132        for _ in 0..cc_count {
133            let flags = b[pos];
134            // one_bit(1) | reserved(4) | cc_valid(1) | cc_type(2)
135            triplets.push(CcTriplet {
136                cc_valid: (flags >> 2) & 0x01 != 0,
137                cc_type: CcType::from_bits(flags),
138                cc_data_1: b[pos + 1],
139                cc_data_2: b[pos + 2],
140            });
141            pos += 3;
142        }
143        Ok(CcData {
144            process_cc_data_flag,
145            triplets,
146        })
147    }
148}
149
150impl Serialize for CcData {
151    type Error = Error;
152    fn serialized_len(&self) -> usize {
153        2 + self.triplets.len() * 3 + 1
154    }
155    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
156        if self.triplets.len() > MAX_CC_COUNT {
157            return Err(Error::TooManyTriplets(self.triplets.len()));
158        }
159        let len = self.serialized_len();
160        if buf.len() < len {
161            return Err(Error::OutputBufferTooSmall {
162                need: len,
163                have: buf.len(),
164            });
165        }
166        // reserved=1, process_cc_data_flag, zero_bit=0, cc_count
167        let cc_count = self.triplets.len() as u8 & 0x1F;
168        buf[0] = 0x80 | (u8::from(self.process_cc_data_flag) << 6) | cc_count;
169        buf[1] = FF;
170        let mut pos = 2;
171        for t in &self.triplets {
172            // one_bit=1, reserved=1111, cc_valid, cc_type
173            buf[pos] = 0xF8 | (u8::from(t.cc_valid) << 2) | t.cc_type.to_bits();
174            buf[pos + 1] = t.cc_data_1;
175            buf[pos + 2] = t.cc_data_2;
176            pos += 3;
177        }
178        buf[pos] = FF; // marker_bits
179        Ok(len)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    fn tr(cc_valid: bool, cc_type: CcType, d1: u8, d2: u8) -> CcTriplet {
188        CcTriplet {
189            cc_valid,
190            cc_type,
191            cc_data_1: d1,
192            cc_data_2: d2,
193        }
194    }
195
196    fn sample() -> CcData {
197        CcData {
198            process_cc_data_flag: true,
199            triplets: alloc::vec![
200                tr(true, CcType::Dtvcc708Start, 0xC1, 0x02),
201                tr(true, CcType::Ntsc608Field1, 0x94, 0x2C),
202                tr(false, CcType::Dtvcc708Data, 0x00, 0x00),
203            ],
204        }
205    }
206
207    #[test]
208    fn round_trip_constructed() {
209        let cc = sample();
210        let bytes = cc.to_bytes();
211        // construct → serialize → re-parse → equal (no raw stash to lean on)
212        assert_eq!(CcData::parse(&bytes).unwrap(), cc);
213        // header sanity: reserved=1, pcdf=1, zero=0, cc_count=3
214        assert_eq!(bytes[0], 0b1100_0011);
215        assert_eq!(bytes[1], 0xFF);
216        assert_eq!(*bytes.last().unwrap(), 0xFF);
217    }
218
219    #[test]
220    fn mutate_field_changes_output() {
221        let a = sample().to_bytes();
222        let mut cc = sample();
223        cc.triplets[0].cc_data_1 = 0x42;
224        let b = cc.to_bytes();
225        assert_ne!(a, b, "mutating a field must change serialized bytes");
226    }
227
228    #[test]
229    fn splits_608_708() {
230        let cc = sample();
231        assert_eq!(cc.cea608().count(), 1);
232        assert_eq!(cc.cea708().count(), 2);
233    }
234
235    #[test]
236    fn empty_round_trip() {
237        let cc = CcData {
238            process_cc_data_flag: false,
239            triplets: alloc::vec![],
240        };
241        let bytes = cc.to_bytes();
242        assert_eq!(bytes, [0x80, 0xFF, 0xFF]); // reserved=1, pcdf=0, count=0; 0xFF; marker
243        assert_eq!(CcData::parse(&bytes).unwrap(), cc);
244    }
245
246    #[test]
247    fn serialize_rejects_over_31_triplets() {
248        let cc = CcData {
249            process_cc_data_flag: true,
250            triplets: alloc::vec![tr(true, CcType::Ntsc608Field1, 0, 0); 32],
251        };
252        let mut buf = [0u8; 200];
253        assert!(matches!(
254            cc.serialize_into(&mut buf),
255            Err(Error::TooManyTriplets(32))
256        ));
257    }
258}