Skip to main content

oximedia_timecode/ltc/
decoder.rs

1//! LTC Decoder - Biphase Mark Code decoding from audio
2//!
3//! This module implements a complete LTC decoder that:
4//! - Analyzes audio waveforms for biphase mark code transitions
5//! - Detects and validates SMPTE sync words
6//! - Extracts timecode and user bits
7//! - Handles variable playback speeds
8//! - Provides error correction and validation
9
10use super::constants::*;
11use crate::{FrameRate, Timecode, TimecodeError};
12
13/// LTC decoder state machine states
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[allow(dead_code)]
16enum DecoderState {
17    /// Searching for sync word
18    Searching,
19    /// Locked to sync, decoding bits
20    Locked,
21    /// Lost sync, attempting to reacquire
22    LostSync,
23}
24
25/// Biphase mark code decoder
26pub struct LtcDecoder {
27    /// Sample rate
28    #[allow(dead_code)]
29    sample_rate: u32,
30    /// Frame rate
31    frame_rate: FrameRate,
32    /// Minimum signal amplitude
33    min_amplitude: f32,
34    /// Current decoder state
35    state: DecoderState,
36    /// Bit buffer (80 bits)
37    bit_buffer: [bool; BITS_PER_FRAME],
38    /// Current bit position in buffer
39    bit_position: usize,
40    /// Zero crossing detector state
41    zero_crossing: ZeroCrossingDetector,
42    /// Bit synchronizer
43    bit_sync: BitSynchronizer,
44    /// Last decoded timecode
45    last_timecode: Option<Timecode>,
46    /// Sync confidence counter
47    sync_confidence: u32,
48    /// Error counter
49    error_count: u32,
50}
51
52impl LtcDecoder {
53    /// Create a new LTC decoder
54    pub fn new(sample_rate: u32, frame_rate: FrameRate, min_amplitude: f32) -> Self {
55        LtcDecoder {
56            sample_rate,
57            frame_rate,
58            min_amplitude,
59            state: DecoderState::Searching,
60            bit_buffer: [false; BITS_PER_FRAME],
61            bit_position: 0,
62            zero_crossing: ZeroCrossingDetector::new(sample_rate, frame_rate),
63            bit_sync: BitSynchronizer::new(sample_rate, frame_rate),
64            last_timecode: None,
65            sync_confidence: 0,
66            error_count: 0,
67        }
68    }
69
70    /// Process audio samples and decode timecode
71    pub fn process_samples(&mut self, samples: &[f32]) -> Result<Option<Timecode>, TimecodeError> {
72        let mut result = None;
73
74        for &sample in samples {
75            // Detect zero crossings
76            if let Some(transition) = self
77                .zero_crossing
78                .process_sample(sample, self.min_amplitude)
79            {
80                // Process the transition
81                if let Some(bit) = self.bit_sync.process_transition(transition) {
82                    // Store the bit
83                    if let Some(tc) = self.process_bit(bit)? {
84                        result = Some(tc);
85                    }
86                }
87            }
88        }
89
90        Ok(result)
91    }
92
93    /// Process a decoded bit
94    fn process_bit(&mut self, bit: bool) -> Result<Option<Timecode>, TimecodeError> {
95        // Store bit in buffer
96        self.bit_buffer[self.bit_position] = bit;
97        self.bit_position += 1;
98
99        // Check if we have a complete frame
100        if self.bit_position >= BITS_PER_FRAME {
101            self.bit_position = 0;
102
103            // Try to decode the frame
104            match self.decode_frame() {
105                Ok(timecode) => {
106                    self.state = DecoderState::Locked;
107                    self.sync_confidence = self.sync_confidence.saturating_add(1).min(100);
108                    self.error_count = 0;
109                    self.last_timecode = Some(timecode);
110                    return Ok(Some(timecode));
111                }
112                Err(_) => {
113                    self.error_count += 1;
114                    if self.error_count > 10 {
115                        self.state = DecoderState::LostSync;
116                        self.sync_confidence = 0;
117                    }
118                }
119            }
120        }
121
122        Ok(None)
123    }
124
125    /// Decode a complete LTC frame from the bit buffer
126    fn decode_frame(&self) -> Result<Timecode, TimecodeError> {
127        // Find sync word position
128        let sync_pos = self.find_sync_word()?;
129
130        // Extract data bits (64 bits before sync word)
131        let mut data_bits = [false; DATA_BITS];
132        for (i, data_bit) in data_bits.iter_mut().enumerate().take(DATA_BITS) {
133            let pos = (sync_pos + BITS_PER_FRAME - SYNC_BITS - DATA_BITS + i) % BITS_PER_FRAME;
134            *data_bit = self.bit_buffer[pos];
135        }
136
137        // Decode timecode from data bits
138        self.decode_timecode_from_bits(&data_bits)
139    }
140
141    /// Find the sync word in the bit buffer
142    fn find_sync_word(&self) -> Result<usize, TimecodeError> {
143        // Convert sync word to bit pattern
144        let sync_bits = self.u16_to_bits(SYNC_WORD);
145
146        // Search for sync word in buffer
147        for start_pos in 0..BITS_PER_FRAME {
148            let mut match_count = 0;
149            for (i, &sync_bit) in sync_bits.iter().enumerate().take(SYNC_BITS) {
150                let pos = (start_pos + i) % BITS_PER_FRAME;
151                if self.bit_buffer[pos] == sync_bit {
152                    match_count += 1;
153                }
154            }
155
156            // Allow up to 2 bit errors in sync word
157            if match_count >= SYNC_BITS - 2 {
158                return Ok(start_pos);
159            }
160        }
161
162        Err(TimecodeError::SyncNotFound)
163    }
164
165    /// Convert u16 to bit array (LSB first, as per LTC spec)
166    fn u16_to_bits(&self, value: u16) -> [bool; 16] {
167        let mut bits = [false; 16];
168        for (i, bit) in bits.iter_mut().enumerate() {
169            *bit = (value & (1 << i)) != 0;
170        }
171        bits
172    }
173
174    /// Decode timecode from 64 data bits
175    fn decode_timecode_from_bits(
176        &self,
177        bits: &[bool; DATA_BITS],
178    ) -> Result<Timecode, TimecodeError> {
179        // LTC bit layout (SMPTE 12M):
180        // Bits 0-3: Frame units
181        // Bits 4-7: User bits 1
182        // Bits 8-9: Frame tens
183        // Bit 10: Drop frame flag
184        // Bit 11: Color frame flag
185        // Bits 12-15: User bits 2
186        // Bits 16-19: Second units
187        // Bits 20-23: User bits 3
188        // Bits 24-26: Second tens
189        // Bit 27: Biphase mark correction (even parity)
190        // Bits 28-31: User bits 4
191        // Bits 32-35: Minute units
192        // Bits 36-39: User bits 5
193        // Bits 40-42: Minute tens
194        // Bit 43: Binary group flag
195        // Bits 44-47: User bits 6
196        // Bits 48-51: Hour units
197        // Bits 52-55: User bits 7
198        // Bits 56-57: Hour tens
199        // Bit 58-63: User bits 8 and flags
200
201        let frame_units = self.bits_to_u8(&bits[0..4]);
202        let frame_tens = self.bits_to_u8(&bits[8..10]);
203        let frames = frame_tens * 10 + frame_units;
204
205        let second_units = self.bits_to_u8(&bits[16..20]);
206        let second_tens = self.bits_to_u8(&bits[24..27]);
207        let seconds = second_tens * 10 + second_units;
208
209        let minute_units = self.bits_to_u8(&bits[32..36]);
210        let minute_tens = self.bits_to_u8(&bits[40..43]);
211        let minutes = minute_tens * 10 + minute_units;
212
213        let hour_units = self.bits_to_u8(&bits[48..52]);
214        let hour_tens = self.bits_to_u8(&bits[56..58]);
215        let hours = hour_tens * 10 + hour_units;
216
217        // Extract drop frame flag
218        let drop_frame = bits[10];
219
220        // Extract user bits
221        let user_bits = self.extract_user_bits(bits);
222
223        // Create timecode
224        let frame_rate = if drop_frame && self.frame_rate == FrameRate::Fps2997NDF {
225            FrameRate::Fps2997DF
226        } else {
227            self.frame_rate
228        };
229
230        let mut timecode = Timecode::new(hours, minutes, seconds, frames, frame_rate)?;
231        timecode.user_bits = user_bits;
232
233        Ok(timecode)
234    }
235
236    /// Convert bit slice to u8 (LSB first)
237    fn bits_to_u8(&self, bits: &[bool]) -> u8 {
238        let mut value = 0u8;
239        for (i, &bit) in bits.iter().enumerate() {
240            if bit {
241                value |= 1 << i;
242            }
243        }
244        value
245    }
246
247    /// Extract user bits from data bits
248    fn extract_user_bits(&self, bits: &[bool; DATA_BITS]) -> u32 {
249        let mut user_bits = 0u32;
250
251        // User bits are scattered throughout the LTC frame
252        // UB1: bits 4-7
253        user_bits |= self.bits_to_u8(&bits[4..8]) as u32;
254        // UB2: bits 12-15
255        user_bits |= (self.bits_to_u8(&bits[12..16]) as u32) << 4;
256        // UB3: bits 20-23
257        user_bits |= (self.bits_to_u8(&bits[20..24]) as u32) << 8;
258        // UB4: bits 28-31
259        user_bits |= (self.bits_to_u8(&bits[28..32]) as u32) << 12;
260        // UB5: bits 36-39
261        user_bits |= (self.bits_to_u8(&bits[36..40]) as u32) << 16;
262        // UB6: bits 44-47
263        user_bits |= (self.bits_to_u8(&bits[44..48]) as u32) << 20;
264        // UB7: bits 52-55
265        user_bits |= (self.bits_to_u8(&bits[52..56]) as u32) << 24;
266        // UB8: bits 59-62 (4 bits)
267        user_bits |= (self.bits_to_u8(&bits[59..63]) as u32) << 28;
268
269        user_bits
270    }
271
272    /// Reset decoder state
273    pub fn reset(&mut self) {
274        self.state = DecoderState::Searching;
275        self.bit_position = 0;
276        self.sync_confidence = 0;
277        self.error_count = 0;
278        self.zero_crossing.reset();
279        self.bit_sync.reset();
280    }
281
282    /// Return the most recently decoded timecode, if any.
283    ///
284    /// This is the same value that was returned by the last successful call
285    /// to [`process_samples`](Self::process_samples).  It is cleared on
286    /// [`reset`](Self::reset).
287    pub fn last_decoded_timecode(&self) -> Option<Timecode> {
288        self.last_timecode
289    }
290
291    /// Check if decoder is synchronized
292    pub fn is_synchronized(&self) -> bool {
293        self.state == DecoderState::Locked && self.sync_confidence >= 10
294    }
295
296    /// Get sync confidence (0.0 to 1.0)
297    pub fn sync_confidence(&self) -> f32 {
298        (self.sync_confidence as f32) / 100.0
299    }
300}
301
302/// Zero crossing detector
303#[allow(dead_code)]
304struct ZeroCrossingDetector {
305    /// Previous sample value
306    prev_sample: f32,
307    /// Sample counter
308    sample_count: u64,
309    /// Expected samples per bit (nominal)
310    samples_per_bit: f32,
311}
312
313impl ZeroCrossingDetector {
314    fn new(sample_rate: u32, frame_rate: FrameRate) -> Self {
315        let fps = frame_rate.as_float();
316        let bits_per_second = fps * BITS_PER_FRAME as f64;
317        let samples_per_bit = sample_rate as f64 / bits_per_second;
318
319        ZeroCrossingDetector {
320            prev_sample: 0.0,
321            sample_count: 0,
322            samples_per_bit: samples_per_bit as f32,
323        }
324    }
325
326    /// Process a sample and detect zero crossings
327    fn process_sample(&mut self, sample: f32, min_amplitude: f32) -> Option<Transition> {
328        self.sample_count += 1;
329
330        // Detect zero crossing with hysteresis
331        let transition = if self.prev_sample < -min_amplitude && sample >= min_amplitude {
332            Some(Transition {
333                sample_index: self.sample_count,
334                rising: true,
335            })
336        } else if self.prev_sample > min_amplitude && sample <= -min_amplitude {
337            Some(Transition {
338                sample_index: self.sample_count,
339                rising: false,
340            })
341        } else {
342            None
343        };
344
345        self.prev_sample = sample;
346        transition
347    }
348
349    fn reset(&mut self) {
350        self.prev_sample = 0.0;
351        self.sample_count = 0;
352    }
353}
354
355/// Transition event
356#[derive(Debug, Clone, Copy)]
357#[allow(dead_code)]
358struct Transition {
359    sample_index: u64,
360    rising: bool,
361}
362
363/// Bit synchronizer - converts transitions to bits
364struct BitSynchronizer {
365    /// Last transition time
366    last_transition: Option<u64>,
367    /// Expected samples per bit cell
368    samples_per_bit: f32,
369    /// Current bit cell phase
370    bit_phase: f32,
371    /// Bit clock accumulator
372    bit_clock: f32,
373    /// Phase locked loop filter
374    pll_filter: PllFilter,
375}
376
377impl BitSynchronizer {
378    fn new(sample_rate: u32, frame_rate: FrameRate) -> Self {
379        let fps = frame_rate.as_float();
380        let bits_per_second = fps * BITS_PER_FRAME as f64;
381        let samples_per_bit = sample_rate as f64 / bits_per_second;
382
383        BitSynchronizer {
384            last_transition: None,
385            samples_per_bit: samples_per_bit as f32,
386            bit_phase: 0.0,
387            bit_clock: 0.0,
388            pll_filter: PllFilter::new(0.1),
389        }
390    }
391
392    /// Process a transition and decode bits
393    fn process_transition(&mut self, transition: Transition) -> Option<bool> {
394        let sample_index = transition.sample_index;
395
396        if let Some(last_idx) = self.last_transition {
397            let samples_since_last = (sample_index - last_idx) as f32;
398
399            // Update PLL
400            let phase_error = samples_since_last - self.samples_per_bit;
401            let correction = self.pll_filter.update(phase_error);
402            self.samples_per_bit += correction;
403
404            // Determine if this is a half-bit or full-bit transition
405            let is_half_bit = samples_since_last < (self.samples_per_bit * 0.75);
406
407            self.last_transition = Some(sample_index);
408
409            if is_half_bit {
410                // This is a mid-bit transition (bit = 1)
411                self.bit_clock = 0.5;
412                return Some(true);
413            } else {
414                // This is a full-bit transition (bit = 0)
415                self.bit_clock = 0.0;
416                return Some(false);
417            }
418        }
419
420        self.last_transition = Some(sample_index);
421        None
422    }
423
424    fn reset(&mut self) {
425        self.last_transition = None;
426        self.bit_phase = 0.0;
427        self.bit_clock = 0.0;
428        self.pll_filter.reset();
429    }
430}
431
432/// Phase-Locked Loop filter for bit clock recovery
433struct PllFilter {
434    /// Loop gain
435    gain: f32,
436    /// Integrator state
437    integrator: f32,
438}
439
440impl PllFilter {
441    fn new(gain: f32) -> Self {
442        PllFilter {
443            gain,
444            integrator: 0.0,
445        }
446    }
447
448    /// Update filter with phase error
449    fn update(&mut self, phase_error: f32) -> f32 {
450        // Proportional + Integral controller
451        let proportional = phase_error * self.gain;
452        self.integrator += phase_error * self.gain * 0.01;
453
454        // Clamp integrator to prevent windup
455        self.integrator = self.integrator.clamp(-10.0, 10.0);
456
457        proportional + self.integrator
458    }
459
460    fn reset(&mut self) {
461        self.integrator = 0.0;
462    }
463}
464
465/// Signal filter for noise reduction
466#[allow(dead_code)]
467struct SignalFilter {
468    /// Filter coefficients (simple low-pass IIR)
469    alpha: f32,
470    /// Previous filtered value
471    prev_value: f32,
472}
473
474impl SignalFilter {
475    #[allow(dead_code)]
476    fn new(cutoff_freq: f32, sample_rate: f32) -> Self {
477        let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff_freq);
478        let dt = 1.0 / sample_rate;
479        let alpha = dt / (rc + dt);
480
481        SignalFilter {
482            alpha,
483            prev_value: 0.0,
484        }
485    }
486
487    /// Apply low-pass filter to sample
488    #[allow(dead_code)]
489    fn process(&mut self, sample: f32) -> f32 {
490        let filtered = self.alpha * sample + (1.0 - self.alpha) * self.prev_value;
491        self.prev_value = filtered;
492        filtered
493    }
494
495    #[allow(dead_code)]
496    fn reset(&mut self) {
497        self.prev_value = 0.0;
498    }
499}
500
501/// Waveform analyzer for signal quality assessment
502#[allow(dead_code)]
503struct WaveformAnalyzer {
504    /// RMS calculator
505    rms_accumulator: f32,
506    /// Sample count for RMS
507    rms_count: u32,
508    /// Peak detector
509    peak_positive: f32,
510    /// Negative peak
511    peak_negative: f32,
512}
513
514impl WaveformAnalyzer {
515    #[allow(dead_code)]
516    fn new() -> Self {
517        WaveformAnalyzer {
518            rms_accumulator: 0.0,
519            rms_count: 0,
520            peak_positive: 0.0,
521            peak_negative: 0.0,
522        }
523    }
524
525    /// Process a sample and update statistics
526    #[allow(dead_code)]
527    fn process_sample(&mut self, sample: f32) {
528        self.rms_accumulator += sample * sample;
529        self.rms_count += 1;
530
531        if sample > self.peak_positive {
532            self.peak_positive = sample;
533        }
534        if sample < self.peak_negative {
535            self.peak_negative = sample;
536        }
537    }
538
539    /// Get RMS value
540    #[allow(dead_code)]
541    fn get_rms(&self) -> f32 {
542        if self.rms_count > 0 {
543            (self.rms_accumulator / self.rms_count as f32).sqrt()
544        } else {
545            0.0
546        }
547    }
548
549    /// Get peak-to-peak amplitude
550    #[allow(dead_code)]
551    fn get_peak_to_peak(&self) -> f32 {
552        self.peak_positive - self.peak_negative
553    }
554
555    #[allow(dead_code)]
556    fn reset(&mut self) {
557        self.rms_accumulator = 0.0;
558        self.rms_count = 0;
559        self.peak_positive = 0.0;
560        self.peak_negative = 0.0;
561    }
562}
563
564/// Drop frame calculator
565#[allow(dead_code)]
566struct DropFrameCalculator;
567
568impl DropFrameCalculator {
569    /// Check if a timecode is valid for drop frame
570    #[allow(dead_code)]
571    fn is_valid_drop_frame(minutes: u8, seconds: u8, frames: u8) -> bool {
572        // Frames 0 and 1 are dropped at the start of each minute except 0, 10, 20, 30, 40, 50
573        if seconds == 0 && frames < 2 && !minutes.is_multiple_of(10) {
574            return false;
575        }
576        true
577    }
578
579    /// Adjust timecode for drop frame
580    #[allow(dead_code)]
581    fn adjust_for_drop_frame(minutes: u8, seconds: u8, frames: u8) -> (u8, u8, u8) {
582        if seconds == 0 && frames < 2 && !minutes.is_multiple_of(10) {
583            // Skip to frame 2
584            (minutes, seconds, 2)
585        } else {
586            (minutes, seconds, frames)
587        }
588    }
589}
590
591/// Error correction using redundancy
592#[allow(dead_code)]
593struct ErrorCorrector {
594    /// History of recent timecodes
595    history: Vec<Timecode>,
596    /// Maximum history size
597    max_history: usize,
598}
599
600impl ErrorCorrector {
601    #[allow(dead_code)]
602    fn new(max_history: usize) -> Self {
603        ErrorCorrector {
604            history: Vec::with_capacity(max_history),
605            max_history,
606        }
607    }
608
609    /// Add timecode to history
610    #[allow(dead_code)]
611    fn add_timecode(&mut self, timecode: Timecode) {
612        self.history.push(timecode);
613        if self.history.len() > self.max_history {
614            self.history.remove(0);
615        }
616    }
617
618    /// Try to correct errors using history
619    #[allow(dead_code)]
620    fn correct_timecode(&self, timecode: &Timecode) -> Option<Timecode> {
621        if self.history.is_empty() {
622            return Some(*timecode);
623        }
624
625        // Check if timecode is sequential with last one
626        if let Some(last) = self.history.last() {
627            let mut expected = *last;
628            if expected.increment().is_ok() {
629                // Check if current timecode is close to expected
630                if Self::is_close(timecode, &expected) {
631                    return Some(*timecode);
632                }
633            }
634        }
635
636        // If not sequential, return the timecode anyway
637        Some(*timecode)
638    }
639
640    /// Check if two timecodes are close (within a few frames)
641    #[allow(dead_code)]
642    fn is_close(tc1: &Timecode, tc2: &Timecode) -> bool {
643        let diff = (tc1.to_frames() as i64 - tc2.to_frames() as i64).abs();
644        diff <= 5
645    }
646
647    #[allow(dead_code)]
648    fn reset(&mut self) {
649        self.history.clear();
650    }
651}
652
653/// Bit pattern validator
654#[allow(dead_code)]
655struct BitPatternValidator;
656
657impl BitPatternValidator {
658    /// Validate that bit patterns make sense for timecode
659    #[allow(dead_code)]
660    fn validate_timecode_bits(bits: &[bool; DATA_BITS]) -> bool {
661        // Check that tens digits are in valid range
662        let frame_tens = Self::bits_to_u8(&bits[8..10]);
663        let second_tens = Self::bits_to_u8(&bits[24..27]);
664        let minute_tens = Self::bits_to_u8(&bits[40..43]);
665        let hour_tens = Self::bits_to_u8(&bits[56..58]);
666
667        // Frame tens should be 0-5 (for 60fps max)
668        if frame_tens > 5 {
669            return false;
670        }
671
672        // Second tens should be 0-5
673        if second_tens > 5 {
674            return false;
675        }
676
677        // Minute tens should be 0-5
678        if minute_tens > 5 {
679            return false;
680        }
681
682        // Hour tens should be 0-2
683        if hour_tens > 2 {
684            return false;
685        }
686
687        true
688    }
689
690    #[allow(dead_code)]
691    fn bits_to_u8(bits: &[bool]) -> u8 {
692        let mut value = 0u8;
693        for (i, &bit) in bits.iter().enumerate() {
694            if bit {
695                value |= 1 << i;
696            }
697        }
698        value
699    }
700}
701
702/// Speed variation detector
703#[allow(dead_code)]
704struct SpeedDetector {
705    /// History of bit periods
706    bit_periods: Vec<f32>,
707    /// Maximum history
708    max_history: usize,
709}
710
711impl SpeedDetector {
712    #[allow(dead_code)]
713    fn new(max_history: usize) -> Self {
714        SpeedDetector {
715            bit_periods: Vec::with_capacity(max_history),
716            max_history,
717        }
718    }
719
720    /// Add a bit period measurement
721    #[allow(dead_code)]
722    fn add_period(&mut self, period: f32) {
723        self.bit_periods.push(period);
724        if self.bit_periods.len() > self.max_history {
725            self.bit_periods.remove(0);
726        }
727    }
728
729    /// Get average period
730    #[allow(dead_code)]
731    fn get_average_period(&self) -> Option<f32> {
732        if self.bit_periods.is_empty() {
733            return None;
734        }
735
736        let sum: f32 = self.bit_periods.iter().sum();
737        Some(sum / self.bit_periods.len() as f32)
738    }
739
740    /// Get speed ratio (1.0 = nominal speed)
741    #[allow(dead_code)]
742    fn get_speed_ratio(&self, nominal_period: f32) -> f32 {
743        if let Some(avg) = self.get_average_period() {
744            nominal_period / avg
745        } else {
746            1.0
747        }
748    }
749
750    #[allow(dead_code)]
751    fn reset(&mut self) {
752        self.bit_periods.clear();
753    }
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn test_decoder_creation() {
762        let decoder = LtcDecoder::new(48000, FrameRate::Fps25, 0.1);
763        assert!(!decoder.is_synchronized());
764    }
765
766    #[test]
767    fn test_zero_crossing_detector() {
768        let mut detector = ZeroCrossingDetector::new(48000, FrameRate::Fps25);
769
770        // Test rising edge
771        let t1 = detector.process_sample(-0.5, 0.1);
772        assert!(t1.is_none());
773
774        let t2 = detector.process_sample(0.5, 0.1);
775        assert!(t2.is_some());
776    }
777
778    #[test]
779    fn test_bits_to_u8() {
780        let decoder = LtcDecoder::new(48000, FrameRate::Fps25, 0.1);
781        let bits = [true, false, true, false]; // Binary: 0101 = 5
782        assert_eq!(decoder.bits_to_u8(&bits), 5);
783    }
784
785    #[test]
786    fn test_drop_frame_validator() {
787        assert!(!DropFrameCalculator::is_valid_drop_frame(1, 0, 0));
788        assert!(!DropFrameCalculator::is_valid_drop_frame(1, 0, 1));
789        assert!(DropFrameCalculator::is_valid_drop_frame(1, 0, 2));
790        assert!(DropFrameCalculator::is_valid_drop_frame(10, 0, 0));
791    }
792}