Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/snd/parameters.rs
5394 views
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 {
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
69
impl 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)]
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
#[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
125
impl 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
149
impl 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)]
182
mod 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
}
556
557