cdp_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 crate::{Flags, Framerate, ServiceInfo, TimeCode};
8
9/// A struct for writing a stream of CDPs
10///
11/// # Examples
12///
13/// ```
14/// # use cdp_types::*;
15/// use cdp_types::cea708_types::{Cea608, DTVCCPacket, Service, tables};
16///
17/// let mut writer = CDPWriter::new();
18/// writer.set_sequence_count(3);
19/// let mut packet = DTVCCPacket::new(0);
20/// let mut service = Service::new(1);
21/// service.push_code(&tables::Code::LatinCapitalA).unwrap();
22/// packet.push_service(service).unwrap();
23/// writer.push_packet(packet);
24///
25/// writer.push_cea608(Cea608::Field1(0x41, 0x80));
26///
27/// writer.set_time_code(Some(TimeCode::new(1, 2, 3, 4, true, false)));
28///
29/// let mut service_info = ServiceInfo::default();
30/// service_info.set_start(true);
31/// service_info.set_complete(true);
32/// let entry = ServiceEntry::new([b'e', b'n', b'g'], FieldOrService::Field(true));
33/// service_info.add_service(entry);
34/// let entry = ServiceEntry::new(
35///     [b'e', b'n', b'g'],
36///     FieldOrService::Service(DigitalServiceEntry::new(1, false, true))
37/// );
38/// service_info.add_service(entry);
39/// writer.set_service_info(Some(service_info));
40///
41/// let framerate = Framerate::from_id(4).unwrap();
42/// let mut data = vec![];
43/// writer.write(framerate, &mut data).unwrap();
44///
45/// let expected = [
46///     0x96, 0x69,         // magic
47///     0x5e,               // CDP length
48///     0x4f,               // framerate
49///     0xf7,               // flags
50///     0x00, 0x03,         // sequence counter
51///     0x71,               // time code start
52///     0xc1,               // hours
53///     0x82,               // minutes
54///     0x83,               // seconds
55///     0x04,               // frames
56///     0x72,               // cc_data id
57///     0xf4,               // cc_data count
58///     0xfc, 0x41, 0x80,   // CEA-608 field 1
59///     0xf9, 0x80, 0x80,   // CEA-608 field 2
60///     0xff, 0x02, 0x21,   // CEA-708 start
61///     0xfe, 0x41, 0x00,   // CEA-708 continued
62///     0xfa, 0x00, 0x00,   // CEA-708 padding
63///     0xfa, 0x00, 0x00,   // .
64///     0xfa, 0x00, 0x00,   // .
65///     0xfa, 0x00, 0x00,   // .
66///     0xfa, 0x00, 0x00,   // .
67///     0xfa, 0x00, 0x00,   // .
68///     0xfa, 0x00, 0x00,   // .
69///     0xfa, 0x00, 0x00,   // .
70///     0xfa, 0x00, 0x00,   // .
71///     0xfa, 0x00, 0x00,   // .
72///     0xfa, 0x00, 0x00,   // .
73///     0xfa, 0x00, 0x00,   // .
74///     0xfa, 0x00, 0x00,   // .
75///     0xfa, 0x00, 0x00,   // .
76///     0xfa, 0x00, 0x00,   // .
77///     0xfa, 0x00, 0x00,   // .
78///     0x73,               // service info id
79///     0xd2,               // start | change | complete | count
80///     0x80,               // service no
81///     b'e', b'n', b'g',   // language
82///     0x7e,               // is_digital | ignored
83///     0x3f, 0xff,         // ignored | reserved
84///     0x81,               // service no
85///     b'e', b'n', b'g',   // language
86///     0xc1,               // is_digital | service no
87///     0x7f, 0xff,         // easy_reader | wide_aspect_ratio | reserved
88///     0x74,               // footer id
89///     0x00, 0x03,         // sequence counter
90///     0xd6,               // checksum
91/// ];
92/// assert_eq!(&data, &expected);
93/// ```
94#[derive(Debug)]
95pub struct CDPWriter {
96    cc_data: cea708_types::CCDataWriter,
97    time_code: Option<TimeCode>,
98    service_info: Option<ServiceInfo>,
99    sequence_count: u16,
100}
101
102impl Default for CDPWriter {
103    fn default() -> Self {
104        let mut cc_data = cea708_types::CCDataWriter::default();
105        cc_data.set_output_padding(true);
106        cc_data.set_output_cea608_padding(true);
107        Self {
108            cc_data,
109            time_code: None,
110            service_info: None,
111            sequence_count: 0,
112        }
113    }
114}
115
116impl CDPWriter {
117    /// Construct a new [`CDPWriter`].
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    /// Push a [`cea708_types::DTVCCPacket`] for writing
123    pub fn push_packet(&mut self, packet: cea708_types::DTVCCPacket) {
124        self.cc_data.push_packet(packet)
125    }
126
127    /// Push a [`cea708_types::Cea608`] byte pair for writing
128    pub fn push_cea608(&mut self, cea608: cea708_types::Cea608) {
129        self.cc_data.push_cea608(cea608)
130    }
131
132    /// Set the optional time code to use for the next CDP packet that is generated.
133    pub fn set_time_code(&mut self, time_code: Option<TimeCode>) {
134        self.time_code = time_code;
135    }
136
137    /// Set the optional [`ServiceInfo`] for the next CDP packet that is generated.
138    pub fn set_service_info(&mut self, service_info: Option<ServiceInfo>) {
139        self.service_info = service_info;
140    }
141
142    /// Set the next packet's sequence count to a specific value
143    pub fn set_sequence_count(&mut self, sequence: u16) {
144        self.sequence_count = sequence;
145    }
146
147    /// Clear all stored data
148    pub fn flush(&mut self) {
149        self.cc_data.flush();
150        self.time_code = None;
151        self.sequence_count = 0;
152        self.service_info = None;
153    }
154
155    /// Write the next CDP packet taking the next relevant CEA-608 byte pairs and
156    /// [`cea708_types::DTVCCPacket`]s.
157    pub fn write<W: std::io::Write>(
158        &mut self,
159        framerate: Framerate,
160        w: &mut W,
161    ) -> Result<(), std::io::Error> {
162        let mut len = 7; // header
163        if self.time_code.is_some() {
164            len += 5;
165        }
166        let mut cc_data = Vec::new();
167        self.cc_data.write(
168            cea708_types::Framerate::new(framerate.numer(), framerate.denom()),
169            &mut cc_data,
170        )?;
171        cc_data[1] = 0xe0 | (cc_data[0] & 0x1f);
172        cc_data[0] = 0x72;
173        len += cc_data.len();
174        if let Some(service) = self.service_info.as_ref() {
175            len += service.byte_len();
176        }
177        len += 4; // footer
178
179        assert!(len <= u8::MAX as usize);
180
181        let mut flags = Flags::CC_DATA_PRESENT | Flags::CAPTION_SERVICE_ACTIVE | 0x1;
182        if self.time_code.is_some() {
183            flags |= Flags::TIME_CODE_PRESENT;
184        }
185        if let Some(svc) = self.service_info.as_ref() {
186            flags |= Flags::SVC_INFO_PRESENT;
187            if svc.is_start() {
188                flags |= Flags::SVC_INFO_START;
189            }
190            if svc.is_change() {
191                flags |= Flags::SVC_INFO_CHANGE;
192            }
193            if svc.is_complete() {
194                flags |= Flags::SVC_INFO_COMPLETE;
195            }
196        }
197
198        let mut checksum: u8 = 0;
199        let data = [
200            0x96,
201            0x69,
202            (len & 0xff) as u8,
203            framerate.id << 4 | 0x0f,
204            flags,
205            ((self.sequence_count & 0xff00) >> 8) as u8,
206            (self.sequence_count & 0xff) as u8,
207        ];
208        for v in data.iter() {
209            checksum = checksum.wrapping_add(*v);
210        }
211        w.write_all(&data)?;
212
213        if let Some(time_code) = self.time_code {
214            let data = [
215                0x71,
216                0xc0 | ((time_code.hours / 10) << 4) | (time_code.hours % 10),
217                0x80 | ((time_code.minutes / 10) << 4) | (time_code.minutes % 10),
218                if time_code.field { 0x80 } else { 0x00 }
219                    | ((time_code.seconds / 10) << 4)
220                    | (time_code.seconds % 10),
221                if time_code.drop_frame { 0x80 } else { 0x0 }
222                    | ((time_code.frames / 10) << 4)
223                    | (time_code.frames % 10),
224            ];
225            for v in data.iter() {
226                checksum = checksum.wrapping_add(*v);
227            }
228            w.write_all(&data)?;
229        }
230
231        for v in cc_data.iter() {
232            checksum = checksum.wrapping_add(*v);
233        }
234        w.write_all(&cc_data)?;
235
236        let mut svc_data = vec![];
237        if let Some(service) = self.service_info.as_mut() {
238            service.write(&mut svc_data)?;
239        }
240
241        for v in svc_data.iter() {
242            checksum = checksum.wrapping_add(*v);
243        }
244        w.write_all(&svc_data)?;
245
246        let data = [
247            0x74,
248            ((self.sequence_count & 0xff00) >> 8) as u8,
249            (self.sequence_count & 0xff) as u8,
250        ];
251        for v in data.iter() {
252            checksum = checksum.wrapping_add(*v);
253        }
254        w.write_all(&data)?;
255        // 256 - checksum without having to use a type larger than u8
256        let checksum_byte = (!checksum).wrapping_add(1);
257        debug_assert!(checksum_byte == ((256 - checksum as u16) as u8));
258        w.write_all(&[checksum_byte])?;
259
260        Ok(())
261    }
262}
263
264#[cfg(test)]
265mod test {
266    use super::*;
267    use crate::tests::*;
268    use crate::*;
269    use cea708_types::{tables, DTVCCPacket, Service};
270
271    static WRITE_CDP: [TestCCData; 2] = [
272        // simple packet with a single service and single code
273        TestCCData {
274            framerate: FRAMERATES[2],
275            cdp_data: &[CDPPacketData {
276                data: &[
277                    0x96,
278                    0x69,                      // magic
279                    0x5A,                      // cdp_len
280                    0x3f,                      //framerate
281                    0x80 | 0x40 | 0x02 | 0x01, // flags
282                    0x12,
283                    0x34,        // sequence counter
284                    0x71,        // time code id
285                    0xc0 | 0x17, // hours
286                    0x80 | 0x59, // minutes
287                    0x80 | 0x57, // seconds
288                    0x80 | 0x18, // frames
289                    0x72,        // cc_data id
290                    0xe0 | 0x18,
291                    0xF8,
292                    0x80,
293                    0x80,
294                    0xF9,
295                    0x80,
296                    0x80,
297                    0xFF,
298                    0x02,
299                    0x21,
300                    0xFE,
301                    0x41,
302                    0x00,
303                    0xFA,
304                    0x00,
305                    0x00,
306                    0xFA,
307                    0x00,
308                    0x00,
309                    0xFA,
310                    0x00,
311                    0x00,
312                    0xFA,
313                    0x00,
314                    0x00,
315                    0xFA,
316                    0x00,
317                    0x00,
318                    0xFA,
319                    0x00,
320                    0x00,
321                    0xFA,
322                    0x00,
323                    0x00,
324                    0xFA,
325                    0x00,
326                    0x00,
327                    0xFA,
328                    0x00,
329                    0x00,
330                    0xFA,
331                    0x00,
332                    0x00,
333                    0xFA,
334                    0x00,
335                    0x00,
336                    0xFA,
337                    0x00,
338                    0x00,
339                    0xFA,
340                    0x00,
341                    0x00,
342                    0xFA,
343                    0x00,
344                    0x00,
345                    0xFA,
346                    0x00,
347                    0x00,
348                    0xFA,
349                    0x00,
350                    0x00,
351                    0xFA,
352                    0x00,
353                    0x00,
354                    0xFA,
355                    0x00,
356                    0x00,
357                    0xFA,
358                    0x00,
359                    0x00,
360                    0xFA,
361                    0x00,
362                    0x00,
363                    0x74, // footer
364                    0x12,
365                    0x34,
366                    0xD1, //checksum
367                ],
368                sequence_count: 0x1234,
369                time_code: Some(TimeCode {
370                    hours: 17,
371                    minutes: 59,
372                    seconds: 57,
373                    frames: 18,
374                    field: true,
375                    drop_frame: true,
376                }),
377                packets: &[CCPacketData {
378                    sequence_no: 0,
379                    services: &[ServiceData {
380                        service_no: 1,
381                        codes: &[tables::Code::LatinCapitalA],
382                    }],
383                }],
384                cea608: &[],
385            }],
386        },
387        TestCCData {
388            framerate: FRAMERATES[2],
389            cdp_data: &[CDPPacketData {
390                data: &[
391                    0x96, // magic
392                    0x69,
393                    0x55,               // cdp_len
394                    0x3f,               // framerate
395                    0x40 | 0x02 | 0x01, // flags
396                    0x34,               // sequence counter
397                    0x12,
398                    0x72,        // cc_data id
399                    0xe0 | 0x18, // cc_count
400                    0xF8,
401                    0x80,
402                    0x80,
403                    0xF9,
404                    0x80,
405                    0x80,
406                    0xFF,
407                    0x02,
408                    0x21,
409                    0xFE,
410                    0x41,
411                    0x00,
412                    0xFA,
413                    0x00,
414                    0x00,
415                    0xFA,
416                    0x00,
417                    0x00,
418                    0xFA,
419                    0x00,
420                    0x00,
421                    0xFA,
422                    0x00,
423                    0x00,
424                    0xFA,
425                    0x00,
426                    0x00,
427                    0xFA,
428                    0x00,
429                    0x00,
430                    0xFA,
431                    0x00,
432                    0x00,
433                    0xFA,
434                    0x00,
435                    0x00,
436                    0xFA,
437                    0x00,
438                    0x00,
439                    0xFA,
440                    0x00,
441                    0x00,
442                    0xFA,
443                    0x00,
444                    0x00,
445                    0xFA,
446                    0x00,
447                    0x00,
448                    0xFA,
449                    0x00,
450                    0x00,
451                    0xFA,
452                    0x00,
453                    0x00,
454                    0xFA,
455                    0x00,
456                    0x00,
457                    0xFA,
458                    0x00,
459                    0x00,
460                    0xFA,
461                    0x00,
462                    0x00,
463                    0xFA,
464                    0x00,
465                    0x00,
466                    0xFA,
467                    0x00,
468                    0x00,
469                    0xFA,
470                    0x00,
471                    0x00,
472                    0x74, // cdp footer
473                    0x34,
474                    0x12,
475                    0xE6, // checksum
476                ],
477                sequence_count: 0x3412,
478                time_code: None,
479                packets: &[CCPacketData {
480                    sequence_no: 0,
481                    services: &[ServiceData {
482                        service_no: 1,
483                        codes: &[tables::Code::LatinCapitalA],
484                    }],
485                }],
486                cea608: &[],
487            }],
488        },
489    ];
490
491    #[test]
492    fn packet_write_cc_data() {
493        test_init_log();
494        for test_data in WRITE_CDP.iter() {
495            info!("writing {test_data:?}");
496            let mut writer = CDPWriter::new();
497            for cdp_data in test_data.cdp_data.iter() {
498                let mut packet_iter = cdp_data.packets.iter();
499                if let Some(packet_data) = packet_iter.next() {
500                    let mut pack = DTVCCPacket::new(packet_data.sequence_no);
501                    for service_data in packet_data.services.iter() {
502                        let mut service = Service::new(service_data.service_no);
503                        for code in service_data.codes.iter() {
504                            service.push_code(code).unwrap();
505                        }
506                        pack.push_service(service).unwrap();
507                    }
508                    writer.push_packet(pack);
509                }
510                for pair in cdp_data.cea608 {
511                    writer.push_cea608(*pair);
512                }
513                writer.set_time_code(cdp_data.time_code);
514                writer.set_sequence_count(cdp_data.sequence_count);
515                let mut written = vec![];
516                writer.write(test_data.framerate, &mut written).unwrap();
517                assert_eq!(cdp_data.data, &written);
518            }
519        }
520    }
521}