/src/adhd/cras/server/processor/src/lib.rs
Line | Count | Source |
1 | | // Copyright 2023 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 | | use std::path::PathBuf; |
7 | | use std::ptr::NonNull; |
8 | | use std::sync::atomic::AtomicUsize; |
9 | | use std::sync::atomic::Ordering; |
10 | | |
11 | | use anyhow::ensure; |
12 | | use anyhow::Context; |
13 | | use audio_processor::cdcfg; |
14 | | use audio_processor::cdcfg::ResolverContext; |
15 | | use audio_processor::config::PipelineBuilder; |
16 | | use audio_processor::config::PreloadedProcessor; |
17 | | use audio_processor::config::Processor; |
18 | | use audio_processor::processors::binding::plugin_processor; |
19 | | use audio_processor::processors::export_plugin; |
20 | | use audio_processor::processors::peer::AudioWorkerSubprocessFactory; |
21 | | use audio_processor::processors::CheckShape; |
22 | | use audio_processor::processors::PluginProcessor; |
23 | | use audio_processor::processors::ThreadedProcessor; |
24 | | use audio_processor::AudioProcessor; |
25 | | use audio_processor::Format; |
26 | | use audio_processor::Pipeline; |
27 | | use cras_common::types_internal::CrasProcessorEffect; |
28 | | use cras_dlc::get_dlc_state_cached; |
29 | | use cras_s2::global::cras_s2_get_beamforming_config_path; |
30 | | use cras_s2::global::cras_s2_get_cras_processor_vars; |
31 | | |
32 | | mod processor_override; |
33 | | mod proto; |
34 | | |
35 | | #[repr(C)] |
36 | | #[derive(Clone, Copy, Debug)] |
37 | | pub enum CrasProcessorWrapMode { |
38 | | WrapModeNone, |
39 | | /// Run the processor pipeline in a separate, dedicated thread. |
40 | | WrapModeDedicatedThread, |
41 | | /// Run the processor pipeline with a ChunkWrapper with the inner block |
42 | | /// size set to [`CrasProcessorConfig::block_size`]. |
43 | | /// In this mode, the caller is allowed to run the pipeline with a block |
44 | | /// size that is different from [`CrasProcessorConfig::block_size`]. |
45 | | WrapModeChunk, |
46 | | /// Like `WrapModeChunk` but the pipeline is run inside a peer processor (sandbox). |
47 | | /// [`CrasProcessorConfig::max_block_size`] must be set in this mode. |
48 | | /// WAVE dump is not supported in this mode. |
49 | | WrapModePeerChunk, |
50 | | } |
51 | | |
52 | | #[repr(C)] |
53 | | #[derive(Clone, Debug)] |
54 | | pub struct CrasProcessorConfig { |
55 | | // The number of channels after apm_processor. |
56 | | channels: usize, |
57 | | block_size: usize, |
58 | | frame_rate: usize, |
59 | | |
60 | | effect: CrasProcessorEffect, |
61 | | |
62 | | wrap_mode: CrasProcessorWrapMode, |
63 | | |
64 | | // Enable processing dumps as WAVE files. |
65 | | wav_dump: bool, |
66 | | |
67 | | /// The max block size when wrap_mode is WrapModePeerChunk. |
68 | | /// Used to determine buffer size to allocate for peer IPC. |
69 | | max_block_size: usize, |
70 | | } |
71 | | |
72 | | impl CrasProcessorConfig { |
73 | 0 | fn format(&self) -> Format { |
74 | 0 | Format { |
75 | 0 | channels: self.channels, |
76 | 0 | block_size: self.block_size, |
77 | 0 | frame_rate: self.frame_rate, |
78 | 0 | } |
79 | 0 | } |
80 | | } |
81 | | |
82 | | pub struct CrasProcessor { |
83 | | id: usize, |
84 | | pipeline: Pipeline, |
85 | | config: CrasProcessorConfig, |
86 | | } |
87 | | |
88 | | impl AudioProcessor for CrasProcessor { |
89 | | type I = f32; |
90 | | type O = f32; |
91 | | |
92 | 0 | fn process<'a>( |
93 | 0 | &'a mut self, |
94 | 0 | input: audio_processor::MultiSlice<'a, Self::I>, |
95 | 0 | ) -> audio_processor::Result<audio_processor::MultiSlice<'a, Self::O>> { |
96 | 0 | self.pipeline.process(input) |
97 | 0 | } |
98 | | |
99 | 0 | fn get_output_format(&self) -> audio_processor::Format { |
100 | 0 | self.pipeline.get_output_format() |
101 | 0 | } |
102 | | } |
103 | | |
104 | | struct CrasProcessorResolverContext { |
105 | | wav_dump_root: Option<PathBuf>, |
106 | | cras_processor_format: Format, |
107 | | } |
108 | | |
109 | | impl audio_processor::cdcfg::ResolverContext for CrasProcessorResolverContext { |
110 | 0 | fn get_wav_dump_root(&self) -> Option<&Path> { |
111 | 0 | self.wav_dump_root.as_deref() |
112 | 0 | } |
113 | | |
114 | 0 | fn get_dlc_root_path(&self, dlc_id: &str) -> anyhow::Result<PathBuf> { |
115 | 0 | let dlc_state = get_dlc_state_cached(dlc_id); |
116 | 0 | ensure!(dlc_state.installed, "{dlc_id} not installed"); |
117 | 0 | Ok(dlc_state.root_path.into()) |
118 | 0 | } |
119 | | |
120 | 0 | fn get_duplicate_channel_0(&self) -> Option<usize> { |
121 | | // Duplicate channel 0 to the cras_processor count when requested. |
122 | 0 | Some(self.cras_processor_format.channels) |
123 | 0 | } |
124 | | } |
125 | | |
126 | | static GLOBAL_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); |
127 | | |
128 | 0 | fn get_noise_cancellation_pipeline_decl( |
129 | 0 | context: &dyn ResolverContext, |
130 | 0 | ) -> anyhow::Result<Processor> { |
131 | 0 | cdcfg::parse( |
132 | 0 | context, |
133 | 0 | &cras_s2_get_cras_processor_vars(), |
134 | 0 | Path::new("/etc/cras/processor/noise_cancellation.txtpb"), |
135 | | ) |
136 | 0 | } |
137 | | |
138 | 0 | fn get_style_transfer_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
139 | 0 | cdcfg::parse( |
140 | 0 | context, |
141 | 0 | &cras_s2_get_cras_processor_vars(), |
142 | 0 | Path::new("/etc/cras/processor/style_transfer.txtpb"), |
143 | | ) |
144 | 0 | } |
145 | | |
146 | 0 | fn get_beamforming_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
147 | 0 | cdcfg::parse( |
148 | 0 | context, |
149 | 0 | &cras_s2_get_cras_processor_vars(), |
150 | 0 | &cras_s2_get_beamforming_config_path().context("beamforming config path unknown")?, |
151 | | ) |
152 | 0 | } |
153 | | |
154 | 0 | fn get_echo_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
155 | 0 | cdcfg::parse( |
156 | 0 | context, |
157 | 0 | &cras_s2_get_cras_processor_vars(), |
158 | 0 | Path::new("/etc/cras/processor/echo.txtpb"), |
159 | | ) |
160 | 0 | } |
161 | | |
162 | 0 | fn get_speaker_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
163 | 0 | cdcfg::parse( |
164 | 0 | context, |
165 | 0 | &cras_s2_get_cras_processor_vars(), |
166 | 0 | Path::new("/etc/cras/processor/speaker_plugin.txtpb"), |
167 | | ) |
168 | 0 | } |
169 | | |
170 | 0 | fn get_headphone_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
171 | 0 | cdcfg::parse( |
172 | 0 | context, |
173 | 0 | &cras_s2_get_cras_processor_vars(), |
174 | 0 | Path::new("/etc/cras/processor/headphone_plugin.txtpb"), |
175 | | ) |
176 | 0 | } |
177 | | |
178 | 0 | fn get_krisp_pipeline_decl(context: &dyn ResolverContext) -> anyhow::Result<Processor> { |
179 | 0 | cdcfg::parse( |
180 | 0 | context, |
181 | 0 | &cras_s2_get_cras_processor_vars(), |
182 | 0 | Path::new("/etc/cras/processor/krisp_nc.txtpb"), |
183 | | ) |
184 | 0 | } |
185 | | |
186 | | impl CrasProcessor { |
187 | 0 | fn new( |
188 | 0 | mut config: CrasProcessorConfig, |
189 | 0 | apm_processor: Option<PluginProcessor>, |
190 | 0 | ) -> anyhow::Result<Self> { |
191 | 0 | let override_config = processor_override::read_system_config().input; |
192 | 0 | if override_config.enabled { |
193 | 0 | config.effect = CrasProcessorEffect::Overridden; |
194 | 0 | } |
195 | 0 | let config = config; |
196 | | |
197 | 0 | let id = GLOBAL_ID_COUNTER.fetch_add(1, Ordering::AcqRel); |
198 | | |
199 | 0 | let dump_base = PathBuf::from(format!("/run/cras/debug/cras_processor_{id}")); |
200 | | |
201 | 0 | let resolver_context = CrasProcessorResolverContext { |
202 | 0 | wav_dump_root: if config.wav_dump { |
203 | 0 | Some(dump_base.clone()) |
204 | | } else { |
205 | 0 | None |
206 | | }, |
207 | 0 | cras_processor_format: config.format(), |
208 | | }; |
209 | 0 | let mut decl = vec![]; |
210 | | |
211 | 0 | if config.wav_dump { |
212 | 0 | std::fs::create_dir_all(&dump_base).context("mkdir dump_base")?; |
213 | 0 | decl.push(Processor::WavSink { |
214 | 0 | path: dump_base.join("input.wav"), |
215 | 0 | }); |
216 | 0 | } |
217 | | |
218 | 0 | if let Some(apm_processor) = apm_processor { |
219 | 0 | decl.push(Processor::Preloaded(PreloadedProcessor { |
220 | 0 | description: "apm", |
221 | 0 | processor: Box::new(apm_processor), |
222 | 0 | })); |
223 | 0 | decl.push(Processor::Preloaded(PreloadedProcessor { |
224 | 0 | description: "CheckShape", |
225 | 0 | processor: Box::new(CheckShape::new(config.format())), |
226 | 0 | })); |
227 | | |
228 | 0 | if config.wav_dump { |
229 | 0 | decl.push(Processor::WavSink { |
230 | 0 | path: dump_base.join("post_apm.wav"), |
231 | 0 | }); |
232 | 0 | } |
233 | 0 | } |
234 | | |
235 | 0 | match config.effect { |
236 | 0 | CrasProcessorEffect::NoEffects => { |
237 | 0 | // Do nothing. |
238 | 0 | } |
239 | 0 | CrasProcessorEffect::Negate => { |
240 | 0 | decl.push(Processor::Negate); |
241 | 0 | } |
242 | | CrasProcessorEffect::NoiseCancellation | CrasProcessorEffect::StyleTransfer => { |
243 | 0 | decl.push(Processor::ShuffleChannels { |
244 | 0 | channel_indexes: vec![0], |
245 | 0 | }); |
246 | 0 | decl.push( |
247 | 0 | get_noise_cancellation_pipeline_decl(&resolver_context) |
248 | 0 | .context("failed get_noise_cancellation_pipeline_decl")?, |
249 | | ); |
250 | 0 | if let CrasProcessorEffect::StyleTransfer = config.effect { |
251 | 0 | decl.push( |
252 | 0 | get_style_transfer_pipeline_decl(&resolver_context) |
253 | 0 | .context("failed get_style_transfer_pipeline_decl")?, |
254 | | ); |
255 | 0 | } |
256 | 0 | decl.push(Processor::ShuffleChannels { |
257 | 0 | channel_indexes: vec![0; config.channels], |
258 | 0 | }); |
259 | | } |
260 | | CrasProcessorEffect::Beamforming => { |
261 | 0 | decl.push( |
262 | 0 | get_beamforming_pipeline_decl(&resolver_context) |
263 | 0 | .context("failed when creating beamforming pipeline")?, |
264 | | ); |
265 | | } |
266 | | CrasProcessorEffect::GenerateEcho => { |
267 | 0 | decl.push( |
268 | 0 | get_echo_pipeline_decl(&resolver_context) |
269 | 0 | .context("failed when creating echo pipeline")?, |
270 | | ); |
271 | | } |
272 | | CrasProcessorEffect::SpeakerPlugin => { |
273 | 0 | decl.push( |
274 | 0 | get_speaker_pipeline_decl(&resolver_context) |
275 | 0 | .context("failed when creating speaker plugin pipeline")?, |
276 | | ); |
277 | | } |
278 | | CrasProcessorEffect::HeadphonePlugin => { |
279 | 0 | decl.push( |
280 | 0 | get_headphone_pipeline_decl(&resolver_context) |
281 | 0 | .context("failed when creating headphone plugin pipeline")?, |
282 | | ); |
283 | | } |
284 | | CrasProcessorEffect::KrispNC => { |
285 | 0 | decl.push(Processor::ShuffleChannels { |
286 | 0 | channel_indexes: vec![0], |
287 | 0 | }); |
288 | 0 | decl.push( |
289 | 0 | get_krisp_pipeline_decl(&resolver_context) |
290 | 0 | .context("failed get_krisp_pipeline_decl")?, |
291 | | ); |
292 | 0 | decl.push(Processor::ShuffleChannels { |
293 | 0 | channel_indexes: vec![0; config.channels], |
294 | 0 | }); |
295 | | } |
296 | | CrasProcessorEffect::Overridden => { |
297 | 0 | if override_config.frame_rate != 0 { |
298 | 0 | decl.push(Processor::Resample { |
299 | 0 | output_frame_rate: override_config.frame_rate as usize, |
300 | 0 | }); |
301 | 0 | } |
302 | 0 | let plugin = Processor::Plugin { |
303 | 0 | path: override_config.plugin_path.clone().into(), |
304 | 0 | constructor: override_config.constructor.clone(), |
305 | 0 | }; |
306 | 0 | decl.push(match override_config.block_size { |
307 | 0 | 0 => plugin, // Use existing block size if 0. |
308 | 0 | block_size => Processor::WrapChunk { |
309 | 0 | inner_block_size: block_size as usize, |
310 | 0 | inner: Box::new(plugin), |
311 | 0 | disallow_hoisting: false, |
312 | 0 | }, |
313 | | }); |
314 | | } |
315 | | }; |
316 | | |
317 | | // Resample to input rate. |
318 | 0 | decl.push(Processor::Resample { |
319 | 0 | output_frame_rate: config.frame_rate, |
320 | 0 | }); |
321 | | |
322 | | // Check that the input format is the same as the output format. |
323 | 0 | decl.push(Processor::CheckFormat { |
324 | 0 | channels: Some(config.channels), |
325 | 0 | block_size: Some(config.block_size), |
326 | 0 | frame_rate: Some(config.frame_rate), |
327 | 0 | }); |
328 | | |
329 | 0 | if config.wav_dump { |
330 | 0 | decl.push(Processor::WavSink { |
331 | 0 | path: dump_base.join("output.wav"), |
332 | 0 | }); |
333 | 0 | } |
334 | | |
335 | 0 | let mut pipeline = Processor::Pipeline { processors: decl }; |
336 | 0 | if matches!( |
337 | 0 | config.wrap_mode, |
338 | | CrasProcessorWrapMode::WrapModeChunk | CrasProcessorWrapMode::WrapModePeerChunk |
339 | | ) { |
340 | 0 | pipeline = Processor::WrapChunk { |
341 | 0 | inner: Box::new(pipeline), |
342 | 0 | inner_block_size: config.block_size, |
343 | 0 | disallow_hoisting: true, |
344 | 0 | }; |
345 | 0 | if matches!(config.wrap_mode, CrasProcessorWrapMode::WrapModePeerChunk) { |
346 | 0 | pipeline = Processor::Peer { |
347 | 0 | processor: Box::new(pipeline), |
348 | 0 | }; |
349 | 0 | } |
350 | 0 | } |
351 | | |
352 | 0 | let decl_debug = format!("{pipeline:?}"); |
353 | 0 | let pipeline = PipelineBuilder::new(Format { |
354 | 0 | block_size: if matches!(config.wrap_mode, CrasProcessorWrapMode::WrapModePeerChunk) { |
355 | 0 | assert_ne!(config.max_block_size, 0); |
356 | 0 | config.max_block_size // Used for allocation. |
357 | | } else { |
358 | 0 | config.block_size |
359 | | }, |
360 | 0 | ..config.format() |
361 | | }) |
362 | | // TODO(b/349784210): Use a hardened worker factory. |
363 | 0 | .with_worker_factory(AudioWorkerSubprocessFactory::default().with_set_thread_priority()) |
364 | 0 | .build(pipeline) |
365 | 0 | .context("failed to build pipeline")?; |
366 | | |
367 | 0 | log::info!("CrasProcessor #{id} created with: {config:?}"); |
368 | 0 | log::info!("CrasProcessor #{id} pipeline: {decl_debug}"); |
369 | | |
370 | 0 | Ok(CrasProcessor { |
371 | 0 | id, |
372 | 0 | pipeline, |
373 | 0 | config, |
374 | 0 | }) |
375 | 0 | } |
376 | | } |
377 | | |
378 | | impl Drop for CrasProcessor { |
379 | 0 | fn drop(&mut self) { |
380 | 0 | log::info!("CrasProcessor #{} dropped", self.id); |
381 | 0 | } |
382 | | } |
383 | | |
384 | | #[repr(C)] |
385 | | pub struct CrasProcessorCreateResult { |
386 | | /// The created processor. |
387 | | pub plugin_processor: *mut plugin_processor, |
388 | | /// The actual effect used in the processor. |
389 | | /// Might be different from what was passed to cras_processor_create. |
390 | | pub effect: CrasProcessorEffect, |
391 | | } |
392 | | |
393 | | impl CrasProcessorCreateResult { |
394 | 0 | fn none() -> Self { |
395 | 0 | Self { |
396 | 0 | plugin_processor: std::ptr::null_mut(), |
397 | 0 | effect: CrasProcessorEffect::NoEffects, |
398 | 0 | } |
399 | 0 | } |
400 | | } |
401 | | |
402 | 0 | unsafe fn create_apm_processor( |
403 | 0 | config: &CrasProcessorConfig, |
404 | 0 | apm_plugin_processor: *mut plugin_processor, |
405 | 0 | ) -> anyhow::Result<Option<PluginProcessor>> { |
406 | 0 | let apm_processor = match NonNull::new(apm_plugin_processor) { |
407 | 0 | Some(apm_plugin_processor) => Some( |
408 | 0 | PluginProcessor::from_handle(apm_plugin_processor.as_ptr(), config.format()) |
409 | 0 | .context("failed PluginProcessor::from_handle")?, |
410 | | ), |
411 | 0 | None => None, |
412 | | }; |
413 | 0 | Ok(apm_processor) |
414 | 0 | } |
415 | | |
416 | | /// Create a CRAS processor. |
417 | | /// |
418 | | /// Returns the created processor (might be NULL), and the applied effect. |
419 | | /// |
420 | | /// # Safety |
421 | | /// |
422 | | /// `config` must point to a CrasProcessorConfig struct. |
423 | | /// `apm_plugin_processor` must point to a plugin_processor or NULL. |
424 | | #[no_mangle] |
425 | 0 | pub unsafe extern "C" fn cras_processor_create( |
426 | 0 | config: *const CrasProcessorConfig, |
427 | 0 | apm_plugin_processor: *mut plugin_processor, |
428 | 0 | ) -> CrasProcessorCreateResult { |
429 | 0 | let config = match config.as_ref() { |
430 | 0 | Some(config) => config, |
431 | | None => { |
432 | 0 | return CrasProcessorCreateResult::none(); |
433 | | } |
434 | | }; |
435 | | |
436 | 0 | let apm_processor = match create_apm_processor(config, apm_plugin_processor) { |
437 | 0 | Ok(apm_processor) => apm_processor, |
438 | 0 | Err(err) => { |
439 | 0 | log::error!("{err:#}"); |
440 | 0 | return CrasProcessorCreateResult::none(); |
441 | | } |
442 | | }; |
443 | | |
444 | 0 | let processor = match CrasProcessor::new(config.clone(), apm_processor) { |
445 | 0 | Ok(processor) => processor, |
446 | 0 | Err(err) => { |
447 | 0 | log::error!( |
448 | 0 | "CrasProcessor::new failed with {:#}, creating no-op processor", |
449 | | err |
450 | | ); |
451 | | |
452 | 0 | let config = config.clone(); |
453 | 0 | CrasProcessor::new( |
454 | 0 | CrasProcessorConfig { |
455 | 0 | effect: CrasProcessorEffect::NoEffects, |
456 | 0 | ..config |
457 | 0 | }, |
458 | | // apm_processor was consumed so create it again. |
459 | 0 | create_apm_processor(&config, apm_plugin_processor).expect("create_apm_processor should not fail given that we created it successfully once"), |
460 | | ) |
461 | 0 | .expect("CrasProcessor::new with CrasProcessorEffect::NoEffects should never fail") |
462 | | } |
463 | | }; |
464 | | |
465 | 0 | let effect = processor.config.effect; |
466 | 0 | let plugin_processor = if matches!( |
467 | 0 | config.wrap_mode, |
468 | | CrasProcessorWrapMode::WrapModeDedicatedThread |
469 | | ) { |
470 | 0 | let threaded_processor = ThreadedProcessor::new(processor, 1); |
471 | 0 | export_plugin(threaded_processor) |
472 | | } else { |
473 | 0 | export_plugin(processor) |
474 | | }; |
475 | 0 | CrasProcessorCreateResult { |
476 | 0 | plugin_processor, |
477 | 0 | effect, |
478 | 0 | } |
479 | 0 | } |
480 | | |
481 | | /// Returns true if override is enabled in the system config file. |
482 | | #[no_mangle] |
483 | 0 | pub extern "C" fn cras_processor_is_override_enabled() -> bool { |
484 | 0 | processor_override::read_system_config().input.enabled |
485 | 0 | } |