1mod fast;
11mod standard;
12
13use crate::api::{EncoderConfig, SceneDetectionSpeed};
14use crate::cpu_features::CpuFeatureLevel;
15use crate::encoder::Sequence;
16use crate::frame::*;
17use crate::me::RefMEStats;
18use crate::util::Pixel;
19use std::collections::BTreeMap;
20use std::num::NonZeroUsize;
21use std::sync::Arc;
22use std::{cmp, u64};
23
24use self::fast::{detect_scale_factor, FAST_THRESHOLD};
25
26const IMP_BLOCK_DIFF_THRESHOLD: f64 = 7.0;
28
29#[inline(always)]
31pub(crate) fn fast_idiv(n: usize, d: NonZeroUsize) -> usize {
32 debug_assert!(d.is_power_of_two());
33
34 n >> d.trailing_zeros()
35}
36
37struct ScaleFunction<T: Pixel> {
38 downscale_in_place:
39 fn(&Plane<T>, &mut Plane<T>),
40 downscale: fn(&Plane<T>) -> Plane<T>,
41 factor: NonZeroUsize,
42}
43
44impl<T: Pixel> ScaleFunction<T> {
45 fn from_scale<const SCALE: usize>() -> Self {
46 assert!(
47 SCALE.is_power_of_two(),
48 "Scaling factor needs to be a nonzero power of two"
49 );
50
51 Self {
52 downscale: Plane::downscale::<SCALE>,
53 downscale_in_place: Plane::downscale_in_place::<SCALE>,
54 factor: NonZeroUsize::new(SCALE).unwrap(),
55 }
56 }
57}
58pub struct SceneChangeDetector<T: Pixel> {
60 threshold: f64,
62 speed_mode: SceneDetectionSpeed,
64 scale_func: Option<ScaleFunction<T>>,
66 downscaled_frame_buffer: Option<[Plane<T>; 2]>,
68 frame_me_stats_buffer: Option<RefMEStats>,
70 lookahead_offset: usize,
72 deque_offset: usize,
74 score_deque: Vec<ScenecutResult>,
76 pixels: usize,
78 bit_depth: usize,
80 cpu_feature_level: CpuFeatureLevel,
82 encoder_config: EncoderConfig,
83 sequence: Arc<Sequence>,
84 pub(crate) intra_costs: BTreeMap<u64, Box<[u32]>>,
87 pub(crate) temp_plane: Option<Plane<T>>,
89}
90
91impl<T: Pixel> SceneChangeDetector<T> {
92 pub fn new(
93 encoder_config: EncoderConfig, cpu_feature_level: CpuFeatureLevel,
94 lookahead_distance: usize, sequence: Arc<Sequence>,
95 ) -> Self {
96 let bit_depth = encoder_config.bit_depth;
97 let speed_mode = if encoder_config.low_latency {
98 SceneDetectionSpeed::Fast
99 } else {
100 encoder_config.speed_settings.scene_detection_mode
101 };
102
103 let scale_func = detect_scale_factor(&sequence, speed_mode);
105
106 let lookahead_offset = if lookahead_distance >= 5 { 5 } else { 0 };
108 let deque_offset = lookahead_offset;
109
110 let score_deque = Vec::with_capacity(5 + lookahead_distance);
111
112 let factor =
114 scale_func.as_ref().map_or(NonZeroUsize::new(1).unwrap(), |x| x.factor);
115
116 let pixels = if speed_mode == SceneDetectionSpeed::Fast {
117 fast_idiv(sequence.max_frame_height as usize, factor)
118 * fast_idiv(sequence.max_frame_width as usize, factor)
119 } else {
120 1
121 };
122
123 let threshold = FAST_THRESHOLD * (bit_depth as f64) / 8.0;
124
125 Self {
126 threshold,
127 speed_mode,
128 scale_func,
129 downscaled_frame_buffer: None,
130 frame_me_stats_buffer: None,
131 lookahead_offset,
132 deque_offset,
133 score_deque,
134 pixels,
135 bit_depth,
136 cpu_feature_level,
137 encoder_config,
138 sequence,
139 intra_costs: BTreeMap::new(),
140 temp_plane: None,
141 }
142 }
143
144 #[profiling::function]
153 pub fn analyze_next_frame(
154 &mut self, frame_set: &[&Arc<Frame<T>>], input_frameno: u64,
155 previous_keyframe: u64,
156 ) -> bool {
157 let distance = input_frameno - previous_keyframe;
162
163 if frame_set.len() <= self.lookahead_offset {
164 return false;
167 }
168
169 if self.encoder_config.speed_settings.scene_detection_mode
170 == SceneDetectionSpeed::None
171 {
172 if let Some(true) = self.handle_min_max_intervals(distance) {
173 return true;
174 };
175 return false;
176 }
177
178 if self.deque_offset > 0
181 && frame_set.len() > self.deque_offset + 1
182 && self.score_deque.is_empty()
183 {
184 self.initialize_score_deque(frame_set, input_frameno, self.deque_offset);
185 } else if self.score_deque.is_empty() {
186 self.initialize_score_deque(
187 frame_set,
188 input_frameno,
189 frame_set.len() - 1,
190 );
191
192 self.deque_offset = frame_set.len() - 2;
193 }
194 if frame_set.len() > self.deque_offset + 1 {
197 self.run_comparison(
198 frame_set[self.deque_offset].clone(),
199 frame_set[self.deque_offset + 1].clone(),
200 input_frameno + self.deque_offset as u64,
201 );
202 } else {
203 self.deque_offset -= 1;
204 }
205
206 let (scenecut, score) = self.adaptive_scenecut();
208 let scenecut = self.handle_min_max_intervals(distance).unwrap_or(scenecut);
209 debug!(
210 "[SC-Detect] Frame {}: Raw={:5.1} ImpBl={:5.1} Bwd={:5.1} Fwd={:5.1} Th={:.1} {}",
211 input_frameno,
212 score.inter_cost,
213 score.imp_block_cost,
214 score.backward_adjusted_cost,
215 score.forward_adjusted_cost,
216 score.threshold,
217 if scenecut { "Scenecut" } else { "No cut" }
218 );
219
220 if self.score_deque.len() > 5 + self.lookahead_offset {
223 self.score_deque.pop();
224 }
225
226 scenecut
227 }
228
229 fn handle_min_max_intervals(&mut self, distance: u64) -> Option<bool> {
230 if distance < self.encoder_config.min_key_frame_interval {
232 return Some(false);
233 }
234 if distance >= self.encoder_config.max_key_frame_interval {
235 return Some(true);
236 }
237 None
238 }
239
240 fn initialize_score_deque(
242 &mut self, frame_set: &[&Arc<Frame<T>>], input_frameno: u64,
243 init_len: usize,
244 ) {
245 for x in 0..init_len {
246 self.run_comparison(
247 frame_set[x].clone(),
248 frame_set[x + 1].clone(),
249 input_frameno + x as u64,
250 );
251 }
252 }
253
254 fn run_comparison(
257 &mut self, frame1: Arc<Frame<T>>, frame2: Arc<Frame<T>>,
258 input_frameno: u64,
259 ) {
260 let mut result = if self.speed_mode == SceneDetectionSpeed::Fast {
261 self.fast_scenecut(frame1, frame2)
262 } else {
263 self.cost_scenecut(frame1, frame2, input_frameno)
264 };
265
266 if self.speed_mode != SceneDetectionSpeed::Fast && self.deque_offset > 0 {
269 if input_frameno == 1 {
270 result.backward_adjusted_cost = 0.0;
273 } else {
274 let mut adjusted_cost = f64::MAX;
275 for other_cost in
276 self.score_deque.iter().take(self.deque_offset).map(|i| i.inter_cost)
277 {
278 let this_cost = result.inter_cost - other_cost;
279 if this_cost < adjusted_cost {
280 adjusted_cost = this_cost;
281 }
282 if adjusted_cost < 0.0 {
283 adjusted_cost = 0.0;
284 break;
285 }
286 }
287 result.backward_adjusted_cost = adjusted_cost;
288 }
289 if !self.score_deque.is_empty() {
290 for i in 0..(cmp::min(self.deque_offset, self.score_deque.len())) {
291 let adjusted_cost =
292 self.score_deque[i].inter_cost - result.inter_cost;
293 if i == 0
294 || adjusted_cost < self.score_deque[i].forward_adjusted_cost
295 {
296 self.score_deque[i].forward_adjusted_cost = adjusted_cost;
297 }
298 if self.score_deque[i].forward_adjusted_cost < 0.0 {
299 self.score_deque[i].forward_adjusted_cost = 0.0;
300 }
301 }
302 }
303 }
304 self.score_deque.insert(0, result);
305 }
306
307 fn adaptive_scenecut(&mut self) -> (bool, ScenecutResult) {
311 let score = self.score_deque[self.deque_offset];
312
313 let imp_block_threshold =
322 IMP_BLOCK_DIFF_THRESHOLD * (self.bit_depth as f64) / 8.0;
323 if !&self.score_deque[self.deque_offset..]
324 .iter()
325 .any(|result| result.imp_block_cost >= imp_block_threshold)
326 {
327 return (false, score);
328 }
329
330 let cost = score.forward_adjusted_cost;
331 if cost >= score.threshold {
332 let back_deque = &self.score_deque[self.deque_offset + 1..];
333 let forward_deque = &self.score_deque[..self.deque_offset];
334 let back_over_tr_count = back_deque
335 .iter()
336 .filter(|result| result.backward_adjusted_cost >= result.threshold)
337 .count();
338 let forward_over_tr_count = forward_deque
339 .iter()
340 .filter(|result| result.forward_adjusted_cost >= result.threshold)
341 .count();
342
343 let back_count_req = if self.speed_mode == SceneDetectionSpeed::Fast {
347 2
350 } else {
351 1
352 };
353 if forward_over_tr_count == 0 && back_over_tr_count >= back_count_req {
354 return (true, score);
355 }
356
357 if back_over_tr_count == 0
360 && forward_over_tr_count == 1
361 && forward_deque[0].forward_adjusted_cost >= forward_deque[0].threshold
362 {
363 return (true, score);
364 }
365
366 if back_over_tr_count != 0 || forward_over_tr_count != 0 {
367 return (false, score);
368 }
369 }
370
371 (cost >= score.threshold, score)
372 }
373}
374
375#[derive(Debug, Clone, Copy)]
376struct ScenecutResult {
377 inter_cost: f64,
378 imp_block_cost: f64,
379 backward_adjusted_cost: f64,
380 forward_adjusted_cost: f64,
381 threshold: f64,
382}