pub const CONTAINER_OVERHEAD: f64 = 0.02;
pub const DEFAULT_AUDIO_BPS: u64 = 128_000;
pub const ABSOLUTE_MIN_VIDEO_BPS: u64 = 60_000;
#[derive(Debug, Clone, Copy)]
pub struct Rung {
pub height: u32,
pub min_video_bps: u64,
}
pub const LADDER: &[Rung] = &[
Rung {
height: 1080,
min_video_bps: 2_500_000,
},
Rung {
height: 720,
min_video_bps: 1_200_000,
},
Rung {
height: 480,
min_video_bps: 600_000,
},
Rung {
height: 360,
min_video_bps: 350_000,
},
Rung {
height: 240,
min_video_bps: 200_000,
},
Rung {
height: 144,
min_video_bps: 100_000,
},
];
pub fn video_bitrate_bps(target_bytes: u64, duration_sec: f64, audio_bps: u64) -> Option<u64> {
if duration_sec <= 0.0 {
return None;
}
let usable_bits = target_bytes as f64 * 8.0 * (1.0 - CONTAINER_OVERHEAD);
let audio_bits = audio_bps as f64 * duration_sec;
let video_bits = usable_bits - audio_bits;
if video_bits <= 0.0 {
return None;
}
Some((video_bits / duration_sec) as u64)
}
pub fn reduce_target_bytes(original_bytes: u64, reduce_fraction: f64) -> u64 {
let keep = (1.0 - reduce_fraction).clamp(0.0, 1.0);
(original_bytes as f64 * keep).round() as u64
}
pub fn choose_height(src_height: u32, video_bps: u64) -> u32 {
for rung in LADDER {
if rung.height <= src_height && video_bps >= rung.min_video_bps {
return rung.height;
}
}
LADDER
.iter()
.filter(|r| r.height <= src_height)
.map(|r| r.height)
.min()
.unwrap_or(src_height)
}
pub fn fit_audio_bps(target_bytes: u64, duration_sec: f64, candidates: &[u64]) -> Option<u64> {
candidates.iter().copied().find(|&audio_bps| {
video_bitrate_bps(target_bytes, duration_sec, audio_bps)
.is_some_and(|v| v >= ABSOLUTE_MIN_VIDEO_BPS)
})
}
pub fn search_crf(target: f64, lo: u8, hi: u8, mut measure: impl FnMut(u8) -> f64) -> (u8, f64) {
let base = measure(lo);
if base < target {
return (lo, base);
}
let mut best = (lo, base);
let (mut a, mut b) = (lo as i32 + 1, hi as i32);
while a <= b {
let mid = a + (b - a) / 2;
let v = measure(mid as u8);
if v >= target {
best = (mid as u8, v);
a = mid + 1;
} else {
b = mid - 1;
}
}
best
}
pub const AUDIO_STEPS: &[u64] = &[
320_000, 256_000, 192_000, 160_000, 128_000, 96_000, 80_000, 64_000, 48_000, 32_000, 24_000,
16_000, 12_000,
];
pub const ABSOLUTE_MIN_AUDIO_BPS: u64 = 12_000;
pub fn audio_bitrate_bps(target_bytes: u64, duration_sec: f64) -> Option<u64> {
if duration_sec <= 0.0 {
return None;
}
let usable_bits = target_bytes as f64 * 8.0 * (1.0 - CONTAINER_OVERHEAD);
let bps = usable_bits / duration_sec;
if bps <= 0.0 {
return None;
}
Some(bps as u64)
}
pub fn snap_audio_bitrate(bps: u64) -> u64 {
AUDIO_STEPS
.iter()
.copied()
.find(|&step| step <= bps)
.unwrap_or_else(|| *AUDIO_STEPS.last().expect("non-empty ladder"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn video_bitrate_subtracts_audio_and_overhead() {
let v = video_bitrate_bps(8_000_000, 60.0, 128_000).unwrap();
assert!((916_000..=918_000).contains(&v), "got {v}");
}
#[test]
fn video_bitrate_none_when_audio_eats_budget() {
assert_eq!(video_bitrate_bps(100_000, 600.0, 128_000), None);
assert_eq!(video_bitrate_bps(1_000_000, 0.0, 0), None);
}
#[test]
fn reduce_keeps_complement() {
assert_eq!(reduce_target_bytes(1_000_000, 0.70), 300_000);
assert_eq!(reduce_target_bytes(1_000_000, 0.0), 1_000_000);
assert_eq!(reduce_target_bytes(1_000_000, 1.0), 0);
}
#[test]
fn choose_height_picks_best_feasible_rung() {
assert_eq!(choose_height(1080, 4_000_000), 1080);
assert_eq!(choose_height(1080, 800_000), 480);
assert_eq!(choose_height(720, 5_000_000), 720);
assert_eq!(choose_height(1080, 10_000), 144);
assert_eq!(choose_height(100, 500_000), 100);
}
#[test]
fn audio_bitrate_fits_target() {
let bps = audio_bitrate_bps(10_000_000, 3480.0).unwrap();
assert!((22_000..=23_000).contains(&bps), "got {bps}");
assert_eq!(audio_bitrate_bps(1_000_000, 0.0), None);
}
#[test]
fn snap_audio_floors_to_a_step() {
assert_eq!(snap_audio_bitrate(22_528), 16_000);
assert_eq!(snap_audio_bitrate(128_000), 128_000);
assert_eq!(snap_audio_bitrate(130_000), 128_000);
assert_eq!(snap_audio_bitrate(1_000_000), 320_000); assert_eq!(snap_audio_bitrate(12_000), 12_000);
}
#[test]
fn search_crf_picks_highest_meeting_target() {
let vmaf = |crf: u8| 100.0 - 1.5 * (crf as f64 - 18.0);
let (crf, v) = search_crf(91.0, 18, 32, vmaf);
assert_eq!(crf, 24);
assert!(v >= 91.0);
assert!(vmaf(crf + 1) < 91.0);
}
#[test]
fn search_crf_best_effort_when_unreachable() {
let vmaf = |crf: u8| 80.0 - (crf as f64 - 18.0);
let (crf, v) = search_crf(95.0, 18, 32, vmaf);
assert_eq!(crf, 18);
assert_eq!(v, 80.0);
}
#[test]
fn search_crf_keeps_top_quality_when_all_pass() {
let vmaf = |crf: u8| 100.0 - 0.1 * (crf as f64 - 18.0);
let (crf, _) = search_crf(50.0, 18, 32, vmaf);
assert_eq!(crf, 32);
}
#[test]
fn fit_audio_steps_down() {
let candidates = [128_000, 96_000, 64_000, 48_000];
assert_eq!(fit_audio_bps(8_000_000, 60.0, &candidates), Some(128_000));
assert_eq!(fit_audio_bps(1_300_000, 60.0, &candidates), Some(96_000));
assert_eq!(fit_audio_bps(50_000, 600.0, &candidates), None);
}
}