1use crate::{FrameRate, Timecode};
23
24#[derive(Debug, Clone, PartialEq)]
29pub struct Smpte309mPacket {
30 pub did: u8,
32 pub sdid: u8,
34 pub payload: [u16; 16],
36}
37
38fn with_parity(data: u8) -> u16 {
50 let ones = data.count_ones();
52 if ones % 2 == 0 {
55 (1u16 << 9) | u16::from(data)
57 } else {
58 u16::from(data)
60 }
61}
62
63fn parity_ok(word: u16) -> bool {
65 (word & 0x3ff).count_ones() % 2 == 1
66}
67
68pub fn encode_anc_timecode(tc: &Timecode, binary_groups: [u8; 4]) -> Smpte309mPacket {
76 let frames_units = tc.frames % 10;
77 let frames_tens = tc.frames / 10;
78 let seconds_units = tc.seconds % 10;
79 let seconds_tens = tc.seconds / 10;
80 let minutes_units = tc.minutes % 10;
81 let minutes_tens = tc.minutes / 10;
82 let hours_units = tc.hours % 10;
83 let hours_tens = tc.hours / 10;
84
85 let word0_data: u8 = frames_units | (frames_tens << 4);
91 let drop_flag: u8 = if tc.frame_rate.drop_frame { 0x80 } else { 0x00 };
95 let word0_data = word0_data | drop_flag;
96
97 let word1_data: u8 = seconds_units | (seconds_tens << 4);
98 let word2_data: u8 = minutes_units | (minutes_tens << 4);
99 let word3_data: u8 = hours_units | (hours_tens << 4);
101
102 let mut payload = [0u16; 16];
104 payload[0] = with_parity(word0_data);
105 payload[1] = with_parity(word1_data);
106 payload[2] = with_parity(word2_data);
107 payload[3] = with_parity(word3_data);
108
109 for (i, &bg) in binary_groups.iter().enumerate() {
111 payload[4 + i] = with_parity(bg);
112 }
113 for i in 8..16usize {
115 payload[i] = with_parity(0x00);
116 }
117
118 Smpte309mPacket {
119 did: 0x60,
120 sdid: 0x60,
121 payload,
122 }
123}
124
125pub fn decode_anc_timecode(pkt: &Smpte309mPacket) -> Option<(Timecode, [u8; 4])> {
131 if pkt.did != 0x60 || pkt.sdid != 0x60 {
132 return None;
133 }
134
135 for &word in &pkt.payload {
137 if !parity_ok(word) {
138 return None;
139 }
140 }
141
142 let w0 = (pkt.payload[0] & 0xff) as u8;
144 let w1 = (pkt.payload[1] & 0xff) as u8;
145 let w2 = (pkt.payload[2] & 0xff) as u8;
146 let w3 = (pkt.payload[3] & 0xff) as u8;
147
148 let drop_frame = (w0 & 0x80) != 0;
149
150 let frames_units = w0 & 0x0f;
151 let frames_tens = (w0 & 0x70) >> 4; let frames = frames_tens * 10 + frames_units;
153
154 let seconds_units = w1 & 0x0f;
155 let seconds_tens = (w1 & 0x70) >> 4;
156 let seconds = seconds_tens * 10 + seconds_units;
157
158 let minutes_units = w2 & 0x0f;
159 let minutes_tens = (w2 & 0x70) >> 4;
160 let minutes = minutes_tens * 10 + minutes_units;
161
162 let hours_units = w3 & 0x0f;
163 let hours_tens = (w3 & 0x70) >> 4;
164 let hours = hours_tens * 10 + hours_units;
165
166 let mut binary_groups = [0u8; 4];
168 for (i, slot) in binary_groups.iter_mut().enumerate() {
169 *slot = (pkt.payload[4 + i] & 0xff) as u8;
170 }
171
172 let frame_rate = if drop_frame {
175 FrameRate::Fps2997DF
176 } else {
177 FrameRate::Fps30
178 };
179
180 let tc = Timecode::from_raw_fields(hours, minutes, seconds, frames, 30, drop_frame, 0);
181
182 let _ = Timecode::new(hours, minutes, seconds, frames, frame_rate).ok()?;
185
186 Some((tc, binary_groups))
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::FrameRate;
193
194 fn make_tc(h: u8, m: u8, s: u8, f: u8, df: bool) -> Timecode {
195 let rate = if df {
196 FrameRate::Fps2997DF
197 } else {
198 FrameRate::Fps30
199 };
200 Timecode::new(h, m, s, f, rate).expect("valid timecode")
201 }
202
203 #[test]
204 fn test_smpte309m_round_trip_zero() {
205 let tc = make_tc(0, 0, 0, 0, false);
206 let pkt = encode_anc_timecode(&tc, [0u8; 4]);
207 let (decoded, _bg) = decode_anc_timecode(&pkt).expect("decode must succeed");
208 assert_eq!(decoded.hours, tc.hours);
209 assert_eq!(decoded.minutes, tc.minutes);
210 assert_eq!(decoded.seconds, tc.seconds);
211 assert_eq!(decoded.frames, tc.frames);
212 }
213
214 #[test]
215 fn test_smpte309m_round_trip_max() {
216 let tc = make_tc(23, 59, 59, 29, false);
218 let pkt = encode_anc_timecode(&tc, [0u8; 4]);
219 let (decoded, _bg) = decode_anc_timecode(&pkt).expect("decode must succeed");
220 assert_eq!(decoded.hours, 23);
221 assert_eq!(decoded.minutes, 59);
222 assert_eq!(decoded.seconds, 59);
223 assert_eq!(decoded.frames, 29);
224 }
225
226 #[test]
227 fn test_smpte309m_parity_correct() {
228 let tc = make_tc(1, 23, 45, 12, false);
229 let pkt = encode_anc_timecode(&tc, [0xAB, 0xCD, 0xEF, 0x01]);
230 for (i, &word) in pkt.payload.iter().enumerate() {
232 assert!(
233 parity_ok(word),
234 "word {i} failed parity check: 0x{word:03x}"
235 );
236 }
237 }
238
239 #[test]
240 fn test_smpte309m_binary_groups_passthrough() {
241 let tc = make_tc(0, 0, 0, 0, false);
242 let bg_in = [0x12u8, 0x34, 0x56, 0x78];
243 let pkt = encode_anc_timecode(&tc, bg_in);
244 let (_decoded, bg_out) = decode_anc_timecode(&pkt).expect("decode must succeed");
245 assert_eq!(bg_out, bg_in);
246 }
247
248 #[test]
249 fn test_smpte309m_drop_frame_flag() {
250 let tc = make_tc(0, 10, 0, 2, true);
252 let pkt = encode_anc_timecode(&tc, [0u8; 4]);
253 let w0_data = (pkt.payload[0] & 0xff) as u8;
255 assert!(
256 (w0_data & 0x80) != 0,
257 "drop-frame flag not set in word 0: 0x{w0_data:02x}"
258 );
259 let (decoded, _) = decode_anc_timecode(&pkt).expect("decode must succeed");
261 assert!(decoded.frame_rate.drop_frame);
262 }
263
264 #[test]
265 fn test_smpte309m_did_sdid_mismatch_returns_none() {
266 let tc = make_tc(0, 0, 0, 0, false);
267 let mut pkt = encode_anc_timecode(&tc, [0u8; 4]);
268 pkt.did = 0x61; assert!(decode_anc_timecode(&pkt).is_none());
270 }
271
272 #[test]
273 fn test_smpte309m_parity_error_returns_none() {
274 let tc = make_tc(0, 0, 0, 0, false);
275 let mut pkt = encode_anc_timecode(&tc, [0u8; 4]);
276 pkt.payload[0] ^= 0x100;
278 assert!(decode_anc_timecode(&pkt).is_none());
279 }
280}