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