Skip to main content

sonora_aec3/
block_processor.rs

1//! Block processor — block-level echo cancellation processing.
2//!
3//! Connects the render delay buffer, render delay controller, and echo remover
4//! to provide full echo cancellation at the block level (64 samples per block).
5//!
6//! Ported from `modules/audio_processing/aec3/block_processor.h/cc`.
7
8use crate::block::Block;
9use crate::block_processor_metrics::BlockProcessorMetrics;
10use crate::common::{BLOCK_SIZE_MS, num_bands_for_rate, valid_full_band_rate};
11use crate::config::EchoCanceller3Config;
12use crate::delay_estimate::DelayEstimate;
13use crate::echo_path_variability::{DelayAdjustment, EchoPathVariability};
14use crate::echo_remover::EchoRemover;
15use crate::render_delay_buffer::{BufferingEvent, RenderDelayBuffer};
16use crate::render_delay_controller::RenderDelayController;
17use sonora_simd::SimdBackend;
18
19/// Block-level echo cancellation processor.
20#[derive(Debug)]
21pub struct BlockProcessor {
22    config: EchoCanceller3Config,
23    capture_properly_started: bool,
24    render_properly_started: bool,
25    sample_rate_hz: usize,
26    render_buffer: RenderDelayBuffer,
27    delay_controller: Option<RenderDelayController>,
28    echo_remover: EchoRemover,
29    metrics: BlockProcessorMetrics,
30    render_event: BufferingEvent,
31    capture_call_counter: usize,
32    estimated_delay: Option<DelayEstimate>,
33}
34
35/// Metrics output from the block processor.
36#[derive(Debug, Clone, Copy, Default)]
37pub struct BlockProcessorMetricsOutput {
38    /// Echo Return Loss in dB: `ERL = 10 log10(P_far / P_echo)`.
39    pub echo_return_loss: f64,
40    /// Echo Return Loss Enhancement in dB: `ERLE = 10 log10(P_echo / P_out)`.
41    pub echo_return_loss_enhancement: f64,
42    /// Instantaneous delay estimate in milliseconds.
43    pub delay_ms: i32,
44}
45
46impl BlockProcessor {
47    pub fn new(
48        config: &EchoCanceller3Config,
49        sample_rate_hz: usize,
50        num_render_channels: usize,
51        num_capture_channels: usize,
52    ) -> Self {
53        let backend = sonora_simd::detect_backend();
54        Self::with_backend(
55            backend,
56            config,
57            sample_rate_hz,
58            num_render_channels,
59            num_capture_channels,
60        )
61    }
62
63    /// Creates a new block processor with an explicit SIMD backend.
64    pub fn with_backend(
65        backend: SimdBackend,
66        config: &EchoCanceller3Config,
67        sample_rate_hz: usize,
68        num_render_channels: usize,
69        num_capture_channels: usize,
70    ) -> Self {
71        debug_assert!(valid_full_band_rate(sample_rate_hz));
72
73        let render_buffer = RenderDelayBuffer::new(config, sample_rate_hz, num_render_channels);
74
75        let delay_controller = if !config.delay.use_external_delay_estimator {
76            Some(RenderDelayController::new(
77                backend,
78                config,
79                num_capture_channels,
80            ))
81        } else {
82            None
83        };
84
85        let echo_remover = EchoRemover::new(
86            backend,
87            config,
88            sample_rate_hz,
89            num_render_channels,
90            num_capture_channels,
91        );
92
93        Self {
94            config: config.clone(),
95            capture_properly_started: false,
96            render_properly_started: false,
97            sample_rate_hz,
98            render_buffer,
99            delay_controller,
100            echo_remover,
101            metrics: BlockProcessorMetrics::new(),
102            render_event: BufferingEvent::None,
103            capture_call_counter: 0,
104            estimated_delay: None,
105        }
106    }
107
108    /// Returns current echo cancellation metrics.
109    pub fn get_metrics(&self) -> BlockProcessorMetricsOutput {
110        let echo_metrics = self.echo_remover.get_metrics();
111        BlockProcessorMetricsOutput {
112            echo_return_loss: echo_metrics.echo_return_loss,
113            echo_return_loss_enhancement: echo_metrics.echo_return_loss_enhancement,
114            delay_ms: self.render_buffer.delay() as i32 * BLOCK_SIZE_MS as i32,
115        }
116    }
117
118    /// Provides an optional external estimate of the audio buffer delay.
119    pub fn set_audio_buffer_delay(&mut self, delay_ms: i32) {
120        self.render_buffer.set_audio_buffer_delay(delay_ms);
121    }
122
123    /// Processes a block of capture data.
124    pub fn process_capture(
125        &mut self,
126        echo_path_gain_change: bool,
127        capture_signal_saturation: bool,
128        linear_output: Option<&mut Block>,
129        capture_block: &mut Block,
130    ) {
131        debug_assert_eq!(
132            num_bands_for_rate(self.sample_rate_hz),
133            capture_block.num_bands()
134        );
135
136        self.capture_call_counter += 1;
137
138        if self.render_properly_started {
139            if !self.capture_properly_started {
140                self.capture_properly_started = true;
141                self.render_buffer.reset();
142                if let Some(ref mut dc) = self.delay_controller {
143                    dc.reset(true);
144                }
145            }
146        } else {
147            // If no render data has yet arrived, do not process the capture signal.
148            self.render_buffer.handle_skipped_capture_processing();
149            return;
150        }
151
152        let mut echo_path_variability =
153            EchoPathVariability::new(echo_path_gain_change, DelayAdjustment::None, false);
154
155        if self.render_event == BufferingEvent::RenderOverrun && self.render_properly_started {
156            echo_path_variability.delay_change = DelayAdjustment::BufferFlush;
157            if let Some(ref mut dc) = self.delay_controller {
158                dc.reset(true);
159            }
160        }
161        self.render_event = BufferingEvent::None;
162
163        // Update the render buffers and prepare for reading.
164        let buffer_event = self.render_buffer.prepare_capture_processing();
165        // Reset the delay controller at render buffer underrun.
166        if buffer_event == BufferingEvent::RenderUnderrun
167            && let Some(ref mut dc) = self.delay_controller
168        {
169            dc.reset(false);
170        }
171
172        let has_delay_estimator = !self.config.delay.use_external_delay_estimator;
173        if has_delay_estimator {
174            let dc = self
175                .delay_controller
176                .as_mut()
177                .expect("delay controller must exist when not using external delay estimator");
178            // Compute and apply the render delay.
179            self.estimated_delay = dc.get_delay(
180                self.render_buffer.get_downsampled_render_buffer(),
181                self.render_buffer.delay(),
182                capture_block,
183            );
184
185            if let Some(ref estimated_delay) = self.estimated_delay {
186                let delay_change = self.render_buffer.align_from_delay(estimated_delay.delay);
187                if delay_change {
188                    echo_path_variability.delay_change = DelayAdjustment::NewDetectedDelay;
189                }
190            }
191
192            echo_path_variability.clock_drift = dc.has_clockdrift();
193        } else {
194            self.render_buffer.align_from_external_delay();
195        }
196
197        // Remove the echo from the capture signal.
198        if has_delay_estimator || self.render_buffer.has_received_buffer_delay() {
199            let render_buffer = self.render_buffer.get_render_buffer();
200            self.echo_remover.process_capture(
201                echo_path_variability,
202                capture_signal_saturation,
203                &self.estimated_delay,
204                &render_buffer,
205                linear_output,
206                capture_block,
207            );
208        }
209
210        // Update the metrics.
211        self.metrics.update_capture(false);
212    }
213
214    /// Buffers a block of render data.
215    pub fn buffer_render(&mut self, block: &Block) {
216        debug_assert_eq!(num_bands_for_rate(self.sample_rate_hz), block.num_bands());
217
218        self.render_event = self.render_buffer.insert(block);
219
220        self.metrics
221            .update_render(self.render_event != BufferingEvent::None);
222
223        self.render_properly_started = true;
224    }
225
226    /// Reports whether echo leakage has been detected.
227    pub fn update_echo_leakage_status(&mut self, leakage_detected: bool) {
228        self.echo_remover
229            .update_echo_leakage_status(leakage_detected);
230    }
231
232    /// Specifies whether the capture output will be used.
233    pub fn set_capture_output_usage(&mut self, capture_output_used: bool) {
234        self.echo_remover
235            .set_capture_output_usage(capture_output_used);
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242    use crate::common::NUM_BLOCKS_PER_SECOND;
243
244    #[test]
245    fn basic_setup_and_api_calls() {
246        for &rate in &[16000, 32000, 48000] {
247            let config = EchoCanceller3Config::default();
248            let mut block_processor = BlockProcessor::new(&config, rate, 1, 1);
249            let block = Block::new_with_value(num_bands_for_rate(rate), 1, 1000.0);
250            let mut capture = block.clone();
251            for _ in 0..1 {
252                block_processor.buffer_render(&block);
253                block_processor.process_capture(false, false, None, &mut capture);
254                block_processor.update_echo_leakage_status(false);
255            }
256        }
257    }
258
259    #[test]
260    fn test_longer_call() {
261        let config = EchoCanceller3Config::default();
262        let mut block_processor = BlockProcessor::new(&config, 16000, 1, 1);
263        let block = Block::new_with_value(1, 1, 1000.0);
264        let mut capture = block.clone();
265        for _ in 0..20 * NUM_BLOCKS_PER_SECOND {
266            block_processor.buffer_render(&block);
267            block_processor.process_capture(false, false, None, &mut capture);
268            block_processor.update_echo_leakage_status(false);
269        }
270    }
271
272    #[test]
273    fn get_metrics_returns_values() {
274        let config = EchoCanceller3Config::default();
275        let block_processor = BlockProcessor::new(&config, 16000, 1, 1);
276        let metrics = block_processor.get_metrics();
277        // delay_ms should be non-negative and a multiple of block_size_ms (4).
278        assert!(metrics.delay_ms >= 0);
279        assert_eq!(metrics.delay_ms % 4, 0);
280    }
281
282    #[test]
283    fn set_audio_buffer_delay() {
284        let config = EchoCanceller3Config::default();
285        let mut block_processor = BlockProcessor::new(&config, 16000, 1, 1);
286        // Should not panic.
287        block_processor.set_audio_buffer_delay(20);
288    }
289
290    #[test]
291    fn set_capture_output_usage() {
292        let config = EchoCanceller3Config::default();
293        let mut block_processor = BlockProcessor::new(&config, 16000, 1, 1);
294        block_processor.set_capture_output_usage(false);
295        block_processor.set_capture_output_usage(true);
296    }
297}