use std::time::{Duration, Instant};
use crate::packet::rtcp::NtpTimestamp;
use crate::RtpTimestamp;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockSource {
System,
Ntp,
Rtp(u32), }
#[derive(Debug, Clone)]
pub struct MediaClock {
clock_rate: u32,
ref_rtp_ts: RtpTimestamp,
ref_ntp_ts: NtpTimestamp,
ref_system_time: Instant,
drift_ppm: f64,
}
impl MediaClock {
pub fn new(clock_rate: u32, ref_rtp_ts: RtpTimestamp, ref_ntp_ts: NtpTimestamp) -> Self {
Self {
clock_rate,
ref_rtp_ts,
ref_ntp_ts,
ref_system_time: Instant::now(),
drift_ppm: 0.0,
}
}
pub fn now(clock_rate: u32, initial_rtp_ts: RtpTimestamp) -> Self {
Self::new(clock_rate, initial_rtp_ts, NtpTimestamp::now())
}
pub fn clock_rate(&self) -> u32 {
self.clock_rate
}
pub fn set_drift(&mut self, ppm: f64) {
self.drift_ppm = ppm;
}
pub fn drift(&self) -> f64 {
self.drift_ppm
}
pub fn update_reference(&mut self, rtp_ts: RtpTimestamp, ntp_ts: NtpTimestamp) {
self.ref_rtp_ts = rtp_ts;
self.ref_ntp_ts = ntp_ts;
self.ref_system_time = Instant::now();
}
pub fn rtp_to_system_time(&self, rtp_ts: RtpTimestamp) -> Instant {
let ticks_diff = rtp_ts.wrapping_sub(self.ref_rtp_ts) as i64;
let seconds_diff =
(ticks_diff as f64 / self.clock_rate as f64) / (1.0 + self.drift_ppm / 1_000_000.0);
if seconds_diff >= 0.0 {
self.ref_system_time + Duration::from_secs_f64(seconds_diff)
} else {
let abs_diff = -seconds_diff;
self.ref_system_time - Duration::from_secs_f64(abs_diff)
}
}
pub fn system_time_to_rtp(&self, time: Instant) -> RtpTimestamp {
let seconds_diff = if time >= self.ref_system_time {
time.duration_since(self.ref_system_time).as_secs_f64()
} else {
-self.ref_system_time.duration_since(time).as_secs_f64()
};
let corrected_seconds = seconds_diff * (1.0 + self.drift_ppm / 1_000_000.0);
let ticks_diff = (corrected_seconds * self.clock_rate as f64) as i64;
self.ref_rtp_ts.wrapping_add(ticks_diff as u32)
}
pub fn rtp_to_ntp(&self, rtp_ts: RtpTimestamp) -> NtpTimestamp {
let ticks_diff = rtp_ts.wrapping_sub(self.ref_rtp_ts) as i64;
let seconds_diff = ticks_diff as f64 / self.clock_rate as f64;
let corrected_seconds = seconds_diff / (1.0 + self.drift_ppm / 1_000_000.0);
let whole_seconds = corrected_seconds.trunc() as u64;
let fraction = (corrected_seconds.fract() * 2.0_f64.powi(32)) as u64;
let ntp_ref_value = self.ref_ntp_ts.to_u64();
let ntp_value =
ntp_ref_value.wrapping_add((whole_seconds << 32) | (fraction & 0xFFFF_FFFF));
NtpTimestamp::from_u64(ntp_value)
}
pub fn ntp_to_rtp(&self, ntp_ts: NtpTimestamp) -> RtpTimestamp {
let ntp_ref_value = self.ref_ntp_ts.to_u64();
let ntp_value = ntp_ts.to_u64();
let seconds_diff = if ntp_value >= ntp_ref_value {
let diff = ntp_value - ntp_ref_value;
(diff >> 32) as f64 + ((diff & 0xFFFF_FFFF) as f64 / 2.0_f64.powi(32))
} else {
let diff = ntp_ref_value - ntp_value;
-((diff >> 32) as f64 + ((diff & 0xFFFF_FFFF) as f64 / 2.0_f64.powi(32)))
};
let corrected_seconds = seconds_diff * (1.0 + self.drift_ppm / 1_000_000.0);
let ticks_diff = (corrected_seconds * self.clock_rate as f64) as i64;
self.ref_rtp_ts.wrapping_add(ticks_diff as u32)
}
pub fn convert_clock_rate(
&self,
timestamp: RtpTimestamp,
from_rate: u32,
to_rate: u32,
) -> RtpTimestamp {
if from_rate == to_rate {
return timestamp;
}
let seconds = timestamp as f64 / from_rate as f64;
let new_timestamp = (seconds * to_rate as f64) as u32;
new_timestamp
}
}
pub fn calculate_sync_offset(
stream1_rtp: RtpTimestamp,
stream1_ntp: NtpTimestamp,
stream1_rate: u32,
stream2_rtp: RtpTimestamp,
stream2_ntp: NtpTimestamp,
stream2_rate: u32,
) -> f64 {
let stream1_time = stream1_rtp as f64 / stream1_rate as f64;
let stream2_time = stream2_rtp as f64 / stream2_rate as f64;
let stream1_ntp_val = stream1_ntp.to_u64();
let stream2_ntp_val = stream2_ntp.to_u64();
let stream1_ntp_seconds = (stream1_ntp_val >> 32) as f64
+ ((stream1_ntp_val & 0xFFFF_FFFF) as f64) / 2.0_f64.powi(32);
let stream2_ntp_seconds = (stream2_ntp_val >> 32) as f64
+ ((stream2_ntp_val & 0xFFFF_FFFF) as f64) / 2.0_f64.powi(32);
let offset_seconds =
(stream2_ntp_seconds - stream1_ntp_seconds) + (stream1_time - stream2_time);
offset_seconds * 1000.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_media_clock_conversion() {
let ref_ntp = NtpTimestamp::now();
let ref_rtp = 48000;
let clock_rate = 8000;
let clock = MediaClock::new(clock_rate, ref_rtp, ref_ntp);
let ntp = clock.rtp_to_ntp(ref_rtp);
assert_eq!(ntp.seconds, ref_ntp.seconds);
assert_eq!(ntp.fraction, ref_ntp.fraction);
let rtp = clock.ntp_to_rtp(ref_ntp);
assert_eq!(rtp, ref_rtp);
let ts_8khz = 8000; let ts_16khz = clock.convert_clock_rate(ts_8khz, 8000, 16000);
assert_eq!(ts_16khz, 16000);
let ts_48khz = clock.convert_clock_rate(ts_8khz, 8000, 48000);
assert_eq!(ts_48khz, 48000); }
#[test]
fn test_sync_offset_calculation() {
let ntp1 = NtpTimestamp::now();
let ntp1_val = ntp1.to_u64();
let ntp2_val = ntp1_val + (100 * 2_u64.pow(32) / 1000); let ntp2 = NtpTimestamp::from_u64(ntp2_val);
let rtp1 = 1600;
let rtp2 = 24000;
let offset = calculate_sync_offset(rtp1, ntp1, 8000, rtp2, ntp2, 48000);
assert!((offset + 200.0).abs() < 1.0); }
}