use std::cmp;
use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use log::debug;
use self::fast::{detect_scale_factor, FAST_THRESHOLD};
use crate::api::SceneDetectionSpeed;
use crate::cpu_features::CpuFeatureLevel;
use crate::frame::{Frame, Plane};
use crate::util::Pixel;
mod fast;
mod standard;
const IMP_BLOCK_DIFF_THRESHOLD: f64 = 7.0;
pub(crate) fn fast_idiv(n: usize, d: NonZeroUsize) -> usize {
debug_assert!(d.is_power_of_two());
n >> d.trailing_zeros()
}
struct ScaleFunction<T: Pixel> {
downscale_in_place:
fn(/* &self: */ &Plane<T>, /* in_plane: */ &mut Plane<T>),
downscale: fn(/* &self: */ &Plane<T>) -> Plane<T>,
factor: NonZeroUsize,
}
impl<T: Pixel> ScaleFunction<T> {
fn from_scale<const SCALE: usize>() -> Self {
assert!(
SCALE.is_power_of_two(),
"Scaling factor needs to be a nonzero power of two"
);
Self {
downscale: Plane::downscale::<SCALE>,
downscale_in_place: Plane::downscale_in_place::<SCALE>,
factor: NonZeroUsize::new(SCALE).unwrap(),
}
}
}
pub struct SceneChangeDetector<T: Pixel> {
scene_detection_mode: SceneDetectionSpeed,
lookahead_offset: usize,
min_key_frame_interval: usize,
max_key_frame_interval: usize,
cpu_feature_level: CpuFeatureLevel,
threshold: f64,
bit_depth: usize,
scaled_pixels: usize,
scale_func: Option<ScaleFunction<T>>,
deque_offset: usize,
downscaled_frame_buffer: Option<[Plane<T>; 2]>,
score_deque: Vec<ScenecutResult>,
temp_plane: Option<Plane<T>>,
pub intra_costs: Option<BTreeMap<usize, Box<[u32]>>>,
}
impl<T: Pixel> SceneChangeDetector<T> {
pub fn new(
resolution: (usize, usize), bit_depth: usize, lookahead_distance: usize,
scene_detection_mode: SceneDetectionSpeed, min_key_frame_interval: usize,
max_key_frame_interval: usize, cpu_feature_level: CpuFeatureLevel,
) -> Self {
let scale_func = detect_scale_factor(resolution, scene_detection_mode);
let lookahead_offset = if lookahead_distance >= 5 { 5 } else { 0 };
let deque_offset = lookahead_offset;
let score_deque = Vec::with_capacity(5 + lookahead_distance);
let factor = scale_func
.as_ref()
.map_or(NonZeroUsize::new(1).expect("constant should not panic"), |x| {
x.factor
});
let pixels = if scene_detection_mode == SceneDetectionSpeed::Fast {
fast_idiv(resolution.1, factor) * fast_idiv(resolution.0, factor)
} else {
1
};
let threshold = FAST_THRESHOLD * (bit_depth as f64) / 8.0;
Self {
threshold,
scene_detection_mode,
scale_func,
lookahead_offset,
deque_offset,
score_deque,
scaled_pixels: pixels,
bit_depth,
min_key_frame_interval,
max_key_frame_interval,
cpu_feature_level,
downscaled_frame_buffer: None,
temp_plane: None,
intra_costs: None,
}
}
pub fn enable_cache(&mut self) {
if self.intra_costs.is_none() {
self.intra_costs = Some(BTreeMap::new());
}
}
pub fn analyze_next_frame(
&mut self, frame_set: &[&Arc<Frame<T>>], input_frameno: usize,
previous_keyframe: usize,
) -> bool {
let distance = input_frameno - previous_keyframe;
if frame_set.len() <= self.lookahead_offset {
return false;
}
if self.scene_detection_mode == SceneDetectionSpeed::None {
if let Some(true) = self.handle_min_max_intervals(distance) {
return true;
};
return false;
}
if self.deque_offset > 0
&& frame_set.len() > self.deque_offset + 1
&& self.score_deque.is_empty()
{
self.initialize_score_deque(frame_set, input_frameno, self.deque_offset);
} else if self.score_deque.is_empty() {
self.initialize_score_deque(
frame_set,
input_frameno,
frame_set.len() - 1,
);
self.deque_offset = frame_set.len() - 2;
}
if frame_set.len() > self.deque_offset + 1 {
self.run_comparison(
frame_set[self.deque_offset].clone(),
frame_set[self.deque_offset + 1].clone(),
input_frameno + self.deque_offset,
);
} else {
self.deque_offset -= 1;
}
let (scenecut, score) = self.adaptive_scenecut();
let scenecut = self.handle_min_max_intervals(distance).unwrap_or(scenecut);
debug!(
"[SC-Detect] Frame {}: Raw={:5.1} ImpBl={:5.1} Bwd={:5.1} Fwd={:5.1} Th={:.1} {}",
input_frameno,
score.inter_cost,
score.imp_block_cost,
score.backward_adjusted_cost,
score.forward_adjusted_cost,
score.threshold,
if scenecut { "Scenecut" } else { "No cut" }
);
if self.score_deque.len() > 5 + self.lookahead_offset {
self.score_deque.pop();
}
scenecut
}
fn handle_min_max_intervals(&mut self, distance: usize) -> Option<bool> {
if distance < self.min_key_frame_interval {
return Some(false);
}
if distance >= self.max_key_frame_interval {
return Some(true);
}
None
}
fn initialize_score_deque(
&mut self, frame_set: &[&Arc<Frame<T>>], input_frameno: usize,
init_len: usize,
) {
for x in 0..init_len {
self.run_comparison(
frame_set[x].clone(),
frame_set[x + 1].clone(),
input_frameno + x,
);
}
}
fn run_comparison(
&mut self, frame1: Arc<Frame<T>>, frame2: Arc<Frame<T>>,
input_frameno: usize,
) {
let mut result = match self.scene_detection_mode {
SceneDetectionSpeed::Fast => self.fast_scenecut(frame1, frame2),
SceneDetectionSpeed::Standard => {
self.cost_scenecut(frame1, frame2, input_frameno)
}
_ => unreachable!(),
};
if self.scene_detection_mode == SceneDetectionSpeed::Standard
&& self.deque_offset > 0
{
if input_frameno == 1 {
result.backward_adjusted_cost = 0.0;
} else {
let mut adjusted_cost = f64::MAX;
for other_cost in
self.score_deque.iter().take(self.deque_offset).map(|i| i.inter_cost)
{
let this_cost = result.inter_cost - other_cost;
if this_cost < adjusted_cost {
adjusted_cost = this_cost;
}
if adjusted_cost < 0.0 {
adjusted_cost = 0.0;
break;
}
}
result.backward_adjusted_cost = adjusted_cost;
}
if !self.score_deque.is_empty() {
for i in 0..cmp::min(self.deque_offset, self.score_deque.len()) {
let adjusted_cost =
self.score_deque[i].inter_cost - result.inter_cost;
if i == 0
|| adjusted_cost < self.score_deque[i].forward_adjusted_cost
{
self.score_deque[i].forward_adjusted_cost = adjusted_cost;
}
if self.score_deque[i].forward_adjusted_cost < 0.0 {
self.score_deque[i].forward_adjusted_cost = 0.0;
}
}
}
}
self.score_deque.insert(0, result);
}
fn adaptive_scenecut(&mut self) -> (bool, ScenecutResult) {
let score = self.score_deque[self.deque_offset];
let imp_block_threshold =
IMP_BLOCK_DIFF_THRESHOLD * (self.bit_depth as f64) / 8.0;
if !&self.score_deque[self.deque_offset..]
.iter()
.any(|result| result.imp_block_cost >= imp_block_threshold)
{
return (false, score);
}
let cost = score.forward_adjusted_cost;
if cost >= score.threshold {
let back_deque = &self.score_deque[self.deque_offset + 1..];
let forward_deque = &self.score_deque[..self.deque_offset];
let back_over_tr_count = back_deque
.iter()
.filter(|result| result.backward_adjusted_cost >= result.threshold)
.count();
let forward_over_tr_count = forward_deque
.iter()
.filter(|result| result.forward_adjusted_cost >= result.threshold)
.count();
let back_count_req =
if self.scene_detection_mode == SceneDetectionSpeed::Fast {
2
} else {
1
};
if forward_over_tr_count == 0 && back_over_tr_count >= back_count_req {
return (true, score);
}
if back_over_tr_count == 0
&& forward_over_tr_count == 1
&& forward_deque[0].forward_adjusted_cost >= forward_deque[0].threshold
{
return (true, score);
}
if back_over_tr_count != 0 || forward_over_tr_count != 0 {
return (false, score);
}
}
(cost >= score.threshold, score)
}
}
#[derive(Debug, Clone, Copy)]
struct ScenecutResult {
inter_cost: f64,
imp_block_cost: f64,
backward_adjusted_cost: f64,
forward_adjusted_cost: f64,
threshold: f64,
}