rvoip-rtp-core 0.2.0

RTP/RTCP protocol implementation for the rvoip stack
Documentation
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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
//! 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::sync::{Arc, Mutex};
use std::time::{Instant, SystemTime, UNIX_EPOCH};

use super::clock::MediaClock;
use crate::packet::rtcp::NtpTimestamp;
use crate::RtpSsrc;
use crate::RtpTimestamp;

/// Mapping between a source stream and a target stream
#[allow(dead_code)] // retained (liveness/Drop hold or reserved); not read
struct StreamMapping {
    /// Source stream SSRC
    #[allow(dead_code)] // retained (liveness/Drop hold or reserved); not read
    source_ssrc: RtpSsrc,

    /// Target stream SSRC
    #[allow(dead_code)] // retained (liveness/Drop hold or reserved); not read
    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 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));
    }
}