rav1e/scenechange/
fast.rs

1use std::{cmp, sync::Arc};
2
3use crate::{
4  api::SceneDetectionSpeed,
5  encoder::Sequence,
6  frame::{Frame, Plane},
7  sad_plane,
8  scenechange::fast_idiv,
9};
10use debug_unreachable::debug_unreachable;
11use v_frame::pixel::Pixel;
12
13use super::{ScaleFunction, SceneChangeDetector, ScenecutResult};
14
15/// Experiments have determined this to be an optimal threshold
16pub(super) const FAST_THRESHOLD: f64 = 18.0;
17
18impl<T: Pixel> SceneChangeDetector<T> {
19  /// The fast algorithm detects fast cuts using a raw difference
20  /// in pixel values between the scaled frames.
21  #[profiling::function]
22  pub(super) fn fast_scenecut(
23    &mut self, frame1: Arc<Frame<T>>, frame2: Arc<Frame<T>>,
24  ) -> ScenecutResult {
25    if let Some(scale_func) = &self.scale_func {
26      // downscale both frames for faster comparison
27      if let Some(frame_buffer) = &mut self.downscaled_frame_buffer {
28        frame_buffer.swap(0, 1);
29        (scale_func.downscale_in_place)(
30          &frame2.planes[0],
31          &mut frame_buffer[1],
32        );
33      } else {
34        self.downscaled_frame_buffer = Some([
35          (scale_func.downscale)(&frame1.planes[0]),
36          (scale_func.downscale)(&frame2.planes[0]),
37        ]);
38      }
39
40      if let Some(frame_buffer) = &self.downscaled_frame_buffer {
41        let &[first, second] = &frame_buffer;
42        let delta = self.delta_in_planes(first, second);
43
44        ScenecutResult {
45          threshold: self.threshold,
46          inter_cost: delta,
47          imp_block_cost: delta,
48          forward_adjusted_cost: delta,
49          backward_adjusted_cost: delta,
50        }
51      } else {
52        // SAFETY: `downscaled_frame_buffer` is always initialized to `Some(..)` with a valid state
53        // before this if/else block is reached.
54        unsafe { debug_unreachable!() }
55      }
56    } else {
57      let delta = self.delta_in_planes(&frame1.planes[0], &frame2.planes[0]);
58
59      ScenecutResult {
60        threshold: self.threshold,
61        inter_cost: delta,
62        imp_block_cost: delta,
63        backward_adjusted_cost: delta,
64        forward_adjusted_cost: delta,
65      }
66    }
67  }
68
69  /// Calculates the average sum of absolute difference (SAD) per pixel between 2 planes
70  #[profiling::function]
71  fn delta_in_planes(&self, plane1: &Plane<T>, plane2: &Plane<T>) -> f64 {
72    let delta = sad_plane::sad_plane(plane1, plane2, self.cpu_feature_level);
73
74    delta as f64 / self.pixels as f64
75  }
76}
77
78/// Scaling factor for frame in scene detection
79pub(super) fn detect_scale_factor<T: Pixel>(
80  sequence: &Arc<Sequence>, speed_mode: SceneDetectionSpeed,
81) -> Option<ScaleFunction<T>> {
82  let small_edge =
83    cmp::min(sequence.max_frame_height, sequence.max_frame_width) as usize;
84  let scale_func = if speed_mode == SceneDetectionSpeed::Fast {
85    match small_edge {
86      0..=240 => None,
87      241..=480 => Some(ScaleFunction::from_scale::<2>()),
88      481..=720 => Some(ScaleFunction::from_scale::<4>()),
89      721..=1080 => Some(ScaleFunction::from_scale::<8>()),
90      1081..=1600 => Some(ScaleFunction::from_scale::<16>()),
91      1601..=usize::MAX => Some(ScaleFunction::from_scale::<32>()),
92      _ => None,
93    }
94  } else {
95    None
96  };
97
98  if let Some(scale_factor) = scale_func.as_ref().map(|x| x.factor) {
99    debug!(
100      "Scene detection scale factor {}, [{},{}] -> [{},{}]",
101      scale_factor,
102      sequence.max_frame_width,
103      sequence.max_frame_height,
104      fast_idiv(sequence.max_frame_width as usize, scale_factor),
105      fast_idiv(sequence.max_frame_height as usize, scale_factor)
106    );
107  }
108
109  scale_func
110}