1 // Copyright 2023 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::fs;
6
7 use fixture::vhost_user::CmdType;
8 use fixture::vhost_user::Config as VuConfig;
9 use fixture::vhost_user::VhostUserBackend;
10 use fixture::vm::Config;
11 use fixture::vm::TestVm;
12 use tempfile::NamedTempFile;
13 use tempfile::TempDir;
14
15 /// Tests audio playback on virtio-snd with file backend
16 ///
17 /// 1. Create a temporal directory for the audio file.
18 /// 2. Start a VM with a virtiofs device for the temporal directory and a virtio-snd device with
19 /// file backend.
20 /// 3. Create a raw audio file in the temporal directory with sox.
21 /// 4. Do playback with aplay.
22 /// 5. Compare the generated audio file and the output from virtio-snd.
23 #[test]
do_playback()24 fn do_playback() {
25 let temp_dir = tempfile::tempdir().unwrap();
26 let temp_dir_path_str = temp_dir.path().to_str().unwrap();
27
28 let config = get_test_vm_config(
29 temp_dir_path_str,
30 vec![
31 "--virtio-snd".to_string(),
32 get_virtio_snd_args(temp_dir_path_str),
33 ],
34 );
35 playback_and_check(config, temp_dir)
36 }
37
38 /// Tests audio playback with vhost user.
39 #[test]
do_playback_with_vhost_user()40 fn do_playback_with_vhost_user() {
41 let temp_dir = tempfile::tempdir().unwrap();
42 let temp_dir_path_str = temp_dir.path().to_str().unwrap();
43
44 let socket = NamedTempFile::new().unwrap();
45 let socket_path_str = socket.path().to_str().unwrap();
46
47 let vu_config = VuConfig::new(CmdType::Device, "snd").extra_args(vec![
48 "snd".to_string(),
49 "--config".to_string(),
50 get_virtio_snd_args(temp_dir_path_str),
51 "--socket-path".to_string(),
52 socket_path_str.to_string(),
53 ]);
54 let _vu_device = VhostUserBackend::new(vu_config).unwrap();
55
56 let config = get_test_vm_config(
57 temp_dir_path_str,
58 vec![
59 "--vhost-user".to_string(),
60 format!("sound,socket={}", socket_path_str),
61 ],
62 );
63 playback_and_check(config, temp_dir)
64 }
65
playback_and_check(config: Config, temp_dir: TempDir)66 fn playback_and_check(config: Config, temp_dir: TempDir) {
67 let mut vm = TestVm::new(config).unwrap();
68 vm.exec_in_guest("mount -t virtiofs tmp2 /mnt").unwrap();
69 vm.exec_in_guest("ls /mnt 1>&2").unwrap();
70 vm.exec_in_guest(
71 "sox -n -b 16 -r 48000 -c 2 -e signed -t raw \
72 /mnt/test_440_48000.raw synth 1 sine 440 vol -10dB",
73 )
74 .unwrap();
75 vm.exec_in_guest(
76 "aplay --buffer-size=48000 --period-size=12000 \
77 -d 1 -f dat -Dhw:0,0 /mnt/test_440_48000.raw",
78 )
79 .unwrap();
80
81 assert!(compare_files(
82 temp_dir,
83 "test_440_48000.raw",
84 "stream-0.out"
85 ));
86 }
87
get_virtio_snd_args(output_file_path_str: &str) -> String88 fn get_virtio_snd_args(output_file_path_str: &str) -> String {
89 format!(
90 "backend=file,playback_path={},playback_size=400000",
91 output_file_path_str
92 )
93 }
94
get_test_vm_config(temp_dir_path_str: &str, snd_args: Vec<String>) -> Config95 fn get_test_vm_config(temp_dir_path_str: &str, snd_args: Vec<String>) -> Config {
96 let mut args = vec![
97 "--shared-dir".to_string(),
98 format!("{}:tmp2:type=fs:cache=always", temp_dir_path_str),
99 ];
100 args.extend(snd_args);
101 Config::new().extra_args(args)
102 }
103
compare_files(temp_dir: TempDir, golden_file_name: &str, output_file_name: &str) -> bool104 fn compare_files(temp_dir: TempDir, golden_file_name: &str, output_file_name: &str) -> bool {
105 // 1 second, 2 channels, 16 bit (2 byte) format, 48000 frame rate.
106 const BYTES_TO_COMPARE: usize = 1 * 2 * 2 * 48000;
107 // Skip the first buffer-size bytes as it's 0 pads.
108 const SKIP_OFFSET: usize = 48000;
109
110 // Open the second file for reading
111 let buf1 = fs::read(temp_dir.path().join(golden_file_name)).unwrap();
112 let buf2 = fs::read(temp_dir.path().join(output_file_name)).unwrap();
113
114 if buf1[..BYTES_TO_COMPARE] != buf2[SKIP_OFFSET..(SKIP_OFFSET + BYTES_TO_COMPARE)] {
115 println!("Files differ");
116 return false;
117 }
118
119 true
120 }
121