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    #[cfg(any(target_os = "android", target_os = "linux"))]
114    #[serde(default)]
115    /// set MADV_DONTFORK on guest memory
116    ///
117    /// Intended for use in combination with protected VMs, where the guest memory can be dangerous
118    /// to access. Some systems, e.g. Android, have tools that fork processes and examine their
119    /// memory. This flag effectively hides the guest memory from those tools.
120    ///
121    /// Not compatible with sandboxing.
122    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        // Invalid effect in device config
308        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        // Invalid client_type in device config
419        check_failure("output_device_config=[[client_type=none]]");
420
421        // Invalid stream type in device config
422        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, // Direction is OUTPUT
452            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, // Direction is INPUT
506            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, // Invalid direction
545            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}