1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
//! Timestamp mapping module
//!
//! This module provides mapping between different timestamp domains,
//! such as RTP timestamps from different streams, NTP timestamps,
//! and system time.
use std::collections::HashMap;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use std::sync::{Arc, Mutex};
use crate::RtpSsrc;
use crate::RtpTimestamp;
use crate::packet::rtcp::NtpTimestamp;
use super::clock::MediaClock;
/// Mapping between a source stream and a target stream
struct StreamMapping {
/// Source stream SSRC
source_ssrc: RtpSsrc,
/// Target stream SSRC
target_ssrc: RtpSsrc,
/// Source stream clock rate
source_rate: u32,
/// Target stream clock rate
target_rate: u32,
/// Source stream reference RTP timestamp
source_rtp_ref: RtpTimestamp,
/// Target stream reference RTP timestamp
target_rtp_ref: RtpTimestamp,
/// Common reference NTP timestamp
ntp_ref: NtpTimestamp,
/// When this mapping was last updated
last_update: Instant,
/// Measured clock drift between source and target (parts per million)
drift_ppm: f64,
}
impl StreamMapping {
/// Create a new mapping between two streams
pub fn new(
source_ssrc: RtpSsrc,
target_ssrc: RtpSsrc,
source_rate: u32,
target_rate: u32,
source_rtp: RtpTimestamp,
target_rtp: RtpTimestamp,
ntp: NtpTimestamp,
) -> Self {
Self {
source_ssrc,
target_ssrc,
source_rate,
target_rate,
source_rtp_ref: source_rtp,
target_rtp_ref: target_rtp,
ntp_ref: ntp,
last_update: Instant::now(),
drift_ppm: 0.0,
}
}
/// Map a source RTP timestamp to the equivalent in the target stream
pub fn map_timestamp(&self, source_rtp: RtpTimestamp) -> RtpTimestamp {
// Calculate time difference in source stream ticks
let source_diff = source_rtp.wrapping_sub(self.source_rtp_ref) as i64;
// Convert to seconds, using source clock rate
let seconds = source_diff as f64 / self.source_rate as f64;
// Apply drift correction
let corrected_seconds = seconds * (1.0 + self.drift_ppm / 1_000_000.0);
// Convert to target stream ticks
let target_diff = (corrected_seconds * self.target_rate as f64) as i64;
// Calculate target timestamp
self.target_rtp_ref.wrapping_add(target_diff as u32)
}
/// Update the mapping with new reference timestamps
pub fn update(
&mut self,
source_rtp: RtpTimestamp,
target_rtp: RtpTimestamp,
ntp: NtpTimestamp
) -> f64 {
// Calculate elapsed time between updates
let now = Instant::now();
let elapsed = now.duration_since(self.last_update).as_secs_f64();
if elapsed > 0.0 {
// Calculate drift between source and target
let source_ticks = source_rtp.wrapping_sub(self.source_rtp_ref) as i64;
let target_ticks = target_rtp.wrapping_sub(self.target_rtp_ref) as i64;
let source_seconds = source_ticks as f64 / self.source_rate as f64;
let target_seconds = target_ticks as f64 / self.target_rate as f64;
// Calculate drift in PPM (parts per million)
let drift = (target_seconds - source_seconds) / elapsed;
self.drift_ppm = drift * 1_000_000.0;
}
// Update reference points
self.source_rtp_ref = source_rtp;
self.target_rtp_ref = target_rtp;
self.ntp_ref = ntp;
self.last_update = now;
// Return drift in milliseconds per second
self.drift_ppm / 1000.0
}
}
/// Manages timestamp mappings between multiple streams
#[derive(Clone)]
pub struct TimestampMapper {
/// Maps from (source_ssrc, target_ssrc) to StreamMapping
mappings: Arc<Mutex<HashMap<(RtpSsrc, RtpSsrc), StreamMapping>>>,
/// Maps from SSRC to its corresponding MediaClock
clocks: Arc<Mutex<HashMap<RtpSsrc, MediaClock>>>,
}
impl TimestampMapper {
/// Create a new timestamp mapper
pub fn new() -> Self {
Self {
mappings: Arc::new(Mutex::new(HashMap::new())),
clocks: Arc::new(Mutex::new(HashMap::new())),
}
}
/// Register a new stream with its clock rate
pub fn register_stream(&self, ssrc: RtpSsrc, clock_rate: u32, initial_rtp: RtpTimestamp) {
if let Ok(mut clocks) = self.clocks.lock() {
// Create a new media clock for this stream
let clock = MediaClock::now(clock_rate, initial_rtp);
clocks.insert(ssrc, clock);
}
}
/// Update stream timing information from an RTCP sender report
pub fn update_from_sr(&self, ssrc: RtpSsrc, ntp: NtpTimestamp, rtp: RtpTimestamp) {
if let Ok(mut clocks) = self.clocks.lock() {
// If we have a clock for this stream, update it
if let Some(clock) = clocks.get_mut(&ssrc) {
clock.update_reference(rtp, ntp);
} else {
// If not, create one if we know the clock rate
// (fallback to common rates based on payload type)
let clock_rate = 8000; // Default to 8kHz
let clock = MediaClock::new(clock_rate, rtp, ntp);
clocks.insert(ssrc, clock);
}
}
}
/// Create or update a mapping between two streams
pub fn map_streams(
&self,
source_ssrc: RtpSsrc,
target_ssrc: RtpSsrc,
source_rtp: RtpTimestamp,
target_rtp: RtpTimestamp,
ntp: NtpTimestamp,
) -> Option<f64> {
// Get the clock rates
let (source_rate, target_rate) = if let Ok(clocks) = self.clocks.lock() {
let source_clock = clocks.get(&source_ssrc)?;
let target_clock = clocks.get(&target_ssrc)?;
(source_clock.clock_rate(), target_clock.clock_rate())
} else {
return None;
};
if let Ok(mut mappings) = self.mappings.lock() {
let key = (source_ssrc, target_ssrc);
// Check if mapping already exists
if let Some(mapping) = mappings.get_mut(&key) {
// Update existing mapping
let drift = mapping.update(source_rtp, target_rtp, ntp);
Some(drift)
} else {
// Create new mapping
let mapping = StreamMapping::new(
source_ssrc, target_ssrc, source_rate, target_rate,
source_rtp, target_rtp, ntp
);
mappings.insert(key, mapping);
Some(0.0) // No drift initially
}
} else {
None
}
}
/// Map a timestamp from one stream to another
pub fn map_timestamp(
&self,
source_ssrc: RtpSsrc,
target_ssrc: RtpSsrc,
source_rtp: RtpTimestamp,
) -> Option<RtpTimestamp> {
if let Ok(mappings) = self.mappings.lock() {
let key = (source_ssrc, target_ssrc);
if let Some(mapping) = mappings.get(&key) {
// Direct mapping exists
Some(mapping.map_timestamp(source_rtp))
} else {
// Try indirect mapping via NTP timestamps
if let Ok(clocks) = self.clocks.lock() {
let source_clock = clocks.get(&source_ssrc)?;
let target_clock = clocks.get(&target_ssrc)?;
// Convert source RTP to NTP
let ntp = source_clock.rtp_to_ntp(source_rtp);
// Convert NTP to target RTP
Some(target_clock.ntp_to_rtp(ntp))
} else {
None
}
}
} else {
None
}
}
/// Get estimated clock drift between two streams in PPM
pub fn get_drift(&self, source_ssrc: RtpSsrc, target_ssrc: RtpSsrc) -> Option<f64> {
if let Ok(mappings) = self.mappings.lock() {
let key = (source_ssrc, target_ssrc);
mappings.get(&key).map(|mapping| mapping.drift_ppm)
} else {
None
}
}
/// Convert an RTP timestamp to wall clock time
pub fn rtp_to_wallclock(&self, ssrc: RtpSsrc, rtp: RtpTimestamp) -> Option<Instant> {
if let Ok(clocks) = self.clocks.lock() {
let clock = clocks.get(&ssrc)?;
Some(clock.rtp_to_system_time(rtp))
} else {
None
}
}
/// Convert wall clock time to an RTP timestamp
pub fn wallclock_to_rtp(&self, ssrc: RtpSsrc, time: Instant) -> Option<RtpTimestamp> {
if let Ok(clocks) = self.clocks.lock() {
let clock = clocks.get(&ssrc)?;
Some(clock.system_time_to_rtp(time))
} else {
None
}
}
/// Get the synchronization offset between two streams in milliseconds
///
/// Returns a positive value if target stream is ahead of source stream
/// and should be delayed to achieve synchronization.
pub fn get_sync_offset(&self, source_ssrc: RtpSsrc, target_ssrc: RtpSsrc) -> Option<f64> {
// Try to get the direct mapping
if let Ok(mappings) = self.mappings.lock() {
let key = (source_ssrc, target_ssrc);
if let Some(mapping) = mappings.get(&key) {
// Calculate how far the streams have drifted
let elapsed = Instant::now().duration_since(mapping.last_update).as_secs_f64();
// Drift in ms per second * elapsed seconds = total drift in ms
let drift_ms = (mapping.drift_ppm / 1000.0) * elapsed;
return Some(drift_ms);
}
}
// If no direct mapping, try to calculate via NTP timestamps
if let Ok(clocks) = self.clocks.lock() {
let source_clock = clocks.get(&source_ssrc)?;
let target_clock = clocks.get(&target_ssrc)?;
// Create a common reference point (now)
let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?;
let ntp = NtpTimestamp::from_duration_since_unix_epoch(now);
// Convert to RTP timestamps in each stream's clock domain
let source_rtp = source_clock.ntp_to_rtp(ntp);
let target_rtp = target_clock.ntp_to_rtp(ntp);
// Calculate playback points in seconds
let source_seconds = source_rtp as f64 / source_clock.clock_rate() as f64;
let target_seconds = target_rtp as f64 / target_clock.clock_rate() as f64;
// Calculate offset in milliseconds
let offset_ms = (target_seconds - source_seconds) * 1000.0;
Some(offset_ms)
} else {
None
}
}
}
impl Default for TimestampMapper {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_mapping() {
// Create a mapping between two streams
let source_ssrc = 0x1234;
let target_ssrc = 0x5678;
let source_rate = 8000; // 8 kHz audio
let target_rate = 90000; // 90 kHz video
let source_rtp = 1600; // 200ms of audio
let target_rtp = 18000; // 200ms of video
let ntp = NtpTimestamp::now();
let mut mapping = StreamMapping::new(
source_ssrc, target_ssrc, source_rate, target_rate,
source_rtp, target_rtp, ntp
);
// Test mapping 400ms (3200 samples) of audio to video
let source_rtp_400ms = source_rtp.wrapping_add(3200);
let target_rtp_400ms = mapping.map_timestamp(source_rtp_400ms);
// Calculate expected value:
// 3200 - 1600 = 1600 audio samples = 200ms
// 200ms * 90000/1000 samples/ms = 18000 video samples
// 18000 + 18000 = 36000
let expected = target_rtp + 36000;
assert_eq!(target_rtp_400ms, expected);
}
#[test]
fn test_timestamp_mapper() {
let mapper = TimestampMapper::new();
// Register two streams
let audio_ssrc = 0x1234;
let video_ssrc = 0x5678;
let audio_rate = 8000; // 8 kHz
let video_rate = 90000; // 90 kHz
mapper.register_stream(audio_ssrc, audio_rate, 800); // 100ms
mapper.register_stream(video_ssrc, video_rate, 9000); // 100ms
// Create mapping with both at 200ms
let ntp = NtpTimestamp::now();
let audio_rtp_200ms = 1600; // 200ms at 8kHz
let video_rtp_200ms = 18000; // 200ms at 90kHz
mapper.map_streams(audio_ssrc, video_ssrc, audio_rtp_200ms, video_rtp_200ms, ntp);
// Map 400ms of audio to video
let audio_rtp_400ms = 3200; // 400ms at 8kHz
let video_rtp_400ms = mapper.map_timestamp(audio_ssrc, video_ssrc, audio_rtp_400ms);
// Expected: video_rtp_200ms + (400ms - 200ms) * 90kHz = video_rtp_200ms + 18000
assert_eq!(video_rtp_400ms, Some(video_rtp_200ms + 18000));
}
}