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 #[cfg(any(target_os = "android", target_os = "linux"))]
114 #[serde(default)]
115 pub unmap_guest_memory_on_fork: bool,
123}
124
125impl Default for Parameters {
126 fn default() -> Self {
127 Parameters {
128 capture: false,
129 num_output_devices: 1,
130 num_input_devices: 1,
131 backend: StreamSourceBackend::NULL,
132 num_output_streams: 1,
133 num_input_streams: 1,
134 playback_path: "".to_string(),
135 playback_size: 0,
136 #[cfg(all(unix, feature = "audio_cras"))]
137 client_type: CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
138 #[cfg(all(unix, feature = "audio_cras"))]
139 socket_type: CrasSocketType::Unified,
140 output_device_config: vec![],
141 input_device_config: vec![],
142 card_index: 0,
143 #[cfg(any(target_os = "android", target_os = "linux"))]
144 unmap_guest_memory_on_fork: false,
145 }
146 }
147}
148
149impl Parameters {
150 pub(crate) fn get_total_output_streams(&self) -> u32 {
151 self.num_output_devices * self.num_output_streams
152 }
153
154 pub(crate) fn get_total_input_streams(&self) -> u32 {
155 self.num_input_devices * self.num_input_streams
156 }
157
158 pub(crate) fn get_total_streams(&self) -> u32 {
159 self.get_total_output_streams() + self.get_total_input_streams()
160 }
161
162 #[allow(dead_code)]
163 pub(crate) fn get_device_params(
164 &self,
165 pcm_info: &virtio_snd_pcm_info,
166 ) -> Result<PCMDeviceParameters, Error> {
167 let device_config = match pcm_info.direction {
168 VIRTIO_SND_D_OUTPUT => &self.output_device_config,
169 VIRTIO_SND_D_INPUT => &self.input_device_config,
170 _ => return Err(Error::InvalidPCMInfoDirection(pcm_info.direction)),
171 };
172 let device_idx = u32::from(pcm_info.hdr.hda_fn_nid) as usize;
173 device_config
174 .get(device_idx)
175 .cloned()
176 .ok_or(Error::InvalidPCMDeviceConfigIndex(device_idx))
177 }
178}
179
180#[cfg(test)]
181#[allow(clippy::needless_update)]
182mod tests {
183 use super::*;
184
185 fn check_failure(s: &str) {
186 serde_keyvalue::from_key_values::<Parameters>(s).expect_err("parse should have failed");
187 }
188
189 fn check_success(
190 s: &str,
191 capture: bool,
192 backend: StreamSourceBackend,
193 num_output_devices: u32,
194 num_input_devices: u32,
195 num_output_streams: u32,
196 num_input_streams: u32,
197 output_device_config: Vec<PCMDeviceParameters>,
198 input_device_config: Vec<PCMDeviceParameters>,
199 ) {
200 let params: Parameters =
201 serde_keyvalue::from_key_values(s).expect("parse should have succeded");
202 assert_eq!(params.capture, capture);
203 assert_eq!(params.backend, backend);
204 assert_eq!(params.num_output_devices, num_output_devices);
205 assert_eq!(params.num_input_devices, num_input_devices);
206 assert_eq!(params.num_output_streams, num_output_streams);
207 assert_eq!(params.num_input_streams, num_input_streams);
208 assert_eq!(params.output_device_config, output_device_config);
209 assert_eq!(params.input_device_config, input_device_config);
210 }
211
212 #[test]
213 fn parameters_fromstr() {
214 check_failure("capture=none");
215 check_success(
216 "capture=false",
217 false,
218 StreamSourceBackend::NULL,
219 1,
220 1,
221 1,
222 1,
223 vec![],
224 vec![],
225 );
226 check_success(
227 "capture=true,num_output_streams=2,num_input_streams=3",
228 true,
229 StreamSourceBackend::NULL,
230 1,
231 1,
232 2,
233 3,
234 vec![],
235 vec![],
236 );
237 check_success(
238 "capture=true,num_output_devices=3,num_input_devices=2",
239 true,
240 StreamSourceBackend::NULL,
241 3,
242 2,
243 1,
244 1,
245 vec![],
246 vec![],
247 );
248 check_success(
249 "capture=true,num_output_devices=2,num_input_devices=3,\
250 num_output_streams=3,num_input_streams=2",
251 true,
252 StreamSourceBackend::NULL,
253 2,
254 3,
255 3,
256 2,
257 vec![],
258 vec![],
259 );
260 check_success(
261 "capture=true,backend=null,num_output_devices=2,num_input_devices=3,\
262 num_output_streams=3,num_input_streams=2",
263 true,
264 StreamSourceBackend::NULL,
265 2,
266 3,
267 3,
268 2,
269 vec![],
270 vec![],
271 );
272 check_success(
273 "output_device_config=[[effects=[aec]],[]]",
274 false,
275 StreamSourceBackend::NULL,
276 1,
277 1,
278 1,
279 1,
280 vec![
281 PCMDeviceParameters {
282 effects: Some(vec![StreamEffect::EchoCancellation]),
283 ..Default::default()
284 },
285 Default::default(),
286 ],
287 vec![],
288 );
289 check_success(
290 "input_device_config=[[effects=[aec]],[]]",
291 false,
292 StreamSourceBackend::NULL,
293 1,
294 1,
295 1,
296 1,
297 vec![],
298 vec![
299 PCMDeviceParameters {
300 effects: Some(vec![StreamEffect::EchoCancellation]),
301 ..Default::default()
302 },
303 Default::default(),
304 ],
305 );
306
307 check_failure("output_device_config=[[effects=[none]]]");
309 }
310
311 #[test]
312 #[cfg(all(unix, feature = "audio_cras"))]
313 fn cras_parameters_fromstr() {
314 fn cras_check_success(
315 s: &str,
316 backend: StreamSourceBackend,
317 client_type: CrasClientType,
318 socket_type: CrasSocketType,
319 output_device_config: Vec<PCMDeviceParameters>,
320 input_device_config: Vec<PCMDeviceParameters>,
321 ) {
322 let params: Parameters =
323 serde_keyvalue::from_key_values(s).expect("parse should have succeded");
324 assert_eq!(params.backend, backend);
325 assert_eq!(params.client_type, client_type);
326 assert_eq!(params.socket_type, socket_type);
327 assert_eq!(params.output_device_config, output_device_config);
328 assert_eq!(params.input_device_config, input_device_config);
329 }
330
331 cras_check_success(
332 "backend=cras",
333 StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
334 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
335 CrasSocketType::Unified,
336 vec![],
337 vec![],
338 );
339 cras_check_success(
340 "backend=cras,client_type=crosvm",
341 StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
342 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
343 CrasSocketType::Unified,
344 vec![],
345 vec![],
346 );
347 cras_check_success(
348 "backend=cras,client_type=arcvm",
349 StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
350 CrasClientType::CRAS_CLIENT_TYPE_ARCVM,
351 CrasSocketType::Unified,
352 vec![],
353 vec![],
354 );
355 check_failure("backend=cras,client_type=none");
356 cras_check_success(
357 "backend=cras,socket_type=legacy",
358 StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
359 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
360 CrasSocketType::Legacy,
361 vec![],
362 vec![],
363 );
364 cras_check_success(
365 "backend=cras,socket_type=unified",
366 StreamSourceBackend::Sys(SysStreamSourceBackend::CRAS),
367 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
368 CrasSocketType::Unified,
369 vec![],
370 vec![],
371 );
372 cras_check_success(
373 "output_device_config=[[client_type=crosvm],[client_type=arcvm,stream_type=pro_audio],[]]",
374 StreamSourceBackend::NULL,
375 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
376 CrasSocketType::Unified,
377 vec![
378 PCMDeviceParameters{
379 client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_CROSVM),
380 stream_type: None,
381 effects: None,
382 },
383 PCMDeviceParameters{
384 client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_ARCVM),
385 stream_type: Some(CrasStreamType::CRAS_STREAM_TYPE_PRO_AUDIO),
386 effects: None,
387 },
388 Default::default(),
389 ],
390 vec![],
391 );
392 cras_check_success(
393 "input_device_config=[[client_type=crosvm],[client_type=arcvm,effects=[aec],stream_type=pro_audio],[effects=[EchoCancellation]],[]]",
394 StreamSourceBackend::NULL,
395 CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
396 CrasSocketType::Unified,
397 vec![],
398 vec![
399 PCMDeviceParameters{
400 client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_CROSVM),
401 stream_type: None,
402 effects: None,
403 },
404 PCMDeviceParameters{
405 client_type: Some(CrasClientType::CRAS_CLIENT_TYPE_ARCVM),
406 stream_type: Some(CrasStreamType::CRAS_STREAM_TYPE_PRO_AUDIO),
407 effects: Some(vec![StreamEffect::EchoCancellation]),
408 },
409 PCMDeviceParameters{
410 client_type: None,
411 stream_type: None,
412 effects: Some(vec![StreamEffect::EchoCancellation]),
413 },
414 Default::default(),
415 ],
416 );
417
418 check_failure("output_device_config=[[client_type=none]]");
420
421 check_failure("output_device_config=[[stream_type=none]]");
423 }
424
425 #[test]
426 fn get_device_params_output() {
427 let params = Parameters {
428 output_device_config: vec![
429 PCMDeviceParameters {
430 effects: Some(vec![StreamEffect::EchoCancellation]),
431 ..Default::default()
432 },
433 PCMDeviceParameters {
434 effects: Some(vec![
435 StreamEffect::EchoCancellation,
436 StreamEffect::EchoCancellation,
437 ]),
438 ..Default::default()
439 },
440 ],
441 ..Default::default()
442 };
443
444 let default_pcm_info = virtio_snd_pcm_info {
445 hdr: virtio_snd_info {
446 hda_fn_nid: 0.into(),
447 },
448 features: 0.into(),
449 formats: 0.into(),
450 rates: 0.into(),
451 direction: VIRTIO_SND_D_OUTPUT, channels_min: 1,
453 channels_max: 6,
454 padding: [0; 5],
455 };
456
457 let mut pcm_info = default_pcm_info;
458 pcm_info.hdr.hda_fn_nid = 0.into();
459 assert_eq!(
460 params.get_device_params(&pcm_info),
461 Ok(params.output_device_config[0].clone())
462 );
463
464 let mut pcm_info = default_pcm_info;
465 pcm_info.hdr.hda_fn_nid = 1.into();
466 assert_eq!(
467 params.get_device_params(&pcm_info),
468 Ok(params.output_device_config[1].clone())
469 );
470
471 let mut pcm_info = default_pcm_info;
472 pcm_info.hdr.hda_fn_nid = 2.into();
473 assert_eq!(
474 params.get_device_params(&pcm_info),
475 Err(Error::InvalidPCMDeviceConfigIndex(2))
476 );
477 }
478
479 #[test]
480 fn get_device_params_input() {
481 let params = Parameters {
482 input_device_config: vec![
483 PCMDeviceParameters {
484 effects: Some(vec![
485 StreamEffect::EchoCancellation,
486 StreamEffect::EchoCancellation,
487 ]),
488 ..Default::default()
489 },
490 PCMDeviceParameters {
491 effects: Some(vec![StreamEffect::EchoCancellation]),
492 ..Default::default()
493 },
494 ],
495 ..Default::default()
496 };
497
498 let default_pcm_info = virtio_snd_pcm_info {
499 hdr: virtio_snd_info {
500 hda_fn_nid: 0.into(),
501 },
502 features: 0.into(),
503 formats: 0.into(),
504 rates: 0.into(),
505 direction: VIRTIO_SND_D_INPUT, channels_min: 1,
507 channels_max: 6,
508 padding: [0; 5],
509 };
510
511 let mut pcm_info = default_pcm_info;
512 pcm_info.hdr.hda_fn_nid = 0.into();
513 assert_eq!(
514 params.get_device_params(&pcm_info),
515 Ok(params.input_device_config[0].clone())
516 );
517
518 let mut pcm_info = default_pcm_info;
519 pcm_info.hdr.hda_fn_nid = 1.into();
520 assert_eq!(
521 params.get_device_params(&pcm_info),
522 Ok(params.input_device_config[1].clone())
523 );
524
525 let mut pcm_info = default_pcm_info;
526 pcm_info.hdr.hda_fn_nid = 2.into();
527 assert_eq!(
528 params.get_device_params(&pcm_info),
529 Err(Error::InvalidPCMDeviceConfigIndex(2))
530 );
531 }
532
533 #[test]
534 fn get_device_params_invalid_direction() {
535 let params = Parameters::default();
536
537 let pcm_info = virtio_snd_pcm_info {
538 hdr: virtio_snd_info {
539 hda_fn_nid: 0.into(),
540 },
541 features: 0.into(),
542 formats: 0.into(),
543 rates: 0.into(),
544 direction: 2, channels_min: 1,
546 channels_max: 6,
547 padding: [0; 5],
548 };
549
550 assert_eq!(
551 params.get_device_params(&pcm_info),
552 Err(Error::InvalidPCMInfoDirection(2))
553 );
554 }
555}