webrtc_audio_processing/
lib.rs

1//! This crate is a wrapper around [PulseAudio's repackaging of WebRTC's AudioProcessing module](https://www.freedesktop.org/software/pulseaudio/webrtc-audio-processing/).
2//!
3//! See `examples/simple.rs` for an example of how to use the library.
4
5#![warn(clippy::all)]
6#![warn(missing_docs)]
7
8mod config;
9
10use std::{error, fmt, sync::Arc};
11use webrtc_audio_processing_sys as ffi;
12
13pub use config::*;
14pub use ffi::NUM_SAMPLES_PER_FRAME;
15
16/// Represents an error inside webrtc::AudioProcessing.
17/// See the documentation of [`webrtc::AudioProcessing::Error`](https://cgit.freedesktop.org/pulseaudio/webrtc-audio-processing/tree/webrtc/modules/audio_processing/include/audio_processing.h?id=9def8cf10d3c97640d32f1328535e881288f700f)
18/// for further details.
19#[derive(Debug)]
20pub struct Error {
21    /// webrtc::AudioProcessing::Error
22    code: i32,
23}
24
25impl fmt::Display for Error {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        write!(f, "ffi::AudioProcessing::Error code: {}", self.code)
28    }
29}
30
31impl error::Error for Error {}
32
33/// `Processor` provides an access to webrtc's audio processing e.g. echo
34/// cancellation and automatic gain control. It can be cloned, and cloned
35/// instances share the same underlying processor module. It's the recommended
36/// way to run the `Processor` in multi-threaded application.
37#[derive(Clone)]
38pub struct Processor {
39    inner: Arc<AudioProcessing>,
40    // TODO: Refactor. It's not necessary to have two frame buffers as
41    // `Processor`s are cloned for each thread.
42    deinterleaved_capture_frame: Vec<Vec<f32>>,
43    deinterleaved_render_frame: Vec<Vec<f32>>,
44}
45
46impl Processor {
47    /// Creates a new `Processor`. `InitializationConfig` is only used on
48    /// instantiation, however new configs can be be passed to `set_config()`
49    /// at any time during processing.
50    pub fn new(config: &ffi::InitializationConfig) -> Result<Self, Error> {
51        Ok(Self {
52            inner: Arc::new(AudioProcessing::new(config)?),
53            deinterleaved_capture_frame: vec![
54                vec![0f32; NUM_SAMPLES_PER_FRAME as usize];
55                config.num_capture_channels as usize
56            ],
57            deinterleaved_render_frame: vec![
58                vec![0f32; NUM_SAMPLES_PER_FRAME as usize];
59                config.num_render_channels as usize
60            ],
61        })
62    }
63
64    /// Processes and modifies the audio frame from a capture device by applying
65    /// signal processing as specified in the config. `frame` should hold an
66    /// interleaved f32 audio frame, with NUM_SAMPLES_PER_FRAME samples.
67    pub fn process_capture_frame(&mut self, frame: &mut [f32]) -> Result<(), Error> {
68        Self::deinterleave(frame, &mut self.deinterleaved_capture_frame);
69        self.inner.process_capture_frame(&mut self.deinterleaved_capture_frame)?;
70        Self::interleave(&self.deinterleaved_capture_frame, frame);
71        Ok(())
72    }
73
74    /// Processes and modifies the audio frame from a capture device by applying
75    /// signal processing as specified in the config. `frame` should be a Vec of
76    /// length 'num_capture_channels', with each inner Vec representing a channel
77    /// with NUM_SAMPLES_PER_FRAME samples.
78    pub fn process_capture_frame_noninterleaved(
79        &mut self,
80        frame: &mut Vec<Vec<f32>>,
81    ) -> Result<(), Error> {
82        self.inner.process_capture_frame(frame)
83    }
84
85    /// Processes and optionally modifies the audio frame from a playback device.
86    /// `frame` should hold an interleaved `f32` audio frame, with
87    /// `NUM_SAMPLES_PER_FRAME` samples.
88    pub fn process_render_frame(&mut self, frame: &mut [f32]) -> Result<(), Error> {
89        Self::deinterleave(frame, &mut self.deinterleaved_render_frame);
90        self.inner.process_render_frame(&mut self.deinterleaved_render_frame)?;
91        Self::interleave(&self.deinterleaved_render_frame, frame);
92        Ok(())
93    }
94
95    /// Processes and optionally modifies the audio frame from a playback device.
96    /// `frame` should be a Vec of length 'num_render_channels', with each inner Vec
97    /// representing a channel with NUM_SAMPLES_PER_FRAME samples.
98    pub fn process_render_frame_noninterleaved(
99        &mut self,
100        frame: &mut Vec<Vec<f32>>,
101    ) -> Result<(), Error> {
102        self.inner.process_render_frame(frame)
103    }
104
105    /// Returns statistics from the last `process_capture_frame()` call.
106    pub fn get_stats(&self) -> Stats {
107        self.inner.get_stats()
108    }
109
110    /// Immediately updates the configurations of the internal signal processor.
111    /// May be called multiple times after the initialization and during
112    /// processing.
113    pub fn set_config(&mut self, config: Config) {
114        self.inner.set_config(config);
115    }
116
117    /// Signals the AEC and AGC that the audio output will be / is muted.
118    /// They may use the hint to improve their parameter adaptation.
119    pub fn set_output_will_be_muted(&self, muted: bool) {
120        self.inner.set_output_will_be_muted(muted);
121    }
122
123    /// De-interleaves multi-channel frame `src` into `dst`.
124    ///
125    /// ```text
126    /// e.g. A stereo frame with 3 samples:
127    ///
128    /// Interleaved
129    /// +---+---+---+---+---+---+
130    /// |L0 |R0 |L1 |R1 |L2 |R2 |
131    /// +---+---+---+---+---+---+
132    ///
133    /// Non-interleaved
134    /// +---+---+---+
135    /// |L0 |L1 |L2 |
136    /// +---+---+---+
137    /// |R0 |R1 |R2 |
138    /// +---+---+---+
139    /// ```
140    fn deinterleave<T: AsMut<[f32]>>(src: &[f32], dst: &mut [T]) {
141        let num_channels = dst.len();
142        let num_samples = dst[0].as_mut().len();
143        assert_eq!(src.len(), num_channels * num_samples);
144        for channel_index in 0..num_channels {
145            for sample_index in 0..num_samples {
146                dst[channel_index].as_mut()[sample_index] =
147                    src[num_channels * sample_index + channel_index];
148            }
149        }
150    }
151
152    /// Reverts the `deinterleave` operation.
153    fn interleave<T: AsRef<[f32]>>(src: &[T], dst: &mut [f32]) {
154        let num_channels = src.len();
155        let num_samples = src[0].as_ref().len();
156        assert_eq!(dst.len(), num_channels * num_samples);
157        for channel_index in 0..num_channels {
158            for sample_index in 0..num_samples {
159                dst[num_channels * sample_index + channel_index] =
160                    src[channel_index].as_ref()[sample_index];
161            }
162        }
163    }
164}
165
166/// Minimal wrapper for safe and synchronized ffi.
167struct AudioProcessing {
168    inner: *mut ffi::AudioProcessing,
169}
170
171impl AudioProcessing {
172    fn new(config: &ffi::InitializationConfig) -> Result<Self, Error> {
173        let mut code = 0;
174        let inner = unsafe { ffi::audio_processing_create(config, &mut code) };
175        if !inner.is_null() {
176            Ok(Self { inner })
177        } else {
178            Err(Error { code })
179        }
180    }
181
182    fn process_capture_frame(&self, frame: &mut Vec<Vec<f32>>) -> Result<(), Error> {
183        let mut frame_ptr = frame.iter_mut().map(|v| v.as_mut_ptr()).collect::<Vec<*mut f32>>();
184        unsafe {
185            let code = ffi::process_capture_frame(self.inner, frame_ptr.as_mut_ptr());
186            if ffi::is_success(code) {
187                Ok(())
188            } else {
189                Err(Error { code })
190            }
191        }
192    }
193
194    fn process_render_frame(&self, frame: &mut Vec<Vec<f32>>) -> Result<(), Error> {
195        let mut frame_ptr = frame.iter_mut().map(|v| v.as_mut_ptr()).collect::<Vec<*mut f32>>();
196        unsafe {
197            let code = ffi::process_render_frame(self.inner, frame_ptr.as_mut_ptr());
198            if ffi::is_success(code) {
199                Ok(())
200            } else {
201                Err(Error { code })
202            }
203        }
204    }
205
206    fn get_stats(&self) -> Stats {
207        unsafe { ffi::get_stats(self.inner).into() }
208    }
209
210    fn set_config(&self, config: Config) {
211        unsafe {
212            ffi::set_config(self.inner, &config.into());
213        }
214    }
215
216    fn set_output_will_be_muted(&self, muted: bool) {
217        unsafe {
218            ffi::set_output_will_be_muted(self.inner, muted);
219        }
220    }
221}
222
223impl Drop for AudioProcessing {
224    fn drop(&mut self) {
225        unsafe {
226            ffi::audio_processing_delete(self.inner);
227        }
228    }
229}
230
231// ffi::AudioProcessing provides thread safety with a few exceptions around
232// the concurrent usage of its getters and setters e.g. `set_stream_delay_ms()`.
233unsafe impl Sync for AudioProcessing {}
234unsafe impl Send for AudioProcessing {}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use std::{thread, time::Duration};
240
241    #[test]
242    fn test_create_failure() {
243        let config =
244            InitializationConfig { num_capture_channels: 0, ..InitializationConfig::default() };
245        assert!(Processor::new(&config).is_err());
246    }
247
248    #[test]
249    fn test_create_drop() {
250        let config = InitializationConfig {
251            num_capture_channels: 1,
252            num_render_channels: 1,
253            ..InitializationConfig::default()
254        };
255        let _p = Processor::new(&config).unwrap();
256    }
257
258    #[test]
259    fn test_deinterleave_interleave() {
260        let num_channels = 2usize;
261        let num_samples = 3usize;
262
263        let interleaved = (0..num_channels * num_samples).map(|v| v as f32).collect::<Vec<f32>>();
264        let mut deinterleaved = vec![vec![-1f32; num_samples]; num_channels];
265        Processor::deinterleave(&interleaved, &mut deinterleaved);
266        assert_eq!(vec![vec![0f32, 2f32, 4f32], vec![1f32, 3f32, 5f32]], deinterleaved);
267
268        let mut interleaved_out = vec![-1f32; num_samples * num_channels];
269        Processor::interleave(&deinterleaved, &mut interleaved_out);
270        assert_eq!(interleaved, interleaved_out);
271    }
272
273    fn sample_stereo_frames() -> (Vec<f32>, Vec<f32>) {
274        let num_samples_per_frame = NUM_SAMPLES_PER_FRAME as usize;
275
276        // Stereo frame with a lower frequency cosine wave.
277        let mut render_frame = Vec::with_capacity(num_samples_per_frame * 2);
278        for i in 0..num_samples_per_frame {
279            render_frame.push((i as f32 / 40.0).cos() * 0.4);
280            render_frame.push((i as f32 / 40.0).cos() * 0.2);
281        }
282
283        // Stereo frame with a higher frequency sine wave, mixed with the cosine
284        // wave from render frame.
285        let mut capture_frame = Vec::with_capacity(num_samples_per_frame * 2);
286        for i in 0..num_samples_per_frame {
287            capture_frame.push((i as f32 / 20.0).sin() * 0.4 + render_frame[i * 2] * 0.2);
288            capture_frame.push((i as f32 / 20.0).sin() * 0.2 + render_frame[i * 2 + 1] * 0.2);
289        }
290
291        (render_frame, capture_frame)
292    }
293
294    #[test]
295    fn test_nominal() {
296        let config = InitializationConfig {
297            num_capture_channels: 2,
298            num_render_channels: 2,
299            ..InitializationConfig::default()
300        };
301        let mut ap = Processor::new(&config).unwrap();
302
303        let config = Config {
304            echo_cancellation: Some(EchoCancellation {
305                suppression_level: EchoCancellationSuppressionLevel::High,
306                stream_delay_ms: None,
307                enable_delay_agnostic: false,
308                enable_extended_filter: false,
309            }),
310            ..Config::default()
311        };
312        ap.set_config(config);
313
314        let (render_frame, capture_frame) = sample_stereo_frames();
315
316        let mut render_frame_output = render_frame.clone();
317        ap.process_render_frame(&mut render_frame_output).unwrap();
318
319        // Render frame should not be modified.
320        assert_eq!(render_frame, render_frame_output);
321
322        let mut capture_frame_output = capture_frame.clone();
323        ap.process_capture_frame(&mut capture_frame_output).unwrap();
324
325        // Echo cancellation should have modified the capture frame.
326        // We don't validate how it's modified. Out of scope for this unit test.
327        assert_ne!(capture_frame, capture_frame_output);
328
329        let stats = ap.get_stats();
330        assert!(stats.echo_return_loss.is_some());
331        println!("{:#?}", stats);
332    }
333
334    #[test]
335    #[ignore]
336    fn test_nominal_threaded() {
337        let config = InitializationConfig {
338            num_capture_channels: 2,
339            num_render_channels: 2,
340            ..InitializationConfig::default()
341        };
342        let ap = Processor::new(&config).unwrap();
343
344        let (render_frame, capture_frame) = sample_stereo_frames();
345
346        let mut config_ap = ap.clone();
347        let config_thread = thread::spawn(move || {
348            thread::sleep(Duration::from_millis(100));
349
350            let config = Config {
351                echo_cancellation: Some(EchoCancellation {
352                    suppression_level: EchoCancellationSuppressionLevel::High,
353                    stream_delay_ms: None,
354                    enable_delay_agnostic: false,
355                    enable_extended_filter: false,
356                }),
357                ..Config::default()
358            };
359            config_ap.set_config(config);
360        });
361
362        let mut render_ap = ap.clone();
363        let render_thread = thread::spawn(move || {
364            for _ in 0..100 {
365                let mut render_frame_output = render_frame.clone();
366                render_ap.process_render_frame(&mut render_frame_output).unwrap();
367
368                thread::sleep(Duration::from_millis(10));
369            }
370        });
371
372        let mut capture_ap = ap.clone();
373        let capture_thread = thread::spawn(move || {
374            for i in 0..100 {
375                let mut capture_frame_output = capture_frame.clone();
376                capture_ap.process_capture_frame(&mut capture_frame_output).unwrap();
377
378                let stats = capture_ap.get_stats();
379                if i < 5 {
380                    // first 50ms
381                    assert!(stats.echo_return_loss.is_none());
382                } else if i >= 95 {
383                    // last 50ms
384                    assert!(stats.echo_return_loss.is_some());
385                }
386
387                thread::sleep(Duration::from_millis(10));
388            }
389        });
390
391        config_thread.join().unwrap();
392        render_thread.join().unwrap();
393        capture_thread.join().unwrap();
394    }
395}