rav1e/scenechange/
standard.rs

1use std::sync::Arc;
2
3use crate::{
4  api::lookahead::{
5    estimate_importance_block_difference, estimate_inter_costs,
6    estimate_intra_costs,
7  },
8  frame::Frame,
9  me::FrameMEStats,
10};
11use v_frame::{math::Fixed, pixel::Pixel};
12
13use super::{SceneChangeDetector, ScenecutResult};
14
15impl<T: Pixel> SceneChangeDetector<T> {
16  /// Run a comparison between two frames to determine if they qualify for a scenecut.
17  ///
18  /// We gather both intra and inter costs for the frames,
19  /// as well as an importance-block-based difference,
20  /// and use all three metrics.
21  pub(super) fn cost_scenecut(
22    &mut self, frame1: Arc<Frame<T>>, frame2: Arc<Frame<T>>,
23    input_frameno: u64,
24  ) -> ScenecutResult {
25    let frame2_inter_ref = Arc::clone(&frame2);
26    let frame1_imp_ref = Arc::clone(&frame1);
27    let frame2_imp_ref = Arc::clone(&frame2);
28
29    let mut intra_cost = 0.0;
30    let mut mv_inter_cost = 0.0;
31    let mut imp_block_cost = 0.0;
32
33    let cols = 2 * self.encoder_config.width.align_power_of_two_and_shift(3);
34    let rows = 2 * self.encoder_config.height.align_power_of_two_and_shift(3);
35
36    let buffer = if let Some(buffer) = &self.frame_me_stats_buffer {
37      Arc::clone(buffer)
38    } else {
39      let frame_me_stats = FrameMEStats::new_arc_array(cols, rows);
40      let clone = Arc::clone(&frame_me_stats);
41      self.frame_me_stats_buffer = Some(frame_me_stats);
42      clone
43    };
44
45    rayon::scope(|s| {
46      s.spawn(|_| {
47        let temp_plane =
48          self.temp_plane.get_or_insert_with(|| frame2.planes[0].clone());
49
50        let intra_costs =
51          self.intra_costs.entry(input_frameno).or_insert_with(|| {
52            estimate_intra_costs(
53              temp_plane,
54              &*frame2,
55              self.bit_depth,
56              self.cpu_feature_level,
57            )
58          });
59
60        intra_cost = intra_costs.iter().map(|&cost| cost as u64).sum::<u64>()
61          as f64
62          / intra_costs.len() as f64;
63        // If we're not using temporal RDO, we won't need these costs later,
64        // so remove them from the cache to avoid a memory leak
65        if !self.encoder_config.temporal_rdo() {
66          self.intra_costs.remove(&input_frameno);
67        };
68      });
69      s.spawn(|_| {
70        mv_inter_cost = estimate_inter_costs(
71          frame2_inter_ref,
72          frame1,
73          self.bit_depth,
74          self.encoder_config.clone(),
75          self.sequence.clone(),
76          buffer,
77        );
78      });
79      s.spawn(|_| {
80        imp_block_cost =
81          estimate_importance_block_difference(frame2_imp_ref, frame1_imp_ref);
82      });
83    });
84
85    // `BIAS` determines how likely we are
86    // to choose a keyframe, between 0.0-1.0.
87    // Higher values mean we are more likely to choose a keyframe.
88    // This value was chosen based on trials using the new
89    // adaptive scenecut code.
90    const BIAS: f64 = 0.7;
91    let threshold = intra_cost * (1.0 - BIAS);
92
93    ScenecutResult {
94      inter_cost: mv_inter_cost,
95      imp_block_cost,
96      threshold,
97      backward_adjusted_cost: 0.0,
98      forward_adjusted_cost: 0.0,
99    }
100  }
101}