1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub struct Rung {
10 pub height: u32,
13
14 pub bitrate: moq_net::bandwidth::Rate,
17}
18
19impl Rung {
20 pub fn new(height: u32, bitrate: moq_net::bandwidth::Rate) -> Self {
22 Self { height, bitrate }
23 }
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
28#[non_exhaustive]
29pub enum Error {
30 #[error("rung {height}p at {bitrate} bps encodes nothing")]
33 Empty {
34 height: u32,
36 bitrate: u64,
38 },
39
40 #[error("rungs {first}p and {second}p share a maximum of {bitrate} bps")]
43 DuplicateBitrate {
44 bitrate: u64,
46 first: u32,
48 second: u32,
50 },
51
52 #[error("rung {height}p at {bitrate} bps does not rise above {below_height}p at {below_bitrate} bps")]
56 Unordered {
57 height: u32,
59 bitrate: u64,
61 below_height: u32,
63 below_bitrate: u64,
65 },
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct Ladder {
76 rungs: Vec<Rung>,
78}
79
80impl Ladder {
81 pub fn new(rungs: impl IntoIterator<Item = Rung>) -> Result<Self, Error> {
89 let mut rungs: Vec<Rung> = rungs.into_iter().collect();
90 for rung in &mut rungs {
91 if rung.height < 2 || rung.bitrate.as_bps() == 0 {
92 return Err(Error::Empty {
93 height: rung.height,
94 bitrate: rung.bitrate.as_bps(),
95 });
96 }
97 rung.height &= !1;
99 }
100
101 rungs.sort_by_key(|rung| (rung.bitrate, rung.height));
102
103 for pair in rungs.windows(2) {
104 let (below, rung) = (pair[0], pair[1]);
105 if below.bitrate == rung.bitrate {
106 return Err(Error::DuplicateBitrate {
107 bitrate: rung.bitrate.as_bps(),
108 first: below.height,
109 second: rung.height,
110 });
111 }
112 if rung.height <= below.height {
113 return Err(Error::Unordered {
114 height: rung.height,
115 bitrate: rung.bitrate.as_bps(),
116 below_height: below.height,
117 below_bitrate: below.bitrate.as_bps(),
118 });
119 }
120 }
121
122 Ok(Self { rungs })
123 }
124
125 pub fn rungs(&self) -> &[Rung] {
127 &self.rungs
128 }
129}
130
131impl Default for Ladder {
132 fn default() -> Self {
135 Self::new([
136 Rung::new(240, moq_net::bandwidth::Rate::from_bps(350_000)),
137 Rung::new(360, moq_net::bandwidth::Rate::from_bps(600_000)),
138 Rung::new(480, moq_net::bandwidth::Rate::from_bps(1_200_000)),
139 Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
140 Rung::new(1080, moq_net::bandwidth::Rate::from_bps(5_000_000)),
141 ])
142 .expect("the default ladder is ordered")
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn heights(ladder: &Ladder) -> Vec<u32> {
151 ladder.rungs().iter().map(|rung| rung.height).collect()
152 }
153
154 #[test]
155 fn default_ladder_is_ordered() {
156 let ladder = Ladder::default();
157 assert_eq!(heights(&ladder), [240, 360, 480, 720, 1080]);
158 }
159
160 #[test]
163 fn custom_ladder_out_of_order() {
164 let ladder = Ladder::new([
165 Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
166 Rung::new(240, moq_net::bandwidth::Rate::from_bps(350_000)),
167 Rung::new(480, moq_net::bandwidth::Rate::from_bps(1_200_000)),
168 ])
169 .unwrap();
170 assert_eq!(heights(&ladder), [240, 480, 720]);
171 assert_eq!(ladder.rungs()[1].bitrate.as_bps(), 1_200_000);
174 assert_eq!(ladder.rungs()[0].bitrate.as_bps(), 350_000);
175 }
176
177 #[test]
179 fn duplicate_ceiling_is_refused() {
180 let err = Ladder::new([
181 Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
182 Rung::new(480, moq_net::bandwidth::Rate::from_bps(2_500_000)),
183 ])
184 .unwrap_err();
185 assert_eq!(
186 err,
187 Error::DuplicateBitrate {
188 bitrate: 2_500_000,
189 first: 480,
190 second: 720,
191 }
192 );
193 }
194
195 #[test]
199 fn duplicate_height_is_refused() {
200 let err = Ladder::new([
201 Rung::new(721, moq_net::bandwidth::Rate::from_bps(2_500_000)),
202 Rung::new(720, moq_net::bandwidth::Rate::from_bps(1_200_000)),
203 ])
204 .unwrap_err();
205 assert_eq!(
206 err,
207 Error::Unordered {
208 height: 720,
209 bitrate: 2_500_000,
210 below_height: 720,
211 below_bitrate: 1_200_000,
212 }
213 );
214 }
215
216 #[test]
219 fn resolution_inversion_is_refused() {
220 let err = Ladder::new([
221 Rung::new(1080, moq_net::bandwidth::Rate::from_bps(1_000_000)),
222 Rung::new(360, moq_net::bandwidth::Rate::from_bps(3_000_000)),
223 ])
224 .unwrap_err();
225 assert_eq!(
226 err,
227 Error::Unordered {
228 height: 360,
229 bitrate: 3_000_000,
230 below_height: 1080,
231 below_bitrate: 1_000_000,
232 }
233 );
234 }
235
236 #[test]
237 fn rung_without_a_rendition_is_refused() {
238 assert_eq!(
239 Ladder::new([Rung::new(1, moq_net::bandwidth::Rate::from_bps(350_000))]).unwrap_err(),
240 Error::Empty {
241 height: 1,
242 bitrate: 350_000
243 }
244 );
245 assert_eq!(
246 Ladder::new([Rung::new(240, moq_net::bandwidth::Rate::from_bps(0))]).unwrap_err(),
247 Error::Empty {
248 height: 240,
249 bitrate: 0
250 }
251 );
252 }
253
254 #[test]
255 fn empty_ladder_is_allowed() {
256 assert!(Ladder::new([]).unwrap().rungs().is_empty());
257 }
258}