#![forbid(unsafe_code)]
use crate::av1::sequence::{
SvcConfig, SvcReferenceMode, TemporalLayerConfig, MAX_TEMPORAL_LAYERS,
};
use crate::error::{CodecError, CodecResult};
#[derive(Clone, Debug)]
pub struct SvcEncoderConfig {
pub svc: SvcConfig,
pub base_qp: u8,
pub keyframe_interval: u64,
pub max_qp: u8,
pub min_qp: u8,
}
impl SvcEncoderConfig {
pub fn new(temporal_layers: u8, spatial_layers: u8) -> Self {
Self {
svc: SvcConfig::new(temporal_layers, spatial_layers),
base_qp: 32,
keyframe_interval: 0,
max_qp: 63,
min_qp: 0,
}
}
pub fn with_base_qp(mut self, qp: u8) -> Self {
self.base_qp = qp.min(63);
self
}
pub fn with_keyframe_interval(mut self, interval: u64) -> Self {
self.keyframe_interval = interval;
self
}
pub fn validate(&self) -> CodecResult<()> {
if self.min_qp > self.max_qp {
return Err(CodecError::InvalidParameter(format!(
"min_qp ({}) > max_qp ({})",
self.min_qp, self.max_qp
)));
}
if self.svc.num_temporal_layers == 0 || self.svc.num_temporal_layers > MAX_TEMPORAL_LAYERS as u8 {
return Err(CodecError::InvalidParameter(format!(
"num_temporal_layers must be in 1..={}, got {}",
MAX_TEMPORAL_LAYERS,
self.svc.num_temporal_layers,
)));
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
struct ReferenceSlot {
frame_index: Option<u64>,
temporal_layer: u8,
is_keyframe: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SvcFrameDecision {
pub frame_index: u64,
pub temporal_layer: u8,
pub qp_offset: i8,
pub effective_qp: u8,
pub is_keyframe: bool,
pub is_droppable: bool,
pub reference_slots: Vec<usize>,
pub reference_mode: SvcReferenceMode,
}
#[derive(Clone, Debug, Default)]
pub struct LayerStats {
pub frame_count: u64,
pub keyframe_count: u64,
pub qp_offset_sum: i64,
}
impl LayerStats {
pub fn avg_qp_offset(&self) -> f64 {
if self.frame_count == 0 {
return 0.0;
}
self.qp_offset_sum as f64 / self.frame_count as f64
}
}
#[derive(Debug)]
pub struct SvcTemporalEncoder {
config: SvcEncoderConfig,
num_layers: u8,
ref_slots: Vec<ReferenceSlot>,
pub layer_stats: Vec<LayerStats>,
frames_processed: u64,
}
impl SvcTemporalEncoder {
pub fn new(config: SvcEncoderConfig) -> Self {
let num_layers = config.svc.num_temporal_layers as usize;
let ref_slots = (0..num_layers)
.map(|_| ReferenceSlot::default())
.collect();
let layer_stats = (0..num_layers).map(|_| LayerStats::default()).collect();
let n = config.svc.num_temporal_layers;
Self {
config,
num_layers: n,
ref_slots,
layer_stats,
frames_processed: 0,
}
}
pub fn decide(&mut self, frame_index: u64) -> SvcFrameDecision {
let temporal_layer = self.config.svc.frame_temporal_layer(frame_index);
let qp_offset = self.config.svc.frame_qp_offset(frame_index);
let raw_qp = self.config.base_qp as i32 + qp_offset as i32;
let effective_qp = raw_qp
.clamp(self.config.min_qp as i32, self.config.max_qp as i32) as u8;
let is_keyframe = frame_index == 0
|| (temporal_layer == 0
&& self.config.keyframe_interval > 0
&& frame_index % self.config.keyframe_interval == 0);
let is_droppable = self.config.svc.is_droppable(frame_index);
let reference_slots = self.select_reference_slots(temporal_layer, is_keyframe);
let reference_mode = self
.config
.svc
.temporal_layers
.get(temporal_layer as usize)
.map_or(SvcReferenceMode::KeyOnly, |l| l.reference_mode.clone());
self.update_ref_slot(frame_index, temporal_layer, is_keyframe);
let stats = &mut self.layer_stats[temporal_layer as usize];
stats.frame_count += 1;
stats.qp_offset_sum += qp_offset as i64;
if is_keyframe {
stats.keyframe_count += 1;
}
self.frames_processed += 1;
SvcFrameDecision {
frame_index,
temporal_layer,
qp_offset,
effective_qp,
is_keyframe,
is_droppable,
reference_slots,
reference_mode,
}
}
pub fn frames_processed(&self) -> u64 {
self.frames_processed
}
pub fn num_temporal_layers(&self) -> u8 {
self.num_layers as u8
}
pub fn stats_for_layer(&self, layer_id: u8) -> Option<&LayerStats> {
self.layer_stats.get(layer_id as usize)
}
pub fn bitrate_fraction(&self, layer_id: u8) -> f32 {
self.config
.svc
.temporal_layers
.get(layer_id as usize)
.map_or(0.0, |l| l.bitrate_fraction)
}
pub fn framerate_fraction(&self, layer_id: u8) -> f32 {
self.config
.svc
.temporal_layers
.get(layer_id as usize)
.map_or(0.0, |l| l.framerate_fraction)
}
pub fn set_temporal_layer_config(&mut self, layer_id: u8, cfg: TemporalLayerConfig) {
self.config.svc.set_temporal_layer(layer_id, cfg);
}
fn select_reference_slots(&self, temporal_layer: u8, is_keyframe: bool) -> Vec<usize> {
if is_keyframe {
return vec![];
}
let mut slots = Vec::with_capacity(2);
for layer in (0..=temporal_layer).rev() {
let slot = &self.ref_slots[layer as usize];
if slot.frame_index.is_some() {
slots.push(layer as usize);
if slots.len() >= 2 {
break;
}
}
}
slots
}
fn update_ref_slot(&mut self, frame_index: u64, temporal_layer: u8, is_keyframe: bool) {
let slot = &mut self.ref_slots[temporal_layer as usize];
slot.frame_index = Some(frame_index);
slot.temporal_layer = temporal_layer;
slot.is_keyframe = is_keyframe;
}
}
#[derive(Debug)]
pub struct SvcTemporalEncoderBuilder {
config: SvcEncoderConfig,
}
impl SvcTemporalEncoderBuilder {
pub fn new(temporal_layers: u8, spatial_layers: u8) -> Self {
Self {
config: SvcEncoderConfig::new(temporal_layers, spatial_layers),
}
}
pub fn base_qp(mut self, qp: u8) -> Self {
self.config.base_qp = qp.min(63);
self
}
pub fn keyframe_interval(mut self, interval: u64) -> Self {
self.config.keyframe_interval = interval;
self
}
pub fn max_qp(mut self, qp: u8) -> Self {
self.config.max_qp = qp.min(63);
self
}
pub fn min_qp(mut self, qp: u8) -> Self {
self.config.min_qp = qp.min(63);
self
}
pub fn build(self) -> SvcTemporalEncoder {
SvcTemporalEncoder::new(self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_encoder(temporal_layers: u8) -> SvcTemporalEncoder {
let cfg = SvcEncoderConfig::new(temporal_layers, 1).with_base_qp(32);
SvcTemporalEncoder::new(cfg)
}
#[test]
fn test_single_layer_all_frames_in_base() {
let mut enc = make_encoder(1);
for i in 0u64..8 {
let d = enc.decide(i);
assert_eq!(d.temporal_layer, 0, "frame {i} should be in base layer");
assert!(!d.is_droppable, "base-layer frames are never droppable");
}
}
#[test]
fn test_three_layers_frame0_is_base() {
let mut enc = make_encoder(3);
let d = enc.decide(0);
assert_eq!(d.temporal_layer, 0);
assert!(d.is_keyframe, "first frame should be a keyframe");
}
#[test]
fn test_three_layers_frame1_is_highest() {
let mut enc = make_encoder(3);
enc.decide(0);
let d = enc.decide(1);
assert_eq!(d.temporal_layer, 2, "frame 1 should be in layer 2");
assert!(d.is_droppable);
}
#[test]
fn test_three_layers_frame2_is_mid() {
let mut enc = make_encoder(3);
enc.decide(0);
enc.decide(1);
let d = enc.decide(2);
assert_eq!(d.temporal_layer, 1);
}
#[test]
fn test_three_layers_frame4_is_base() {
let mut enc = make_encoder(3);
for i in 0u64..4 {
enc.decide(i);
}
let d = enc.decide(4);
assert_eq!(d.temporal_layer, 0, "frame 4 (period boundary) should be base layer");
}
#[test]
fn test_effective_qp_clamped() {
let mut cfg = SvcEncoderConfig::new(3, 1);
cfg.base_qp = 60;
cfg.max_qp = 63;
cfg.min_qp = 0;
let mut enc = SvcTemporalEncoder::new(cfg);
for i in 0u64..8 {
let d = enc.decide(i);
assert!(d.effective_qp <= 63, "effective_qp must be <= 63 at frame {i}");
}
}
#[test]
fn test_base_layer_has_smallest_qp() {
let mut enc = make_encoder(3);
let d_base = enc.decide(0); let d_high = enc.decide(1);
assert!(
d_base.effective_qp <= d_high.effective_qp,
"base layer should have lower or equal effective QP (got {} vs {})",
d_base.effective_qp,
d_high.effective_qp
);
}
#[test]
fn test_first_frame_is_keyframe() {
let mut enc = make_encoder(2);
let d = enc.decide(0);
assert!(d.is_keyframe);
assert!(d.reference_slots.is_empty(), "keyframes have no references");
}
#[test]
fn test_periodic_keyframe() {
let cfg = SvcEncoderConfig::new(2, 1)
.with_base_qp(32)
.with_keyframe_interval(4);
let mut enc = SvcTemporalEncoder::new(cfg);
for i in 0u64..8 {
let d = enc.decide(i);
if i == 0 || i == 4 {
assert!(d.is_keyframe, "frame {i} should be keyframe");
}
}
}
#[test]
fn test_stats_frame_count() {
let mut enc = make_encoder(3);
let n = 8u64;
for i in 0..n {
enc.decide(i);
}
assert_eq!(enc.frames_processed(), n);
let total: u64 = (0..3u8).map(|l| enc.stats_for_layer(l).unwrap().frame_count).sum();
assert_eq!(total, n, "all frames should be counted across layers");
}
#[test]
fn test_stats_out_of_bounds_layer_returns_none() {
let enc = make_encoder(2);
assert!(enc.stats_for_layer(10).is_none());
}
#[test]
fn test_builder_creates_encoder() {
let enc = SvcTemporalEncoderBuilder::new(3, 1)
.base_qp(24)
.keyframe_interval(60)
.max_qp(55)
.build();
assert_eq!(enc.num_temporal_layers(), 3);
}
#[test]
fn test_bitrate_fractions_sum_to_one() {
let enc = make_encoder(3);
let total: f32 = (0..3u8).map(|l| enc.bitrate_fraction(l)).sum();
assert!(
(total - 1.0).abs() < 0.01,
"bitrate fractions should sum to ~1.0, got {total}"
);
}
#[test]
fn test_framerate_fraction_base_layer_is_smallest() {
let enc = make_encoder(3);
let fr0 = enc.framerate_fraction(0);
let fr2 = enc.framerate_fraction(2);
assert!(fr0 <= fr2, "base layer framerate fraction should be ≤ highest layer");
}
#[test]
fn test_config_validate_min_gt_max_qp() {
let mut cfg = SvcEncoderConfig::new(2, 1);
cfg.min_qp = 40;
cfg.max_qp = 20;
assert!(cfg.validate().is_err());
}
#[test]
fn test_config_validate_ok() {
let cfg = SvcEncoderConfig::new(3, 1)
.with_base_qp(32)
.with_keyframe_interval(120);
assert!(cfg.validate().is_ok());
}
}