Skip to main content

oximedia_timecode/vitc/
mod.rs

1//! Vertical Interval Timecode (VITC) reading and writing
2//!
3//! VITC encodes timecode in the vertical blanking interval of a video signal.
4//! Typically encoded on lines 10-20 of fields 1 and 2.
5//!
6//! # VITC Structure
7//! - 90 bits total per line
8//! - Each bit is 2 pixels wide (for NTSC/PAL)
9//! - Contains timecode, user bits, and CRC
10//!
11//! # Advantages over LTC
12//! - Readable at zero speed and in pause
13//! - Not affected by audio dropouts
14//! - Can be read from both fields
15//!
16//! # Encoding
17//! - Bits encoded as luminance levels (black/white)
18//! - Bit 0: Black level
19//! - Bit 1: White level
20//! - Sync bits mark start and end of data
21
22pub mod decoder;
23pub mod encoder;
24
25use crate::{FrameRate, Timecode, TimecodeError, TimecodeReader, TimecodeWriter};
26
27/// VITC reader configuration
28#[derive(Debug, Clone)]
29pub struct VitcReaderConfig {
30    /// Video standard
31    pub video_standard: VideoStandard,
32    /// Frame rate
33    pub frame_rate: FrameRate,
34    /// Scan lines to read (typically 10-20)
35    pub scan_lines: Vec<u16>,
36    /// Field preference (F1, F2, or both)
37    pub field_preference: FieldPreference,
38}
39
40impl Default for VitcReaderConfig {
41    fn default() -> Self {
42        VitcReaderConfig {
43            video_standard: VideoStandard::Pal,
44            frame_rate: FrameRate::Fps25,
45            scan_lines: vec![19, 21], // Common VITC lines for PAL
46            field_preference: FieldPreference::Both,
47        }
48    }
49}
50
51/// Video standard
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum VideoStandard {
54    /// NTSC (525 lines, 60Hz field rate)
55    Ntsc,
56    /// PAL (625 lines, 50Hz field rate)
57    Pal,
58}
59
60impl VideoStandard {
61    /// Get total lines per frame
62    pub fn total_lines(&self) -> u16 {
63        match self {
64            VideoStandard::Ntsc => 525,
65            VideoStandard::Pal => 625,
66        }
67    }
68
69    /// Get active video lines
70    pub fn active_lines(&self) -> u16 {
71        match self {
72            VideoStandard::Ntsc => 486,
73            VideoStandard::Pal => 576,
74        }
75    }
76
77    /// Get pixels per line (for digital video)
78    pub fn pixels_per_line(&self) -> u16 {
79        match self {
80            VideoStandard::Ntsc => 720,
81            VideoStandard::Pal => 720,
82        }
83    }
84}
85
86/// Field preference for VITC reading
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum FieldPreference {
89    /// Read from field 1 only
90    Field1,
91    /// Read from field 2 only
92    Field2,
93    /// Read from both fields (use first valid)
94    Both,
95}
96
97/// VITC reader
98pub struct VitcReader {
99    decoder: decoder::VitcDecoder,
100    frame_rate: FrameRate,
101}
102
103impl VitcReader {
104    /// Create a new VITC reader with configuration
105    pub fn new(config: VitcReaderConfig) -> Self {
106        let frame_rate = config.frame_rate;
107        VitcReader {
108            decoder: decoder::VitcDecoder::new(config),
109            frame_rate,
110        }
111    }
112
113    /// Process a video line and attempt to decode VITC
114    pub fn process_line(
115        &mut self,
116        line_number: u16,
117        field: u8,
118        pixels: &[u8],
119    ) -> Result<Option<Timecode>, TimecodeError> {
120        self.decoder.process_line(line_number, field, pixels)
121    }
122
123    /// Reset the decoder state
124    pub fn reset(&mut self) {
125        self.decoder.reset();
126    }
127
128    /// Get CRC error count
129    pub fn crc_errors(&self) -> u32 {
130        self.decoder.crc_errors()
131    }
132}
133
134impl TimecodeReader for VitcReader {
135    fn read_timecode(&mut self) -> Result<Option<Timecode>, TimecodeError> {
136        // In practice, video lines would be fed externally
137        Ok(None)
138    }
139
140    fn frame_rate(&self) -> FrameRate {
141        self.frame_rate
142    }
143
144    fn is_synchronized(&self) -> bool {
145        self.decoder.is_synchronized()
146    }
147}
148
149/// VITC writer configuration
150#[derive(Debug, Clone)]
151pub struct VitcWriterConfig {
152    /// Video standard
153    pub video_standard: VideoStandard,
154    /// Frame rate
155    pub frame_rate: FrameRate,
156    /// Scan lines to write (typically 19 and 21 for PAL)
157    pub scan_lines: Vec<u16>,
158    /// Write to both fields
159    pub both_fields: bool,
160}
161
162impl Default for VitcWriterConfig {
163    fn default() -> Self {
164        VitcWriterConfig {
165            video_standard: VideoStandard::Pal,
166            frame_rate: FrameRate::Fps25,
167            scan_lines: vec![19, 21],
168            both_fields: true,
169        }
170    }
171}
172
173/// VITC writer
174pub struct VitcWriter {
175    encoder: encoder::VitcEncoder,
176    frame_rate: FrameRate,
177}
178
179impl VitcWriter {
180    /// Create a new VITC writer with configuration
181    pub fn new(config: VitcWriterConfig) -> Self {
182        let frame_rate = config.frame_rate;
183        VitcWriter {
184            encoder: encoder::VitcEncoder::new(config),
185            frame_rate,
186        }
187    }
188
189    /// Encode a timecode to VITC pixel data
190    pub fn encode_line(
191        &mut self,
192        timecode: &Timecode,
193        field: u8,
194    ) -> Result<Vec<u8>, TimecodeError> {
195        self.encoder.encode_line(timecode, field)
196    }
197
198    /// Reset the encoder state
199    pub fn reset(&mut self) {
200        self.encoder.reset();
201    }
202}
203
204impl TimecodeWriter for VitcWriter {
205    fn write_timecode(&mut self, timecode: &Timecode) -> Result<(), TimecodeError> {
206        // Encode for field 1
207        let _pixels_f1 = self.encode_line(timecode, 1)?;
208        // Encode for field 2
209        let _pixels_f2 = self.encode_line(timecode, 2)?;
210        // In a real implementation, pixels would be written to video output
211        Ok(())
212    }
213
214    fn frame_rate(&self) -> FrameRate {
215        self.frame_rate
216    }
217
218    fn flush(&mut self) -> Result<(), TimecodeError> {
219        Ok(())
220    }
221}
222
223/// VITC bit patterns and constants
224pub(crate) mod constants {
225    /// Number of bits in a VITC line
226    pub const BITS_PER_LINE: usize = 90;
227
228    /// Number of data bits (timecode + user bits + CRC)
229    pub const DATA_BITS: usize = 82;
230
231    /// Number of sync bits at start
232    pub const SYNC_START_BITS: usize = 2;
233
234    /// Number of sync bits at end
235    #[allow(dead_code)]
236    pub const SYNC_END_BITS: usize = 6;
237
238    /// Pixels per bit (typically 2)
239    pub const PIXELS_PER_BIT: usize = 2;
240
241    /// Black level (bit 0)
242    pub const BLACK_LEVEL: u8 = 16;
243
244    /// White level (bit 1)
245    pub const WHITE_LEVEL: u8 = 235;
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_vitc_reader_creation() {
254        let config = VitcReaderConfig::default();
255        let _reader = VitcReader::new(config);
256    }
257
258    #[test]
259    fn test_vitc_writer_creation() {
260        let config = VitcWriterConfig::default();
261        let _writer = VitcWriter::new(config);
262    }
263
264    #[test]
265    fn test_video_standard() {
266        assert_eq!(VideoStandard::Ntsc.total_lines(), 525);
267        assert_eq!(VideoStandard::Pal.total_lines(), 625);
268        assert_eq!(VideoStandard::Ntsc.pixels_per_line(), 720);
269    }
270
271    #[test]
272    fn test_constants() {
273        assert_eq!(constants::BITS_PER_LINE, 90);
274        assert_eq!(constants::PIXELS_PER_BIT, 2);
275    }
276}