cea708_types/
writer.rs

1// Copyright (C) 2025 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
7use std::collections::VecDeque;
8use std::time::Duration;
9
10use muldiv::MulDiv;
11
12use log::trace;
13
14use crate::{Cea608, DTVCCPacket, Framerate};
15
16/// An error enum returned when writing data fails
17#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
18pub enum WriterError {
19    /// Writing would overflow by how many bytes
20    #[error("Writing the data would overflow by {0} bytes")]
21    WouldOverflow(usize),
22    /// It is not possible to write to this resource
23    #[error("The resource is not writable")]
24    ReadOnly,
25    /// It is not possible to write an empty service
26    #[error("Empty service was attempted to be written")]
27    EmptyService,
28}
29
30/// A struct for writing cc_data packets
31#[derive(Debug, Default)]
32pub struct CCDataWriter {
33    // settings
34    output_cea608_padding: bool,
35    output_padding: bool,
36    // state
37    packets: VecDeque<DTVCCPacket>,
38    // part of a packet we could not fit into the previous packet
39    pending_packet_data: Vec<u8>,
40    cea608_1: VecDeque<(u8, u8)>,
41    cea608_2: VecDeque<(u8, u8)>,
42    last_cea608_was_field1: bool,
43}
44
45impl CCDataWriter {
46    /// Whether to output padding CEA-608 bytes when not enough enough data has been provided
47    pub fn set_output_cea608_padding(&mut self, output_cea608_padding: bool) {
48        self.output_cea608_padding = output_cea608_padding;
49    }
50
51    /// Whether padding CEA-608 bytes will be used
52    pub fn output_cea608_padding(&self) -> bool {
53        self.output_cea608_padding
54    }
55
56    /// Whether to output padding data in the CCP bitstream when not enough data has been provided
57    pub fn set_output_padding(&mut self, output_padding: bool) {
58        self.output_padding = output_padding;
59    }
60
61    /// Whether padding data will be produced in the CCP
62    pub fn output_padding(&self) -> bool {
63        self.output_padding
64    }
65
66    /// Push a [`DTVCCPacket`] for writing
67    pub fn push_packet(&mut self, packet: DTVCCPacket) {
68        self.packets.push_front(packet)
69    }
70
71    /// Push a [`Cea608`] byte pair for writing
72    pub fn push_cea608(&mut self, cea608: Cea608) {
73        match cea608 {
74            Cea608::Field1(byte0, byte1) => {
75                if byte0 != 0x80 || byte1 != 0x80 {
76                    self.cea608_1.push_front((byte0, byte1))
77                }
78            }
79            Cea608::Field2(byte0, byte1) => {
80                if byte0 != 0x80 || byte1 != 0x80 {
81                    self.cea608_2.push_front((byte0, byte1))
82                }
83            }
84        }
85    }
86
87    /// Clear all stored data
88    pub fn flush(&mut self) {
89        self.packets.clear();
90        self.pending_packet_data.clear();
91        self.cea608_1.clear();
92        self.cea608_2.clear();
93    }
94
95    /// The amount of time that is currently stored for CEA-608 field 1 data
96    pub fn buffered_cea608_field1_duration(&self) -> Duration {
97        // CEA-608 has a max bitrate of 60000 * 2 / 1001 bytes/s
98        Duration::from_micros(
99            (self.cea608_1.len() as u64)
100                .mul_div_ceil(1001 * 1_000_000, 60000)
101                .unwrap(),
102        )
103    }
104
105    /// The amount of time that is currently stored for CEA-608 field 2 data
106    pub fn buffered_cea608_field2_duration(&self) -> Duration {
107        // CEA-608 has a max bitrate of 60000 * 2 / 1001 bytes/s
108        Duration::from_micros(
109            (self.cea608_2.len() as u64)
110                .mul_div_ceil(1001 * 1_000_000, 60000)
111                .unwrap(),
112        )
113    }
114
115    fn buffered_packet_bytes(&self) -> usize {
116        self.pending_packet_data.len()
117            + self
118                .packets
119                .iter()
120                .map(|packet| packet.len())
121                .sum::<usize>()
122    }
123
124    /// The amount of time that is currently stored for CCP data
125    pub fn buffered_packet_duration(&self) -> Duration {
126        // CEA-708 has a max bitrate of 9600000 / 1001 bits/s
127        Duration::from_micros(
128            ((self.buffered_packet_bytes() + 1) as u64 / 2)
129                .mul_div_ceil(2 * 1001 * 1_000_000, 9_600_000 / 8)
130                .unwrap(),
131        )
132    }
133
134    /// Write the next cc_data packet taking the next relevant CEA-608 byte pairs and
135    /// [`DTVCCPacket`]s.  The framerate provided determines how many bytes are written.
136    pub fn write<W: std::io::Write>(
137        &mut self,
138        framerate: Framerate,
139        w: &mut W,
140    ) -> Result<(), std::io::Error> {
141        let mut cea608_pair_rem = if self.output_cea608_padding {
142            framerate.cea608_pairs_per_frame()
143        } else {
144            framerate
145                .cea608_pairs_per_frame()
146                .min(self.cea608_1.len().max(self.cea608_2.len() * 2))
147        };
148
149        let mut cc_count_rem = if self.output_padding {
150            framerate.max_cc_count()
151        } else {
152            framerate.max_cc_count().min(
153                cea608_pair_rem
154                    + self.pending_packet_data.len() / 3
155                    + self.packets.iter().map(|p| p.cc_count()).sum::<usize>(),
156            )
157        };
158        trace!("writing with cc_count: {cc_count_rem} and {cea608_pair_rem} cea608 pairs");
159
160        let reserved = 0x80;
161        let process_cc_flag = 0x40;
162        w.write_all(&[
163            reserved | process_cc_flag | (cc_count_rem & 0x1f) as u8,
164            0xFF,
165        ])?;
166        while cc_count_rem > 0 {
167            if cea608_pair_rem > 0 {
168                if !self.last_cea608_was_field1 {
169                    trace!("attempting to write a cea608 byte pair from field 1");
170                    if let Some((byte0, byte1)) = self.cea608_1.pop_back() {
171                        w.write_all(&[0xFC, byte0, byte1])?;
172                        cc_count_rem -= 1;
173                    } else if !self.cea608_2.is_empty() {
174                        // need to write valid field 0 if we are going to write field 1
175                        w.write_all(&[0xFC, 0x80, 0x80])?;
176                        cc_count_rem -= 1;
177                    } else if self.output_cea608_padding {
178                        w.write_all(&[0xF8, 0x80, 0x80])?;
179                        cc_count_rem -= 1;
180                    }
181                    self.last_cea608_was_field1 = true;
182                } else {
183                    trace!("attempting to write a cea608 byte pair from field 2");
184                    if let Some((byte0, byte1)) = self.cea608_2.pop_back() {
185                        w.write_all(&[0xFD, byte0, byte1])?;
186                        cc_count_rem -= 1;
187                    } else if self.output_cea608_padding {
188                        w.write_all(&[0xF9, 0x80, 0x80])?;
189                        cc_count_rem -= 1;
190                    }
191                    self.last_cea608_was_field1 = false;
192                }
193                cea608_pair_rem -= 1;
194            } else {
195                let mut current_packet_data = &mut self.pending_packet_data;
196                let mut packet_offset = 0;
197                while packet_offset >= current_packet_data.len() {
198                    if let Some(packet) = self.packets.pop_back() {
199                        trace!("starting packet {packet:?}");
200                        packet.write_as_cc_data(&mut current_packet_data)?;
201                    } else {
202                        trace!("no packet to write");
203                        break;
204                    }
205                }
206
207                trace!("cea708 pending data length {}", current_packet_data.len(),);
208
209                while packet_offset < current_packet_data.len() && cc_count_rem > 0 {
210                    assert!(current_packet_data.len() >= packet_offset + 3);
211                    w.write_all(&current_packet_data[packet_offset..packet_offset + 3])?;
212                    packet_offset += 3;
213                    cc_count_rem -= 1;
214                }
215
216                self.pending_packet_data = current_packet_data[packet_offset..].to_vec();
217
218                if self.packets.is_empty() && self.pending_packet_data.is_empty() {
219                    // no more data to write
220                    if self.output_padding {
221                        trace!("writing {cc_count_rem} padding bytes");
222                        while cc_count_rem > 0 {
223                            w.write_all(&[0xFA, 0x00, 0x00])?;
224                            cc_count_rem -= 1;
225                        }
226                    }
227                    break;
228                }
229            }
230        }
231        Ok(())
232    }
233}