rtcp_types/feedback/
sli.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use crate::feedback::FciFeedbackPacketType;
4use crate::{prelude::*, RtcpParseError, RtcpWriteError};
5
6/// Slice Loss Information
7#[derive(Debug)]
8pub struct Sli<'a> {
9    data: &'a [u8],
10}
11
12impl Sli<'_> {
13    /// The macro blocks that have been lost
14    pub fn lost_macroblocks(&self) -> impl Iterator<Item = MacroBlockEntry> + '_ {
15        MacroBlockIter {
16            data: self.data,
17            i: 0,
18        }
19    }
20
21    /// Create a new [`SliBuilder`]
22    pub fn builder() -> SliBuilder {
23        SliBuilder { lost_mbs: vec![] }
24    }
25}
26
27impl<'a> FciParser<'a> for Sli<'a> {
28    const PACKET_TYPE: FciFeedbackPacketType = FciFeedbackPacketType::PAYLOAD;
29    const FCI_FORMAT: u8 = 2;
30
31    fn parse(data: &'a [u8]) -> Result<Self, RtcpParseError> {
32        if data.len() < 4 {
33            return Err(RtcpParseError::Truncated {
34                expected: 4,
35                actual: data.len(),
36            });
37        }
38        Ok(Self { data })
39    }
40}
41
42struct MacroBlockIter<'a> {
43    data: &'a [u8],
44    i: usize,
45}
46
47impl Iterator for MacroBlockIter<'_> {
48    type Item = MacroBlockEntry;
49
50    fn next(&mut self) -> Option<Self::Item> {
51        if self.i + 3 > self.data.len() {
52            return None;
53        }
54        let data = [
55            self.data[self.i],
56            self.data[self.i + 1],
57            self.data[self.i + 2],
58            self.data[self.i + 3],
59        ];
60        self.i += 4;
61        Some(MacroBlockEntry::decode(data))
62    }
63}
64
65/// A macro block entry
66#[derive(Debug, PartialEq, Eq, Copy, Clone)]
67pub struct MacroBlockEntry {
68    start: u16,
69    count: u16,
70    picture_id: u8,
71}
72
73impl MacroBlockEntry {
74    fn encode(&self) -> [u8; 4] {
75        let mut ret = [0; 4];
76        ret[0] = ((self.start & 0x1fe0) >> 5) as u8;
77        ret[1] = ((self.start & 0x1f) << 3) as u8 | ((self.count & 0x1c00) >> 10) as u8;
78        ret[2] = ((self.count & 0x03fc) >> 2) as u8;
79        ret[3] = ((self.count & 0x0003) as u8) << 6 | self.picture_id & 0x3f;
80        ret
81    }
82
83    fn decode(data: [u8; 4]) -> Self {
84        let start = (data[0] as u16) << 5 | (data[1] as u16 & 0xf8) >> 3;
85        let count =
86            ((data[1] & 0x07) as u16) << 10 | (data[2] as u16) << 2 | (data[3] as u16 & 0xc0) >> 6;
87        let picture_id = data[3] & 0x3f;
88        Self {
89            start,
90            count,
91            picture_id,
92        }
93    }
94}
95
96/// Builder for Slice Loss Information
97#[derive(Debug)]
98pub struct SliBuilder {
99    lost_mbs: Vec<MacroBlockEntry>,
100}
101
102impl SliBuilder {
103    /// Add a lost macro block to the SLI
104    pub fn add_lost_macroblock(
105        mut self,
106        start_macroblock: u16,
107        count_macroblocks: u16,
108        picture_id: u8,
109    ) -> Self {
110        self.lost_mbs.push(MacroBlockEntry {
111            start: start_macroblock,
112            count: count_macroblocks,
113            picture_id,
114        });
115        self
116    }
117}
118
119impl FciBuilder<'_> for SliBuilder {
120    fn format(&self) -> u8 {
121        2
122    }
123
124    fn supports_feedback_type(&self) -> FciFeedbackPacketType {
125        FciFeedbackPacketType::PAYLOAD
126    }
127}
128
129impl RtcpPacketWriter for SliBuilder {
130    fn calculate_size(&self) -> Result<usize, RtcpWriteError> {
131        Ok(4 * self.lost_mbs.len())
132    }
133
134    fn write_into_unchecked(&self, buf: &mut [u8]) -> usize {
135        let mut idx = 0;
136        for entry in self.lost_mbs.iter() {
137            let encoded = entry.encode();
138            buf[idx] = encoded[0];
139            buf[idx + 1] = encoded[1];
140            buf[idx + 2] = encoded[2];
141            buf[idx + 3] = encoded[3];
142            idx += 4;
143        }
144        idx
145    }
146
147    fn get_padding(&self) -> Option<u8> {
148        None
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::feedback::PayloadFeedback;
156
157    #[test]
158    fn macroblock_entries() {
159        let mut start = 0;
160        loop {
161            let mut count = 0;
162            loop {
163                for picture_id in 0..=0x3f {
164                    let mbe = MacroBlockEntry {
165                        start,
166                        count,
167                        picture_id,
168                    };
169                    let other = MacroBlockEntry::decode(mbe.encode());
170                    assert_eq!(mbe, other);
171                }
172                count = (count << 1) | 1;
173                if count > 0x1fff {
174                    break;
175                }
176            }
177            start = (start << 1) | 1;
178            if start > 0x1fff {
179                break;
180            }
181        }
182    }
183
184    #[test]
185    fn sli_build_parse() {
186        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
187        let mut data = [0; REQ_LEN];
188        let sli = {
189            let fci = Sli::builder().add_lost_macroblock(0x1234, 0x0987, 0x25);
190            PayloadFeedback::builder_owned(fci)
191                .sender_ssrc(0x98765432)
192                .media_ssrc(0x10fedcba)
193        };
194        assert_eq!(sli.calculate_size().unwrap(), REQ_LEN);
195        let len = sli.write_into(&mut data).unwrap();
196        assert_eq!(len, REQ_LEN);
197        assert_eq!(
198            data,
199            [
200                0x82, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x91, 0xa2,
201                0x61, 0xe5
202            ]
203        );
204
205        let fb = PayloadFeedback::parse(&data).unwrap();
206
207        assert_eq!(fb.sender_ssrc(), 0x98765432);
208        assert_eq!(fb.media_ssrc(), 0x10fedcba);
209        let sli = fb.parse_fci::<Sli>().unwrap();
210        let mut mb_iter = sli.lost_macroblocks();
211        assert_eq!(
212            mb_iter.next(),
213            Some(MacroBlockEntry {
214                start: 0x1234,
215                count: 0x987,
216                picture_id: 0x25
217            })
218        );
219        assert_eq!(mb_iter.next(), None);
220    }
221
222    #[test]
223    fn sli_build_ref() {
224        const REQ_LEN: usize = PayloadFeedback::MIN_PACKET_LEN + 4;
225        let mut data = [0; REQ_LEN];
226        let fci = Sli::builder().add_lost_macroblock(0x1234, 0x0987, 0x25);
227        let sli = PayloadFeedback::builder(&fci)
228            .sender_ssrc(0x98765432)
229            .media_ssrc(0x10fedcba);
230        assert_eq!(sli.calculate_size().unwrap(), REQ_LEN);
231        let len = sli.write_into(&mut data).unwrap();
232        assert_eq!(len, REQ_LEN);
233        assert_eq!(
234            data,
235            [
236                0x82, 0xce, 0x00, 0x03, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x91, 0xa2,
237                0x61, 0xe5
238            ]
239        );
240    }
241}