#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateControlMode {
Cbr,
CappedVbr,
}
#[derive(Debug, Clone, Copy)]
pub struct RateControlConfig {
pub mode: RateControlMode,
pub target_bitrate: u32,
pub max_bitrate: u32,
pub vbv_buffer_bits: u32,
pub fps_num: u32,
pub fps_den: u32,
pub min_qp: i32,
pub max_qp: i32,
}
impl RateControlConfig {
pub fn cbr(target_bitrate: u32, fps_num: u32, fps_den: u32) -> Self {
Self {
mode: RateControlMode::Cbr,
target_bitrate,
max_bitrate: target_bitrate,
vbv_buffer_bits: target_bitrate,
fps_num,
fps_den,
min_qp: 10,
max_qp: 51,
}
}
pub fn capped_vbr(target_bitrate: u32, max_bitrate: u32, fps_num: u32, fps_den: u32) -> Self {
Self {
mode: RateControlMode::CappedVbr,
target_bitrate,
max_bitrate,
vbv_buffer_bits: max_bitrate,
fps_num,
fps_den,
min_qp: 10,
max_qp: 51,
}
}
fn validate(&self) {
assert!(self.target_bitrate > 0, "target_bitrate must be > 0");
assert!(self.vbv_buffer_bits > 0, "vbv_buffer_bits must be > 0");
assert!(self.fps_num > 0 && self.fps_den > 0, "fps must be > 0");
assert!(
(0..=51).contains(&self.min_qp) && (0..=51).contains(&self.max_qp),
"QP bounds must be within 0..=51"
);
assert!(self.min_qp <= self.max_qp, "min_qp must be <= max_qp");
if self.mode == RateControlMode::CappedVbr {
assert!(
self.max_bitrate >= self.target_bitrate,
"max_bitrate must be >= target_bitrate in capped-VBR mode"
);
}
}
fn arrival_rate(&self) -> f64 {
match self.mode {
RateControlMode::Cbr => f64::from(self.target_bitrate),
RateControlMode::CappedVbr => f64::from(self.max_bitrate.max(self.target_bitrate)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RcFrameKind {
Idr,
P,
B,
}
const B_QP_OFFSET: f64 = 2.0;
const NUM_KINDS: usize = 3;
#[derive(Debug, Clone, Copy)]
pub struct FramePlan {
pub qp: i32,
pub target_bits: f64,
pub max_bits: u64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CommitOutcome {
pub filler_bits: u64,
pub vbv_underflow: bool,
}
fn qstep(qp: i32) -> f64 {
(2.0f64).powf((qp as f64 - 4.0) / 6.0)
}
fn qp_from_qstep(step: f64) -> f64 {
4.0 + 6.0 * step.max(1e-9).log2()
}
#[derive(Debug, Clone)]
pub struct RateController {
cfg: RateControlConfig,
bits_per_frame: f64,
arrival_per_frame: f64,
fullness: f64,
cplx: [f64; NUM_KINDS],
cplx_seeded: [bool; NUM_KINDS],
last_qp: [Option<i32>; NUM_KINDS],
kind_count: [u64; NUM_KINDS],
frames: u64,
total_bits: u64,
total_filler_bits: u64,
pixels_per_frame: f64,
}
impl RateController {
pub fn new(cfg: RateControlConfig, width: u32, height: u32) -> Self {
cfg.validate();
assert!(width > 0 && height > 0);
let bits_per_frame =
f64::from(cfg.target_bitrate) * f64::from(cfg.fps_den) / f64::from(cfg.fps_num);
let arrival_per_frame =
cfg.arrival_rate() * f64::from(cfg.fps_den) / f64::from(cfg.fps_num);
Self {
cfg,
bits_per_frame,
arrival_per_frame,
fullness: 0.6 * f64::from(cfg.vbv_buffer_bits),
cplx: [0.0; NUM_KINDS],
cplx_seeded: [false; NUM_KINDS],
last_qp: [None; NUM_KINDS],
kind_count: [0; NUM_KINDS],
frames: 0,
total_bits: 0,
total_filler_bits: 0,
pixels_per_frame: f64::from(width) * f64::from(height),
}
}
pub fn config(&self) -> &RateControlConfig {
&self.cfg
}
pub fn cpb_fullness(&self) -> f64 {
self.fullness
}
pub fn average_bitrate(&self) -> Option<f64> {
if self.frames == 0 {
return None;
}
let seconds =
self.frames as f64 * f64::from(self.cfg.fps_den) / f64::from(self.cfg.fps_num);
Some(self.total_bits as f64 / seconds)
}
pub fn total_filler_bits(&self) -> u64 {
self.total_filler_bits
}
fn kind_index(kind: RcFrameKind) -> usize {
match kind {
RcFrameKind::Idr => 0,
RcFrameKind::P => 1,
RcFrameKind::B => 2,
}
}
fn kind_qp_bias(kind: RcFrameKind) -> i32 {
match kind {
RcFrameKind::Idr => -2,
RcFrameKind::P => 0,
RcFrameKind::B => 2,
}
}
fn kind_weights(&self) -> [f64; NUM_KINDS] {
const IDR_DEFAULT_RATIO: f64 = 4.0;
const B_DEFAULT_RATIO: f64 = 0.6;
let refc = if self.cplx_seeded[1] && self.cplx[1] > 0.0 {
self.cplx[1]
} else if self.cplx_seeded[0] && self.cplx[0] > 0.0 {
self.cplx[0] / IDR_DEFAULT_RATIO
} else if self.cplx_seeded[2] && self.cplx[2] > 0.0 {
self.cplx[2] / B_DEFAULT_RATIO
} else {
1.0
};
let u_i = if self.cplx_seeded[0] {
(self.cplx[0] / refc).clamp(1.0, 6.0) * refc
} else {
IDR_DEFAULT_RATIO * refc
};
let u_p = if self.cplx_seeded[1] {
self.cplx[1].max(refc * 0.1)
} else {
refc
};
let u_b_raw = if self.cplx_seeded[2] {
(self.cplx[2] / refc).clamp(0.125, 1.5) * refc
} else {
B_DEFAULT_RATIO * refc
};
let u_b = u_b_raw / (2.0f64).powf(B_QP_OFFSET / 6.0);
[u_i, u_p, u_b]
}
fn kind_mix(&self) -> [f64; NUM_KINDS] {
const PRIOR_FRAMES: f64 = 30.0;
const PRIOR: [f64; NUM_KINDS] = [0.1, 0.9, 0.0];
let total = self.frames as f64 + PRIOR_FRAMES;
[0usize, 1, 2].map(|k| (PRIOR[k] * PRIOR_FRAMES + self.kind_count[k] as f64) / total)
}
fn nearest_seeded_kind(&self, kind: RcFrameKind) -> Option<(RcFrameKind, i32)> {
let order: [RcFrameKind; 2] = match kind {
RcFrameKind::Idr => [RcFrameKind::P, RcFrameKind::B],
RcFrameKind::P => [RcFrameKind::Idr, RcFrameKind::B],
RcFrameKind::B => [RcFrameKind::P, RcFrameKind::Idr],
};
order
.into_iter()
.find_map(|k| self.last_qp[Self::kind_index(k)].map(|qp| (k, qp)))
}
fn bootstrap_qp(&self, kind: RcFrameKind) -> i32 {
let bpp = self.bits_per_frame / self.pixels_per_frame;
let base = if bpp > 0.5 {
18
} else if bpp > 0.25 {
22
} else if bpp > 0.12 {
26
} else if bpp > 0.06 {
30
} else if bpp > 0.03 {
34
} else {
38
};
let biased = match kind {
RcFrameKind::Idr => base - 3,
RcFrameKind::P => base,
RcFrameKind::B => base + 2,
};
biased.clamp(self.cfg.min_qp, self.cfg.max_qp)
}
fn frame_target(&self, kind: RcFrameKind) -> f64 {
let buffer = f64::from(self.cfg.vbv_buffer_bits);
let fps = f64::from(self.cfg.fps_num) / f64::from(self.cfg.fps_den);
let window = fps.max(1.0);
let mut t = self.bits_per_frame;
match self.cfg.mode {
RateControlMode::Cbr => {
t += 0.5 * (self.fullness - 0.55 * buffer) / window;
}
RateControlMode::CappedVbr => {
if self.frames > 0 {
let ideal = self.bits_per_frame * self.frames as f64;
let err = self.total_bits as f64 - ideal;
let correction = (2.0 * err / window).clamp(-0.6 * t, 0.6 * t);
t -= correction;
}
let low_water = 0.25 * (self.fullness - 0.35 * buffer) / window;
t += low_water.min(0.0);
}
}
let u = self.kind_weights();
let f = self.kind_mix();
let denom: f64 = (0..NUM_KINDS).map(|j| f[j] * u[j]).sum();
if denom > 0.0 {
t *= u[Self::kind_index(kind)] / denom;
}
let max_safe = 0.9 * (self.fullness + self.arrival_per_frame);
t.clamp(self.bits_per_frame * 0.1, max_safe.max(1.0))
}
pub fn plan_frame(&self, kind: RcFrameKind) -> FramePlan {
let k = Self::kind_index(kind);
let target = self.frame_target(kind);
let qp = if self.cplx_seeded[k] {
let want_step = self.cplx[k] / target.max(1.0);
let raw = qp_from_qstep(want_step).round() as i32;
let clamped = match self.last_qp[k] {
Some(prev) => raw.clamp(prev - 3, prev + 3),
None => raw,
};
clamped.clamp(self.cfg.min_qp, self.cfg.max_qp)
} else if let Some((other_kind, other_qp)) = self.nearest_seeded_kind(kind) {
let bias = Self::kind_qp_bias(kind) - Self::kind_qp_bias(other_kind);
(other_qp + bias).clamp(self.cfg.min_qp, self.cfg.max_qp)
} else {
self.bootstrap_qp(kind)
};
let max_bits = (0.95 * (self.fullness + self.arrival_per_frame)).max(1.0) as u64;
FramePlan {
qp,
target_bits: target,
max_bits,
}
}
pub fn commit_frame(&mut self, kind: RcFrameKind, qp_used: i32, bits: u64) -> CommitOutcome {
let k = Self::kind_index(kind);
assert!((0..=51).contains(&qp_used));
let observed = bits as f64 * qstep(qp_used);
self.cplx[k] = if self.cplx_seeded[k] {
0.6 * self.cplx[k] + 0.4 * observed
} else {
observed
};
self.cplx_seeded[k] = true;
self.last_qp[k] = Some(qp_used);
self.kind_count[k] += 1;
self.frames += 1;
self.total_bits += bits;
let buffer = f64::from(self.cfg.vbv_buffer_bits);
self.fullness += self.arrival_per_frame;
let mut outcome = CommitOutcome::default();
self.fullness -= bits as f64;
if self.fullness < 0.0 {
outcome.vbv_underflow = true;
self.fullness = 0.0;
}
if self.fullness > buffer {
match self.cfg.mode {
RateControlMode::Cbr => {
let filler = self.fullness - buffer;
outcome.filler_bits = filler.ceil() as u64;
self.total_filler_bits += outcome.filler_bits;
self.fullness = buffer;
}
RateControlMode::CappedVbr => {
self.fullness = buffer;
}
}
}
outcome
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_bits(complexity: f64, qp: i32) -> u64 {
(complexity / qstep(qp)).max(64.0) as u64
}
fn run_sequence(
mut rc: RateController,
frames: usize,
gop: usize,
complexity_i: f64,
complexity_p: f64,
wobble: impl Fn(usize) -> f64,
) -> (f64, RateController) {
let mut total_bits = 0u64;
for n in 0..frames {
let kind = if n % gop == 0 {
RcFrameKind::Idr
} else {
RcFrameKind::P
};
let plan = rc.plan_frame(kind);
assert!((rc.config().min_qp..=rc.config().max_qp).contains(&plan.qp));
let base = match kind {
RcFrameKind::Idr => complexity_i,
RcFrameKind::P | RcFrameKind::B => complexity_p,
};
let mut qp = plan.qp;
let mut bits = synth_bits(base * wobble(n), qp);
while bits > plan.max_bits && qp < rc.config().max_qp {
qp += 2;
bits = synth_bits(base * wobble(n), qp);
}
let outcome = rc.commit_frame(kind, qp, bits);
assert!(
!outcome.vbv_underflow,
"underflow at frame {n} (bits={bits}, cap={})",
plan.max_bits
);
total_bits += bits + outcome.filler_bits;
}
let seconds =
frames as f64 * f64::from(rc.config().fps_den) / f64::from(rc.config().fps_num);
(total_bits as f64 / seconds, rc)
}
#[test]
fn cbr_converges_and_fills_channel() {
let cfg = RateControlConfig::cbr(1_000_000, 30, 1);
let rc = RateController::new(cfg, 640, 368);
let f_start = rc.cpb_fullness();
let (sent_rate, rc) = run_sequence(rc, 300, 30, 3.0e6, 6.0e5, |n| {
1.0 + 0.3 * ((n % 7) as f64 - 3.0) / 3.0
});
let seconds = 10.0;
let arrivals = 1_000_000.0 * seconds;
let expected_sent = arrivals + (f_start - rc.cpb_fullness());
let sent = sent_rate * seconds;
assert!(
(sent - expected_sent).abs() < 400.0,
"CBR conservation broken: sent {sent}, expected {expected_sent}"
);
let payload = rc.average_bitrate().unwrap();
assert!(
(payload - 1_000_000.0).abs() / 1_000_000.0 < 0.10,
"payload average {payload} too far from target"
);
}
#[test]
fn capped_vbr_average_tracks_target() {
let cfg = RateControlConfig::capped_vbr(800_000, 1_600_000, 25, 1);
let rc = RateController::new(cfg, 640, 368);
let (_, rc) = run_sequence(rc, 500, 50, 2.5e6, 5.0e5, |n| {
if (n / 100) % 2 == 0 {
1.0
} else {
2.2 }
});
let avg = rc.average_bitrate().unwrap();
let err = (avg - 800_000.0).abs() / 800_000.0;
assert!(err < 0.05, "capped-VBR average {avg} off by {err:.4}");
assert_eq!(rc.total_filler_bits(), 0, "VBR must not request filler");
}
#[test]
fn bucket_never_escapes_bounds() {
let cfg = RateControlConfig {
mode: RateControlMode::Cbr,
target_bitrate: 200_000,
max_bitrate: 200_000,
vbv_buffer_bits: 100_000, fps_num: 30,
fps_den: 1,
min_qp: 10,
max_qp: 51,
};
let mut rc = RateController::new(cfg, 320, 240);
for n in 0..200 {
let kind = if n % 40 == 0 {
RcFrameKind::Idr
} else {
RcFrameKind::P
};
let plan = rc.plan_frame(kind);
let outcome = rc.commit_frame(kind, plan.qp, plan.max_bits);
assert!(!outcome.vbv_underflow, "cap-sized frame underflowed at {n}");
let f = rc.cpb_fullness();
assert!((0.0..=100_000.0 + 1.0).contains(&f), "fullness {f} escaped");
}
}
fn run_b_sequence(
mut rc: RateController,
minigops: usize,
gop_anchors: usize,
nb: usize,
cplx: [f64; 3],
) -> (f64, RateController, Vec<(RcFrameKind, i32)>) {
let mut total_bits = 0u64;
let mut log = Vec::new();
let mut frames = 0usize;
for m in 0..minigops {
let anchor_kind = if m % gop_anchors == 0 {
RcFrameKind::Idr
} else {
RcFrameKind::P
};
for kind in std::iter::once(anchor_kind).chain(std::iter::repeat_n(RcFrameKind::B, nb))
{
let plan = rc.plan_frame(kind);
let base = cplx[RateController::kind_index(kind)];
let mut qp = plan.qp;
let mut bits = synth_bits(base, qp);
while bits > plan.max_bits && qp < rc.config().max_qp {
qp += 2;
bits = synth_bits(base, qp);
}
let outcome = rc.commit_frame(kind, qp, bits);
assert!(!outcome.vbv_underflow, "underflow in minigop {m}");
total_bits += bits + outcome.filler_bits;
log.push((kind, qp));
frames += 1;
}
}
let seconds =
frames as f64 * f64::from(rc.config().fps_den) / f64::from(rc.config().fps_num);
(total_bits as f64 / seconds, rc, log)
}
#[test]
fn cbr_b_gop_converges_with_qp_hierarchy() {
let cfg = RateControlConfig::cbr(1_000_000, 30, 1);
let rc = RateController::new(cfg, 640, 368);
let f_start = rc.cpb_fullness();
let (sent_rate, rc, log) = run_b_sequence(rc, 100, 10, 2, [3.0e6, 6.0e5, 2.5e5]);
let seconds = 10.0;
let arrivals = 1_000_000.0 * seconds;
let expected_sent = arrivals + (f_start - rc.cpb_fullness());
let sent = sent_rate * seconds;
assert!(
(sent - expected_sent).abs() < 400.0,
"CBR B-GOP conservation broken: sent {sent}, expected {expected_sent}"
);
let payload = rc.average_bitrate().unwrap();
assert!(
(payload - 1_000_000.0).abs() / 1_000_000.0 < 0.10,
"CBR B-GOP payload average {payload} too far from target"
);
let tail = &log[log.len() / 2..];
let mean = |k: RcFrameKind| {
let v: Vec<i32> = tail
.iter()
.filter(|(kind, _)| *kind == k)
.map(|&(_, qp)| qp)
.collect();
assert!(!v.is_empty());
v.iter().sum::<i32>() as f64 / v.len() as f64
};
let (qi, qp_, qb) = (
mean(RcFrameKind::Idr),
mean(RcFrameKind::P),
mean(RcFrameKind::B),
);
assert!(
qb > qp_ - 0.5 && qb < qp_ + 6.0,
"B mean QP {qb:.2} not above P mean QP {qp_:.2}"
);
assert!(
qi <= qb + 0.5,
"IDR mean QP {qi:.2} above B mean QP {qb:.2}"
);
}
#[test]
fn capped_vbr_b_gop_tracks_average() {
let cfg = RateControlConfig::capped_vbr(800_000, 1_600_000, 25, 1);
let rc = RateController::new(cfg, 640, 368);
let (_, rc, _) = run_b_sequence(rc, 150, 15, 3, [2.5e6, 5.0e5, 2.0e5]);
let avg = rc.average_bitrate().unwrap();
let err = (avg - 800_000.0).abs() / 800_000.0;
assert!(err < 0.05, "capped-VBR B-GOP average {avg} off by {err:.4}");
assert_eq!(rc.total_filler_bits(), 0, "VBR must not request filler");
}
#[test]
fn b_gets_smaller_target_than_p() {
let cfg = RateControlConfig::cbr(1_000_000, 30, 1);
let mut rc = RateController::new(cfg, 640, 368);
rc.commit_frame(RcFrameKind::Idr, 28, 60_000);
rc.commit_frame(RcFrameKind::P, 28, 60_000);
rc.commit_frame(RcFrameKind::B, 28, 60_000);
let pp = rc.plan_frame(RcFrameKind::P);
let pb = rc.plan_frame(RcFrameKind::B);
assert!(
pb.target_bits < pp.target_bits,
"B target {} not below P target {}",
pb.target_bits,
pp.target_bits
);
}
#[test]
fn unseeded_b_derives_qp_from_p_plus_offset() {
let cfg = RateControlConfig::cbr(600_000, 30, 1);
let mut rc = RateController::new(cfg, 320, 240);
rc.commit_frame(RcFrameKind::Idr, 24, 90_000);
rc.commit_frame(RcFrameKind::P, 27, 25_000);
let plan = rc.plan_frame(RcFrameKind::B);
assert_eq!(plan.qp, 29, "cold-start B QP should be last P QP + 2");
let mut rc2 = RateController::new(cfg, 320, 240);
rc2.commit_frame(RcFrameKind::Idr, 24, 90_000);
let plan_p = rc2.plan_frame(RcFrameKind::P);
assert_eq!(plan_p.qp, 26, "cold-start P QP should be last IDR QP + 2");
}
#[test]
fn idr_gets_larger_target_than_p() {
let cfg = RateControlConfig::cbr(1_000_000, 30, 1);
let mut rc = RateController::new(cfg, 640, 368);
rc.commit_frame(RcFrameKind::Idr, 28, 120_000);
rc.commit_frame(RcFrameKind::P, 30, 20_000);
let pi = rc.plan_frame(RcFrameKind::Idr);
let pp = rc.plan_frame(RcFrameKind::P);
assert!(pi.target_bits > pp.target_bits);
}
#[test]
fn plan_is_pure_and_commit_advances() {
let cfg = RateControlConfig::cbr(500_000, 30, 1);
let mut rc = RateController::new(cfg, 320, 240);
let a = rc.plan_frame(RcFrameKind::Idr);
let b = rc.plan_frame(RcFrameKind::Idr);
assert_eq!(a.qp, b.qp);
assert_eq!(a.max_bits, b.max_bits);
rc.commit_frame(RcFrameKind::Idr, a.qp, 40_000);
assert_eq!(rc.average_bitrate().unwrap(), 40_000.0 * 30.0);
}
#[test]
fn qstep_anchors() {
assert!((qstep(4) - 1.0).abs() < 1e-12);
assert!((qstep(10) - 2.0).abs() < 1e-12);
assert!((qstep(16) - 4.0).abs() < 1e-12);
for qp in 0..=51 {
let back = qp_from_qstep(qstep(qp)).round() as i32;
assert_eq!(back, qp);
}
}
#[test]
#[should_panic(expected = "max_bitrate must be >= target_bitrate")]
fn vbr_max_below_target_panics() {
let cfg = RateControlConfig {
mode: RateControlMode::CappedVbr,
target_bitrate: 1_000_000,
max_bitrate: 500_000,
vbv_buffer_bits: 1_000_000,
fps_num: 30,
fps_den: 1,
min_qp: 10,
max_qp: 51,
};
let _ = RateController::new(cfg, 320, 240);
}
}