cea708_types/
lib.rs

1// Copyright (C) 2023 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the MIT license <LICENSE-MIT> or
4// http://opensource.org/licenses/MIT>, at your option. This file may not be
5// copied, modified, or distributed except according to those terms.
6
7//! # cea708-types
8//!
9//! Provides the necessary infrastructure to read and write [DTVCCPacket]'s containing [Service]s
10//! with various [tables::Code]s
11//!
12//! The reference for this implementation is the [ANSI/CTA-708-E R-2018](https://shop.cta.tech/products/digital-television-dtv-closed-captioning) specification.
13
14use std::collections::VecDeque;
15use std::time::Duration;
16
17use muldiv::MulDiv;
18
19use log::{debug, trace, warn};
20
21pub mod tables;
22
23/// Various possible errors when parsing data
24#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
25pub enum ParserError {
26    /// Length of data does not match length advertised
27    #[error("The length of the data ({actual}) does not match the advertised expected ({expected}) length")]
28    LengthMismatch {
29        /// The expected size
30        expected: usize,
31        /// The actual size
32        actual: usize,
33    },
34    /// CEA-608 comaptibility bytes encountered after CEA-708
35    #[error("CEA-608 compatibility bytes were found after CEA-708 bytes at position {byte_pos}")]
36    Cea608AfterCea708 {
37        /// Position of the offending bytes
38        byte_pos: usize,
39    },
40}
41
42/// An error enum returned when writing data fails
43#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
44pub enum WriterError {
45    /// Writing would overflow by how many bytes
46    #[error("Writing the data would overflow by {0} bytes")]
47    WouldOverflow(usize),
48    /// It is not possible to write to this resource
49    #[error("The resource is not writable")]
50    ReadOnly,
51}
52
53impl From<tables::CodeError> for ParserError {
54    fn from(err: tables::CodeError) -> Self {
55        match err {
56            tables::CodeError::LengthMismatch { expected, actual } => {
57                ParserError::LengthMismatch { expected, actual }
58            }
59        }
60    }
61}
62
63/// A CEA-608 compatibility byte pair
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum Cea608 {
66    Field1(u8, u8),
67    Field2(u8, u8),
68}
69
70/// Parses a byte stream of `cc_data` bytes into indivdual [`DTVCCPacket`]s.
71#[derive(Debug, Default)]
72pub struct CCDataParser {
73    pending_data: Vec<u8>,
74    packets: VecDeque<DTVCCPacket>,
75    cea608: Option<Vec<Cea608>>,
76    have_initial_ccp_header: bool,
77    ccp_bytes_needed: usize,
78}
79
80impl CCDataParser {
81    /// Create a new [CCDataParser]
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    pub fn handle_cea608(&mut self) {
87        self.cea608 = Some(vec![]);
88    }
89
90    /// Push a complete `cc_data` packet into the parser for processing.
91    ///
92    /// Will fail with [ParserError::LengthMismatch] if the length of the data does not match the
93    /// number of cc triples specified in the `cc_data` header.
94    ///
95    /// Any CEA-608 data provided after valid CEA-708 data will return
96    /// [ParserError::Cea608AfterCea708].
97    pub fn push(&mut self, data: &[u8]) -> Result<(), ParserError> {
98        trace!("parsing {data:?}");
99        if let Some(ref mut cea608) = self.cea608 {
100            cea608.clear();
101        }
102
103        if data.len() < 5 {
104            // enough for 2 byte header plus 1 byte triple
105            return Ok(());
106        }
107        let process_cc_data_flag = data[0] & 0x40 > 0;
108        if !process_cc_data_flag {
109            return Ok(());
110        }
111
112        let cc_count = data[0] & 0x1F;
113        if cc_count == 0 {
114            return Ok(());
115        }
116        trace!("cc_count: {cc_count}, len = {}", data.len());
117        if (cc_count * 3 + 2) as usize != data.len() {
118            return Err(ParserError::LengthMismatch {
119                expected: (cc_count * 3 + 1) as usize,
120                actual: data.len(),
121            });
122        }
123
124        let mut ccp_data = vec![];
125        let mut in_dtvcc = false;
126
127        // re-add first byte to pending_data
128        let mut pending_data = vec![];
129        for (i, d) in self.pending_data.chunks(2).enumerate() {
130            if i == 0 {
131                pending_data.push(0xFF);
132            } else {
133                pending_data.push(0xFE);
134            }
135            pending_data.extend(d);
136            if d.len() == 1 {
137                pending_data.push(0x00);
138            }
139        }
140
141        // find the start of ccp in data
142        let ccp_offset;
143        {
144            let mut ret = None;
145            for (i, triple) in data[2..].chunks_exact(3).enumerate() {
146                let cc_valid = (triple[0] & 0x04) == 0x04;
147                let cc_type = triple[0] & 0x3;
148                trace!(
149                    "byte:{} triple 0x{:02x} 0x{:02x} 0x{:02x}. valid: {cc_valid}, type: {cc_type}",
150                    i * 3,
151                    triple[0],
152                    triple[1],
153                    triple[2]
154                );
155                if (cc_type & 0b10) > 0 {
156                    in_dtvcc = true;
157                }
158                if !cc_valid {
159                    continue;
160                }
161                if !in_dtvcc && (cc_type == 0b00 || cc_type == 0b01) {
162                    trace!(
163                        "have cea608 bytes type {cc_type} 0x{:02x} 0x{:02x}",
164                        triple[1],
165                        triple[2]
166                    );
167                    if let Some(ref mut cea608) = self.cea608 {
168                        let pair = match cc_type {
169                            0b00 => Cea608::Field1(triple[1], triple[2]),
170                            0b01 => Cea608::Field2(triple[1], triple[2]),
171                            _ => unreachable!(),
172                        };
173                        cea608.push(pair);
174                    }
175                    continue;
176                }
177
178                if in_dtvcc && (cc_type == 0b00 || cc_type == 0b01) {
179                    // invalid packet construction;
180                    warn!("cea608 bytes after cea708 data at byte:{}", i * 3);
181                    return Err(ParserError::Cea608AfterCea708 { byte_pos: i * 3 });
182                }
183
184                if ret.is_none() {
185                    ret = Some(i * 3);
186                }
187            }
188
189            if let Some(ret) = ret {
190                ccp_offset = 2 + ret
191            } else {
192                // no data to process
193                return Ok(());
194            }
195        }
196        trace!("ccp offset in input data is at index {ccp_offset}");
197
198        let mut data_iter = pending_data.iter().chain(data[ccp_offset..].iter());
199        let mut i = 0;
200        in_dtvcc = false;
201        loop {
202            let byte0 = data_iter.next();
203            let byte1 = data_iter.next();
204            let byte2 = data_iter.next();
205            i += 3;
206            let (Some(byte0), Some(byte1), Some(byte2)) = (byte0, byte1, byte2) else {
207                break;
208            };
209            let cc_valid = (byte0 & 0x04) == 0x04;
210            let cc_type = byte0 & 0x3;
211            if (cc_type & 0b10) > 0 {
212                in_dtvcc = true;
213            }
214            if !cc_valid {
215                continue;
216            }
217            if !in_dtvcc && (cc_type == 0b00 || cc_type == 0b01) {
218                // 608-in-708 data should not be hit as we skip over it
219                unreachable!();
220            }
221
222            if (cc_type & 0b11) == 0b11 {
223                trace!("found ccp header at index {}", i - 3);
224                self.have_initial_ccp_header = true;
225                // a header byte truncates the size of any previous packet
226                match DTVCCPacket::parse(&ccp_data) {
227                    Ok(packet) => self.packets.push_front(packet),
228                    Err(ParserError::LengthMismatch { .. }) => (),
229                    Err(e) => {
230                        eprintln!("{e:?}");
231                        unreachable!()
232                    }
233                }
234                in_dtvcc = false;
235                ccp_data = vec![];
236                let (_seq_no, packet_len) = DTVCCPacket::parse_hdr_byte(*byte1);
237                trace!("waiting for {} dtvcc bytes", packet_len + 1);
238                self.ccp_bytes_needed = packet_len + 1;
239            }
240
241            if self.have_initial_ccp_header {
242                trace!("pushing 0x{:02x?}{:02x?}", byte1, byte2);
243                if self.ccp_bytes_needed > 0 {
244                    ccp_data.push(*byte1);
245                    self.ccp_bytes_needed -= 1;
246                }
247                if self.ccp_bytes_needed > 0 {
248                    ccp_data.push(*byte2);
249                    self.ccp_bytes_needed -= 1;
250                }
251            }
252        }
253
254        if self.ccp_bytes_needed == 0 {
255            match DTVCCPacket::parse(&ccp_data) {
256                Ok(packet) => self.packets.push_front(packet),
257                Err(ParserError::LengthMismatch { .. }) => (),
258                _ => unreachable!(),
259            }
260            ccp_data = vec![];
261        }
262
263        self.pending_data = ccp_data;
264
265        Ok(())
266    }
267
268    /// Clear any internal buffers
269    pub fn flush(&mut self) {
270        *self = Self::default();
271    }
272
273    /// Pop a valid [DTVCCPacket] or None if no packet could be parsed
274    pub fn pop_packet(&mut self) -> Option<DTVCCPacket> {
275        let ret = self.packets.pop_back();
276        trace!("popped {ret:?}");
277        ret
278    }
279
280    /// Any [`Cea608`] bytes in the last parsed `cc_data`
281    pub fn cea608(&mut self) -> Option<&[Cea608]> {
282        self.cea608.as_deref()
283    }
284}
285
286/// A framerate.  Framerates larger than 60fps are not well supported.
287#[derive(Debug, Copy, Clone)]
288pub struct Framerate {
289    numer: u32,
290    denom: u32,
291}
292
293impl Framerate {
294    /// Create a new [`Framerate`]
295    pub const fn new(numer: u32, denom: u32) -> Self {
296        Self { numer, denom }
297    }
298
299    /// The numerator of this [`Framerate`] fraction
300    pub fn numer(&self) -> u32 {
301        self.numer
302    }
303
304    /// The denominator of this [`Framerate`] fraction
305    pub fn denom(&self) -> u32 {
306        self.denom
307    }
308
309    fn cea608_pairs_per_frame(&self) -> usize {
310        // CEA-608 has a max bitrate of 960 bits/s for a single field
311        // TODO: handle alternating counts for 24fps
312        60.mul_div_round(self.denom, self.numer).unwrap() as usize
313    }
314
315    fn max_cc_count(&self) -> usize {
316        // CEA-708 has a max bitrate of 9_600 bits/s
317        600.mul_div_round(self.denom, self.numer).unwrap() as usize
318    }
319}
320
321/// A struct for writing cc_data packets
322#[derive(Debug, Default)]
323pub struct CCDataWriter {
324    // settings
325    output_cea608_padding: bool,
326    output_padding: bool,
327    // state
328    packets: VecDeque<DTVCCPacket>,
329    // part of a packet we could not fit into the previous packet
330    pending_packet_data: Vec<u8>,
331    cea608_1: VecDeque<(u8, u8)>,
332    cea608_2: VecDeque<(u8, u8)>,
333    last_cea608_was_field1: bool,
334}
335
336impl CCDataWriter {
337    /// Whether to output padding CEA-608 bytes when not enough enough data has been provided
338    pub fn set_output_cea608_padding(&mut self, output_cea608_padding: bool) {
339        self.output_cea608_padding = output_cea608_padding;
340    }
341
342    /// Whether padding CEA-608 bytes will be used
343    pub fn output_cea608_padding(&self) -> bool {
344        self.output_cea608_padding
345    }
346
347    /// Whether to output padding data in the CCP bitstream when not enough data has been provided
348    pub fn set_output_padding(&mut self, output_padding: bool) {
349        self.output_padding = output_padding;
350    }
351
352    /// Whether padding data will be produced in the CCP
353    pub fn output_padding(&self) -> bool {
354        self.output_padding
355    }
356
357    /// Push a [`DTVCCPacket`] for writing
358    pub fn push_packet(&mut self, packet: DTVCCPacket) {
359        self.packets.push_front(packet)
360    }
361
362    /// Push a [`Cea608`] byte pair for writing
363    pub fn push_cea608(&mut self, cea608: Cea608) {
364        match cea608 {
365            Cea608::Field1(byte0, byte1) => {
366                if byte0 != 0x80 || byte1 != 0x80 {
367                    self.cea608_1.push_front((byte0, byte1))
368                }
369            }
370            Cea608::Field2(byte0, byte1) => {
371                if byte0 != 0x80 || byte1 != 0x80 {
372                    self.cea608_2.push_front((byte0, byte1))
373                }
374            }
375        }
376    }
377
378    /// Clear all stored data
379    pub fn flush(&mut self) {
380        self.packets.clear();
381        self.pending_packet_data.clear();
382        self.cea608_1.clear();
383        self.cea608_2.clear();
384    }
385
386    /// The amount of time that is currently stored for CEA-608 field 1 data
387    pub fn buffered_cea608_field1_duration(&self) -> Duration {
388        // CEA-608 has a max bitrate of 60000 * 2 / 1001 bytes/s
389        Duration::from_micros(
390            (self.cea608_1.len() as u64)
391                .mul_div_ceil(1001 * 1_000_000, 60000)
392                .unwrap(),
393        )
394    }
395
396    /// The amount of time that is currently stored for CEA-608 field 2 data
397    pub fn buffered_cea608_field2_duration(&self) -> Duration {
398        // CEA-608 has a max bitrate of 60000 * 2 / 1001 bytes/s
399        Duration::from_micros(
400            (self.cea608_2.len() as u64)
401                .mul_div_ceil(1001 * 1_000_000, 60000)
402                .unwrap(),
403        )
404    }
405
406    fn buffered_packet_bytes(&self) -> usize {
407        self.pending_packet_data.len()
408            + self
409                .packets
410                .iter()
411                .map(|packet| packet.len())
412                .sum::<usize>()
413    }
414
415    /// The amount of time that is currently stored for CCP data
416    pub fn buffered_packet_duration(&self) -> Duration {
417        // CEA-708 has a max bitrate of 9600000 / 1001 bits/s
418        Duration::from_micros(
419            ((self.buffered_packet_bytes() + 1) as u64 / 2)
420                .mul_div_ceil(2 * 1001 * 1_000_000, 9_600_000 / 8)
421                .unwrap(),
422        )
423    }
424
425    /// Write the next cc_data packet taking the next relevant CEA-608 byte pairs and
426    /// [`DTVCCPacket`]s.  The framerate provided determines how many bytes are written.
427    pub fn write<W: std::io::Write>(
428        &mut self,
429        framerate: Framerate,
430        w: &mut W,
431    ) -> Result<(), std::io::Error> {
432        let mut cea608_pair_rem = if self.output_cea608_padding {
433            framerate.cea608_pairs_per_frame()
434        } else {
435            framerate
436                .cea608_pairs_per_frame()
437                .min(self.cea608_1.len().max(self.cea608_2.len() * 2))
438        };
439
440        let mut cc_count_rem = if self.output_padding {
441            framerate.max_cc_count()
442        } else {
443            framerate.max_cc_count().min(
444                cea608_pair_rem
445                    + self.pending_packet_data.len() / 3
446                    + self.packets.iter().map(|p| p.cc_count()).sum::<usize>(),
447            )
448        };
449        trace!("writing with cc_count: {cc_count_rem} and {cea608_pair_rem} cea608 pairs");
450
451        let reserved = 0x80;
452        let process_cc_flag = 0x40;
453        w.write_all(&[
454            reserved | process_cc_flag | (cc_count_rem & 0x1f) as u8,
455            0xFF,
456        ])?;
457        while cc_count_rem > 0 {
458            if cea608_pair_rem > 0 {
459                if !self.last_cea608_was_field1 {
460                    trace!("attempting to write a cea608 byte pair from field 1");
461                    if let Some((byte0, byte1)) = self.cea608_1.pop_back() {
462                        w.write_all(&[0xFC, byte0, byte1])?;
463                        cc_count_rem -= 1;
464                    } else if !self.cea608_2.is_empty() {
465                        // need to write valid field 0 if we are going to write field 1
466                        w.write_all(&[0xFC, 0x80, 0x80])?;
467                        cc_count_rem -= 1;
468                    } else if self.output_cea608_padding {
469                        w.write_all(&[0xF8, 0x80, 0x80])?;
470                        cc_count_rem -= 1;
471                    }
472                    self.last_cea608_was_field1 = true;
473                } else {
474                    trace!("attempting to write a cea608 byte pair from field 2");
475                    if let Some((byte0, byte1)) = self.cea608_2.pop_back() {
476                        w.write_all(&[0xFD, byte0, byte1])?;
477                        cc_count_rem -= 1;
478                    } else if self.output_cea608_padding {
479                        w.write_all(&[0xF9, 0x80, 0x80])?;
480                        cc_count_rem -= 1;
481                    }
482                    self.last_cea608_was_field1 = false;
483                }
484                cea608_pair_rem -= 1;
485            } else {
486                let mut current_packet_data = &mut self.pending_packet_data;
487                let mut packet_offset = 0;
488                while packet_offset >= current_packet_data.len() {
489                    if let Some(packet) = self.packets.pop_back() {
490                        trace!("starting packet {packet:?}");
491                        packet.write_as_cc_data(&mut current_packet_data)?;
492                    } else {
493                        trace!("no packet to write");
494                        break;
495                    }
496                }
497
498                trace!("cea708 pending data length {}", current_packet_data.len(),);
499
500                while packet_offset < current_packet_data.len() && cc_count_rem > 0 {
501                    assert!(current_packet_data.len() >= packet_offset + 3);
502                    w.write_all(&current_packet_data[packet_offset..packet_offset + 3])?;
503                    packet_offset += 3;
504                    cc_count_rem -= 1;
505                }
506
507                self.pending_packet_data = current_packet_data[packet_offset..].to_vec();
508
509                if self.packets.is_empty() && self.pending_packet_data.is_empty() {
510                    // no more data to write
511                    if self.output_padding {
512                        trace!("writing {cc_count_rem} padding bytes");
513                        while cc_count_rem > 0 {
514                            w.write_all(&[0xFA, 0x00, 0x00])?;
515                            cc_count_rem -= 1;
516                        }
517                    }
518                    break;
519                }
520            }
521        }
522        Ok(())
523    }
524}
525
526/// A packet in the `cc_data` bitstream
527#[derive(Debug)]
528pub struct DTVCCPacket {
529    seq_no: u8,
530    services: Vec<Service>,
531}
532
533impl DTVCCPacket {
534    /// Create a new [DTVCCPacket] with the specified sequence number.
535    ///
536    /// # Panics
537    ///
538    /// * If seq_no >= 4
539    pub fn new(seq_no: u8) -> Self {
540        if seq_no > 3 {
541            panic!("DTVCCPacket sequence numbers must be between 0 and 3 inclusive, not {seq_no}");
542        }
543        Self {
544            seq_no,
545            services: vec![],
546        }
547    }
548
549    /// The sequence number of the DTVCCPacket
550    ///
551    /// # Examples
552    /// ```
553    /// # use cea708_types::*;
554    /// let packet = DTVCCPacket::new(2);
555    /// assert_eq!(2, packet.sequence_no());
556    /// ```
557    pub fn sequence_no(&self) -> u8 {
558        self.seq_no
559    }
560
561    /// The amount of free space (in bytes) that can by placed inside this [DTVCCPacket]
562    pub fn free_space(&self) -> usize {
563        // 128 is the max size of a DTVCCPacket, minus 1 for the header
564        128 - self.len()
565    }
566
567    /// The number of bytes this [DTVCCPacket] will use when written to a byte stream.
568    ///
569    /// # Examples
570    /// ```
571    /// # use cea708_types::{*, tables::*};
572    /// let mut packet = DTVCCPacket::new(2);
573    /// assert_eq!(0, packet.len());
574    /// let mut service = Service::new(1);
575    /// service.push_code(&Code::LatinCapitalA).unwrap();
576    /// packet.push_service(service);
577    /// assert_eq!(3, packet.len());
578    /// ```
579    pub fn len(&self) -> usize {
580        let services_len = self.services.iter().map(|s| s.len()).sum::<usize>();
581        if services_len > 0 {
582            1 + services_len
583        } else {
584            0
585        }
586    }
587
588    /// Push a completed service block into this [DTVCCPacket]
589    ///
590    /// # Examples
591    /// ```
592    /// # use cea708_types::{*, tables::*};
593    /// let mut packet = DTVCCPacket::new(2);
594    /// assert_eq!(0, packet.len());
595    /// let mut service = Service::new(1);
596    /// service.push_code(&Code::LatinCapitalA).unwrap();
597    /// packet.push_service(service);
598    /// assert_eq!(3, packet.len());
599    /// ```
600    pub fn push_service(&mut self, service: Service) -> Result<(), WriterError> {
601        // TODO: fail if we would overrun max size
602        if service.len() > self.free_space() {
603            return Err(WriterError::WouldOverflow(
604                service.len() - self.free_space(),
605            ));
606        }
607        self.services.push(service);
608        Ok(())
609    }
610
611    fn parse_hdr_byte(byte: u8) -> (u8, usize) {
612        let seq_no = (byte & 0xC0) >> 6;
613        let len = byte & 0x3F;
614        let len = if len == 0 {
615            127usize
616        } else {
617            ((len as usize) * 2) - 1
618        };
619        (seq_no, len)
620    }
621
622    /// Parse bytes into a [DTVCCPacket]
623    ///
624    /// Will return [ParserError::LengthMismatch] if the data is shorter than the length advertised in
625    /// the [DTVCCPacket] header.
626    ///
627    /// Will return errors from [Service::parse] if parsing the contained [Service]s fails.
628    ///
629    /// # Examples
630    /// ```
631    /// # use cea708_types::{*, tables::*};
632    /// let data = [0x02, 0x21, 0x41, 0x00];
633    /// let packet = DTVCCPacket::parse(&data).unwrap();
634    /// assert_eq!(3, packet.len());
635    /// assert_eq!(0, packet.sequence_no());
636    /// ```
637    pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
638        if data.is_empty() {
639            return Err(ParserError::LengthMismatch {
640                expected: 1,
641                actual: 0,
642            });
643        }
644        let (seq_no, len) = Self::parse_hdr_byte(data[0]);
645        trace!(
646            "dtvcc seq:{seq_no} len {len} data {data_len}",
647            data_len = data.len()
648        );
649        if (len + 1) < data.len() {
650            return Err(ParserError::LengthMismatch {
651                expected: len + 1,
652                actual: data.len(),
653            });
654        }
655
656        let mut offset = 1;
657        let mut services = vec![];
658        while offset < data.len() {
659            let service = Service::parse(&data[offset..])?;
660            trace!("parsed service {service:?}, len:{}", service.len());
661            if service.len() == 0 {
662                offset += 1;
663                continue;
664            }
665            offset += service.len();
666            services.push(service);
667        }
668        Ok(Self { seq_no, services })
669    }
670
671    /// The [Service]s for this [DTVCCPacket]
672    pub fn services(&self) -> &[Service] {
673        &self.services
674    }
675
676    fn cc_count(&self) -> usize {
677        (self.len() + 1) / 2
678    }
679
680    fn hdr_byte(&self) -> u8 {
681        let packet_size_code = if self.len() == 127 {
682            0
683        } else {
684            (self.len() + 1) / 2
685        };
686        (self.seq_no & 0x3) << 6 | packet_size_code as u8
687    }
688
689    /// Write the [DTVCCPacket] to a byte stream
690    ///
691    /// # Examples
692    /// ```
693    /// # use cea708_types::{*, tables::*};
694    /// let mut packet = DTVCCPacket::new(2);
695    /// let mut service = Service::new(1);
696    /// service.push_code(&Code::LatinCapitalA).unwrap();
697    /// packet.push_service(service);
698    /// let mut written = vec![];
699    /// packet.write(&mut written);
700    /// let expected = [0x82, 0x21, 0x41, 0x00];
701    /// assert_eq!(written, expected);
702    /// ```
703    pub fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
704        // TODO: fail if we would overrun max size
705        w.write_all(&[self.hdr_byte()])?;
706        for service in self.services.iter() {
707            service.write(w)?;
708        }
709        if self.len() % 2 == 1 {
710            w.write_all(&[0x00])?;
711        }
712        Ok(())
713    }
714
715    fn write_as_cc_data<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
716        // TODO: fail if we would overrun max size
717        // TODO: handle framerate?
718        if self.services.is_empty() {
719            return Ok(());
720        }
721        let mut written = vec![];
722        for service in self.services.iter() {
723            service.write(&mut written)?;
724            trace!("wrote service {service:?}");
725        }
726        w.write_all(&[0xFF, self.hdr_byte(), written[0]])?;
727        for pair in written[1..].chunks(2) {
728            let cc_valid = 0x04;
729            let cc_type = 0b10;
730            let reserved = 0xF8;
731            w.write_all(&[reserved | cc_valid | cc_type])?;
732            w.write_all(pair)?;
733            if pair.len() == 1 {
734                w.write_all(&[0x00])?;
735            }
736        }
737        Ok(())
738    }
739}
740
741/// A [Service] in a [DTVCCPacket]
742///
743/// As specified in CEA-708, there can be a maximum of 63 services.  Service 1 is the primary
744/// caption service and Service 2 is the secondary caption service.  All other services are
745/// undefined.
746#[derive(Debug, Clone)]
747pub struct Service {
748    number: u8,
749    codes: Vec<tables::Code>,
750}
751
752impl Service {
753    /// Create a new [Service]
754    ///
755    /// # Panics
756    ///
757    /// * if number >= 64
758    pub fn new(service_no: u8) -> Self {
759        if service_no >= 64 {
760            panic!("Service numbers must be between 0 and 63 inclusive, not {service_no}");
761        }
762        Self {
763            number: service_no,
764            codes: vec![],
765        }
766    }
767
768    /// Returns the number of this [Service]
769    ///
770    /// # Examples
771    /// ```
772    /// # use cea708_types::{*, tables::*};
773    /// let mut service = Service::new(1);
774    /// assert_eq!(service.number(), 1);
775    /// ```
776    pub fn number(&self) -> u8 {
777        self.number
778    }
779
780    fn codes_len(&self) -> usize {
781        self.codes.iter().map(|c| c.byte_len()).sum()
782    }
783
784    /// The amount of free space (in bytes) that can by placed inside this [Service] block
785    ///
786    /// # Examples
787    /// ```
788    /// # use cea708_types::{*, tables::*};
789    /// let service = Service::new(1);
790    /// assert_eq!(service.free_space(), 31);
791    /// ```
792    pub fn free_space(&self) -> usize {
793        // 31 is the maximum size of a service block
794        31 - self.codes_len()
795    }
796
797    /// The length in bytes of this [Service] block
798    ///
799    /// # Examples
800    /// ```
801    /// # use cea708_types::{*, tables::*};
802    /// let mut service = Service::new(1);
803    /// assert_eq!(service.len(), 0);
804    /// service.push_code(&Code::LatinCapitalA).unwrap();
805    /// assert_eq!(service.len(), 2);
806    /// service.push_code(&Code::LatinCapitalB).unwrap();
807    /// assert_eq!(service.len(), 3);
808    /// ```
809    pub fn len(&self) -> usize {
810        if self.number == 0 {
811            return 0;
812        }
813        if self.codes.is_empty() {
814            return 0;
815        }
816        let hdr_size = if self.number >= 7 { 2 } else { 1 };
817        hdr_size + self.codes_len()
818    }
819
820    /// Push a [tables::Code] to the end of this [Service]
821    ///
822    /// # Errors
823    ///
824    /// * [WriterError::ReadOnly] if [Service] is number 0 (called the NULL Service)
825    /// * [WriterError::WouldOverflow] if adding the [tables::Code] would cause to [Service] to overflow
826    ///
827    /// # Examples
828    /// ```
829    /// # use cea708_types::{*, tables::*};
830    /// let mut service = Service::new(1);
831    /// service.push_code(&Code::LatinCapitalA).unwrap();
832    /// ```
833    pub fn push_code(&mut self, code: &tables::Code) -> Result<(), WriterError> {
834        // TODO: errors?
835        if self.number == 0 {
836            return Err(WriterError::ReadOnly);
837        }
838
839        if code.byte_len() > self.free_space() {
840            let overflow_bytes = code.byte_len() - self.free_space();
841            debug!("pushing would overflow by {overflow_bytes} bytes");
842            return Err(WriterError::WouldOverflow(overflow_bytes));
843        }
844        trace!("pushing {code:?}");
845        self.codes.push(code.clone());
846        Ok(())
847    }
848
849    /// Parse a [Service] from a set of bytes
850    ///
851    /// # Errors
852    ///
853    /// * [ParserError::LengthMismatch] if the length of the data is less than the size advertised in the
854    /// header
855    ///
856    /// # Examples
857    /// ```
858    /// # use cea708_types::{*, tables::*};
859    /// let bytes = [0x21, 0x41];
860    /// let service = Service::parse(&bytes).unwrap();
861    /// assert_eq!(service.number(), 1);
862    /// assert_eq!(service.codes()[0], Code::LatinCapitalA);
863    /// ```
864    pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
865        if data.is_empty() {
866            return Err(ParserError::LengthMismatch {
867                expected: 1,
868                actual: 0,
869            });
870        }
871        let byte = data[0];
872        let mut service_no = (byte & 0xE0) >> 5;
873        let block_size = (byte & 0x1F) as usize;
874        let mut idx = 1;
875        trace!("block_size: {block_size}");
876        if service_no == 7 && block_size != 0 {
877            if data.len() == 1 {
878                return Err(ParserError::LengthMismatch {
879                    expected: 2,
880                    actual: data.len(),
881                });
882            }
883            let byte2 = data[1];
884            service_no = byte2 & 0x3F;
885            idx += 1;
886        }
887
888        if data.len() < idx + block_size {
889            return Err(ParserError::LengthMismatch {
890                expected: idx + block_size,
891                actual: data.len(),
892            });
893        }
894
895        if service_no != 0 {
896            Ok(Self {
897                number: service_no,
898                codes: tables::Code::from_data(&data[idx..idx + block_size])?,
899            })
900        } else {
901            Ok(Self {
902                number: 0,
903                codes: vec![],
904            })
905        }
906    }
907
908    /// The ordered list of [tables::Code]s present in this [Service] block
909    ///
910    /// # Examples
911    /// ```
912    /// # use cea708_types::{*, tables::*};
913    /// let mut service = Service::new(1);
914    /// service.push_code(&Code::LatinCapitalA).unwrap();
915    /// let codes = service.codes();
916    /// assert_eq!(codes, [Code::LatinCapitalA]);
917    /// ```
918    pub fn codes(&self) -> &[tables::Code] {
919        &self.codes
920    }
921
922    /// Write the [Service] block to a byte stream
923    ///
924    /// # Examples
925    /// ```
926    /// # use cea708_types::{*, tables::*};
927    /// let mut service = Service::new(1);
928    /// service.push_code(&Code::LatinCapitalA).unwrap();
929    /// let mut written = vec![];
930    /// service.write(&mut written);
931    /// let expected = [0x21, 0x41];
932    /// assert_eq!(written, expected);
933    /// ```
934    pub fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
935        // TODO: fail if we would overrun max size
936        let len = (self.codes_len() & 0x3F) as u8;
937        if self.number > 7 {
938            let mut buf = [0; 2];
939            buf[0] = 0xC0 | len;
940            buf[1] = self.number;
941            w.write_all(&buf)?;
942        } else {
943            let byte = (self.number & 0x7) << 5 | len;
944            w.write_all(&[byte])?;
945        }
946        for code in self.codes.iter() {
947            code.write(w)?;
948        }
949        Ok(())
950    }
951}
952
953#[cfg(test)]
954mod test {
955    use super::*;
956    use crate::tests::*;
957
958    #[test]
959    fn simple_parse_dtvcc() {
960        test_init_log();
961        let data = [0x02, 0x01 << 5 | 0x01, 0x2A];
962        let dtvcc = DTVCCPacket::parse(&data).unwrap();
963        let services = dtvcc.services();
964        assert_eq!(services.len(), 1);
965        for service in services.iter() {
966            assert_eq!(service.number, 1);
967            let codes = service.codes();
968            for code in codes.iter() {
969                trace!("parsed {code:?}");
970            }
971        }
972    }
973
974    #[test]
975    fn simple_write_dtvcc() {
976        test_init_log();
977        let mut service = Service::new(1);
978        let code = tables::Code::Asterisk;
979        service.push_code(&code).unwrap();
980        let mut dtvcc = DTVCCPacket::new(0);
981        dtvcc.push_service(service).unwrap();
982        let mut written = vec![];
983        dtvcc.write(&mut written).unwrap();
984        let data = [0x02, 0x01 << 5 | 0x01, 0x2A, 0x00];
985        assert_eq!(written, data);
986    }
987
988    #[derive(Debug)]
989    struct ServiceData<'a> {
990        service_no: u8,
991        codes: &'a [tables::Code],
992    }
993
994    #[derive(Debug)]
995    struct PacketData<'a> {
996        sequence_no: u8,
997        services: &'a [ServiceData<'a>],
998    }
999
1000    #[derive(Debug)]
1001    struct TestCCData<'a> {
1002        framerate: Framerate,
1003        cc_data: &'a [&'a [u8]],
1004        packets: &'a [PacketData<'a>],
1005        cea608: &'a [&'a [Cea608]],
1006    }
1007
1008    static TEST_CC_DATA: [TestCCData; 8] = [
1009        // simple packet with a single service and single code
1010        TestCCData {
1011            framerate: Framerate::new(25, 1),
1012            cc_data: &[&[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x02, 0x21, 0xFE, 0x41, 0x00]],
1013            packets: &[PacketData {
1014                sequence_no: 0,
1015                services: &[ServiceData {
1016                    service_no: 1,
1017                    codes: &[tables::Code::LatinCapitalA],
1018                }],
1019            }],
1020            cea608: &[],
1021        },
1022        // simple packet with a single service and two codes
1023        TestCCData {
1024            framerate: Framerate::new(25, 1),
1025            cc_data: &[&[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x02, 0x22, 0xFE, 0x41, 0x42]],
1026            packets: &[PacketData {
1027                sequence_no: 0,
1028                services: &[ServiceData {
1029                    service_no: 1,
1030                    codes: &[tables::Code::LatinCapitalA, tables::Code::LatinCapitalB],
1031                }],
1032            }],
1033            cea608: &[],
1034        },
1035        // two packets each with a single service and single code
1036        TestCCData {
1037            framerate: Framerate::new(25, 1),
1038            cc_data: &[
1039                &[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x02, 0x21, 0xFE, 0x41, 0x00],
1040                &[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x42, 0x21, 0xFE, 0x42, 0x00],
1041            ],
1042            packets: &[
1043                PacketData {
1044                    sequence_no: 0,
1045                    services: &[ServiceData {
1046                        service_no: 1,
1047                        codes: &[tables::Code::LatinCapitalA],
1048                    }],
1049                },
1050                PacketData {
1051                    sequence_no: 1,
1052                    services: &[ServiceData {
1053                        service_no: 1,
1054                        codes: &[tables::Code::LatinCapitalB],
1055                    }],
1056                },
1057            ],
1058            cea608: &[],
1059        },
1060        // two packets with a single service and one code split across both packets
1061        TestCCData {
1062            framerate: Framerate::new(25, 1),
1063            cc_data: &[
1064                &[0x80 | 0x40 | 0x01, 0xFF, 0xFF, 0x02, 0x21],
1065                &[0x80 | 0x40 | 0x01, 0xFF, 0xFE, 0x41, 0x00],
1066            ],
1067            packets: &[PacketData {
1068                sequence_no: 0,
1069                services: &[ServiceData {
1070                    service_no: 1,
1071                    codes: &[tables::Code::LatinCapitalA],
1072                }],
1073            }],
1074            cea608: &[],
1075        },
1076        // simple packet with a single null service
1077        TestCCData {
1078            framerate: Framerate::new(25, 1),
1079            cc_data: &[&[0x80 | 0x40 | 0x01, 0xFF, 0xFF, 0x01, 0x00]],
1080            packets: &[PacketData {
1081                sequence_no: 0,
1082                services: &[],
1083            }],
1084            cea608: &[],
1085        },
1086        // DTVCCPacket with two services
1087        TestCCData {
1088            framerate: Framerate::new(25, 1),
1089            cc_data: &[&[
1090                0x80 | 0x40 | 0x03,
1091                0xFF,
1092                0xFF,
1093                0x03,
1094                0x21,
1095                0xFE,
1096                0x41,
1097                0x41,
1098                0xFE,
1099                0x42,
1100                0x00,
1101            ]],
1102            packets: &[PacketData {
1103                sequence_no: 0,
1104                services: &[
1105                    ServiceData {
1106                        service_no: 1,
1107                        codes: &[tables::Code::LatinCapitalA],
1108                    },
1109                    ServiceData {
1110                        service_no: 2,
1111                        codes: &[tables::Code::LatinCapitalB],
1112                    },
1113                ],
1114            }],
1115            cea608: &[],
1116        },
1117        // cc_data with two DTVCCPacket
1118        TestCCData {
1119            framerate: Framerate::new(25, 1),
1120            cc_data: &[&[
1121                0x80 | 0x40 | 0x04,
1122                0xFF,
1123                0xFF,
1124                0x02,
1125                0x21,
1126                0xFE,
1127                0x41,
1128                0x00,
1129                0xFF,
1130                0x42,
1131                0x41,
1132                0xFE,
1133                0x42,
1134                0x00,
1135            ]],
1136            packets: &[
1137                PacketData {
1138                    sequence_no: 0,
1139                    services: &[ServiceData {
1140                        service_no: 1,
1141                        codes: &[tables::Code::LatinCapitalA],
1142                    }],
1143                },
1144                PacketData {
1145                    sequence_no: 1,
1146                    services: &[ServiceData {
1147                        service_no: 2,
1148                        codes: &[tables::Code::LatinCapitalB],
1149                    }],
1150                },
1151            ],
1152            cea608: &[],
1153        },
1154        // two packets with a single service and one code split across both packets with 608
1155        // padding data
1156        TestCCData {
1157            framerate: Framerate::new(25, 1),
1158            cc_data: &[
1159                &[
1160                    0x80 | 0x40 | 0x03,
1161                    0xFF,
1162                    0xFC,
1163                    0x61,
1164                    0x62,
1165                    0xFD,
1166                    0x63,
1167                    0x64,
1168                    0xFF,
1169                    0x02,
1170                    0x21,
1171                ],
1172                &[
1173                    0x80 | 0x40 | 0x03,
1174                    0xFF,
1175                    0xFC,
1176                    0x41,
1177                    0x42,
1178                    0xFD,
1179                    0x43,
1180                    0x44,
1181                    0xFE,
1182                    0x41,
1183                    0x00,
1184                ],
1185            ],
1186            packets: &[PacketData {
1187                sequence_no: 0,
1188                services: &[ServiceData {
1189                    service_no: 1,
1190                    codes: &[tables::Code::LatinCapitalA],
1191                }],
1192            }],
1193            cea608: &[
1194                &[Cea608::Field1(0x61, 0x62), Cea608::Field2(0x63, 0x64)],
1195                &[Cea608::Field1(0x41, 0x42), Cea608::Field2(0x43, 0x44)],
1196            ],
1197        },
1198    ];
1199
1200    #[test]
1201    fn cc_data_parse() {
1202        test_init_log();
1203        for (i, test_data) in TEST_CC_DATA.iter().enumerate() {
1204            log::info!("parsing {i}: {test_data:?}");
1205            let mut parser = CCDataParser::new();
1206            if !test_data.cea608.is_empty() {
1207                parser.handle_cea608();
1208            }
1209            let mut expected_iter = test_data.packets.iter();
1210            let mut cea608_iter = test_data.cea608.iter();
1211            for data in test_data.cc_data.iter() {
1212                debug!("pushing {data:?}");
1213                parser.push(data).unwrap();
1214                while let Some(packet) = parser.pop_packet() {
1215                    let expected = expected_iter.next().unwrap();
1216                    assert_eq!(expected.sequence_no, packet.sequence_no());
1217                    let services = packet.services();
1218                    let mut expected_service_iter = expected.services.iter();
1219                    for parsed_service in services.iter() {
1220                        let expected_service = expected_service_iter.next().unwrap();
1221                        assert_eq!(parsed_service.number(), expected_service.service_no);
1222                        assert_eq!(expected_service.codes, parsed_service.codes());
1223                    }
1224                    assert!(expected_service_iter.next().is_none());
1225                }
1226                assert_eq!(parser.cea608().as_ref(), cea608_iter.next());
1227            }
1228            assert!(parser.pop_packet().is_none());
1229            assert!(expected_iter.next().is_none());
1230            assert!(cea608_iter.next().is_none());
1231        }
1232    }
1233
1234    static WRITE_CC_DATA: [TestCCData; 7] = [
1235        // simple packet with a single service and single code
1236        TestCCData {
1237            framerate: Framerate::new(25, 1),
1238            cc_data: &[&[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x02, 0x21, 0xFE, 0x41, 0x00]],
1239            packets: &[PacketData {
1240                sequence_no: 0,
1241                services: &[ServiceData {
1242                    service_no: 1,
1243                    codes: &[tables::Code::LatinCapitalA],
1244                }],
1245            }],
1246            cea608: &[],
1247        },
1248        // simple packet with a single service and two codes
1249        TestCCData {
1250            framerate: Framerate::new(25, 1),
1251            cc_data: &[&[0x80 | 0x40 | 0x02, 0xFF, 0xFF, 0x02, 0x22, 0xFE, 0x41, 0x42]],
1252            packets: &[PacketData {
1253                sequence_no: 0,
1254                services: &[ServiceData {
1255                    service_no: 1,
1256                    codes: &[tables::Code::LatinCapitalA, tables::Code::LatinCapitalB],
1257                }],
1258            }],
1259            cea608: &[],
1260        },
1261        // packet with a full service service
1262        TestCCData {
1263            framerate: Framerate::new(25, 1),
1264            cc_data: &[&[
1265                0x80 | 0x40 | 0x11,
1266                0xFF,
1267                0xFF,
1268                0xC0 | 0x11,
1269                0x20 | 0x1F,
1270                0xFE,
1271                0x41,
1272                0x42,
1273                0xFE,
1274                0x43,
1275                0x44,
1276                0xFE,
1277                0x45,
1278                0x46,
1279                0xFE,
1280                0x47,
1281                0x48,
1282                0xFE,
1283                0x49,
1284                0x4A,
1285                0xFE,
1286                0x4B,
1287                0x4C,
1288                0xFE,
1289                0x4D,
1290                0x4E,
1291                0xFE,
1292                0x4F,
1293                0x50,
1294                0xFE,
1295                0x51,
1296                0x52,
1297                0xFE,
1298                0x53,
1299                0x54,
1300                0xFE,
1301                0x55,
1302                0x56,
1303                0xFE,
1304                0x57,
1305                0x58,
1306                0xFE,
1307                0x59,
1308                0x5A,
1309                0xFE,
1310                0x61,
1311                0x62,
1312                0xFE,
1313                0x63,
1314                0x64,
1315                0xFE,
1316                0x65,
1317                0x0,
1318            ]],
1319            packets: &[PacketData {
1320                sequence_no: 3,
1321                services: &[ServiceData {
1322                    service_no: 1,
1323                    codes: &[
1324                        tables::Code::LatinCapitalA,
1325                        tables::Code::LatinCapitalB,
1326                        tables::Code::LatinCapitalC,
1327                        tables::Code::LatinCapitalD,
1328                        tables::Code::LatinCapitalE,
1329                        tables::Code::LatinCapitalF,
1330                        tables::Code::LatinCapitalG,
1331                        tables::Code::LatinCapitalH,
1332                        tables::Code::LatinCapitalI,
1333                        tables::Code::LatinCapitalJ,
1334                        tables::Code::LatinCapitalK,
1335                        tables::Code::LatinCapitalL,
1336                        tables::Code::LatinCapitalM,
1337                        tables::Code::LatinCapitalN,
1338                        tables::Code::LatinCapitalO,
1339                        tables::Code::LatinCapitalP,
1340                        tables::Code::LatinCapitalQ,
1341                        tables::Code::LatinCapitalR,
1342                        tables::Code::LatinCapitalS,
1343                        tables::Code::LatinCapitalT,
1344                        tables::Code::LatinCapitalU,
1345                        tables::Code::LatinCapitalV,
1346                        tables::Code::LatinCapitalW,
1347                        tables::Code::LatinCapitalX,
1348                        tables::Code::LatinCapitalY,
1349                        tables::Code::LatinCapitalZ,
1350                        tables::Code::LatinLowerA,
1351                        tables::Code::LatinLowerB,
1352                        tables::Code::LatinLowerC,
1353                        tables::Code::LatinLowerD,
1354                        tables::Code::LatinLowerE,
1355                    ],
1356                }],
1357            }],
1358            cea608: &[],
1359        },
1360        // simple packet with only cea608 data
1361        TestCCData {
1362            framerate: Framerate::new(25, 1),
1363            cc_data: &[&[0x80 | 0x40 | 0x01, 0xFF, 0xFC, 0x41, 0x42]],
1364            packets: &[],
1365            cea608: &[&[Cea608::Field1(0x41, 0x42)]],
1366        },
1367        // simple packet with only cea608 field 1 data
1368        TestCCData {
1369            framerate: Framerate::new(25, 1),
1370            cc_data: &[&[0x80 | 0x40 | 0x02, 0xFF, 0xFC, 0x80, 0x80, 0xFD, 0x41, 0x42]],
1371            packets: &[],
1372            cea608: &[&[Cea608::Field2(0x41, 0x42)]],
1373        },
1374        // simple packet that will span two outputs
1375        TestCCData {
1376            framerate: Framerate::new(60, 1),
1377            cc_data: &[
1378                &[
1379                    0x80 | 0x40 | 0x0A,
1380                    0xFF,
1381                    0xFC,
1382                    0x20,
1383                    0x42,
1384                    0xFF,
1385                    0xC0 | 0x11,
1386                    0x20 | 0x1F,
1387                    0xFE,
1388                    0x41,
1389                    0x42,
1390                    0xFE,
1391                    0x43,
1392                    0x44,
1393                    0xFE,
1394                    0x45,
1395                    0x46,
1396                    0xFE,
1397                    0x47,
1398                    0x48,
1399                    0xFE,
1400                    0x49,
1401                    0x4A,
1402                    0xFE,
1403                    0x4B,
1404                    0x4C,
1405                    0xFE,
1406                    0x4D,
1407                    0x4E,
1408                    0xFE,
1409                    0x4F,
1410                    0x50,
1411                ],
1412                &[
1413                    0x80 | 0x40 | 0x09,
1414                    0xFF,
1415                    0xFD,
1416                    0x21,
1417                    0x43,
1418                    0xFE,
1419                    0x51,
1420                    0x52,
1421                    0xFE,
1422                    0x53,
1423                    0x54,
1424                    0xFE,
1425                    0x55,
1426                    0x56,
1427                    0xFE,
1428                    0x57,
1429                    0x58,
1430                    0xFE,
1431                    0x59,
1432                    0x5A,
1433                    0xFE,
1434                    0x61,
1435                    0x62,
1436                    0xFE,
1437                    0x63,
1438                    0x64,
1439                    0xFE,
1440                    0x65,
1441                    0x0,
1442                ],
1443            ],
1444            packets: &[PacketData {
1445                sequence_no: 3,
1446                services: &[ServiceData {
1447                    service_no: 1,
1448                    codes: &[
1449                        tables::Code::LatinCapitalA,
1450                        tables::Code::LatinCapitalB,
1451                        tables::Code::LatinCapitalC,
1452                        tables::Code::LatinCapitalD,
1453                        tables::Code::LatinCapitalE,
1454                        tables::Code::LatinCapitalF,
1455                        tables::Code::LatinCapitalG,
1456                        tables::Code::LatinCapitalH,
1457                        tables::Code::LatinCapitalI,
1458                        tables::Code::LatinCapitalJ,
1459                        tables::Code::LatinCapitalK,
1460                        tables::Code::LatinCapitalL,
1461                        tables::Code::LatinCapitalM,
1462                        tables::Code::LatinCapitalN,
1463                        tables::Code::LatinCapitalO,
1464                        tables::Code::LatinCapitalP,
1465                        tables::Code::LatinCapitalQ,
1466                        tables::Code::LatinCapitalR,
1467                        tables::Code::LatinCapitalS,
1468                        tables::Code::LatinCapitalT,
1469                        tables::Code::LatinCapitalU,
1470                        tables::Code::LatinCapitalV,
1471                        tables::Code::LatinCapitalW,
1472                        tables::Code::LatinCapitalX,
1473                        tables::Code::LatinCapitalY,
1474                        tables::Code::LatinCapitalZ,
1475                        tables::Code::LatinLowerA,
1476                        tables::Code::LatinLowerB,
1477                        tables::Code::LatinLowerC,
1478                        tables::Code::LatinLowerD,
1479                        tables::Code::LatinLowerE,
1480                    ],
1481                }],
1482            }],
1483            cea608: &[&[Cea608::Field1(0x20, 0x42), Cea608::Field2(0x21, 0x43)]],
1484        },
1485        // simple packet with multiple cea608 that will span two outputs
1486        TestCCData {
1487            framerate: Framerate::new(24, 1),
1488            cc_data: &[
1489                &[0x80 | 0x40 | 0x02, 0xFF, 0xFC, 0x20, 0x42, 0xFD, 0x21, 0x43],
1490                &[0x80 | 0x40 | 0x02, 0xFF, 0xFC, 0x22, 0x44, 0xFD, 0x23, 0x45],
1491            ],
1492            packets: &[PacketData {
1493                sequence_no: 3,
1494                services: &[],
1495            }],
1496            cea608: &[
1497                &[Cea608::Field1(0x20, 0x42), Cea608::Field2(0x21, 0x43)],
1498                &[Cea608::Field1(0x22, 0x44), Cea608::Field2(0x23, 0x45)],
1499            ],
1500        },
1501    ];
1502
1503    #[test]
1504    fn packet_write_cc_data() {
1505        test_init_log();
1506        for test_data in WRITE_CC_DATA.iter() {
1507            log::info!("writing {test_data:?}");
1508            let mut packet_iter = test_data.packets.iter();
1509            let mut cea608_iter = test_data.cea608.iter();
1510            let mut writer = CCDataWriter::default();
1511            for cc_data in test_data.cc_data.iter() {
1512                if let Some(packet_data) = packet_iter.next() {
1513                    let mut pack = DTVCCPacket::new(packet_data.sequence_no);
1514                    for service_data in packet_data.services.iter() {
1515                        let mut service = Service::new(service_data.service_no);
1516                        for code in service_data.codes.iter() {
1517                            service.push_code(code).unwrap();
1518                        }
1519                        pack.push_service(service).unwrap();
1520                    }
1521                    writer.push_packet(pack);
1522                }
1523                if let Some(&cea608) = cea608_iter.next() {
1524                    for pair in cea608 {
1525                        writer.push_cea608(*pair);
1526                    }
1527                }
1528                let mut written = vec![];
1529                writer.write(test_data.framerate, &mut written).unwrap();
1530                assert_eq!(cc_data, &written);
1531            }
1532        }
1533    }
1534
1535    #[test]
1536    fn framerate_cea608_pairs_per_frame() {
1537        assert_eq!(Framerate::new(60, 1).cea608_pairs_per_frame(), 1);
1538        assert_eq!(Framerate::new(30, 1).cea608_pairs_per_frame(), 2);
1539    }
1540
1541    #[test]
1542    fn framerate_max_cc_count() {
1543        assert_eq!(Framerate::new(60, 1).max_cc_count(), 10);
1544        assert_eq!(Framerate::new(30, 1).max_cc_count(), 20);
1545    }
1546
1547    #[test]
1548    fn framerate_new() {
1549        let fps = Framerate::new(30, 8);
1550        assert_eq!(fps.numer(), 30);
1551        assert_eq!(fps.denom(), 8);
1552    }
1553}
1554
1555#[cfg(test)]
1556pub(crate) mod tests {
1557    use std::sync::OnceLock;
1558
1559    static TRACING: OnceLock<()> = OnceLock::new();
1560
1561    pub fn test_init_log() {
1562        TRACING.get_or_init(|| {
1563            env_logger::init();
1564        });
1565    }
1566}