xref: /aosp_15_r20/external/crosvm/devices/src/virtio/snd/parameters.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 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::num::ParseIntError;
6 use std::str::ParseBoolError;
7 
8 use audio_streams::StreamEffect;
9 #[cfg(all(unix, feature = "audio_cras"))]
10 use libcras::CrasClientType;
11 #[cfg(all(unix, feature = "audio_cras"))]
12 use libcras::CrasSocketType;
13 #[cfg(all(unix, feature = "audio_cras"))]
14 use libcras::CrasStreamType;
15 use serde::Deserialize;
16 use serde::Serialize;
17 use serde_keyvalue::FromKeyValues;
18 use thiserror::Error as ThisError;
19 
20 use crate::virtio::snd::constants::*;
21 use crate::virtio::snd::layout::*;
22 use crate::virtio::snd::sys::StreamSourceBackend as SysStreamSourceBackend;
23 
24 #[derive(ThisError, Debug, PartialEq, Eq)]
25 pub enum Error {
26     /// Unknown snd parameter value.
27     #[error("Invalid snd parameter value ({0}): {1}")]
28     InvalidParameterValue(String, String),
29     /// Failed to parse bool value.
30     #[error("Invalid bool value: {0}")]
31     InvalidBoolValue(ParseBoolError),
32     /// Failed to parse int value.
33     #[error("Invalid int value: {0}")]
34     InvalidIntValue(ParseIntError),
35     // Invalid backend.
36     #[error("Backend is not implemented")]
37     InvalidBackend,
38     /// Failed to parse parameters.
39     #[error("Invalid snd parameter: {0}")]
40     UnknownParameter(String),
41     /// Invalid PCM device config index. Happens when the length of PCM device config is less than
42     /// the number of PCM devices.
43     #[error("Invalid PCM device config index: {0}")]
44     InvalidPCMDeviceConfigIndex(usize),
45     /// Invalid PCM info direction (VIRTIO_SND_D_OUTPUT = 0, VIRTIO_SND_D_INPUT = 1)
46     #[error("Invalid PCM Info direction: {0}")]
47     InvalidPCMInfoDirection(u8),
48 }
49 
50 #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
51 #[serde(into = "String", try_from = "&str")]
52 pub enum StreamSourceBackend {
53     NULL,
54     FILE,
55     Sys(SysStreamSourceBackend),
56 }
57 
58 // Implemented to make backend serialization possible, since we deserialize from str.
59 impl From<StreamSourceBackend> for String {
from(backend: StreamSourceBackend) -> Self60     fn from(backend: StreamSourceBackend) -> Self {
61         match backend {
62             StreamSourceBackend::NULL => "null".to_owned(),
63             StreamSourceBackend::FILE => "file".to_owned(),
64             StreamSourceBackend::Sys(sys_backend) => sys_backend.into(),
65         }
66     }
67 }
68 
69 impl TryFrom<&str> for StreamSourceBackend {
70     type Error = Error;
71 
try_from(s: &str) -> Result<Self, Self::Error>72     fn try_from(s: &str) -> Result<Self, Self::Error> {
73         match s {
74             "null" => Ok(StreamSourceBackend::NULL),
75             "file" => Ok(StreamSourceBackend::FILE),
76             _ => SysStreamSourceBackend::try_from(s).map(StreamSourceBackend::Sys),
77         }
78     }
79 }
80 
81 /// Holds the parameters for each PCM device
82 #[derive(Debug, Clone, Default, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
83 #[serde(deny_unknown_fields, default)]
84 pub struct PCMDeviceParameters {
85     #[cfg(all(unix, feature = "audio_cras"))]
86     pub client_type: Option<CrasClientType>,
87     #[cfg(all(unix, feature = "audio_cras"))]
88     pub stream_type: Option<CrasStreamType>,
89     pub effects: Option<Vec<StreamEffect>>,
90 }
91 
92 /// Holds the parameters for a cras sound device
93 #[derive(Debug, Clone, Deserialize, Serialize, FromKeyValues)]
94 #[serde(deny_unknown_fields, default)]
95 pub struct Parameters {
96     pub capture: bool,
97     pub num_output_devices: u32,
98     pub num_input_devices: u32,
99     pub backend: StreamSourceBackend,
100     pub num_output_streams: u32,
101     pub num_input_streams: u32,
102     pub playback_path: String,
103     pub playback_size: usize,
104     #[cfg(all(unix, feature = "audio_cras"))]
105     #[serde(deserialize_with = "libcras::deserialize_cras_client_type")]
106     pub client_type: CrasClientType,
107     #[cfg(all(unix, feature = "audio_cras"))]
108     pub socket_type: CrasSocketType,
109     pub output_device_config: Vec<PCMDeviceParameters>,
110     pub input_device_config: Vec<PCMDeviceParameters>,
111     pub card_index: usize,
112 }
113 
114 impl Default for Parameters {
default() -> Self115     fn default() -> Self {
116         Parameters {
117             capture: false,
118             num_output_devices: 1,
119             num_input_devices: 1,
120             backend: StreamSourceBackend::NULL,
121             num_output_streams: 1,
122             num_input_streams: 1,
123             playback_path: "".to_string(),
124             playback_size: 0,
125             #[cfg(all(unix, feature = "audio_cras"))]
126             client_type: CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
127             #[cfg(all(unix, feature = "audio_cras"))]
128             socket_type: CrasSocketType::Unified,
129             output_device_config: vec![],
130             input_device_config: vec![],
131             card_index: 0,
132         }
133     }
134 }
135 
136 impl Parameters {
get_total_output_streams(&self) -> u32137     pub(crate) fn get_total_output_streams(&self) -> u32 {
138         self.num_output_devices * self.num_output_streams
139     }
140 
get_total_input_streams(&self) -> u32141     pub(crate) fn get_total_input_streams(&self) -> u32 {
142         self.num_input_devices * self.num_input_streams
143     }
144 
get_total_streams(&self) -> u32145     pub(crate) fn get_total_streams(&self) -> u32 {
146         self.get_total_output_streams() + self.get_total_input_streams()
147     }
148 
149     #[allow(dead_code)]
get_device_params( &self, pcm_info: &virtio_snd_pcm_info, ) -> Result<PCMDeviceParameters, Error>150     pub(crate) fn get_device_params(
151         &self,
152         pcm_info: &virtio_snd_pcm_info,
153     ) -> Result<PCMDeviceParameters, Error> {
154         let device_config = match pcm_info.direction {
155             VIRTIO_SND_D_OUTPUT => &self.output_device_config,
156             VIRTIO_SND_D_INPUT => &self.input_device_config,
157             _ => return Err(Error::InvalidPCMInfoDirection(pcm_info.direction)),
158         };
159         let device_idx = u32::from(pcm_info.hdr.hda_fn_nid) as usize;
160         device_config
161             .get(device_idx)
162             .cloned()
163             .ok_or(Error::InvalidPCMDeviceConfigIndex(device_idx))
164     }
165 }
166 
167 #[cfg(test)]
168 #[allow(clippy::needless_update)]
169 mod tests {
170     use super::*;
171 
check_failure(s: &str)172     fn check_failure(s: &str) {
173         serde_keyvalue::from_key_values::<Parameters>(s).expect_err("parse should have failed");
174     }
175 
check_success( s: &str, capture: bool, backend: StreamSourceBackend, num_output_devices: u32, num_input_devices: u32, num_output_streams: u32, num_input_streams: u32, output_device_config: Vec<PCMDeviceParameters>, input_device_config: Vec<PCMDeviceParameters>, )176     fn check_success(
177         s: &str,
178         capture: bool,
179         backend: StreamSourceBackend,
180         num_output_devices: u32,
181         num_input_devices: u32,
182         num_output_streams: u32,
183         num_input_streams: u32,
184         output_device_config: Vec<PCMDeviceParameters>,
185         input_device_config: Vec<PCMDeviceParameters>,
186     ) {
187         let params: Parameters =
188             serde_keyvalue::from_key_values(s).expect("parse should have succeded");
189         assert_eq!(params.capture, capture);
190         assert_eq!(params.backend, backend);
191         assert_eq!(params.num_output_devices, num_output_devices);
192         assert_eq!(params.num_input_devices, num_input_devices);
193         assert_eq!(params.num_output_streams, num_output_streams);
194         assert_eq!(params.num_input_streams, num_input_streams);
195         assert_eq!(params.output_device_config, output_device_config);
196         assert_eq!(params.input_device_config, input_device_config);
197     }
198 
199     #[test]
parameters_fromstr()200     fn parameters_fromstr() {
201         check_failure("capture=none");
202         check_success(
203             "capture=false",
204             false,
205             StreamSourceBackend::NULL,
206             1,
207             1,
208             1,
209             1,
210             vec![],
211             vec![],
212         );
213         check_success(
214             "capture=true,num_output_streams=2,num_input_streams=3",
215             true,
216             StreamSourceBackend::NULL,
217             1,
218             1,
219             2,
220             3,
221             vec![],
222             vec![],
223         );
224         check_success(
225             "capture=true,num_output_devices=3,num_input_devices=2",
226             true,
227             StreamSourceBackend::NULL,
228             3,
229             2,
230             1,
231             1,
232             vec![],
233             vec![],
234         );
235         check_success(
236             "capture=true,num_output_devices=2,num_input_devices=3,\
237             num_output_streams=3,num_input_streams=2",
238             true,
239             StreamSourceBackend::NULL,
240             2,
241             3,
242             3,
243             2,
244             vec![],
245             vec![],
246         );
247         check_success(
248             "capture=true,backend=null,num_output_devices=2,num_input_devices=3,\
249             num_output_streams=3,num_input_streams=2",
250             true,
251             StreamSourceBackend::NULL,
252             2,
253             3,
254             3,
255             2,
256             vec![],
257             vec![],
258         );
259         check_success(
260             "output_device_config=[[effects=[aec]],[]]",
261             false,
262             StreamSourceBackend::NULL,
263             1,
264             1,
265             1,
266             1,
267             vec![
268                 PCMDeviceParameters {
269                     effects: Some(vec![StreamEffect::EchoCancellation]),
270                     ..Default::default()
271                 },
272                 Default::default(),
273             ],
274             vec![],
275         );
276         check_success(
277             "input_device_config=[[effects=[aec]],[]]",
278             false,
279             StreamSourceBackend::NULL,
280             1,
281             1,
282             1,
283             1,
284             vec![],
285             vec![
286                 PCMDeviceParameters {
287                     effects: Some(vec![StreamEffect::EchoCancellation]),
288                     ..Default::default()
289                 },
290                 Default::default(),
291             ],
292         );
293 
294         // Invalid effect in device config
295         check_failure("output_device_config=[[effects=[none]]]");
296     }
297 
298     #[test]
299     #[cfg(all(unix, feature = "audio_cras"))]
cras_parameters_fromstr()300     fn cras_parameters_fromstr() {
301         fn cras_check_success(
302             s: &str,
303             backend: StreamSourceBackend,
304             client_type: CrasClientType,
305             socket_type: CrasSocketType,
306             output_device_config: Vec<PCMDeviceParameters>,
307             input_device_config: Vec<PCMDeviceParameters>,
308         ) {
309             let params: Parameters =
310                 serde_keyvalue::from_key_values(s).expect("parse should have succeded");
311             assert_eq!(params.backend, backend);
312             assert_eq!(params.client_type, client_type);
313             assert_eq!(params.socket_type, socket_type);
314             assert_eq!(params.output_device_config, output_device_config);
315             assert_eq!(params.input_device_config, input_device_config);
316         }
317 
318         cras_check_success(
319             "backend=cras",
320             StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
321             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
322             CrasSocketType::Unified,
323             vec![],
324             vec![],
325         );
326         cras_check_success(
327             "backend=cras,client_type=crosvm",
328             StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
329             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
330             CrasSocketType::Unified,
331             vec![],
332             vec![],
333         );
334         cras_check_success(
335             "backend=cras,client_type=arcvm",
336             StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
337             CrasClientType::CRAS_CLIENT_TYPE_ARCVM,
338             CrasSocketType::Unified,
339             vec![],
340             vec![],
341         );
342         check_failure("backend=cras,client_type=none");
343         cras_check_success(
344             "backend=cras,socket_type=legacy",
345             StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
346             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
347             CrasSocketType::Legacy,
348             vec![],
349             vec![],
350         );
351         cras_check_success(
352             "backend=cras,socket_type=unified",
353             StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
354             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
355             CrasSocketType::Unified,
356             vec![],
357             vec![],
358         );
359         cras_check_success(
360             "output_device_config=[[client_type=crosvm],[client_type=arcvm,stream_type=pro_audio],[]]",
361             StreamSourceBackend::NULL,
362             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
363             CrasSocketType::Unified,
364             vec![
365                 PCMDeviceParameters{
366                     client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_CROSVM),
367                     stream_type: None,
368                     effects: None,
369                 },
370                 PCMDeviceParameters{
371                     client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_ARCVM),
372                     stream_type: Some(CrasStreamType::CRAS_STREAM_TYPE_PRO_AUDIO),
373                     effects: None,
374                 },
375                 Default::default(),
376                 ],
377             vec![],
378         );
379         cras_check_success(
380             "input_device_config=[[client_type=crosvm],[client_type=arcvm,effects=[aec],stream_type=pro_audio],[effects=[EchoCancellation]],[]]",
381             StreamSourceBackend::NULL,
382             CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
383             CrasSocketType::Unified,
384             vec![],
385             vec![
386                 PCMDeviceParameters{
387                     client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_CROSVM),
388                     stream_type: None,
389                     effects: None,
390                 },
391                 PCMDeviceParameters{
392                     client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_ARCVM),
393                     stream_type: Some(CrasStreamType::CRAS_STREAM_TYPE_PRO_AUDIO),
394                     effects: Some(vec![StreamEffect::EchoCancellation]),
395                 },
396                 PCMDeviceParameters{
397                     client_type: None,
398                     stream_type: None,
399                     effects: Some(vec![StreamEffect::EchoCancellation]),
400                 },
401                 Default::default(),
402                 ],
403         );
404 
405         // Invalid client_type in device config
406         check_failure("output_device_config=[[client_type=none]]");
407 
408         // Invalid stream type in device config
409         check_failure("output_device_config=[[stream_type=none]]");
410     }
411 
412     #[test]
get_device_params_output()413     fn get_device_params_output() {
414         let params = Parameters {
415             output_device_config: vec![
416                 PCMDeviceParameters {
417                     effects: Some(vec![StreamEffect::EchoCancellation]),
418                     ..Default::default()
419                 },
420                 PCMDeviceParameters {
421                     effects: Some(vec![
422                         StreamEffect::EchoCancellation,
423                         StreamEffect::EchoCancellation,
424                     ]),
425                     ..Default::default()
426                 },
427             ],
428             ..Default::default()
429         };
430 
431         let default_pcm_info = virtio_snd_pcm_info {
432             hdr: virtio_snd_info {
433                 hda_fn_nid: 0.into(),
434             },
435             features: 0.into(),
436             formats: 0.into(),
437             rates: 0.into(),
438             direction: VIRTIO_SND_D_OUTPUT, // Direction is OUTPUT
439             channels_min: 1,
440             channels_max: 6,
441             padding: [0; 5],
442         };
443 
444         let mut pcm_info = default_pcm_info;
445         pcm_info.hdr.hda_fn_nid = 0.into();
446         assert_eq!(
447             params.get_device_params(&pcm_info),
448             Ok(params.output_device_config[0].clone())
449         );
450 
451         let mut pcm_info = default_pcm_info;
452         pcm_info.hdr.hda_fn_nid = 1.into();
453         assert_eq!(
454             params.get_device_params(&pcm_info),
455             Ok(params.output_device_config[1].clone())
456         );
457 
458         let mut pcm_info = default_pcm_info;
459         pcm_info.hdr.hda_fn_nid = 2.into();
460         assert_eq!(
461             params.get_device_params(&pcm_info),
462             Err(Error::InvalidPCMDeviceConfigIndex(2))
463         );
464     }
465 
466     #[test]
get_device_params_input()467     fn get_device_params_input() {
468         let params = Parameters {
469             input_device_config: vec![
470                 PCMDeviceParameters {
471                     effects: Some(vec![
472                         StreamEffect::EchoCancellation,
473                         StreamEffect::EchoCancellation,
474                     ]),
475                     ..Default::default()
476                 },
477                 PCMDeviceParameters {
478                     effects: Some(vec![StreamEffect::EchoCancellation]),
479                     ..Default::default()
480                 },
481             ],
482             ..Default::default()
483         };
484 
485         let default_pcm_info = virtio_snd_pcm_info {
486             hdr: virtio_snd_info {
487                 hda_fn_nid: 0.into(),
488             },
489             features: 0.into(),
490             formats: 0.into(),
491             rates: 0.into(),
492             direction: VIRTIO_SND_D_INPUT, // Direction is INPUT
493             channels_min: 1,
494             channels_max: 6,
495             padding: [0; 5],
496         };
497 
498         let mut pcm_info = default_pcm_info;
499         pcm_info.hdr.hda_fn_nid = 0.into();
500         assert_eq!(
501             params.get_device_params(&pcm_info),
502             Ok(params.input_device_config[0].clone())
503         );
504 
505         let mut pcm_info = default_pcm_info;
506         pcm_info.hdr.hda_fn_nid = 1.into();
507         assert_eq!(
508             params.get_device_params(&pcm_info),
509             Ok(params.input_device_config[1].clone())
510         );
511 
512         let mut pcm_info = default_pcm_info;
513         pcm_info.hdr.hda_fn_nid = 2.into();
514         assert_eq!(
515             params.get_device_params(&pcm_info),
516             Err(Error::InvalidPCMDeviceConfigIndex(2))
517         );
518     }
519 
520     #[test]
get_device_params_invalid_direction()521     fn get_device_params_invalid_direction() {
522         let params = Parameters::default();
523 
524         let pcm_info = virtio_snd_pcm_info {
525             hdr: virtio_snd_info {
526                 hda_fn_nid: 0.into(),
527             },
528             features: 0.into(),
529             formats: 0.into(),
530             rates: 0.into(),
531             direction: 2, // Invalid direction
532             channels_min: 1,
533             channels_max: 6,
534             padding: [0; 5],
535         };
536 
537         assert_eq!(
538             params.get_device_params(&pcm_info),
539             Err(Error::InvalidPCMInfoDirection(2))
540         );
541     }
542 }
543