#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Rung {
pub height: u32,
pub bitrate: moq_net::bandwidth::Rate,
}
impl Rung {
pub fn new(height: u32, bitrate: moq_net::bandwidth::Rate) -> Self {
Self { height, bitrate }
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("rung {height}p at {bitrate} bps encodes nothing")]
Empty {
height: u32,
bitrate: u64,
},
#[error("rungs {first}p and {second}p share a maximum of {bitrate} bps")]
DuplicateBitrate {
bitrate: u64,
first: u32,
second: u32,
},
#[error("rung {height}p at {bitrate} bps does not rise above {below_height}p at {below_bitrate} bps")]
Unordered {
height: u32,
bitrate: u64,
below_height: u32,
below_bitrate: u64,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Ladder {
rungs: Vec<Rung>,
}
impl Ladder {
pub fn new(rungs: impl IntoIterator<Item = Rung>) -> Result<Self, Error> {
let mut rungs: Vec<Rung> = rungs.into_iter().collect();
for rung in &mut rungs {
if rung.height < 2 || rung.bitrate.as_bps() == 0 {
return Err(Error::Empty {
height: rung.height,
bitrate: rung.bitrate.as_bps(),
});
}
rung.height &= !1;
}
rungs.sort_by_key(|rung| (rung.bitrate, rung.height));
for pair in rungs.windows(2) {
let (below, rung) = (pair[0], pair[1]);
if below.bitrate == rung.bitrate {
return Err(Error::DuplicateBitrate {
bitrate: rung.bitrate.as_bps(),
first: below.height,
second: rung.height,
});
}
if rung.height <= below.height {
return Err(Error::Unordered {
height: rung.height,
bitrate: rung.bitrate.as_bps(),
below_height: below.height,
below_bitrate: below.bitrate.as_bps(),
});
}
}
Ok(Self { rungs })
}
pub fn rungs(&self) -> &[Rung] {
&self.rungs
}
}
impl Default for Ladder {
fn default() -> Self {
Self::new([
Rung::new(240, moq_net::bandwidth::Rate::from_bps(350_000)),
Rung::new(360, moq_net::bandwidth::Rate::from_bps(600_000)),
Rung::new(480, moq_net::bandwidth::Rate::from_bps(1_200_000)),
Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
Rung::new(1080, moq_net::bandwidth::Rate::from_bps(5_000_000)),
])
.expect("the default ladder is ordered")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn heights(ladder: &Ladder) -> Vec<u32> {
ladder.rungs().iter().map(|rung| rung.height).collect()
}
#[test]
fn default_ladder_is_ordered() {
let ladder = Ladder::default();
assert_eq!(heights(&ladder), [240, 360, 480, 720, 1080]);
}
#[test]
fn custom_ladder_out_of_order() {
let ladder = Ladder::new([
Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
Rung::new(240, moq_net::bandwidth::Rate::from_bps(350_000)),
Rung::new(480, moq_net::bandwidth::Rate::from_bps(1_200_000)),
])
.unwrap();
assert_eq!(heights(&ladder), [240, 480, 720]);
assert_eq!(ladder.rungs()[1].bitrate.as_bps(), 1_200_000);
assert_eq!(ladder.rungs()[0].bitrate.as_bps(), 350_000);
}
#[test]
fn duplicate_ceiling_is_refused() {
let err = Ladder::new([
Rung::new(720, moq_net::bandwidth::Rate::from_bps(2_500_000)),
Rung::new(480, moq_net::bandwidth::Rate::from_bps(2_500_000)),
])
.unwrap_err();
assert_eq!(
err,
Error::DuplicateBitrate {
bitrate: 2_500_000,
first: 480,
second: 720,
}
);
}
#[test]
fn duplicate_height_is_refused() {
let err = Ladder::new([
Rung::new(721, moq_net::bandwidth::Rate::from_bps(2_500_000)),
Rung::new(720, moq_net::bandwidth::Rate::from_bps(1_200_000)),
])
.unwrap_err();
assert_eq!(
err,
Error::Unordered {
height: 720,
bitrate: 2_500_000,
below_height: 720,
below_bitrate: 1_200_000,
}
);
}
#[test]
fn resolution_inversion_is_refused() {
let err = Ladder::new([
Rung::new(1080, moq_net::bandwidth::Rate::from_bps(1_000_000)),
Rung::new(360, moq_net::bandwidth::Rate::from_bps(3_000_000)),
])
.unwrap_err();
assert_eq!(
err,
Error::Unordered {
height: 360,
bitrate: 3_000_000,
below_height: 1080,
below_bitrate: 1_000_000,
}
);
}
#[test]
fn rung_without_a_rendition_is_refused() {
assert_eq!(
Ladder::new([Rung::new(1, moq_net::bandwidth::Rate::from_bps(350_000))]).unwrap_err(),
Error::Empty {
height: 1,
bitrate: 350_000
}
);
assert_eq!(
Ladder::new([Rung::new(240, moq_net::bandwidth::Rate::from_bps(0))]).unwrap_err(),
Error::Empty {
height: 240,
bitrate: 0
}
);
}
#[test]
fn empty_ladder_is_allowed() {
assert!(Ladder::new([]).unwrap().rungs().is_empty());
}
}