Skip to main content

rtc_interceptor/flexfec/
coverage.rs

1//! Which media packets each repair packet protects.
2
3use super::bit_array::BitArray;
4
5/// The most media packets one FEC block can protect.
6///
7/// The three packet masks describe 110 positions between them, so this is a property of the wire
8/// format rather than a tuning choice.
9pub const MAX_MEDIA_PACKETS: u32 = 110;
10
11/// The most repair packets one block can produce.
12pub const MAX_FEC_PACKETS: u32 = MAX_MEDIA_PACKETS;
13
14/// The assignment of media packets to repair packets.
15///
16/// Repair packets are **interleaved**: with two of them, the first covers media packets 0, 2,
17/// 4, … and the second covers 1, 3, 5, …. Interleaving is what makes the scheme tolerate a burst —
18/// consecutive losses land in different repair packets, and each can be recovered independently,
19/// whereas contiguous blocks would put a burst entirely inside one and recover none of it.
20#[derive(Debug, Clone)]
21pub struct ProtectionCoverage {
22    masks: Vec<BitArray>,
23    num_fec_packets: u32,
24    num_media_packets: u32,
25}
26
27impl ProtectionCoverage {
28    /// Assign `num_media_packets` media packets across `num_fec_packets` repair packets.
29    ///
30    /// Returns `None` when the media count is zero or beyond what the masks can describe.
31    pub fn new(num_media_packets: u32, num_fec_packets: u32) -> Option<Self> {
32        if num_media_packets == 0 || num_media_packets > MAX_MEDIA_PACKETS {
33            return None;
34        }
35
36        let mut coverage = Self {
37            masks: vec![BitArray::new(); MAX_FEC_PACKETS as usize],
38            num_fec_packets: 0,
39            num_media_packets: 0,
40        };
41        coverage.update(num_media_packets, num_fec_packets);
42        Some(coverage)
43    }
44
45    /// Recompute the assignment for a new block shape.
46    ///
47    /// A no-op when the shape has not changed, which is the common case: a sender protecting a
48    /// steady stream keeps the same counts block after block.
49    pub fn update(&mut self, num_media_packets: u32, num_fec_packets: u32) {
50        if num_media_packets == 0 || num_media_packets > MAX_MEDIA_PACKETS {
51            return;
52        }
53        if num_media_packets == self.num_media_packets && num_fec_packets == self.num_fec_packets {
54            return;
55        }
56
57        self.num_media_packets = num_media_packets;
58        self.num_fec_packets = num_fec_packets.min(MAX_FEC_PACKETS);
59        for mask in &mut self.masks {
60            mask.reset();
61        }
62
63        for fec_index in 0..self.num_fec_packets {
64            let mut media_index = fec_index;
65            while media_index < num_media_packets {
66                self.masks[fec_index as usize].set_bit(media_index);
67                media_index += self.num_fec_packets;
68            }
69        }
70    }
71
72    /// How many repair packets this covers.
73    pub fn num_fec_packets(&self) -> u32 {
74        self.num_fec_packets
75    }
76
77    /// How many media packets this covers.
78    pub fn num_media_packets(&self) -> u32 {
79        self.num_media_packets
80    }
81
82    /// The packet mask of one repair packet.
83    pub fn mask(&self, fec_index: u32) -> Option<&BitArray> {
84        (fec_index < self.num_fec_packets).then(|| &self.masks[fec_index as usize])
85    }
86
87    /// The media packet indices that `fec_index` protects, in order.
88    ///
89    /// Upstream returns a stateful iterator with `Reset`/`First`/`HasNext`, because its encoder
90    /// walks the same coverage three times. A plain `Vec` of indices says the same thing without
91    /// the cursor, and the caller can walk it as often as it likes.
92    pub fn covered_by(&self, fec_index: u32) -> Vec<u32> {
93        let Some(mask) = self.mask(fec_index) else {
94            return Vec::new();
95        };
96        (0..self.num_media_packets)
97            .filter(|&media_index| mask.bit(media_index))
98            .collect()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    /// Coverage as a grid, for readable assertions: one row per repair packet, one column per
107    /// media packet.
108    fn grid(coverage: &ProtectionCoverage) -> Vec<Vec<u32>> {
109        (0..coverage.num_fec_packets())
110            .map(|fec_index| coverage.covered_by(fec_index))
111            .collect()
112    }
113
114    #[test]
115    fn one_repair_packet_covers_everything() {
116        let coverage = ProtectionCoverage::new(5, 1).expect("valid shape");
117        assert_eq!(vec![vec![0, 1, 2, 3, 4]], grid(&coverage));
118    }
119
120    /// The property that makes FEC useful against bursts: consecutive media packets are covered
121    /// by *different* repair packets, so losing two in a row is still recoverable.
122    #[test]
123    fn repair_packets_interleave_rather_than_taking_contiguous_blocks() {
124        let coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
125        assert_eq!(vec![vec![0, 2, 4], vec![1, 3, 5]], grid(&coverage));
126
127        let coverage = ProtectionCoverage::new(7, 3).expect("valid shape");
128        assert_eq!(vec![vec![0, 3, 6], vec![1, 4], vec![2, 5]], grid(&coverage));
129    }
130
131    #[test]
132    fn every_media_packet_is_covered_exactly_once() {
133        for num_media in 1..=20u32 {
134            for num_fec in 1..=5u32 {
135                let coverage = ProtectionCoverage::new(num_media, num_fec).expect("valid shape");
136                let mut covered: Vec<u32> = grid(&coverage).into_iter().flatten().collect();
137                covered.sort_unstable();
138
139                assert_eq!(
140                    (0..num_media).collect::<Vec<_>>(),
141                    covered,
142                    "{num_media} media packets across {num_fec} repair packets"
143                );
144            }
145        }
146    }
147
148    /// More repair packets than media packets leaves the surplus covering nothing — they carry no
149    /// information, and the encoder skips them rather than emitting empty repair packets.
150    #[test]
151    fn surplus_repair_packets_cover_nothing() {
152        let coverage = ProtectionCoverage::new(2, 4).expect("valid shape");
153        assert_eq!(vec![vec![0], vec![1], vec![], vec![]], grid(&coverage));
154    }
155
156    #[test]
157    fn an_impossible_shape_is_rejected() {
158        assert!(
159            ProtectionCoverage::new(0, 1).is_none(),
160            "nothing to protect"
161        );
162        assert!(
163            ProtectionCoverage::new(MAX_MEDIA_PACKETS + 1, 1).is_none(),
164            "beyond what the packet masks can describe"
165        );
166        assert!(ProtectionCoverage::new(MAX_MEDIA_PACKETS, 1).is_some());
167    }
168
169    #[test]
170    fn the_full_range_reaches_the_last_mask() {
171        let coverage = ProtectionCoverage::new(MAX_MEDIA_PACKETS, 1).expect("valid shape");
172        let mask = coverage.mask(0).expect("one repair packet");
173
174        assert!(mask.bit(0), "the first media packet");
175        assert!(mask.bit(MAX_MEDIA_PACKETS - 1), "and the 110th");
176        assert_ne!(0, mask.mask3(), "which only the third mask can describe");
177    }
178
179    #[test]
180    fn updating_to_the_same_shape_changes_nothing() {
181        let mut coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
182        let before = grid(&coverage);
183
184        coverage.update(6, 2);
185        assert_eq!(before, grid(&coverage));
186
187        coverage.update(4, 2);
188        assert_eq!(
189            vec![vec![0, 2], vec![1, 3]],
190            grid(&coverage),
191            "a different shape does recompute"
192        );
193    }
194
195    #[test]
196    fn an_impossible_update_leaves_the_previous_coverage_intact() {
197        let mut coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
198        let before = grid(&coverage);
199
200        coverage.update(0, 2);
201        coverage.update(MAX_MEDIA_PACKETS + 1, 2);
202
203        assert_eq!(before, grid(&coverage));
204    }
205
206    #[test]
207    fn asking_about_a_repair_packet_that_does_not_exist_yields_nothing() {
208        let coverage = ProtectionCoverage::new(5, 2).expect("valid shape");
209        assert!(coverage.mask(2).is_none());
210        assert!(coverage.covered_by(2).is_empty());
211        assert!(coverage.covered_by(u32::MAX).is_empty());
212    }
213}