| 1 | use std::sync::Arc; |
| 2 | |
| 3 | use crate::{ |
| 4 | api::lookahead::{ |
| 5 | estimate_importance_block_difference, estimate_inter_costs, |
| 6 | estimate_intra_costs, |
| 7 | }, |
| 8 | frame::Frame, |
| 9 | me::FrameMEStats, |
| 10 | }; |
| 11 | use v_frame::{math::Fixed, pixel::Pixel}; |
| 12 | |
| 13 | use super::{SceneChangeDetector, ScenecutResult}; |
| 14 | |
| 15 | impl<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 | } |
| 102 | |