1#![allow(dead_code)]
18#![allow(clippy::cast_possible_truncation)]
19
20use crate::{FrameRate, Timecode, TimecodeError};
21
22pub const ATC_DID: u16 = 0x60;
24
25pub const ATC_SDID_LTC: u16 = 0x60;
27
28pub const ATC_SDID_VITC: u16 = 0x61;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum AtcType {
34 Ltc,
36 Vitc,
38}
39
40#[derive(Debug, Clone)]
45pub struct AtcPacket {
46 pub atc_type: AtcType,
48 pub timecode: Timecode,
50 pub user_bits: u32,
52 pub continuity_count_valid: bool,
54 pub continuity_count: u8,
56 pub valid: bool,
58}
59
60impl AtcPacket {
61 #[must_use]
63 pub fn new(atc_type: AtcType, timecode: Timecode) -> Self {
64 Self {
65 atc_type,
66 timecode,
67 user_bits: 0,
68 continuity_count_valid: false,
69 continuity_count: 0,
70 valid: true,
71 }
72 }
73
74 #[must_use]
76 pub fn ltc(timecode: Timecode) -> Self {
77 Self::new(AtcType::Ltc, timecode)
78 }
79
80 #[must_use]
82 pub fn vitc(timecode: Timecode) -> Self {
83 Self::new(AtcType::Vitc, timecode)
84 }
85
86 #[must_use]
88 pub fn with_user_bits(mut self, user_bits: u32) -> Self {
89 self.user_bits = user_bits;
90 self
91 }
92
93 #[must_use]
95 pub fn with_continuity(mut self, count: u8) -> Self {
96 self.continuity_count = count & 0x7F;
97 self.continuity_count_valid = true;
98 self
99 }
100
101 #[must_use]
106 pub fn to_sdi_words(&self) -> Vec<u16> {
107 let mut words = Vec::with_capacity(16);
108
109 words.push(0x000);
111 words.push(0x3FF);
112 words.push(0x3FF);
113
114 let did = add_parity_9bit(ATC_DID as u8);
116 words.push(did);
117
118 let sdid = match self.atc_type {
120 AtcType::Ltc => add_parity_9bit(ATC_SDID_LTC as u8),
121 AtcType::Vitc => add_parity_9bit(ATC_SDID_VITC as u8),
122 };
123 words.push(sdid);
124
125 let dc = add_parity_9bit(9);
127 words.push(dc);
128
129 let tc_bytes = encode_timecode_bytes(&self.timecode, self.user_bits);
131 for &byte in &tc_bytes {
132 words.push(add_parity_9bit(byte));
133 }
134
135 let cont = if self.continuity_count_valid {
137 0x80 | (self.continuity_count & 0x7F)
138 } else {
139 0x00
140 };
141 words.push(add_parity_9bit(cont));
142
143 let checksum = compute_checksum(&words[3..]);
145 words.push(checksum);
146
147 words
148 }
149
150 pub fn from_sdi_words(words: &[u16]) -> Result<Self, TimecodeError> {
157 if words.len() < 16 {
159 return Err(TimecodeError::InvalidConfiguration);
160 }
161
162 if words[0] != 0x000 || words[1] != 0x3FF || words[2] != 0x3FF {
164 return Err(TimecodeError::InvalidConfiguration);
165 }
166
167 let did = (words[3] & 0xFF) as u8;
168 if did != ATC_DID as u8 {
169 return Err(TimecodeError::InvalidConfiguration);
170 }
171
172 let sdid = (words[4] & 0xFF) as u8;
173 let atc_type = match sdid {
174 s if s == ATC_SDID_LTC as u8 => AtcType::Ltc,
175 s if s == ATC_SDID_VITC as u8 => AtcType::Vitc,
176 _ => return Err(TimecodeError::InvalidConfiguration),
177 };
178
179 let dc = (words[5] & 0xFF) as usize;
180 if dc < 9 || words.len() < 6 + dc + 1 {
181 return Err(TimecodeError::InvalidConfiguration);
182 }
183
184 let mut tc_bytes = [0u8; 8];
186 for (i, b) in tc_bytes.iter_mut().enumerate() {
187 *b = (words[6 + i] & 0xFF) as u8;
188 }
189 let cont_byte = (words[14] & 0xFF) as u8;
190
191 let (timecode, user_bits) = decode_timecode_bytes(&tc_bytes)?;
192
193 let continuity_count_valid = (cont_byte & 0x80) != 0;
194 let continuity_count = cont_byte & 0x7F;
195
196 Ok(Self {
197 atc_type,
198 timecode,
199 user_bits,
200 continuity_count_valid,
201 continuity_count,
202 valid: true,
203 })
204 }
205}
206
207fn encode_timecode_bytes(tc: &Timecode, user_bits: u32) -> [u8; 8] {
209 let drop_flag: u8 = if tc.frame_rate.drop_frame { 0x40 } else { 0x00 };
210 let frame_units = tc.frames % 10;
211 let frame_tens = tc.frames / 10;
212
213 let sec_units = tc.seconds % 10;
214 let sec_tens = tc.seconds / 10;
215
216 let min_units = tc.minutes % 10;
217 let min_tens = tc.minutes / 10;
218
219 let hour_units = tc.hours % 10;
220 let hour_tens = tc.hours / 10;
221
222 let ub = [
224 ((user_bits >> 28) & 0xF) as u8,
225 ((user_bits >> 24) & 0xF) as u8,
226 ((user_bits >> 20) & 0xF) as u8,
227 ((user_bits >> 16) & 0xF) as u8,
228 ((user_bits >> 12) & 0xF) as u8,
229 ((user_bits >> 8) & 0xF) as u8,
230 ((user_bits >> 4) & 0xF) as u8,
231 (user_bits & 0xF) as u8,
232 ];
233
234 [
235 (frame_units & 0x0F) | (ub[0] << 4),
236 (frame_tens & 0x03) | drop_flag | (ub[1] << 4),
237 (sec_units & 0x0F) | (ub[2] << 4),
238 (sec_tens & 0x07) | (ub[3] << 4),
239 (min_units & 0x0F) | (ub[4] << 4),
240 (min_tens & 0x07) | (ub[5] << 4),
241 (hour_units & 0x0F) | (ub[6] << 4),
242 (hour_tens & 0x03) | (ub[7] << 4),
243 ]
244}
245
246fn decode_timecode_bytes(bytes: &[u8; 8]) -> Result<(Timecode, u32), TimecodeError> {
248 let frame_units = bytes[0] & 0x0F;
249 let frame_tens = bytes[1] & 0x03;
250 let drop_frame = (bytes[1] & 0x40) != 0;
251 let sec_units = bytes[2] & 0x0F;
252 let sec_tens = bytes[3] & 0x07;
253 let min_units = bytes[4] & 0x0F;
254 let min_tens = bytes[5] & 0x07;
255 let hour_units = bytes[6] & 0x0F;
256 let hour_tens = bytes[7] & 0x03;
257
258 let hours = hour_tens * 10 + hour_units;
259 let minutes = min_tens * 10 + min_units;
260 let seconds = sec_tens * 10 + sec_units;
261 let frames = frame_tens * 10 + frame_units;
262
263 let frame_rate = if drop_frame {
264 FrameRate::Fps2997DF
265 } else {
266 FrameRate::Fps25
267 };
268
269 let ub: [u8; 8] = [
271 (bytes[0] >> 4) & 0x0F,
272 (bytes[1] >> 4) & 0x0F,
273 (bytes[2] >> 4) & 0x0F,
274 (bytes[3] >> 4) & 0x0F,
275 (bytes[4] >> 4) & 0x0F,
276 (bytes[5] >> 4) & 0x0F,
277 (bytes[6] >> 4) & 0x0F,
278 (bytes[7] >> 4) & 0x0F,
279 ];
280
281 let decoded_user_bits = ((ub[0] as u32) << 28)
282 | ((ub[1] as u32) << 24)
283 | ((ub[2] as u32) << 20)
284 | ((ub[3] as u32) << 16)
285 | ((ub[4] as u32) << 12)
286 | ((ub[5] as u32) << 8)
287 | ((ub[6] as u32) << 4)
288 | (ub[7] as u32);
289
290 let timecode = Timecode::new(hours, minutes, seconds, frames, frame_rate)?;
291
292 Ok((timecode, decoded_user_bits))
293}
294
295#[must_use]
299fn add_parity_9bit(byte: u8) -> u16 {
300 let value = byte as u16;
301 let ones = value.count_ones();
302 let parity_bit: u16 = if ones % 2 == 0 { 0x100 } else { 0x000 };
303 let not_bit: u16 = if parity_bit != 0 { 0x000 } else { 0x200 };
304 value | parity_bit | not_bit
305}
306
307#[must_use]
309fn compute_checksum(words: &[u16]) -> u16 {
310 let sum: u32 = words.iter().map(|&w| (w & 0x1FF) as u32).sum();
311 let checksum = (sum & 0x1FF) as u16;
312 let b8 = (checksum >> 8) & 1;
314 checksum | ((1 - b8) << 9)
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::FrameRate;
321
322 fn make_tc() -> Timecode {
323 Timecode::new(1, 30, 0, 12, FrameRate::Fps25).expect("valid timecode")
324 }
325
326 #[test]
327 fn test_atc_packet_creation() {
328 let tc = make_tc();
329 let pkt = AtcPacket::ltc(tc);
330 assert_eq!(pkt.atc_type, AtcType::Ltc);
331 assert!(pkt.valid);
332 }
333
334 #[test]
335 fn test_atc_to_sdi_words_length() {
336 let tc = make_tc();
337 let pkt = AtcPacket::ltc(tc);
338 let words = pkt.to_sdi_words();
339 assert_eq!(words.len(), 16);
341 }
342
343 #[test]
344 fn test_atc_adf_header() {
345 let tc = make_tc();
346 let pkt = AtcPacket::vitc(tc);
347 let words = pkt.to_sdi_words();
348 assert_eq!(words[0], 0x000);
349 assert_eq!(words[1], 0x3FF);
350 assert_eq!(words[2], 0x3FF);
351 }
352
353 #[test]
354 fn test_parity_9bit_even_input() {
355 let w = add_parity_9bit(0x00);
357 assert_eq!(w & 0x100, 0x100); }
359
360 #[test]
361 fn test_encode_decode_timecode_bytes() {
362 let tc = make_tc();
363 let bytes = encode_timecode_bytes(&tc, 0xDEAD_BEEF);
364 let (decoded_tc, decoded_ub) = decode_timecode_bytes(&bytes).expect("decode ok");
365 assert_eq!(decoded_tc.hours, tc.hours);
366 assert_eq!(decoded_tc.minutes, tc.minutes);
367 assert_eq!(decoded_tc.seconds, tc.seconds);
368 assert_eq!(decoded_tc.frames, tc.frames);
369 assert_eq!(decoded_ub, 0xDEAD_BEEF);
370 }
371
372 #[test]
373 fn test_atc_round_trip_ltc() {
374 let tc = make_tc();
375 let pkt = AtcPacket::ltc(tc).with_user_bits(0x1234_5678);
376 let words = pkt.to_sdi_words();
377 let decoded = AtcPacket::from_sdi_words(&words).expect("decode ok");
378 assert_eq!(decoded.atc_type, AtcType::Ltc);
379 assert_eq!(decoded.timecode.hours, 1);
380 assert_eq!(decoded.timecode.minutes, 30);
381 assert_eq!(decoded.timecode.seconds, 0);
382 assert_eq!(decoded.timecode.frames, 12);
383 assert_eq!(decoded.user_bits, 0x1234_5678);
384 }
385
386 #[test]
387 fn test_atc_round_trip_vitc() {
388 let tc = Timecode::new(23, 59, 59, 24, FrameRate::Fps25).expect("valid tc");
389 let pkt = AtcPacket::vitc(tc);
390 let words = pkt.to_sdi_words();
391 let decoded = AtcPacket::from_sdi_words(&words).expect("decode ok");
392 assert_eq!(decoded.atc_type, AtcType::Vitc);
393 assert_eq!(decoded.timecode.hours, 23);
394 assert_eq!(decoded.timecode.seconds, 59);
395 }
396
397 #[test]
398 fn test_atc_continuity_counter() {
399 let tc = make_tc();
400 let pkt = AtcPacket::ltc(tc).with_continuity(42);
401 assert!(pkt.continuity_count_valid);
402 assert_eq!(pkt.continuity_count, 42);
403 let words = pkt.to_sdi_words();
404 let decoded = AtcPacket::from_sdi_words(&words).expect("decode ok");
405 assert!(decoded.continuity_count_valid);
406 assert_eq!(decoded.continuity_count, 42);
407 }
408
409 #[test]
410 fn test_atc_from_sdi_words_too_short() {
411 let words = vec![0u16; 5];
412 assert!(AtcPacket::from_sdi_words(&words).is_err());
413 }
414
415 #[test]
416 fn test_atc_from_sdi_words_bad_adf() {
417 let mut words = vec![0u16; 16];
418 words[0] = 0x123; words[1] = 0x3FF;
420 words[2] = 0x3FF;
421 assert!(AtcPacket::from_sdi_words(&words).is_err());
422 }
423
424 #[test]
425 fn test_atc_zero_user_bits() {
426 let tc = make_tc();
427 let pkt = AtcPacket::ltc(tc).with_user_bits(0);
428 let words = pkt.to_sdi_words();
429 let decoded = AtcPacket::from_sdi_words(&words).expect("decode ok");
430 assert_eq!(decoded.user_bits, 0);
431 }
432
433 #[test]
434 fn test_atc_max_user_bits() {
435 let tc = make_tc();
436 let bytes = encode_timecode_bytes(&tc, 0x0FFF_FFFF);
437 let (_, decoded_ub) = decode_timecode_bytes(&bytes).expect("decode ok");
438 assert_eq!(decoded_ub, 0x0FFF_FFFF);
442 }
443}