Coverage Report

Created: 2025-06-24 06:17

/src/adhd/audio_processor/src/pipeline.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2024 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::path::Path;
6
7
use anyhow::Context;
8
use hound::WavSpec;
9
use hound::WavWriter;
10
11
use crate::processors::WavSink;
12
use crate::AudioProcessor;
13
use crate::Format;
14
use crate::MultiSlice;
15
16
pub type ProcessorVec = Vec<Box<dyn AudioProcessor<I = f32, O = f32> + Send>>;
17
18
pub struct Pipeline {
19
    pub input_format: Format,
20
    pub vec: ProcessorVec,
21
}
22
23
impl Pipeline {
24
0
    pub fn new(input_format: Format) -> Self {
25
0
        Pipeline {
26
0
            input_format,
27
0
            vec: Vec::new(),
28
0
        }
29
0
    }
30
31
0
    pub fn add(&mut self, processor: impl AudioProcessor<I = f32, O = f32> + Send + 'static) {
32
0
        self.vec.push(Box::new(processor));
33
0
    }
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::negate::InPlaceNegateAudioProcessor<f32>>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::wav::WavSink<std::io::buffered::bufwriter::BufWriter<std::fs::File>>>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::profile::Profile<audio_processor::processors::plugin::dynamic::DynamicPluginProcessor>>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::plugin::dynamic::DynamicPluginProcessor>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::chunk_wrapper::ChunkWrapper<audio_processor::pipeline::Pipeline, f32>>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::speex::SpeexResampler>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::shuffle_channels::ShuffleChannels>
Unexecuted instantiation: <audio_processor::pipeline::Pipeline>::add::<audio_processor::processors::peer::managed::ManagedBlockingSeqPacketProcessor>
34
35
0
    pub fn add_wav_dump(&mut self, filename: &Path) -> anyhow::Result<()> {
36
0
        let format = self.get_output_format();
37
0
        {
38
0
            self.add(WavSink::new(
39
0
                WavWriter::create(
40
0
                    filename,
41
0
                    WavSpec {
42
0
                        channels: format.channels.try_into().context("channels.try_into()")?,
43
0
                        sample_rate: format
44
0
                            .frame_rate
45
0
                            .try_into()
46
0
                            .context("frame_rate.try_into()")?,
47
                        bits_per_sample: 32,
48
0
                        sample_format: hound::SampleFormat::Float,
49
0
                    },
50
0
                )
51
0
                .context("WavWriter::create")?,
52
0
                format.block_size,
53
0
            ));
54
0
            anyhow::Result::<()>::Ok(())
55
0
        }
56
0
        .context("add_wav_dump")?;
57
0
        Ok(())
58
0
    }
59
}
60
61
impl AudioProcessor for Pipeline {
62
    type I = f32;
63
    type O = f32;
64
65
0
    fn process<'a>(
66
0
        &'a mut self,
67
0
        mut input: MultiSlice<'a, Self::I>,
68
0
    ) -> crate::Result<MultiSlice<'a, Self::O>> {
69
0
        for processor in self.vec.iter_mut() {
70
0
            input = processor.process(input)?;
71
        }
72
0
        Ok(input)
73
0
    }
74
75
0
    fn get_output_format(&self) -> Format {
76
0
        match self.vec.last() {
77
0
            Some(last) => last.get_output_format(),
78
0
            None => self.input_format,
79
        }
80
0
    }
81
}
82
83
#[cfg(test)]
84
mod tests {
85
    use anyhow::Context;
86
    use tempfile::TempDir;
87
88
    use crate::processors::NegateAudioProcessor;
89
    use crate::util::read_wav;
90
    use crate::AudioProcessor;
91
    use crate::Format;
92
    use crate::MultiBuffer;
93
    use crate::Pipeline;
94
95
    #[test]
96
    fn test_pipeline() -> anyhow::Result<()> {
97
        let tmpd = TempDir::new().context("tempdir")?;
98
99
        let dump1 = tmpd.path().join("dump-1.wav");
100
        let dump2 = tmpd.path().join("dump-2.wav");
101
102
        let mut p = Pipeline::new(Format {
103
            channels: 1,
104
            block_size: 4,
105
            frame_rate: 48000,
106
        });
107
108
        p.add_wav_dump(&dump1).unwrap();
109
        p.add(NegateAudioProcessor::new(Format {
110
            channels: 1,
111
            block_size: 4,
112
            frame_rate: 48000,
113
        }));
114
        p.add_wav_dump(&dump2).unwrap();
115
116
        assert_eq!(
117
            p.get_output_format(),
118
            Format {
119
                channels: 1,
120
                block_size: 4,
121
                frame_rate: 48000,
122
            }
123
        );
124
125
        let mut buf = MultiBuffer::from(vec![vec![1f32, 2., 3., 4.]]);
126
        let out = p.process(buf.as_multi_slice()).unwrap();
127
128
        // Check output.
129
        assert_eq!(out.into_raw(), [[-1., -2., -3., -4.]]);
130
        // Drop p to flush wav dumps.
131
        drop(p);
132
133
        let (_, wav1) = read_wav::<f32>(&dump1).unwrap();
134
        let (_, wav2) = read_wav::<f32>(&dump2).unwrap();
135
        assert_eq!(wav1.to_vecs(), [[1., 2., 3., 4.]]);
136
        assert_eq!(wav2.to_vecs(), [[-1., -2., -3., -4.]]);
137
138
        Ok(())
139
    }
140
}