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}