Skip to main content

oximedia_codec/
picture_timing.rs

1//! Picture timing supplemental enhancement information (SEI) types.
2//!
3//! Provides structures for representing H.264/H.265 picture timing SEI messages,
4//! including clock timestamps and PicStruct values used to signal interlacing
5//! and repeat-field behaviour.
6
7#![allow(dead_code)]
8
9/// Pic-struct values from H.264 Table D-1 / H.265 Table D.2.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PicStruct {
12    /// Frame (progressive).
13    Frame,
14    /// Top field only.
15    TopField,
16    /// Bottom field only.
17    BottomField,
18    /// Top field, then bottom field.
19    TopBottomField,
20    /// Bottom field, then top field.
21    BottomTopField,
22    /// Top field, bottom field, then top field repeated.
23    TopBottomTopField,
24    /// Bottom field, top field, then bottom field repeated.
25    BottomTopBottomField,
26    /// Frame doubling.
27    FrameDoubling,
28    /// Frame tripling.
29    FrameTripling,
30}
31
32impl PicStruct {
33    /// Returns the number of progressive-scan frames implied by this PicStruct.
34    ///
35    /// For field-pair types this is 1; for doubling/tripling it is 2/3.
36    pub fn progressive_frame_count(self) -> u32 {
37        match self {
38            Self::Frame
39            | Self::TopField
40            | Self::BottomField
41            | Self::TopBottomField
42            | Self::BottomTopField
43            | Self::TopBottomTopField
44            | Self::BottomTopBottomField => 1,
45            Self::FrameDoubling => 2,
46            Self::FrameTripling => 3,
47        }
48    }
49
50    /// Returns `true` if this PicStruct represents a progressive frame.
51    pub fn is_progressive(self) -> bool {
52        matches!(
53            self,
54            Self::Frame | Self::FrameDoubling | Self::FrameTripling
55        )
56    }
57
58    /// Returns `true` if this PicStruct involves field repetition.
59    pub fn has_repeated_field(self) -> bool {
60        matches!(self, Self::TopBottomTopField | Self::BottomTopBottomField)
61    }
62}
63
64/// A clock timestamp embedded in a picture timing SEI.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct ClockTimestamp {
67    /// Hours component (0–23).
68    pub hours: u8,
69    /// Minutes component (0–59).
70    pub minutes: u8,
71    /// Seconds component (0–59).
72    pub seconds: u8,
73    /// N_frames field from the bitstream.
74    pub n_frames: u32,
75    /// Discontinuity flag.
76    pub discontinuity: bool,
77    /// Clock timestamp count (ct_type): 0=progressive, 1=interlaced, 2=unknown.
78    pub ct_type: u8,
79    /// Nuit field clock (numerator of offset from nominal).
80    pub nuit_field_based: bool,
81    /// Counting type (0–6).
82    pub counting_type: u8,
83}
84
85impl ClockTimestamp {
86    /// Converts the timestamp to whole milliseconds from midnight.
87    ///
88    /// Does not account for `n_frames` (frame-level precision is codec-dependent).
89    pub fn to_ms(self) -> u64 {
90        let h = self.hours as u64;
91        let m = self.minutes as u64;
92        let s = self.seconds as u64;
93        (h * 3600 + m * 60 + s) * 1000
94    }
95
96    /// Returns `true` when the discontinuity flag is set.
97    pub fn is_discontinuity(self) -> bool {
98        self.discontinuity
99    }
100}
101
102/// Picture timing SEI payload.
103#[derive(Debug, Clone)]
104pub struct PictureTiming {
105    /// CpbDpbDelaysPresentFlag controls whether cpb/dpb removal delays appear.
106    pub cpb_dpb_delays_present: bool,
107    /// Cpb removal delay in ticks.
108    pub cpb_removal_delay: u32,
109    /// Dpb output delay in ticks.
110    pub dpb_output_delay: u32,
111    /// PicStruct field (present when `pic_struct_present_flag` is set in VUI).
112    pub pic_struct: Option<PicStruct>,
113    /// Up to 3 optional clock timestamps.
114    pub clock_timestamps: Vec<ClockTimestamp>,
115    /// `repeat_first_field` flag extracted from the SEI.
116    pub repeat_first_field: bool,
117}
118
119impl PictureTiming {
120    /// Creates a minimal PictureTiming with no clock timestamps.
121    pub fn new(cpb_removal_delay: u32, dpb_output_delay: u32) -> Self {
122        Self {
123            cpb_dpb_delays_present: true,
124            cpb_removal_delay,
125            dpb_output_delay,
126            pic_struct: None,
127            clock_timestamps: Vec::new(),
128            repeat_first_field: false,
129        }
130    }
131
132    /// Returns `true` when the `repeat_first_field` flag is set.
133    pub fn is_repeat_first_field(&self) -> bool {
134        self.repeat_first_field
135    }
136
137    /// Returns the PicStruct if present.
138    pub fn pic_struct(&self) -> Option<PicStruct> {
139        self.pic_struct
140    }
141
142    /// Number of clock timestamps attached.
143    pub fn timestamp_count(&self) -> usize {
144        self.clock_timestamps.len()
145    }
146}
147
148/// Parser for picture timing SEI payloads.
149#[derive(Debug, Default)]
150pub struct PictureTimingParser {
151    pic_struct_present: bool,
152    cpb_dpb_delays_present: bool,
153}
154
155impl PictureTimingParser {
156    /// Creates a new parser.
157    ///
158    /// `pic_struct_present` — from SPS VUI `pic_struct_present_flag`.
159    /// `cpb_dpb_delays_present` — from HRD parameters presence.
160    pub fn new(pic_struct_present: bool, cpb_dpb_delays_present: bool) -> Self {
161        Self {
162            pic_struct_present,
163            cpb_dpb_delays_present,
164        }
165    }
166
167    /// Parses a raw SEI payload byte slice into a [`PictureTiming`].
168    ///
169    /// Returns `None` if the slice is too short to contain valid data.
170    pub fn parse(&self, data: &[u8]) -> Option<PictureTiming> {
171        if data.is_empty() {
172            return None;
173        }
174        // Simplified: treat first byte as encoded PicStruct (0–8).
175        let raw_ps = data[0] & 0x0F;
176        let pic_struct = if self.pic_struct_present {
177            Some(Self::decode_pic_struct(raw_ps))
178        } else {
179            None
180        };
181
182        let (cpb_removal_delay, dpb_output_delay) =
183            if self.cpb_dpb_delays_present && data.len() >= 5 {
184                let cpb = u16::from_be_bytes([data[1], data[2]]) as u32;
185                let dpb = u16::from_be_bytes([data[3], data[4]]) as u32;
186                (cpb, dpb)
187            } else {
188                (0, 0)
189            };
190
191        let repeat_first_field = data.len() >= 6 && (data[5] & 0x01) != 0;
192
193        Some(PictureTiming {
194            cpb_dpb_delays_present: self.cpb_dpb_delays_present,
195            cpb_removal_delay,
196            dpb_output_delay,
197            pic_struct,
198            clock_timestamps: Vec::new(),
199            repeat_first_field,
200        })
201    }
202
203    fn decode_pic_struct(raw: u8) -> PicStruct {
204        match raw {
205            1 => PicStruct::TopField,
206            2 => PicStruct::BottomField,
207            3 => PicStruct::TopBottomField,
208            4 => PicStruct::BottomTopField,
209            5 => PicStruct::TopBottomTopField,
210            6 => PicStruct::BottomTopBottomField,
211            7 => PicStruct::FrameDoubling,
212            8 => PicStruct::FrameTripling,
213            _ => PicStruct::Frame,
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_pic_struct_progressive_frame_count_frame() {
224        assert_eq!(PicStruct::Frame.progressive_frame_count(), 1);
225    }
226
227    #[test]
228    fn test_pic_struct_progressive_frame_count_doubling() {
229        assert_eq!(PicStruct::FrameDoubling.progressive_frame_count(), 2);
230    }
231
232    #[test]
233    fn test_pic_struct_progressive_frame_count_tripling() {
234        assert_eq!(PicStruct::FrameTripling.progressive_frame_count(), 3);
235    }
236
237    #[test]
238    fn test_pic_struct_field_count_is_one() {
239        assert_eq!(PicStruct::TopField.progressive_frame_count(), 1);
240        assert_eq!(PicStruct::BottomField.progressive_frame_count(), 1);
241    }
242
243    #[test]
244    fn test_pic_struct_is_progressive() {
245        assert!(PicStruct::Frame.is_progressive());
246        assert!(PicStruct::FrameDoubling.is_progressive());
247        assert!(!PicStruct::TopField.is_progressive());
248    }
249
250    #[test]
251    fn test_pic_struct_has_repeated_field() {
252        assert!(PicStruct::TopBottomTopField.has_repeated_field());
253        assert!(PicStruct::BottomTopBottomField.has_repeated_field());
254        assert!(!PicStruct::TopBottomField.has_repeated_field());
255    }
256
257    #[test]
258    fn test_clock_timestamp_to_ms_zero() {
259        let ts = ClockTimestamp {
260            hours: 0,
261            minutes: 0,
262            seconds: 0,
263            n_frames: 0,
264            discontinuity: false,
265            ct_type: 0,
266            nuit_field_based: false,
267            counting_type: 0,
268        };
269        assert_eq!(ts.to_ms(), 0);
270    }
271
272    #[test]
273    fn test_clock_timestamp_to_ms_one_hour() {
274        let ts = ClockTimestamp {
275            hours: 1,
276            minutes: 0,
277            seconds: 0,
278            n_frames: 0,
279            discontinuity: false,
280            ct_type: 0,
281            nuit_field_based: false,
282            counting_type: 0,
283        };
284        assert_eq!(ts.to_ms(), 3_600_000);
285    }
286
287    #[test]
288    fn test_clock_timestamp_to_ms_mixed() {
289        let ts = ClockTimestamp {
290            hours: 1,
291            minutes: 30,
292            seconds: 45,
293            n_frames: 0,
294            discontinuity: false,
295            ct_type: 0,
296            nuit_field_based: false,
297            counting_type: 0,
298        };
299        // 1h + 30m + 45s = 5445s = 5_445_000 ms
300        assert_eq!(ts.to_ms(), 5_445_000);
301    }
302
303    #[test]
304    fn test_clock_timestamp_is_discontinuity() {
305        let ts = ClockTimestamp {
306            hours: 0,
307            minutes: 0,
308            seconds: 0,
309            n_frames: 0,
310            discontinuity: true,
311            ct_type: 0,
312            nuit_field_based: false,
313            counting_type: 0,
314        };
315        assert!(ts.is_discontinuity());
316    }
317
318    #[test]
319    fn test_picture_timing_new() {
320        let pt = PictureTiming::new(100, 200);
321        assert_eq!(pt.cpb_removal_delay, 100);
322        assert_eq!(pt.dpb_output_delay, 200);
323        assert!(!pt.is_repeat_first_field());
324        assert_eq!(pt.timestamp_count(), 0);
325    }
326
327    #[test]
328    fn test_picture_timing_repeat_first_field() {
329        let mut pt = PictureTiming::new(0, 0);
330        pt.repeat_first_field = true;
331        assert!(pt.is_repeat_first_field());
332    }
333
334    #[test]
335    fn test_parser_empty_data_returns_none() {
336        let parser = PictureTimingParser::new(false, false);
337        assert!(parser.parse(&[]).is_none());
338    }
339
340    #[test]
341    fn test_parser_frame_pic_struct() {
342        let parser = PictureTimingParser::new(true, false);
343        // byte 0 lower nibble = 0 → Frame
344        let pt = parser.parse(&[0x00]).expect("parse should succeed");
345        assert_eq!(pt.pic_struct, Some(PicStruct::Frame));
346    }
347
348    #[test]
349    fn test_parser_top_field_pic_struct() {
350        let parser = PictureTimingParser::new(true, false);
351        let pt = parser.parse(&[0x01]).expect("parse should succeed");
352        assert_eq!(pt.pic_struct, Some(PicStruct::TopField));
353    }
354
355    #[test]
356    fn test_parser_cpb_dpb_delays() {
357        let parser = PictureTimingParser::new(false, true);
358        // cpb = 0x0064 = 100, dpb = 0x00C8 = 200
359        let data = [0x00, 0x00, 0x64, 0x00, 0xC8, 0x00];
360        let pt = parser.parse(&data).expect("parse should succeed");
361        assert_eq!(pt.cpb_removal_delay, 100);
362        assert_eq!(pt.dpb_output_delay, 200);
363    }
364
365    #[test]
366    fn test_parser_repeat_first_field_flag() {
367        let parser = PictureTimingParser::new(false, true);
368        let data = [0x00, 0x00, 0x01, 0x00, 0x02, 0x01];
369        let pt = parser.parse(&data).expect("parse should succeed");
370        assert!(pt.is_repeat_first_field());
371    }
372
373    #[test]
374    fn test_parser_no_pic_struct_when_flag_false() {
375        let parser = PictureTimingParser::new(false, false);
376        let pt = parser.parse(&[0x07]).expect("parse should succeed");
377        assert!(pt.pic_struct.is_none());
378    }
379}