Skip to main content

oximedia_timecode/vitc/
encoder.rs

1//! VITC Encoder - Generate timecode for video scan lines
2//!
3//! This module implements a complete VITC encoder that:
4//! - Encodes timecode and user bits to VITC bit patterns
5//! - Generates pixel data for embedding in video lines
6//! - Calculates and inserts CRC checksums
7//! - Handles field synchronization
8//! - Supports all standard video formats
9
10use super::constants::*;
11use super::{VideoStandard, VitcWriterConfig};
12use crate::{FrameRate, Timecode, TimecodeError};
13
14/// VITC encoder
15pub struct VitcEncoder {
16    /// Configuration
17    #[allow(dead_code)]
18    config: VitcWriterConfig,
19    /// Current field being encoded
20    current_field: u8,
21}
22
23impl VitcEncoder {
24    /// Create a new VITC encoder
25    pub fn new(config: VitcWriterConfig) -> Self {
26        VitcEncoder {
27            config,
28            current_field: 1,
29        }
30    }
31
32    /// Encode a timecode to pixel data for a video line
33    pub fn encode_line(
34        &mut self,
35        timecode: &Timecode,
36        field: u8,
37    ) -> Result<Vec<u8>, TimecodeError> {
38        // Create bit array
39        let bits = self.timecode_to_bits(timecode, field)?;
40
41        // Convert bits to pixels
42        let pixels = self.bits_to_pixels(&bits);
43
44        Ok(pixels)
45    }
46
47    /// Convert timecode to VITC bit array
48    fn timecode_to_bits(
49        &self,
50        timecode: &Timecode,
51        field: u8,
52    ) -> Result<[bool; BITS_PER_LINE], TimecodeError> {
53        let mut bits = [false; BITS_PER_LINE];
54
55        // Start sync bits (0-1): 11 (white-white)
56        bits[0] = true;
57        bits[1] = true;
58
59        // Data bits start at position 2
60        let mut data_bits = [false; DATA_BITS];
61
62        // Decompose timecode
63        let frame_units = timecode.frames % 10;
64        let frame_tens = timecode.frames / 10;
65        let second_units = timecode.seconds % 10;
66        let second_tens = timecode.seconds / 10;
67        let minute_units = timecode.minutes % 10;
68        let minute_tens = timecode.minutes / 10;
69        let hour_units = timecode.hours % 10;
70        let hour_tens = timecode.hours / 10;
71
72        // Encode frame units (bits 0-3)
73        self.encode_bcd(&mut data_bits, 0, frame_units);
74
75        // User bits 1 (bits 4-7)
76        self.encode_nibble(&mut data_bits, 4, (timecode.user_bits & 0xF) as u8);
77
78        // Frame tens (bits 8-9)
79        self.encode_bcd(&mut data_bits, 8, frame_tens);
80
81        // Drop frame flag (bit 10)
82        data_bits[10] = timecode.frame_rate.drop_frame;
83
84        // Color frame flag (bit 11)
85        data_bits[11] = false;
86
87        // User bits 2 (bits 12-15)
88        self.encode_nibble(&mut data_bits, 12, ((timecode.user_bits >> 4) & 0xF) as u8);
89
90        // Second units (bits 16-19)
91        self.encode_bcd(&mut data_bits, 16, second_units);
92
93        // User bits 3 (bits 20-23)
94        self.encode_nibble(&mut data_bits, 20, ((timecode.user_bits >> 8) & 0xF) as u8);
95
96        // Second tens (bits 24-26)
97        self.encode_bcd(&mut data_bits, 24, second_tens);
98
99        // Field mark (bit 27) - 0 for field 1, 1 for field 2
100        data_bits[27] = field == 2;
101
102        // User bits 4 (bits 28-31)
103        self.encode_nibble(&mut data_bits, 28, ((timecode.user_bits >> 12) & 0xF) as u8);
104
105        // Minute units (bits 32-35)
106        self.encode_bcd(&mut data_bits, 32, minute_units);
107
108        // User bits 5 (bits 36-39)
109        self.encode_nibble(&mut data_bits, 36, ((timecode.user_bits >> 16) & 0xF) as u8);
110
111        // Minute tens (bits 40-42)
112        self.encode_bcd(&mut data_bits, 40, minute_tens);
113
114        // Binary group flag (bit 43)
115        data_bits[43] = false;
116
117        // User bits 6 (bits 44-47)
118        self.encode_nibble(&mut data_bits, 44, ((timecode.user_bits >> 20) & 0xF) as u8);
119
120        // Hour units (bits 48-51)
121        self.encode_bcd(&mut data_bits, 48, hour_units);
122
123        // User bits 7 (bits 52-55)
124        self.encode_nibble(&mut data_bits, 52, ((timecode.user_bits >> 24) & 0xF) as u8);
125
126        // Hour tens (bits 56-57)
127        self.encode_bcd(&mut data_bits, 56, hour_tens);
128
129        // Reserved bit (58)
130        data_bits[58] = false;
131
132        // User bits 8 (bits 59-73)
133        self.encode_nibble(&mut data_bits, 59, ((timecode.user_bits >> 28) & 0xF) as u8);
134
135        // Calculate and insert CRC (bits 74-81)
136        let crc = self.calculate_crc(&data_bits[0..72]);
137        for i in 0..8 {
138            data_bits[74 + i] = (crc & (1 << i)) != 0;
139        }
140
141        // Copy data bits to main bit array
142        bits[SYNC_START_BITS..(DATA_BITS + SYNC_START_BITS)]
143            .copy_from_slice(&data_bits[..DATA_BITS]);
144
145        // End sync bits (84-89): 001111 (black-black-white-white-white-white)
146        bits[84] = false;
147        bits[85] = false;
148        bits[86] = true;
149        bits[87] = true;
150        bits[88] = true;
151        bits[89] = true;
152
153        Ok(bits)
154    }
155
156    /// Encode a BCD digit
157    fn encode_bcd(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
158        for i in 0..4 {
159            if start + i < DATA_BITS {
160                bits[start + i] = (value & (1 << i)) != 0;
161            }
162        }
163    }
164
165    /// Encode a 4-bit nibble
166    fn encode_nibble(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
167        for i in 0..4 {
168            if start + i < DATA_BITS {
169                bits[start + i] = (value & (1 << i)) != 0;
170            }
171        }
172    }
173
174    /// Calculate CRC for VITC
175    fn calculate_crc(&self, bits: &[bool]) -> u8 {
176        let mut crc = 0u8;
177
178        for &bit in bits {
179            let feedback = ((crc & 0x80) != 0) ^ bit;
180            crc <<= 1;
181            if feedback {
182                crc ^= 0x07; // Polynomial: x^8 + x^2 + x^1 + x^0
183            }
184        }
185
186        crc
187    }
188
189    /// Convert bits to pixel data
190    fn bits_to_pixels(&self, bits: &[bool; BITS_PER_LINE]) -> Vec<u8> {
191        let mut pixels = Vec::with_capacity(BITS_PER_LINE * PIXELS_PER_BIT);
192
193        for &bit in bits {
194            let level = if bit { WHITE_LEVEL } else { BLACK_LEVEL };
195
196            // Each bit is PIXELS_PER_BIT pixels wide
197            for _ in 0..PIXELS_PER_BIT {
198                pixels.push(level);
199            }
200        }
201
202        pixels
203    }
204
205    /// Reset encoder state
206    pub fn reset(&mut self) {
207        self.current_field = 1;
208    }
209
210    /// Set current field
211    pub fn set_field(&mut self, field: u8) {
212        self.current_field = field;
213    }
214
215    /// Get current field
216    pub fn field(&self) -> u8 {
217        self.current_field
218    }
219}
220
221/// Multi-line VITC writer for redundancy
222pub struct MultiLineVitcWriter {
223    /// Encoder
224    encoder: VitcEncoder,
225    /// Lines to write
226    lines: Vec<u16>,
227}
228
229impl MultiLineVitcWriter {
230    /// Create a multi-line writer
231    pub fn new(config: VitcWriterConfig) -> Self {
232        let lines = config.scan_lines.clone();
233        MultiLineVitcWriter {
234            encoder: VitcEncoder::new(config),
235            lines,
236        }
237    }
238
239    /// Encode timecode for all configured lines
240    pub fn encode_all_lines(
241        &mut self,
242        timecode: &Timecode,
243        field: u8,
244    ) -> Result<Vec<(u16, Vec<u8>)>, TimecodeError> {
245        let mut results = Vec::new();
246
247        for &line in &self.lines {
248            let pixels = self.encoder.encode_line(timecode, field)?;
249            results.push((line, pixels));
250        }
251
252        Ok(results)
253    }
254}
255
256/// VITC line buffer for video frame integration
257pub struct VitcLineBuffer {
258    /// Video standard
259    #[allow(dead_code)]
260    video_standard: VideoStandard,
261    /// Frame buffer (stores VITC lines only)
262    lines: Vec<(u16, u8, Vec<u8>)>, // (line_number, field, pixels)
263}
264
265impl VitcLineBuffer {
266    /// Create a new line buffer
267    pub fn new(video_standard: VideoStandard) -> Self {
268        VitcLineBuffer {
269            video_standard,
270            lines: Vec::new(),
271        }
272    }
273
274    /// Add a VITC line to the buffer
275    pub fn add_line(&mut self, line_number: u16, field: u8, pixels: Vec<u8>) {
276        // Remove existing line with same line number and field
277        self.lines
278            .retain(|(ln, f, _)| !(*ln == line_number && *f == field));
279
280        self.lines.push((line_number, field, pixels));
281    }
282
283    /// Get all lines for a specific field
284    pub fn get_field_lines(&self, field: u8) -> Vec<(u16, &[u8])> {
285        self.lines
286            .iter()
287            .filter(|(_, f, _)| *f == field)
288            .map(|(ln, _, pixels)| (*ln, pixels.as_slice()))
289            .collect()
290    }
291
292    /// Clear the buffer
293    pub fn clear(&mut self) {
294        self.lines.clear();
295    }
296
297    /// Get total lines
298    pub fn line_count(&self) -> usize {
299        self.lines.len()
300    }
301}
302
303/// Pixel level adjustment for different video standards
304pub struct PixelLevelAdjuster {
305    /// Black level
306    black_level: u8,
307    /// White level
308    white_level: u8,
309}
310
311impl PixelLevelAdjuster {
312    /// Create adjuster for video standard
313    pub fn new(video_standard: VideoStandard) -> Self {
314        match video_standard {
315            VideoStandard::Ntsc => PixelLevelAdjuster {
316                black_level: 16,
317                white_level: 235,
318            },
319            VideoStandard::Pal => PixelLevelAdjuster {
320                black_level: 16,
321                white_level: 235,
322            },
323        }
324    }
325
326    /// Create with custom levels
327    pub fn with_levels(black_level: u8, white_level: u8) -> Self {
328        PixelLevelAdjuster {
329            black_level,
330            white_level,
331        }
332    }
333
334    /// Adjust bit to pixel level
335    pub fn bit_to_pixel(&self, bit: bool) -> u8 {
336        if bit {
337            self.white_level
338        } else {
339            self.black_level
340        }
341    }
342
343    /// Get black level
344    pub fn black_level(&self) -> u8 {
345        self.black_level
346    }
347
348    /// Get white level
349    pub fn white_level(&self) -> u8 {
350        self.white_level
351    }
352}
353
354/// Rise time shaper for cleaner edges
355pub struct RiseTimeShaper {
356    /// Rise time in pixels
357    rise_time_pixels: usize,
358}
359
360impl RiseTimeShaper {
361    /// Create with rise time
362    pub fn new(rise_time_pixels: usize) -> Self {
363        RiseTimeShaper {
364            rise_time_pixels: rise_time_pixels.max(1),
365        }
366    }
367
368    /// Shape pixel transitions
369    pub fn shape_pixels(&self, pixels: &[u8]) -> Vec<u8> {
370        let mut shaped = Vec::with_capacity(pixels.len());
371
372        if pixels.is_empty() {
373            return shaped;
374        }
375
376        shaped.push(pixels[0]);
377
378        for i in 1..pixels.len() {
379            let current = pixels[i];
380            let prev = pixels[i - 1];
381
382            if current != prev {
383                // Transition detected - apply shaping
384                let diff = current as i16 - prev as i16;
385                for j in 0..self.rise_time_pixels.min(pixels.len() - i) {
386                    let progress = (j + 1) as f32 / self.rise_time_pixels as f32;
387                    let value = prev as f32 + diff as f32 * progress;
388                    shaped.push(value as u8);
389                }
390                // Skip the shaped pixels
391                for _ in 0..self.rise_time_pixels.min(pixels.len() - i) {
392                    if i < pixels.len() {
393                        shaped.push(current);
394                    }
395                }
396            } else {
397                shaped.push(current);
398            }
399        }
400
401        shaped.truncate(pixels.len());
402        shaped
403    }
404}
405
406/// Blanking level inserter
407pub struct BlankingInserter {
408    /// Blanking level
409    blanking_level: u8,
410}
411
412impl BlankingInserter {
413    /// Create with blanking level
414    pub fn new(blanking_level: u8) -> Self {
415        BlankingInserter { blanking_level }
416    }
417
418    /// Insert blanking before and after VITC
419    pub fn insert_blanking(
420        &self,
421        vitc_pixels: &[u8],
422        total_width: usize,
423        start_offset: usize,
424    ) -> Vec<u8> {
425        let mut full_line = vec![self.blanking_level; total_width];
426
427        // Copy VITC pixels at the specified offset
428        let end_offset = (start_offset + vitc_pixels.len()).min(total_width);
429        for (i, &pixel) in vitc_pixels.iter().enumerate() {
430            if start_offset + i < end_offset {
431                full_line[start_offset + i] = pixel;
432            }
433        }
434
435        full_line
436    }
437}
438
439/// VITC frame generator for continuous encoding
440pub struct VitcFrameGenerator {
441    /// Writer
442    writer: MultiLineVitcWriter,
443    /// Current timecode
444    current_timecode: Option<Timecode>,
445    /// Frame rate
446    #[allow(dead_code)]
447    frame_rate: FrameRate,
448}
449
450impl VitcFrameGenerator {
451    /// Create a new frame generator
452    pub fn new(config: VitcWriterConfig) -> Self {
453        let frame_rate = config.frame_rate;
454        VitcFrameGenerator {
455            writer: MultiLineVitcWriter::new(config),
456            current_timecode: None,
457            frame_rate,
458        }
459    }
460
461    /// Set starting timecode
462    pub fn set_timecode(&mut self, timecode: Timecode) {
463        self.current_timecode = Some(timecode);
464    }
465
466    /// Generate VITC for next frame
467    pub fn generate_frame(&mut self) -> Result<Vec<(u16, u8, Vec<u8>)>, TimecodeError> {
468        if let Some(ref mut tc) = self.current_timecode {
469            let mut results = Vec::new();
470
471            // Generate for field 1
472            let field1_lines = self.writer.encode_all_lines(tc, 1)?;
473            for (line, pixels) in field1_lines {
474                results.push((line, 1, pixels));
475            }
476
477            // Generate for field 2
478            let field2_lines = self.writer.encode_all_lines(tc, 2)?;
479            for (line, pixels) in field2_lines {
480                results.push((line, 2, pixels));
481            }
482
483            // Increment timecode
484            tc.increment()?;
485
486            Ok(results)
487        } else {
488            Err(TimecodeError::InvalidConfiguration)
489        }
490    }
491
492    /// Get current timecode
493    pub fn current_timecode(&self) -> Option<&Timecode> {
494        self.current_timecode.as_ref()
495    }
496}
497
498/// User bits helpers for VITC
499pub struct VitcUserBitsHelper;
500
501impl VitcUserBitsHelper {
502    /// Validate user bits for VITC
503    pub fn validate_user_bits(user_bits: u32) -> bool {
504        // User bits are 32 bits, all values are valid
505        let _ = user_bits;
506        true
507    }
508
509    /// Extract user bits group
510    pub fn extract_group(user_bits: u32, group: u8) -> u8 {
511        let shift = (group as u32) * 4;
512        ((user_bits >> shift) & 0xF) as u8
513    }
514
515    /// Set user bits group
516    pub fn set_group(user_bits: u32, group: u8, value: u8) -> u32 {
517        let shift = (group as u32) * 4;
518        let mask = !(0xF << shift);
519        (user_bits & mask) | ((value as u32 & 0xF) << shift)
520    }
521}
522
523/// Pixel pattern validator
524pub struct PixelPatternValidator;
525
526impl PixelPatternValidator {
527    /// Validate that pixel pattern is suitable for VITC
528    pub fn validate_pattern(pixels: &[u8]) -> bool {
529        if pixels.len() < BITS_PER_LINE * PIXELS_PER_BIT {
530            return false;
531        }
532
533        // Check that pixels are within valid range
534        for &pixel in pixels {
535            if !(16..=235).contains(&pixel) {
536                return false;
537            }
538        }
539
540        true
541    }
542
543    /// Check sync pattern
544    pub fn check_sync_pattern(pixels: &[u8]) -> bool {
545        if pixels.len() < 4 {
546            return false;
547        }
548
549        // First 4 pixels should be white (start sync)
550        pixels[0] > 200 && pixels[1] > 200 && pixels[2] > 200 && pixels[3] > 200
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557
558    #[test]
559    fn test_encoder_creation() {
560        let config = VitcWriterConfig::default();
561        let encoder = VitcEncoder::new(config);
562        assert_eq!(encoder.field(), 1);
563    }
564
565    #[test]
566    fn test_encode_line() {
567        let config = VitcWriterConfig::default();
568        let mut encoder = VitcEncoder::new(config);
569
570        let timecode = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).unwrap();
571        let pixels = encoder.encode_line(&timecode, 1).unwrap();
572
573        assert_eq!(pixels.len(), BITS_PER_LINE * PIXELS_PER_BIT);
574    }
575
576    #[test]
577    fn test_crc_calculation() {
578        let config = VitcWriterConfig::default();
579        let encoder = VitcEncoder::new(config);
580
581        let bits = [false; 72];
582        let crc = encoder.calculate_crc(&bits);
583        assert_eq!(crc, 0);
584    }
585
586    #[test]
587    fn test_pixel_level_adjuster() {
588        let adjuster = PixelLevelAdjuster::new(VideoStandard::Pal);
589        assert_eq!(adjuster.bit_to_pixel(false), 16);
590        assert_eq!(adjuster.bit_to_pixel(true), 235);
591    }
592
593    #[test]
594    fn test_user_bits_helper() {
595        let user_bits = 0x12345678u32;
596        assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 0), 0x8);
597        assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 1), 0x7);
598
599        let modified = VitcUserBitsHelper::set_group(user_bits, 0, 0xA);
600        assert_eq!(VitcUserBitsHelper::extract_group(modified, 0), 0xA);
601    }
602
603    #[test]
604    fn test_line_buffer() {
605        let mut buffer = VitcLineBuffer::new(VideoStandard::Pal);
606        buffer.add_line(19, 1, vec![16; 180]);
607        buffer.add_line(21, 1, vec![16; 180]);
608
609        assert_eq!(buffer.line_count(), 2);
610
611        let field1_lines = buffer.get_field_lines(1);
612        assert_eq!(field1_lines.len(), 2);
613    }
614}