1use std::num::ParseIntError;
6use std::str::ParseBoolError;
7
8use audio_streams::StreamEffect;
9#[cfg(all(unix, feature = "audio_cras"))]
10use libcras::CrasClientType;
11#[cfg(all(unix, feature = "audio_cras"))]
12use libcras::CrasSocketType;
13#[cfg(all(unix, feature = "audio_cras"))]
14use libcras::CrasStreamType;
15use serde::Deserialize;
16use serde::Serialize;
17use serde_keyvalue::FromKeyValues;
18use thiserror::Error as ThisError;
19
20use crate::virtio::snd::constants::*;
21use crate::virtio::snd::layout::*;
22use crate::virtio::snd::sys::StreamSourceBackend as SysStreamSourceBackend;
23
24#[derive(ThisError, Debug, PartialEq, Eq)]
25pub enum Error {
26 #[error("Invalid snd parameter value ({0}): {1}")]
28 InvalidParameterValue(String, String),
29 #[error("Invalid bool value: {0}")]
31 InvalidBoolValue(ParseBoolError),
32 #[error("Invalid int value: {0}")]
34 InvalidIntValue(ParseIntError),
35 #[error("Backend is not implemented")]
37 InvalidBackend,
38 #[error("Invalid snd parameter: {0}")]
40 UnknownParameter(String),
41 #[error("Invalid PCM device config index: {0}")]
44 InvalidPCMDeviceConfigIndex(usize),
45 #[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")]
52pub enum StreamSourceBackend {
53 NULL,
54 FILE,
55 Sys(SysStreamSourceBackend),
56}
57
58impl From<StreamSourceBackend> for String {
60 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
69impl TryFrom<&str> for StreamSourceBackend {
70 type Error = Error;
71
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#[derive(Debug, Clone, Default, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
83#[serde(deny_unknown_fields, default)]
84pub 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#[derive(Debug, Clone, Deserialize, Serialize, FromKeyValues)]
94#[serde(deny_unknown_fields, default)]
95pub 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
114impl Default for Parameters {
115 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
136impl Parameters {
137 pub(crate) fn get_total_output_streams(&self) -> u32 {
138 self.num_output_devices * self.num_output_streams
139 }
140
141 pub(crate) fn get_total_input_streams(&self) -> u32 {
142 self.num_input_devices * self.num_input_streams
143 }
144
145 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)]
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)]
169mod tests {
170 use super::*;
171
172 fn check_failure(s: &str) {
173 serde_keyvalue::from_key_values::<Parameters>(s).expect_err("parse should have failed");
174 }
175
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]
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 check_failure("output_device_config=[[effects=[none]]]");
296 }
297
298 #[test]
299 #[cfg(all(unix, feature = "audio_cras"))]
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 check_failure("output_device_config=[[client_type=none]]");
407
408 check_failure("output_device_config=[[stream_type=none]]");
410 }
411
412 #[test]
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, 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]
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, 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]
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, 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}