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 { from(backend: StreamSourceBackend) -> Self60 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 try_from(s: &str) -> Result<Self, Self::Error>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 114 impl Default for Parameters { default() -> Self115 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 136 impl Parameters { get_total_output_streams(&self) -> u32137 pub(crate) fn get_total_output_streams(&self) -> u32 { 138 self.num_output_devices * self.num_output_streams 139 } 140 get_total_input_streams(&self) -> u32141 pub(crate) fn get_total_input_streams(&self) -> u32 { 142 self.num_input_devices * self.num_input_streams 143 } 144 get_total_streams(&self) -> u32145 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)] get_device_params( &self, pcm_info: &virtio_snd_pcm_info, ) -> Result<PCMDeviceParameters, Error>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)] 169 mod tests { 170 use super::*; 171 check_failure(s: &str)172 fn check_failure(s: &str) { 173 serde_keyvalue::from_key_values::<Parameters>(s).expect_err("parse should have failed"); 174 } 175 check_success( s: &str, capture: bool, backend: StreamSourceBackend, num_output_devices: u32, num_input_devices: u32, num_output_streams: u32, num_input_streams: u32, output_device_config: Vec<PCMDeviceParameters>, input_device_config: Vec<PCMDeviceParameters>, )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] parameters_fromstr()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"))] cras_parameters_fromstr()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] get_device_params_output()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] get_device_params_input()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] get_device_params_invalid_direction()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 } 543