symphonia_core/
units.rs

1// Symphonia
2// Copyright (c) 2019-2022 The Project Symphonia Developers.
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8//! The `units` module provides definitions for common units.
9
10use std::fmt;
11
12/// A `TimeStamp` represents an instantenous instant in time since the start of a stream. One
13/// `TimeStamp` "tick" is equivalent to the stream's `TimeBase` in seconds.
14pub type TimeStamp = u64;
15
16/// A `Duration` indicates a positive span of time.
17pub type Duration = u64;
18
19/// `Time` represents a duration of time in seconds, or the number of seconds since an arbitrary
20/// epoch. `Time` is stored as an integer number of seconds plus any remaining fraction of a second
21/// as a floating point value.
22#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
23pub struct Time {
24    pub seconds: u64,
25    pub frac: f64,
26}
27
28impl Time {
29    const SECONDS_PER_MINUTE: u64 = 60;
30    const SECONDS_PER_HOUR: u64 = 60 * 60;
31    const NANOSECONDS_PER_SECOND: u32 = 1_000_000_000;
32    const NANOSECONDS_PER_SECOND_INV: f64 = 1.0 / 1_000_000_000.0;
33
34    pub fn new(seconds: u64, frac: f64) -> Self {
35        Time { seconds, frac }
36    }
37
38    pub fn from_ss(s: u8, ns: u32) -> Option<Time> {
39        if s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
40            return None;
41        }
42
43        let seconds = u64::from(s);
44        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);
45
46        Some(Time { seconds, frac })
47    }
48
49    pub fn from_mmss(m: u8, s: u8, ns: u32) -> Option<Time> {
50        if m > 59 || s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
51            return None;
52        }
53
54        let seconds = (Time::SECONDS_PER_MINUTE * u64::from(m)) + u64::from(s);
55        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);
56
57        Some(Time { seconds, frac })
58    }
59
60    pub fn from_hhmmss(h: u32, m: u8, s: u8, ns: u32) -> Option<Time> {
61        if m > 59 || s > 59 || ns >= Time::NANOSECONDS_PER_SECOND {
62            return None;
63        }
64
65        let seconds = (Time::SECONDS_PER_HOUR * u64::from(h))
66            + (Time::SECONDS_PER_MINUTE * u64::from(m))
67            + u64::from(s);
68
69        let frac = Time::NANOSECONDS_PER_SECOND_INV * f64::from(ns);
70
71        Some(Time { seconds, frac })
72    }
73}
74
75impl From<u8> for Time {
76    fn from(seconds: u8) -> Self {
77        Time::new(u64::from(seconds), 0.0)
78    }
79}
80
81impl From<u16> for Time {
82    fn from(seconds: u16) -> Self {
83        Time::new(u64::from(seconds), 0.0)
84    }
85}
86
87impl From<u32> for Time {
88    fn from(seconds: u32) -> Self {
89        Time::new(u64::from(seconds), 0.0)
90    }
91}
92
93impl From<u64> for Time {
94    fn from(seconds: u64) -> Self {
95        Time::new(seconds, 0.0)
96    }
97}
98
99impl From<f32> for Time {
100    fn from(seconds: f32) -> Self {
101        if seconds >= 0.0 {
102            Time::new(seconds.trunc() as u64, f64::from(seconds.fract()))
103        }
104        else {
105            Time::new(0, 0.0)
106        }
107    }
108}
109
110impl From<f64> for Time {
111    fn from(seconds: f64) -> Self {
112        if seconds >= 0.0 {
113            Time::new(seconds.trunc() as u64, seconds.fract())
114        }
115        else {
116            Time::new(0, 0.0)
117        }
118    }
119}
120
121impl From<std::time::Duration> for Time {
122    fn from(duration: std::time::Duration) -> Self {
123        Time::new(duration.as_secs(), f64::from(duration.subsec_nanos()) / 1_000_000_000.0)
124    }
125}
126
127impl From<Time> for std::time::Duration {
128    fn from(time: Time) -> Self {
129        std::time::Duration::new(time.seconds, (1_000_000_000.0 * time.frac) as u32)
130    }
131}
132
133/// A `TimeBase` is the conversion factor between time, expressed in seconds, and a `TimeStamp` or
134/// `Duration`.
135///
136/// In other words, a `TimeBase` is the length in seconds of one tick of a `TimeStamp` or
137/// `Duration`.
138#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
139pub struct TimeBase {
140    /// The numerator.
141    pub numer: u32,
142    /// The denominator.
143    pub denom: u32,
144}
145
146impl TimeBase {
147    /// Creates a new `TimeBase`. Panics if either the numerator or denominator is 0.
148    pub fn new(numer: u32, denom: u32) -> Self {
149        if numer == 0 || denom == 0 {
150            panic!("TimeBase cannot have 0 numerator or denominator");
151        }
152
153        TimeBase { numer, denom }
154    }
155
156    /// Accurately calculates a `Time` using the `TimeBase` and the provided `TimeStamp`. On
157    /// overflow, the seconds field of `Time` wraps.
158    pub fn calc_time(&self, ts: TimeStamp) -> Time {
159        assert!(self.numer > 0 && self.denom > 0, "TimeBase numerator or denominator are 0.");
160
161        // The dividend requires up-to 96-bits (32-bit timebase numerator * 64-bit timestamp).
162        let dividend = u128::from(ts) * u128::from(self.numer);
163
164        // For an accurate floating point division, both the dividend and divisor must have an
165        // accurate floating point representation. A 64-bit floating point value has a mantissa of
166        // 52 bits and can therefore accurately represent a 52-bit integer. The divisor (the
167        // denominator of the timebase) is limited to 32-bits. Therefore, if the dividend
168        // requires less than 52-bits, a straight-forward floating point division can be used to
169        // calculate the time.
170        if dividend < (1 << 52) {
171            let seconds = (dividend as f64) / f64::from(self.denom);
172
173            Time::new(seconds.trunc() as u64, seconds.fract())
174        }
175        else {
176            // If the dividend requires more than 52 bits, calculate the integer portion using
177            // integer arithmetic, then calculate the fractional part separately.
178            let quotient = dividend / u128::from(self.denom);
179
180            // The remainder is the fractional portion before being divided by the divisor (the
181            // denominator). The remainder will never equal or exceed the divisor (or else the
182            // fractional part would be >= 1.0), so the remainder must fit within a u32.
183            let rem = (dividend - (quotient * u128::from(self.denom))) as u32;
184
185            // Calculate the fractional portion. Since both the remainder and denominator are 32-bit
186            // integers now, 64-bit floating point division will provide enough accuracy.
187            let frac = f64::from(rem) / f64::from(self.denom);
188
189            Time::new(quotient as u64, frac)
190        }
191    }
192
193    /// Accurately calculates a `TimeStamp` from the given `Time` using the `TimeBase` as the
194    /// conversion factor. On overflow, the `TimeStamp` wraps.
195    pub fn calc_timestamp(&self, time: Time) -> TimeStamp {
196        assert!(self.numer > 0 && self.denom > 0, "TimeBase numerator or denominator are 0.");
197        assert!(time.frac >= 0.0 && time.frac < 1.0, "Invalid range for Time fractional part.");
198
199        // The dividing factor.
200        let k = 1.0 / f64::from(self.numer);
201
202        // Multiplying seconds by the denominator requires up-to 96-bits (32-bit timebase
203        // denominator * 64-bit timestamp).
204        let product = u128::from(time.seconds) * u128::from(self.denom);
205
206        // Like calc_time, a 64-bit floating-point value only has 52-bits of integer precision.
207        // If the product requires more than 52-bits, split the product into upper and lower parts
208        // and multiply by k separately, before adding back together.
209        let a = if product > (1 << 52) {
210            // Split the 96-bit product into 48-bit halves.
211            let u = ((product & !0xffff_ffff_ffff) >> 48) as u64;
212            let l = ((product & 0xffff_ffff_ffff) >> 0) as u64;
213
214            let uk = (u as f64) * k;
215            let ul = (l as f64) * k;
216
217            // Add the upper and lower halves.
218            ((uk as u64) << 48).wrapping_add(ul as u64)
219        }
220        else {
221            ((product as f64) * k) as u64
222        };
223
224        // The fractional portion can be calculate directly using floating point arithemtic.
225        let b = (k * f64::from(self.denom) * time.frac) as u64;
226
227        a.wrapping_add(b)
228    }
229}
230
231impl From<TimeBase> for f64 {
232    fn from(timebase: TimeBase) -> Self {
233        f64::from(timebase.numer) / f64::from(timebase.denom)
234    }
235}
236
237impl fmt::Display for TimeBase {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        write!(f, "{}/{}", self.numer, self.denom)
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::{Time, TimeBase};
246    use std::time::Duration;
247
248    #[test]
249    fn verify_timebase() {
250        // Verify accuracy of timestamp -> time
251        let tb1 = TimeBase::new(1, 320);
252
253        assert_eq!(tb1.calc_time(0), Time::new(0, 0.0));
254        assert_eq!(tb1.calc_time(12_345), Time::new(38, 0.578125));
255        assert_eq!(tb1.calc_time(0x0f_ffff_ffff_ffff), Time::new(14_073_748_835_532, 0.796875));
256        assert_eq!(tb1.calc_time(0x10_0000_0000_0001), Time::new(14_073_748_835_532, 0.803125));
257        assert_eq!(tb1.calc_time(u64::MAX), Time::new(57_646_075_230_342_348, 0.796875));
258
259        // Verify overflow wraps seconds
260        let tb2 = TimeBase::new(320, 1);
261        assert_eq!(tb2.calc_time(u64::MAX), Time::new(18_446_744_073_709_551_296, 0.0));
262
263        // Verify accuracy of time -> timestamp
264        assert_eq!(tb1.calc_timestamp(Time::new(0, 0.0)), 0);
265        assert_eq!(tb1.calc_timestamp(Time::new(38, 0.578125)), 12_345);
266        assert_eq!(
267            tb1.calc_timestamp(Time::new(14_073_748_835_532, 0.796875)),
268            0x0f_ffff_ffff_ffff
269        );
270        assert_eq!(
271            tb1.calc_timestamp(Time::new(14_073_748_835_532, 0.803125)),
272            0x10_0000_0000_0001
273        );
274        assert_eq!(tb1.calc_timestamp(Time::new(57_646_075_230_342_348, 0.796875)), u64::MAX);
275    }
276
277    #[test]
278    fn verify_duration_to_time() {
279        // Verify accuracy of Duration -> Time
280        let dur1 = Duration::from_secs_f64(38.578125);
281        let time1 = Time::from(dur1);
282
283        assert_eq!(time1.seconds, 38);
284        assert_eq!(time1.frac, 0.578125);
285    }
286
287    #[test]
288    fn verify_time_to_duration() {
289        // Verify accuracy of Time -> Duration
290        let time1 = Time::new(38, 0.578125);
291        let dur1 = Duration::from(time1);
292
293        let seconds = dur1.as_secs_f64();
294
295        assert_eq!(seconds.trunc(), 38.0);
296        assert_eq!(seconds.fract(), 0.578125);
297    }
298}