use std::collections::HashMap;
use std::time::Instant;
use crate::packet::rtcp::NtpTimestamp;
use crate::RtpSsrc;
pub struct MediaSync {
streams: HashMap<RtpSsrc, StreamSyncData>,
reference_ssrc: Option<RtpSsrc>,
created_at: Instant,
}
pub struct StreamSyncData {
pub ssrc: RtpSsrc,
pub clock_rate: u32,
pub last_ntp: Option<NtpTimestamp>,
pub last_rtp: Option<u32>,
pub last_update: Option<Instant>,
pub clock_drift_ppm: f64,
pub drift_history: Vec<f64>,
}
impl MediaSync {
pub fn new() -> Self {
Self {
streams: HashMap::new(),
reference_ssrc: None,
created_at: Instant::now(),
}
}
pub fn register_stream(&mut self, ssrc: RtpSsrc, clock_rate: u32) {
let sync_data = StreamSyncData {
ssrc,
clock_rate,
last_ntp: None,
last_rtp: None,
last_update: None,
clock_drift_ppm: 0.0,
drift_history: Vec::new(),
};
self.streams.insert(ssrc, sync_data);
if self.reference_ssrc.is_none() {
self.reference_ssrc = Some(ssrc);
}
}
pub fn get_streams(&self) -> &HashMap<RtpSsrc, StreamSyncData> {
&self.streams
}
pub fn set_reference_stream(&mut self, ssrc: RtpSsrc) {
if self.streams.contains_key(&ssrc) {
self.reference_ssrc = Some(ssrc);
}
}
pub fn update_from_sr(&mut self, ssrc: RtpSsrc, ntp: NtpTimestamp, rtp: u32) {
let now = Instant::now();
if let Some(stream) = self.streams.get_mut(&ssrc) {
let prev_ntp = stream.last_ntp;
let prev_rtp = stream.last_rtp;
let prev_update = stream.last_update;
stream.last_ntp = Some(ntp);
stream.last_rtp = Some(rtp);
stream.last_update = Some(now);
if let (Some(p_ntp), Some(p_rtp), Some(p_update)) = (prev_ntp, prev_rtp, prev_update) {
let ntp_elapsed_seconds =
ntp.to_u64().wrapping_sub(p_ntp.to_u64()) as f64 / 2.0_f64.powi(32);
let rtp_elapsed_cycles = rtp.wrapping_sub(p_rtp) as f64 / stream.clock_rate as f64;
let _our_elapsed = now.duration_since(p_update).as_secs_f64();
let raw_drift = (ntp_elapsed_seconds - rtp_elapsed_cycles) / ntp_elapsed_seconds;
let drift_ppm = raw_drift * 1_000_000.0;
stream.drift_history.push(drift_ppm);
if stream.drift_history.len() > 10 {
stream.drift_history.remove(0);
}
let avg_drift =
stream.drift_history.iter().sum::<f64>() / stream.drift_history.len() as f64;
stream.clock_drift_ppm = avg_drift;
}
}
}
pub fn convert_timestamp(
&self,
from_ssrc: RtpSsrc,
to_ssrc: RtpSsrc,
rtp_ts: u32,
) -> Option<u32> {
let from_stream = self.streams.get(&from_ssrc)?;
let to_stream = self.streams.get(&to_ssrc)?;
if from_stream.last_ntp.is_none()
|| from_stream.last_rtp.is_none()
|| to_stream.last_ntp.is_none()
|| to_stream.last_rtp.is_none()
{
return None;
}
let _from_ntp = from_stream.last_ntp.unwrap();
let from_rtp_ref = from_stream.last_rtp.unwrap();
let _to_ntp = to_stream.last_ntp.unwrap();
let to_rtp_ref = to_stream.last_rtp.unwrap();
let rtp_diff = rtp_ts.wrapping_sub(from_rtp_ref) as i64;
let seconds_diff = rtp_diff as f64 / from_stream.clock_rate as f64;
let corrected_seconds = seconds_diff * (1.0 + from_stream.clock_drift_ppm / 1_000_000.0);
let to_rtp_diff = (corrected_seconds * to_stream.clock_rate as f64) as i64;
Some(to_rtp_ref.wrapping_add(to_rtp_diff as u32))
}
pub fn rtp_to_ntp(&self, ssrc: RtpSsrc, rtp_ts: u32) -> Option<NtpTimestamp> {
let stream = self.streams.get(&ssrc)?;
if stream.last_ntp.is_none() || stream.last_rtp.is_none() {
return None;
}
let ntp_ref = stream.last_ntp.unwrap();
let rtp_ref = stream.last_rtp.unwrap();
let rtp_diff = rtp_ts.wrapping_sub(rtp_ref) as i64;
let seconds_diff = rtp_diff as f64 / stream.clock_rate as f64;
let corrected_seconds = seconds_diff * (1.0 + stream.clock_drift_ppm / 1_000_000.0);
let ntp_seconds_diff = corrected_seconds.trunc() as u64;
let ntp_fraction_diff =
((corrected_seconds.fract() * 2.0_f64.powi(32)) as u64) & 0xFFFF_FFFF;
let ntp_ref_value = ntp_ref.to_u64();
let target_ntp_value =
ntp_ref_value.wrapping_add((ntp_seconds_diff << 32) | ntp_fraction_diff);
Some(NtpTimestamp::from_u64(target_ntp_value))
}
pub fn ntp_to_rtp(&self, ssrc: RtpSsrc, ntp: NtpTimestamp) -> Option<u32> {
let stream = self.streams.get(&ssrc)?;
if stream.last_ntp.is_none() || stream.last_rtp.is_none() {
return None;
}
let ntp_ref = stream.last_ntp.unwrap();
let rtp_ref = stream.last_rtp.unwrap();
let ntp_ref_value = ntp_ref.to_u64();
let ntp_value = ntp.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 + stream.clock_drift_ppm / 1_000_000.0);
let rtp_diff = (corrected_seconds * stream.clock_rate as f64) as i64;
Some(rtp_ref.wrapping_add(rtp_diff as u32))
}
pub fn get_clock_drift_ppm(&self, ssrc: RtpSsrc) -> Option<f64> {
self.streams.get(&ssrc).map(|s| s.clock_drift_ppm)
}
pub fn are_synchronized(&self, ssrc1: RtpSsrc, ssrc2: RtpSsrc, tolerance_ms: f64) -> bool {
let stream1 = match self.streams.get(&ssrc1) {
Some(s) => s,
None => return false,
};
let stream2 = match self.streams.get(&ssrc2) {
Some(s) => s,
None => return false,
};
if stream1.last_ntp.is_none()
|| stream1.last_rtp.is_none()
|| stream2.last_ntp.is_none()
|| stream2.last_rtp.is_none()
{
return false;
}
let tolerance_sec = tolerance_ms / 1000.0;
let session_duration = Instant::now().duration_since(self.created_at).as_secs_f64();
let max_drift_ppm = (tolerance_sec / session_duration) * 1_000_000.0;
let drift_diff = (stream1.clock_drift_ppm - stream2.clock_drift_ppm).abs();
drift_diff <= max_drift_ppm
}
}
impl Default for MediaSync {
fn default() -> Self {
Self::new()
}
}
pub mod clock;
pub mod mapping;