devices/virtio/snd/
parameters.rs

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
5use 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    /// 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")]
52pub enum StreamSourceBackend {
53    NULL,
54    FILE,
55    Sys(SysStreamSourceBackend),
56}
57
58// Implemented to make backend serialization possible, since we deserialize from str.
59impl 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/// Holds the parameters for each PCM device
82#[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/// Holds the parameters for a cras sound device
93#[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        // Invalid effect in device config
295        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        // 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]
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]
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]
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}