use std::{
cmp::{Eq, Ordering, PartialEq, PartialOrd},
fmt::{self, Debug, Formatter},
ops::{Add, AddAssign, Sub, SubAssign},
time::Duration,
};
extern "C" {
fn ffw_rescale_q(n: i64, aq_num: u32, aq_den: u32, bq_num: u32, bq_den: u32) -> i64;
fn ffw_null_timestamp() -> i64;
}
#[derive(Copy, Clone)]
pub struct TimeBase {
num: u32,
den: u32,
}
impl TimeBase {
pub const MICROSECONDS: TimeBase = TimeBase::new(1, 1_000_000);
pub const fn new(num: u32, den: u32) -> Self {
Self { num, den }
}
pub fn num(&self) -> u32 {
self.num
}
pub fn den(&self) -> u32 {
self.den
}
}
impl Debug for TimeBase {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "{}/{}", self.num(), self.den())
}
}
#[derive(Copy, Clone)]
pub struct Timestamp {
timestamp: i64,
time_base: TimeBase,
}
impl Timestamp {
pub fn null() -> Self {
unsafe {
Self {
timestamp: ffw_null_timestamp(),
time_base: TimeBase::MICROSECONDS,
}
}
}
pub const fn new(timestamp: i64, time_base: TimeBase) -> Self {
Self {
timestamp,
time_base,
}
}
pub const fn from_secs(timestamp: i64) -> Self {
Self::new(timestamp, TimeBase::new(1, 1))
}
pub const fn from_millis(timestamp: i64) -> Self {
Self::new(timestamp, TimeBase::new(1, 1_000))
}
pub const fn from_micros(timestamp: i64) -> Self {
Self::new(timestamp, TimeBase::new(1, 1_000_000))
}
pub const fn from_nanos(timestamp: i64) -> Self {
Self::new(timestamp, TimeBase::new(1, 1_000_000_000))
}
pub fn time_base(&self) -> TimeBase {
self.time_base
}
pub fn timestamp(&self) -> i64 {
self.timestamp
}
pub fn with_raw_timestamp(mut self, timestamp: i64) -> Self {
self.timestamp = timestamp;
self
}
pub fn is_null(&self) -> bool {
unsafe { self.timestamp == ffw_null_timestamp() }
}
pub fn with_time_base(&self, time_base: TimeBase) -> Self {
let timestamp = if self.is_null() {
self.timestamp
} else {
unsafe {
ffw_rescale_q(
self.timestamp,
self.time_base.num,
self.time_base.den,
time_base.num,
time_base.den,
)
}
};
Self {
timestamp,
time_base,
}
}
pub fn as_secs(&self) -> Option<i64> {
if self.is_null() {
None
} else {
let ts = self.with_time_base(TimeBase::new(1, 1));
Some(ts.timestamp)
}
}
pub fn as_millis(&self) -> Option<i64> {
if self.is_null() {
None
} else {
let ts = self.with_time_base(TimeBase::new(1, 1_000));
Some(ts.timestamp)
}
}
pub fn as_micros(&self) -> Option<i64> {
if self.is_null() {
None
} else {
let ts = self.with_time_base(TimeBase::new(1, 1_000_000));
Some(ts.timestamp)
}
}
pub fn as_nanos(&self) -> Option<i64> {
if self.is_null() {
None
} else {
let ts = self.with_time_base(TimeBase::new(1, 1_000_000_000));
Some(ts.timestamp)
}
}
pub fn as_f32(&self) -> Option<f32> {
if self.is_null() {
None
} else {
Some(self.timestamp as f32 * self.time_base.num as f32 / self.time_base.den as f32)
}
}
pub fn as_f64(&self) -> Option<f64> {
if self.is_null() {
None
} else {
Some(self.timestamp as f64 * self.time_base.num as f64 / self.time_base.den as f64)
}
}
}
impl Debug for Timestamp {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
if let Some(millis) = self.as_millis() {
write!(f, "{}.{:03}s", millis / 1_000, millis % 1_000)
} else {
write!(f, "(null)")
}
}
}
impl Add<Duration> for Timestamp {
type Output = Timestamp;
fn add(mut self, rhs: Duration) -> Self::Output {
self += rhs;
self
}
}
impl AddAssign<Duration> for Timestamp {
fn add_assign(&mut self, rhs: Duration) {
if self.is_null() {
return;
}
self.timestamp += Self::from_secs(rhs.as_secs() as i64)
.with_time_base(self.time_base)
.timestamp();
self.timestamp += Self::from_nanos(rhs.subsec_nanos() as i64)
.with_time_base(self.time_base)
.timestamp();
}
}
impl Sub<Duration> for Timestamp {
type Output = Timestamp;
fn sub(mut self, rhs: Duration) -> Self::Output {
self -= rhs;
self
}
}
impl SubAssign<Duration> for Timestamp {
fn sub_assign(&mut self, rhs: Duration) {
if self.is_null() {
return;
}
self.timestamp -= Self::from_secs(rhs.as_secs() as i64)
.with_time_base(self.time_base)
.timestamp();
self.timestamp -= Self::from_nanos(rhs.subsec_nanos() as i64)
.with_time_base(self.time_base)
.timestamp();
}
}
impl Sub for Timestamp {
type Output = Duration;
fn sub(mut self, rhs: Self) -> Self::Output {
assert!(!self.is_null());
assert!(!rhs.is_null());
let rhs = rhs.with_time_base(self.time_base);
self.timestamp -= rhs.timestamp;
if self.timestamp < 0 {
panic!("out of range");
}
let secs = self.with_time_base(TimeBase::new(1, 1));
self.timestamp -= secs.with_time_base(self.time_base).timestamp();
let nanos = self.as_nanos().unwrap();
Duration::new(secs.timestamp as u64, nanos as u32)
}
}
impl PartialEq for Timestamp {
fn eq(&self, other: &Timestamp) -> bool {
let a = self.as_micros();
let b = other.as_micros();
a == b
}
}
impl Eq for Timestamp {}
impl PartialOrd for Timestamp {
fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering> {
if let Some(a) = self.as_micros() {
if let Some(b) = other.as_micros() {
return a.partial_cmp(&b);
}
}
None
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{TimeBase, Timestamp};
#[test]
fn test_duration_add() {
let mut ts = Timestamp::new(333, TimeBase::new(1, 90_000));
ts += Duration::from_millis(100);
assert_eq!(ts.timestamp, 9333);
}
#[test]
fn test_duration_sub() {
let mut ts = Timestamp::new(333, TimeBase::new(1, 90_000));
ts -= Duration::from_millis(50);
assert_eq!(ts.timestamp, -4167);
}
#[test]
fn test_timestamp_sub() {
let a = Timestamp::new(333, TimeBase::new(1, 90_000));
let b = Timestamp::new(79, TimeBase::new(1, 50_000));
let delta = a - b;
assert_eq!(delta.as_secs(), 0);
assert_eq!(delta.subsec_nanos(), 2_122_222);
}
#[test]
fn test_comparisons() {
let a = Timestamp::from_secs(1);
let b = Timestamp::from_millis(1_000);
assert_eq!(a, b);
let a = Timestamp::from_secs(1);
let b = Timestamp::from_millis(1_001);
assert_ne!(a, b);
assert!(a < b);
let a = Timestamp::from_secs(1);
let b = Timestamp::from_micros(1_000_001);
assert_ne!(a, b);
assert!(a < b);
let a = Timestamp::from_secs(1);
let b = Timestamp::from_nanos(1_000_000_001);
assert_eq!(a, b);
}
}