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