Skip to main content

oximedia_timecode/vitc/
decoder.rs

1//! VITC Decoder - Extract timecode from video scan lines
2//!
3//! This module implements a complete VITC decoder that:
4//! - Analyzes video scan lines for VITC patterns
5//! - Detects sync bits and data bits
6//! - Validates CRC checksums
7//! - Extracts timecode and user bits
8//! - Handles field identification
9
10use super::constants::*;
11use super::{FieldPreference, VitcReaderConfig};
12use crate::{FrameRate, Timecode, TimecodeError};
13
14/// VITC decoder
15pub struct VitcDecoder {
16    /// Configuration
17    config: VitcReaderConfig,
18    /// Last decoded timecode
19    last_timecode: Option<Timecode>,
20    /// CRC error counter
21    crc_error_count: u32,
22    /// Sync counter
23    sync_count: u32,
24    /// Bit slicer threshold
25    threshold: u8,
26}
27
28impl VitcDecoder {
29    /// Create a new VITC decoder
30    pub fn new(config: VitcReaderConfig) -> Self {
31        VitcDecoder {
32            config,
33            last_timecode: None,
34            crc_error_count: 0,
35            sync_count: 0,
36            threshold: 128,
37        }
38    }
39
40    /// Process a video line and decode VITC
41    pub fn process_line(
42        &mut self,
43        line_number: u16,
44        field: u8,
45        pixels: &[u8],
46    ) -> Result<Option<Timecode>, TimecodeError> {
47        // Check if this line should contain VITC
48        if !self.config.scan_lines.contains(&line_number) {
49            return Ok(None);
50        }
51
52        // Check field preference
53        match self.config.field_preference {
54            FieldPreference::Field1 if field != 1 => return Ok(None),
55            FieldPreference::Field2 if field != 2 => return Ok(None),
56            _ => {}
57        }
58
59        // Decode bits from pixels
60        let bits = self.pixels_to_bits(pixels)?;
61
62        // Validate and decode timecode
63        match self.decode_vitc_bits(&bits, field) {
64            Ok(timecode) => {
65                self.sync_count = self.sync_count.saturating_add(1);
66                self.last_timecode = Some(timecode);
67                Ok(Some(timecode))
68            }
69            Err(e) => {
70                if e == TimecodeError::CrcError {
71                    self.crc_error_count += 1;
72                }
73                Err(e)
74            }
75        }
76    }
77
78    /// Convert pixels to bit array
79    fn pixels_to_bits(&mut self, pixels: &[u8]) -> Result<[bool; BITS_PER_LINE], TimecodeError> {
80        if pixels.len() < BITS_PER_LINE * PIXELS_PER_BIT {
81            return Err(TimecodeError::BufferTooSmall);
82        }
83
84        // Auto-adjust threshold based on signal levels
85        self.adjust_threshold(pixels);
86
87        let mut bits = [false; BITS_PER_LINE];
88
89        for (i, bit) in bits.iter_mut().enumerate().take(BITS_PER_LINE) {
90            // Sample the middle of each bit period
91            let pixel_index = i * PIXELS_PER_BIT + PIXELS_PER_BIT / 2;
92            if pixel_index < pixels.len() {
93                *bit = pixels[pixel_index] > self.threshold;
94            }
95        }
96
97        Ok(bits)
98    }
99
100    /// Adjust threshold based on signal statistics
101    fn adjust_threshold(&mut self, pixels: &[u8]) {
102        let mut min_level = 255u8;
103        let mut max_level = 0u8;
104
105        for &pixel in pixels.iter().take(BITS_PER_LINE * PIXELS_PER_BIT) {
106            min_level = min_level.min(pixel);
107            max_level = max_level.max(pixel);
108        }
109
110        // Set threshold to midpoint
111        self.threshold = ((min_level as u16 + max_level as u16) / 2) as u8;
112    }
113
114    /// Decode VITC from bit array
115    fn decode_vitc_bits(
116        &self,
117        bits: &[bool; BITS_PER_LINE],
118        field: u8,
119    ) -> Result<Timecode, TimecodeError> {
120        // Validate sync bits
121        self.validate_sync_bits(bits)?;
122
123        // Extract data bits (skip sync bits)
124        let mut data_bits = [false; DATA_BITS];
125        data_bits[..DATA_BITS]
126            .copy_from_slice(&bits[SYNC_START_BITS..(DATA_BITS + SYNC_START_BITS)]);
127
128        // Validate CRC
129        self.validate_crc(&data_bits)?;
130
131        // Decode timecode from data bits
132        self.decode_timecode_from_bits(&data_bits, field)
133    }
134
135    /// Validate sync bits
136    fn validate_sync_bits(&self, bits: &[bool; BITS_PER_LINE]) -> Result<(), TimecodeError> {
137        // Start sync: bits 0-1 should be 11 (white-white)
138        if !bits[0] || !bits[1] {
139            return Err(TimecodeError::SyncNotFound);
140        }
141
142        // End sync: bits 84-89 should be 001111 (black-black-white-white-white-white)
143        if bits[84] || bits[85] || !bits[86] || !bits[87] || !bits[88] || !bits[89] {
144            return Err(TimecodeError::SyncNotFound);
145        }
146
147        Ok(())
148    }
149
150    /// Validate CRC
151    fn validate_crc(&self, data_bits: &[bool; DATA_BITS]) -> Result<(), TimecodeError> {
152        // Extract CRC from bits 74-81
153        let received_crc = self.extract_crc(data_bits);
154
155        // Calculate CRC over bits 2-73
156        let calculated_crc = self.calculate_crc(&data_bits[0..72]);
157
158        if received_crc != calculated_crc {
159            return Err(TimecodeError::CrcError);
160        }
161
162        Ok(())
163    }
164
165    /// Extract CRC from data bits
166    fn extract_crc(&self, data_bits: &[bool; DATA_BITS]) -> u8 {
167        let mut crc = 0u8;
168        for i in 0..8 {
169            if data_bits[74 + i] {
170                crc |= 1 << i;
171            }
172        }
173        crc
174    }
175
176    /// Calculate CRC (8-bit CRC with polynomial x^8 + x^2 + x^1 + x^0)
177    fn calculate_crc(&self, bits: &[bool]) -> u8 {
178        let mut crc = 0u8;
179
180        for &bit in bits {
181            let feedback = ((crc & 0x80) != 0) ^ bit;
182            crc <<= 1;
183            if feedback {
184                crc ^= 0x07; // Polynomial: x^8 + x^2 + x^1 + x^0
185            }
186        }
187
188        crc
189    }
190
191    /// Decode timecode from data bits
192    fn decode_timecode_from_bits(
193        &self,
194        data_bits: &[bool; DATA_BITS],
195        field: u8,
196    ) -> Result<Timecode, TimecodeError> {
197        // VITC bit layout (SMPTE 12M):
198        // Similar to LTC but with some differences for field identification
199
200        // Bits 0-3: Frame units
201        let frame_units = self.bits_to_u8(&data_bits[0..4]);
202
203        // Bits 4-7: User bits 1
204        let _user_bits_1 = self.bits_to_u8(&data_bits[4..8]);
205
206        // Bits 8-9: Frame tens
207        let frame_tens = self.bits_to_u8(&data_bits[8..10]);
208
209        // Bit 10: Drop frame flag
210        let drop_frame = data_bits[10];
211
212        // Bit 11: Color frame flag
213        let _color_frame = data_bits[11];
214
215        // Bits 12-15: User bits 2
216        let _user_bits_2 = self.bits_to_u8(&data_bits[12..16]);
217
218        // Bits 16-19: Second units
219        let second_units = self.bits_to_u8(&data_bits[16..20]);
220
221        // Bits 20-23: User bits 3
222        let _user_bits_3 = self.bits_to_u8(&data_bits[20..24]);
223
224        // Bits 24-26: Second tens
225        let second_tens = self.bits_to_u8(&data_bits[24..27]);
226
227        // Bit 27: Field mark (1 = field 2, 0 = field 1)
228        let field_mark = data_bits[27];
229
230        // Validate field mark
231        if (field_mark && field != 2) || (!field_mark && field != 1) {
232            // Field mismatch - not necessarily an error, but worth noting
233        }
234
235        // Bits 28-31: User bits 4
236        let _user_bits_4 = self.bits_to_u8(&data_bits[28..32]);
237
238        // Bits 32-35: Minute units
239        let minute_units = self.bits_to_u8(&data_bits[32..36]);
240
241        // Bits 36-39: User bits 5
242        let _user_bits_5 = self.bits_to_u8(&data_bits[36..40]);
243
244        // Bits 40-42: Minute tens
245        let minute_tens = self.bits_to_u8(&data_bits[40..43]);
246
247        // Bit 43: Binary group flag
248        let _binary_group = data_bits[43];
249
250        // Bits 44-47: User bits 6
251        let _user_bits_6 = self.bits_to_u8(&data_bits[44..48]);
252
253        // Bits 48-51: Hour units
254        let hour_units = self.bits_to_u8(&data_bits[48..52]);
255
256        // Bits 52-55: User bits 7
257        let _user_bits_7 = self.bits_to_u8(&data_bits[52..56]);
258
259        // Bits 56-57: Hour tens
260        let hour_tens = self.bits_to_u8(&data_bits[56..58]);
261
262        // Bits 58-73: Reserved and user bits 8
263
264        // Compose timecode values
265        let frames = frame_tens * 10 + frame_units;
266        let seconds = second_tens * 10 + second_units;
267        let minutes = minute_tens * 10 + minute_units;
268        let hours = hour_tens * 10 + hour_units;
269
270        // Determine frame rate
271        let frame_rate = if drop_frame && self.config.frame_rate == FrameRate::Fps2997NDF {
272            FrameRate::Fps2997DF
273        } else {
274            self.config.frame_rate
275        };
276
277        // Create timecode
278        let timecode = Timecode::new(hours, minutes, seconds, frames, frame_rate)?;
279
280        Ok(timecode)
281    }
282
283    /// Convert bit slice to u8
284    fn bits_to_u8(&self, bits: &[bool]) -> u8 {
285        let mut value = 0u8;
286        for (i, &bit) in bits.iter().enumerate() {
287            if bit {
288                value |= 1 << i;
289            }
290        }
291        value
292    }
293
294    /// Extract user bits from data bits
295    #[allow(dead_code)]
296    fn extract_user_bits(&self, data_bits: &[bool; DATA_BITS]) -> u32 {
297        let mut user_bits = 0u32;
298
299        // User bits scattered throughout VITC frame (same as LTC)
300        user_bits |= self.bits_to_u8(&data_bits[4..8]) as u32;
301        user_bits |= (self.bits_to_u8(&data_bits[12..16]) as u32) << 4;
302        user_bits |= (self.bits_to_u8(&data_bits[20..24]) as u32) << 8;
303        user_bits |= (self.bits_to_u8(&data_bits[28..32]) as u32) << 12;
304        user_bits |= (self.bits_to_u8(&data_bits[36..40]) as u32) << 16;
305        user_bits |= (self.bits_to_u8(&data_bits[44..48]) as u32) << 20;
306        user_bits |= (self.bits_to_u8(&data_bits[52..56]) as u32) << 24;
307
308        user_bits
309    }
310
311    /// Reset decoder state
312    pub fn reset(&mut self) {
313        self.last_timecode = None;
314        self.crc_error_count = 0;
315        self.sync_count = 0;
316        self.threshold = 128;
317    }
318
319    /// Check if decoder is synchronized
320    pub fn is_synchronized(&self) -> bool {
321        self.sync_count >= 5 && self.last_timecode.is_some()
322    }
323
324    /// Get CRC error count
325    pub fn crc_errors(&self) -> u32 {
326        self.crc_error_count
327    }
328}
329
330/// Bit pattern analyzer
331#[allow(dead_code)]
332struct BitPatternAnalyzer {
333    /// Run-length encoding buffer
334    run_lengths: Vec<usize>,
335    /// Current run length
336    current_run: usize,
337    /// Current bit value
338    current_bit: bool,
339}
340
341impl BitPatternAnalyzer {
342    #[allow(dead_code)]
343    fn new() -> Self {
344        BitPatternAnalyzer {
345            run_lengths: Vec::new(),
346            current_run: 0,
347            current_bit: false,
348        }
349    }
350
351    /// Add a bit to the analyzer
352    #[allow(dead_code)]
353    fn add_bit(&mut self, bit: bool) {
354        if bit == self.current_bit {
355            self.current_run += 1;
356        } else {
357            if self.current_run > 0 {
358                self.run_lengths.push(self.current_run);
359            }
360            self.current_bit = bit;
361            self.current_run = 1;
362        }
363    }
364
365    /// Finish analysis
366    #[allow(dead_code)]
367    fn finish(&mut self) {
368        if self.current_run > 0 {
369            self.run_lengths.push(self.current_run);
370        }
371    }
372
373    /// Get run lengths
374    #[allow(dead_code)]
375    fn get_run_lengths(&self) -> &[usize] {
376        &self.run_lengths
377    }
378
379    #[allow(dead_code)]
380    fn reset(&mut self) {
381        self.run_lengths.clear();
382        self.current_run = 0;
383        self.current_bit = false;
384    }
385}
386
387/// Line quality analyzer
388pub struct LineQualityAnalyzer {
389    /// Minimum pixel value seen
390    min_pixel: u8,
391    /// Maximum pixel value seen
392    max_pixel: u8,
393    /// Pixel count
394    pixel_count: usize,
395    /// Sum of pixels (for average)
396    pixel_sum: u64,
397}
398
399impl Default for LineQualityAnalyzer {
400    fn default() -> Self {
401        Self::new()
402    }
403}
404
405impl LineQualityAnalyzer {
406    pub fn new() -> Self {
407        LineQualityAnalyzer {
408            min_pixel: 255,
409            max_pixel: 0,
410            pixel_count: 0,
411            pixel_sum: 0,
412        }
413    }
414
415    /// Analyze a line of pixels
416    pub fn analyze(&mut self, pixels: &[u8]) {
417        for &pixel in pixels {
418            self.min_pixel = self.min_pixel.min(pixel);
419            self.max_pixel = self.max_pixel.max(pixel);
420            self.pixel_sum += pixel as u64;
421            self.pixel_count += 1;
422        }
423    }
424
425    /// Get signal-to-noise ratio estimate
426    pub fn get_snr_estimate(&self) -> f32 {
427        let dynamic_range = (self.max_pixel as i16 - self.min_pixel as i16).abs() as f32;
428        if dynamic_range > 0.0 {
429            20.0 * (dynamic_range / 255.0).log10()
430        } else {
431            0.0
432        }
433    }
434
435    /// Get average pixel value
436    pub fn get_average(&self) -> f32 {
437        if self.pixel_count > 0 {
438            self.pixel_sum as f32 / self.pixel_count as f32
439        } else {
440            0.0
441        }
442    }
443
444    /// Get dynamic range
445    pub fn get_dynamic_range(&self) -> u8 {
446        self.max_pixel.saturating_sub(self.min_pixel)
447    }
448
449    pub fn reset(&mut self) {
450        self.min_pixel = 255;
451        self.max_pixel = 0;
452        self.pixel_count = 0;
453        self.pixel_sum = 0;
454    }
455}
456
457/// Field detector
458pub struct FieldDetector {
459    /// History of field marks
460    field_history: Vec<bool>,
461    /// Maximum history size
462    max_history: usize,
463}
464
465impl FieldDetector {
466    pub fn new(max_history: usize) -> Self {
467        FieldDetector {
468            field_history: Vec::with_capacity(max_history),
469            max_history,
470        }
471    }
472
473    /// Add a field mark
474    pub fn add_field_mark(&mut self, field_mark: bool) {
475        self.field_history.push(field_mark);
476        if self.field_history.len() > self.max_history {
477            self.field_history.remove(0);
478        }
479    }
480
481    /// Get predominant field
482    pub fn get_predominant_field(&self) -> Option<u8> {
483        if self.field_history.is_empty() {
484            return None;
485        }
486
487        let field2_count = self.field_history.iter().filter(|&&f| f).count();
488        let field1_count = self.field_history.len() - field2_count;
489
490        if field2_count > field1_count {
491            Some(2)
492        } else {
493            Some(1)
494        }
495    }
496
497    pub fn reset(&mut self) {
498        self.field_history.clear();
499    }
500}
501
502/// Multi-line VITC reader for redundancy
503pub struct MultiLineVitcReader {
504    /// Decoders for different lines
505    decoders: Vec<(u16, VitcDecoder)>,
506}
507
508impl MultiLineVitcReader {
509    /// Create a multi-line reader
510    pub fn new(config: VitcReaderConfig) -> Self {
511        let mut decoders = Vec::new();
512
513        for &line in &config.scan_lines {
514            let mut line_config = config.clone();
515            line_config.scan_lines = vec![line];
516            decoders.push((line, VitcDecoder::new(line_config)));
517        }
518
519        MultiLineVitcReader { decoders }
520    }
521
522    /// Process a line with all decoders
523    pub fn process_line(
524        &mut self,
525        line_number: u16,
526        field: u8,
527        pixels: &[u8],
528    ) -> Vec<(u16, Result<Option<Timecode>, TimecodeError>)> {
529        let mut results = Vec::new();
530
531        for (line, decoder) in &mut self.decoders {
532            if *line == line_number {
533                let result = decoder.process_line(line_number, field, pixels);
534                results.push((*line, result));
535            }
536        }
537
538        results
539    }
540
541    /// Get the best timecode from multiple results
542    pub fn get_best_timecode(
543        &self,
544        results: &[(u16, Result<Option<Timecode>, TimecodeError>)],
545    ) -> Option<Timecode> {
546        for (_line, result) in results {
547            if let Ok(Some(tc)) = result {
548                return Some(*tc);
549            }
550        }
551        None
552    }
553}
554
555/// Error correction for VITC
556pub struct VitcErrorCorrector {
557    /// History of recent timecodes
558    history: Vec<Timecode>,
559    /// Maximum history size
560    max_history: usize,
561}
562
563impl VitcErrorCorrector {
564    pub fn new(max_history: usize) -> Self {
565        VitcErrorCorrector {
566            history: Vec::with_capacity(max_history),
567            max_history,
568        }
569    }
570
571    /// Add a timecode to history
572    pub fn add_timecode(&mut self, timecode: Timecode) {
573        self.history.push(timecode);
574        if self.history.len() > self.max_history {
575            self.history.remove(0);
576        }
577    }
578
579    /// Try to correct or validate a timecode
580    pub fn correct_timecode(&self, timecode: &Timecode) -> Option<Timecode> {
581        if self.history.is_empty() {
582            return Some(*timecode);
583        }
584
585        // Check if timecode is sequential with recent history
586        if let Some(last) = self.history.last() {
587            let mut expected = *last;
588            if expected.increment().is_ok() {
589                // Allow some tolerance for frame differences
590                let diff = (timecode.to_frames() as i64 - expected.to_frames() as i64).abs();
591                if diff <= 2 {
592                    return Some(*timecode);
593                }
594            }
595        }
596
597        // If not sequential, check if it's consistent with history trend
598        Some(*timecode)
599    }
600
601    pub fn reset(&mut self) {
602        self.history.clear();
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609
610    #[test]
611    fn test_decoder_creation() {
612        let config = VitcReaderConfig::default();
613        let decoder = VitcDecoder::new(config);
614        assert!(!decoder.is_synchronized());
615    }
616
617    #[test]
618    fn test_crc_calculation() {
619        let config = VitcReaderConfig::default();
620        let decoder = VitcDecoder::new(config);
621
622        let bits = [false; 72];
623        let crc = decoder.calculate_crc(&bits);
624        assert_eq!(crc, 0);
625    }
626
627    #[test]
628    fn test_bits_to_u8() {
629        let config = VitcReaderConfig::default();
630        let decoder = VitcDecoder::new(config);
631
632        let bits = [true, false, true, false]; // Binary: 0101 = 5
633        assert_eq!(decoder.bits_to_u8(&bits), 5);
634    }
635
636    #[test]
637    fn test_line_quality_analyzer() {
638        let mut analyzer = LineQualityAnalyzer::new();
639        let pixels = vec![16, 235, 16, 235]; // Black-white pattern
640
641        analyzer.analyze(&pixels);
642        assert!(analyzer.get_dynamic_range() > 200);
643    }
644
645    #[test]
646    fn test_field_detector() {
647        let mut detector = FieldDetector::new(10);
648
649        // Add mostly field 2 marks
650        detector.add_field_mark(true);
651        detector.add_field_mark(true);
652        detector.add_field_mark(false);
653
654        assert_eq!(detector.get_predominant_field(), Some(2));
655    }
656}