1 | use std::{cmp, sync::Arc}; |
2 | |
3 | use crate::{ |
4 | api::SceneDetectionSpeed, |
5 | encoder::Sequence, |
6 | frame::{Frame, Plane}, |
7 | sad_plane, |
8 | scenechange::fast_idiv, |
9 | }; |
10 | use debug_unreachable::debug_unreachable; |
11 | use v_frame::pixel::Pixel; |
12 | |
13 | use super::{ScaleFunction, SceneChangeDetector, ScenecutResult}; |
14 | |
15 | /// Experiments have determined this to be an optimal threshold |
16 | pub(super) const FAST_THRESHOLD: f64 = 18.0; |
17 | |
18 | impl<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 |
79 | pub(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 | |