Skip to main content

sightloom_core/
stamp.rs

1//! Frame timing and multi-source stamps.
2
3use crate::{CoreError, SourceId};
4
5/// Presentation timestamp as rational media time.
6///
7/// Values are stored as `ticks / timescale` seconds. Both fields must be
8/// finite-compatible integers; `timescale` must be non-zero.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct MediaTime {
11    ticks: i64,
12    timescale: u32,
13}
14
15impl Default for MediaTime {
16    fn default() -> Self {
17        Self {
18            ticks: 0,
19            timescale: 1,
20        }
21    }
22}
23
24impl MediaTime {
25    /// Creates a media time when `timescale` is non-zero.
26    ///
27    /// # Errors
28    ///
29    /// Returns [`CoreError::InvalidMediaTime`] when `timescale` is zero.
30    pub const fn new(ticks: i64, timescale: u32) -> Result<Self, CoreError> {
31        if timescale == 0 {
32            return Err(CoreError::InvalidMediaTime);
33        }
34        Ok(Self { ticks, timescale })
35    }
36
37    /// Returns the tick count.
38    #[must_use]
39    pub const fn ticks(self) -> i64 {
40        self.ticks
41    }
42
43    /// Returns the timescale (ticks per second).
44    #[must_use]
45    pub const fn timescale(self) -> u32 {
46        self.timescale
47    }
48
49    /// Converts this media time to whole nanoseconds, saturating on overflow.
50    #[must_use]
51    pub fn as_nanos(self) -> i64 {
52        let ticks = i128::from(self.ticks);
53        let scale = i128::from(self.timescale);
54        let nanos = ticks
55            .saturating_mul(1_000_000_000)
56            .checked_div(scale)
57            .unwrap_or(0);
58        i64::try_from(nanos).unwrap_or(if nanos.is_positive() {
59            i64::MAX
60        } else {
61            i64::MIN
62        })
63    }
64
65    /// Signed duration between `self` and `earlier` in nanoseconds.
66    #[must_use]
67    pub fn duration_since_ns(self, earlier: Self) -> i64 {
68        self.as_nanos().saturating_sub(earlier.as_nanos())
69    }
70}
71
72/// Temporal and source identity of a single frame sample.
73#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct FrameStamp {
75    /// Media source that produced the frame.
76    pub source_id: SourceId,
77    /// Zero-based frame index within the source timeline.
78    pub frame_index: u64,
79    /// Presentation timestamp for the frame.
80    pub pts: MediaTime,
81    /// Optional host wall-clock capture time in nanoseconds since the Unix epoch.
82    pub wall_clock_ns: Option<i64>,
83}
84
85impl FrameStamp {
86    /// Creates a frame stamp from media time components.
87    #[must_use]
88    pub fn new(
89        source_id: SourceId,
90        frame_index: u64,
91        pts: MediaTime,
92        wall_clock_ns: Option<i64>,
93    ) -> Self {
94        Self {
95            source_id,
96            frame_index,
97            pts,
98            wall_clock_ns,
99        }
100    }
101}