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}
111