xref: /linux/tools/testing/selftests/alsa/pcm-test.c (revision 7351324c6f486ae35865b430c8e30f07da9f378e)
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // kselftest for the ALSA PCM API
4 //
5 // Original author: Jaroslav Kysela <perex@perex.cz>
6 // Copyright (c) 2022 Red Hat Inc.
7 
8 // This test will iterate over all cards detected in the system, exercising
9 // every PCM device it can find.  This may conflict with other system
10 // software if there is audio activity so is best run on a system with a
11 // minimal active userspace.
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stdbool.h>
16 #include <errno.h>
17 #include <assert.h>
18 
19 #include "../kselftest.h"
20 #include "alsa-local.h"
21 
22 typedef struct timespec timestamp_t;
23 
24 struct pcm_data {
25 	snd_pcm_t *handle;
26 	int card;
27 	int device;
28 	int subdevice;
29 	snd_pcm_stream_t stream;
30 	snd_config_t *pcm_config;
31 	struct pcm_data *next;
32 };
33 
34 struct pcm_data *pcm_list = NULL;
35 
36 int num_missing = 0;
37 struct pcm_data *pcm_missing = NULL;
38 
39 enum test_class {
40 	TEST_CLASS_DEFAULT,
41 	TEST_CLASS_SYSTEM,
42 };
43 
44 void timestamp_now(timestamp_t *tstamp)
45 {
46 	if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
47 		ksft_exit_fail_msg("clock_get_time\n");
48 }
49 
50 long long timestamp_diff_ms(timestamp_t *tstamp)
51 {
52 	timestamp_t now, diff;
53 	timestamp_now(&now);
54 	if (tstamp->tv_nsec > now.tv_nsec) {
55 		diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
56 		diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
57 	} else {
58 		diff.tv_sec = now.tv_sec - tstamp->tv_sec;
59 		diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
60 	}
61 	return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
62 }
63 
64 static long device_from_id(snd_config_t *node)
65 {
66 	const char *id;
67 	char *end;
68 	long v;
69 
70 	if (snd_config_get_id(node, &id))
71 		ksft_exit_fail_msg("snd_config_get_id\n");
72 	errno = 0;
73 	v = strtol(id, &end, 10);
74 	if (errno || *end)
75 		return -1;
76 	return v;
77 }
78 
79 static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
80 {
81 	struct pcm_data *pcm_data;
82 
83 	for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
84 		if (pcm_data->card != card)
85 			continue;
86 		if (pcm_data->device != device)
87 			continue;
88 		if (pcm_data->subdevice != subdevice)
89 			continue;
90 		if (pcm_data->stream != stream)
91 			continue;
92 		return;
93 	}
94 	pcm_data = calloc(1, sizeof(*pcm_data));
95 	if (!pcm_data)
96 		ksft_exit_fail_msg("Out of memory\n");
97 	pcm_data->card = card;
98 	pcm_data->device = device;
99 	pcm_data->subdevice = subdevice;
100 	pcm_data->stream = stream;
101 	pcm_data->next = pcm_missing;
102 	pcm_missing = pcm_data;
103 	num_missing++;
104 }
105 
106 static void missing_devices(int card, snd_config_t *card_config)
107 {
108 	snd_config_t *pcm_config, *node1, *node2;
109 	snd_config_iterator_t i1, i2, next1, next2;
110 	int device, subdevice;
111 
112 	pcm_config = conf_get_subtree(card_config, "pcm", NULL);
113 	if (!pcm_config)
114 		return;
115 	snd_config_for_each(i1, next1, pcm_config) {
116 		node1 = snd_config_iterator_entry(i1);
117 		device = device_from_id(node1);
118 		if (device < 0)
119 			continue;
120 		if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
121 			continue;
122 		snd_config_for_each(i2, next2, node1) {
123 			node2 = snd_config_iterator_entry(i2);
124 			subdevice = device_from_id(node2);
125 			if (subdevice < 0)
126 				continue;
127 			if (conf_get_subtree(node2, "PLAYBACK", NULL))
128 				missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
129 			if (conf_get_subtree(node2, "CAPTURE", NULL))
130 				missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
131 		}
132 	}
133 }
134 
135 static void find_pcms(void)
136 {
137 	char name[32], key[64];
138 	int card, dev, subdev, count, direction, err;
139 	snd_pcm_stream_t stream;
140 	struct pcm_data *pcm_data;
141 	snd_ctl_t *handle;
142 	snd_pcm_info_t *pcm_info;
143 	snd_config_t *config, *card_config, *pcm_config;
144 
145 	snd_pcm_info_alloca(&pcm_info);
146 
147 	card = -1;
148 	if (snd_card_next(&card) < 0 || card < 0)
149 		return;
150 
151 	config = get_alsalib_config();
152 
153 	while (card >= 0) {
154 		sprintf(name, "hw:%d", card);
155 
156 		err = snd_ctl_open_lconf(&handle, name, 0, config);
157 		if (err < 0) {
158 			ksft_print_msg("Failed to get hctl for card %d: %s\n",
159 				       card, snd_strerror(err));
160 			goto next_card;
161 		}
162 
163 		card_config = conf_by_card(card);
164 
165 		dev = -1;
166 		while (1) {
167 			if (snd_ctl_pcm_next_device(handle, &dev) < 0)
168 				ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
169 			if (dev < 0)
170 				break;
171 
172 			for (direction = 0; direction < 2; direction++) {
173 				stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
174 				sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
175 				pcm_config = conf_get_subtree(card_config, key, NULL);
176 				if (conf_get_bool(card_config, key, "skip", false)) {
177 					ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
178 					continue;
179 				}
180 				snd_pcm_info_set_device(pcm_info, dev);
181 				snd_pcm_info_set_subdevice(pcm_info, 0);
182 				snd_pcm_info_set_stream(pcm_info, stream);
183 				err = snd_ctl_pcm_info(handle, pcm_info);
184 				if (err == -ENOENT)
185 					continue;
186 				if (err < 0)
187 					ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
188 							   dev, 0, stream);
189 				count = snd_pcm_info_get_subdevices_count(pcm_info);
190 				for (subdev = 0; subdev < count; subdev++) {
191 					sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
192 					if (conf_get_bool(card_config, key, "skip", false)) {
193 						ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
194 							       subdev, snd_pcm_stream_name(stream));
195 						continue;
196 					}
197 					pcm_data = calloc(1, sizeof(*pcm_data));
198 					if (!pcm_data)
199 						ksft_exit_fail_msg("Out of memory\n");
200 					pcm_data->card = card;
201 					pcm_data->device = dev;
202 					pcm_data->subdevice = subdev;
203 					pcm_data->stream = stream;
204 					pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
205 					pcm_data->next = pcm_list;
206 					pcm_list = pcm_data;
207 				}
208 			}
209 		}
210 
211 		/* check for missing devices */
212 		missing_devices(card, card_config);
213 
214 	next_card:
215 		snd_ctl_close(handle);
216 		if (snd_card_next(&card) < 0) {
217 			ksft_print_msg("snd_card_next");
218 			break;
219 		}
220 	}
221 
222 	snd_config_delete(config);
223 }
224 
225 static void test_pcm_time(struct pcm_data *data, enum test_class class,
226 			  const char *test_name, snd_config_t *pcm_cfg)
227 {
228 	char name[64], key[128], msg[256];
229 	const char *cs;
230 	int i, err;
231 	snd_pcm_t *handle = NULL;
232 	snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
233 	snd_pcm_format_t format, old_format;
234 	const char *alt_formats[8];
235 	unsigned char *samples = NULL;
236 	snd_pcm_sframes_t frames;
237 	long long ms;
238 	long rate, channels, period_size, buffer_size;
239 	unsigned int rrate;
240 	snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
241 	timestamp_t tstamp;
242 	bool pass = false;
243 	snd_pcm_hw_params_t *hw_params;
244 	snd_pcm_sw_params_t *sw_params;
245 	const char *test_class_name;
246 	bool skip = true;
247 	const char *desc;
248 
249 	desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
250 	if (desc)
251 		ksft_print_msg("%s\n", desc);
252 
253 	switch (class) {
254 	case TEST_CLASS_DEFAULT:
255 		test_class_name = "default";
256 		break;
257 	case TEST_CLASS_SYSTEM:
258 		test_class_name = "system";
259 		break;
260 	default:
261 		ksft_exit_fail_msg("Unknown test class %d\n", class);
262 		break;
263 	}
264 
265 	snd_pcm_hw_params_alloca(&hw_params);
266 	snd_pcm_sw_params_alloca(&sw_params);
267 
268 	cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
269 	format = snd_pcm_format_value(cs);
270 	if (format == SND_PCM_FORMAT_UNKNOWN)
271 		ksft_exit_fail_msg("Wrong format '%s'\n", cs);
272 	conf_get_string_array(pcm_cfg, "alt_formats", NULL,
273 				alt_formats, ARRAY_SIZE(alt_formats), NULL);
274 	rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
275 	channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
276 	period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
277 	buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);
278 
279 	samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
280 	if (!samples)
281 		ksft_exit_fail_msg("Out of memory\n");
282 	snd_pcm_format_set_silence(format, samples, rate * channels);
283 
284 	sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
285 	err = snd_pcm_open(&handle, name, data->stream, 0);
286 	if (err < 0) {
287 		snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
288 		goto __close;
289 	}
290 
291 	err = snd_pcm_hw_params_any(handle, hw_params);
292 	if (err < 0) {
293 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
294 		goto __close;
295 	}
296 	err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
297 	if (err < 0) {
298 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
299 		goto __close;
300 	}
301 	err = snd_pcm_hw_params_set_access(handle, hw_params, access);
302 	if (err < 0) {
303 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
304 					   snd_pcm_access_name(access), snd_strerror(err));
305 		goto __close;
306 	}
307 	i = -1;
308 __format:
309 	err = snd_pcm_hw_params_set_format(handle, hw_params, format);
310 	if (err < 0) {
311 		i++;
312 		if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
313 			old_format = format;
314 			format = snd_pcm_format_value(alt_formats[i]);
315 			if (format != SND_PCM_FORMAT_UNKNOWN) {
316 				ksft_print_msg("%s.%d.%d.%d.%s.%s format %s -> %s\n",
317 						 test_name,
318 						 data->card, data->device, data->subdevice,
319 						 snd_pcm_stream_name(data->stream),
320 						 snd_pcm_access_name(access),
321 						 snd_pcm_format_name(old_format),
322 						 snd_pcm_format_name(format));
323 				samples = realloc(samples, (rate * channels *
324 							    snd_pcm_format_physical_width(format)) / 8);
325 				if (!samples)
326 					ksft_exit_fail_msg("Out of memory\n");
327 				snd_pcm_format_set_silence(format, samples, rate * channels);
328 				goto __format;
329 			}
330 		}
331 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
332 					   snd_pcm_format_name(format), snd_strerror(err));
333 		goto __close;
334 	}
335 	err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
336 	if (err < 0) {
337 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
338 		goto __close;
339 	}
340 	rrate = rate;
341 	err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
342 	if (err < 0) {
343 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
344 		goto __close;
345 	}
346 	if (rrate != rate) {
347 		snprintf(msg, sizeof(msg), "rate mismatch %ld != %ld", rate, rrate);
348 		goto __close;
349 	}
350 	rperiod_size = period_size;
351 	err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
352 	if (err < 0) {
353 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
354 		goto __close;
355 	}
356 	rbuffer_size = buffer_size;
357 	err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
358 	if (err < 0) {
359 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
360 		goto __close;
361 	}
362 	err = snd_pcm_hw_params(handle, hw_params);
363 	if (err < 0) {
364 		snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
365 		goto __close;
366 	}
367 
368 	err = snd_pcm_sw_params_current(handle, sw_params);
369 	if (err < 0) {
370 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
371 		goto __close;
372 	}
373 	if (data->stream == SND_PCM_STREAM_PLAYBACK) {
374 		start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
375 	} else {
376 		start_threshold = rperiod_size;
377 	}
378 	err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
379 	if (err < 0) {
380 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
381 		goto __close;
382 	}
383 	err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
384 	if (err < 0) {
385 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
386 		goto __close;
387 	}
388 	err = snd_pcm_sw_params(handle, sw_params);
389 	if (err < 0) {
390 		snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
391 		goto __close;
392 	}
393 
394 	ksft_print_msg("%s.%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
395 		         test_class_name, test_name,
396 			 data->card, data->device, data->subdevice,
397 			 snd_pcm_stream_name(data->stream),
398 			 snd_pcm_access_name(access),
399 			 snd_pcm_format_name(format),
400 			 (long)rate, (long)channels,
401 			 (long)rperiod_size, (long)rbuffer_size,
402 			 (long)start_threshold);
403 
404 	/* Set all the params, actually run the test */
405 	skip = false;
406 
407 	timestamp_now(&tstamp);
408 	for (i = 0; i < 4; i++) {
409 		if (data->stream == SND_PCM_STREAM_PLAYBACK) {
410 			frames = snd_pcm_writei(handle, samples, rate);
411 			if (frames < 0) {
412 				snprintf(msg, sizeof(msg),
413 					 "Write failed: expected %d, wrote %li", rate, frames);
414 				goto __close;
415 			}
416 			if (frames < rate) {
417 				snprintf(msg, sizeof(msg),
418 					 "expected %d, wrote %li", rate, frames);
419 				goto __close;
420 			}
421 		} else {
422 			frames = snd_pcm_readi(handle, samples, rate);
423 			if (frames < 0) {
424 				snprintf(msg, sizeof(msg),
425 					 "expected %d, wrote %li", rate, frames);
426 				goto __close;
427 			}
428 			if (frames < rate) {
429 				snprintf(msg, sizeof(msg),
430 					 "expected %d, wrote %li", rate, frames);
431 				goto __close;
432 			}
433 		}
434 	}
435 
436 	snd_pcm_drain(handle);
437 	ms = timestamp_diff_ms(&tstamp);
438 	if (ms < 3900 || ms > 4100) {
439 		snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms);
440 		goto __close;
441 	}
442 
443 	msg[0] = '\0';
444 	pass = true;
445 __close:
446 	switch (class) {
447 	case TEST_CLASS_SYSTEM:
448 		test_class_name = "system";
449 		/*
450 		 * Anything specified as specific to this system
451 		 * should always be supported.
452 		 */
453 		ksft_test_result(!skip, "%s.%s.%d.%d.%d.%s.params\n",
454 				 test_class_name, test_name,
455 				 data->card, data->device, data->subdevice,
456 				 snd_pcm_stream_name(data->stream));
457 		break;
458 	default:
459 		break;
460 	}
461 
462 	if (!skip)
463 		ksft_test_result(pass, "%s.%s.%d.%d.%d.%s%s%s\n",
464 				 test_class_name, test_name,
465 				 data->card, data->device, data->subdevice,
466 				 snd_pcm_stream_name(data->stream),
467 				 msg[0] ? " " : "", msg);
468 	else
469 		ksft_test_result_skip("%s.%s.%d.%d.%d.%s%s%s\n",
470 				 test_class_name, test_name,
471 				 data->card, data->device, data->subdevice,
472 				 snd_pcm_stream_name(data->stream),
473 				 msg[0] ? " " : "", msg);
474 	free(samples);
475 	if (handle)
476 		snd_pcm_close(handle);
477 }
478 
479 void run_time_tests(struct pcm_data *pcm, enum test_class class,
480 		    snd_config_t *cfg)
481 {
482 	const char *test_name, *test_type;
483 	snd_config_t *pcm_cfg;
484 	snd_config_iterator_t i, next;
485 
486 	if (!cfg)
487 		return;
488 
489 	cfg = conf_get_subtree(cfg, "test", NULL);
490 	if (cfg == NULL)
491 		return;
492 
493 	snd_config_for_each(i, next, cfg) {
494 		pcm_cfg = snd_config_iterator_entry(i);
495 		if (snd_config_get_id(pcm_cfg, &test_name) < 0)
496 			ksft_exit_fail_msg("snd_config_get_id\n");
497 		test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
498 		if (strcmp(test_type, "time") == 0)
499 			test_pcm_time(pcm, class, test_name, pcm_cfg);
500 		else
501 			ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
502 	}
503 }
504 
505 int main(void)
506 {
507 	struct pcm_data *pcm;
508 	snd_config_t *global_config, *default_pcm_config, *cfg, *pcm_cfg;
509 	int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
510 
511 	ksft_print_header();
512 
513 	global_config = conf_load_from_file("pcm-test.conf");
514 	default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
515 	if (default_pcm_config == NULL)
516 		ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");
517 
518 	conf_load();
519 
520 	find_pcms();
521 
522 	num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);
523 
524 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
525 		num_pcm_tests += num_std_pcm_tests;
526 		cfg = pcm->pcm_config;
527 		if (cfg == NULL)
528 			continue;
529 		/* Setting params is reported as a separate test */
530 		num_tests = conf_get_count(cfg, "test", NULL) * 2;
531 		if (num_tests > 0)
532 			num_pcm_tests += num_tests;
533 	}
534 
535 	ksft_set_plan(num_missing + num_pcm_tests);
536 
537 	for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
538 		ksft_test_result(false, "test.missing.%d.%d.%d.%s\n",
539 				 pcm->card, pcm->device, pcm->subdevice,
540 				 snd_pcm_stream_name(pcm->stream));
541 	}
542 
543 	for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
544 		run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
545 		run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
546 	}
547 
548 	snd_config_delete(global_config);
549 	conf_free();
550 
551 	ksft_exit_pass();
552 
553 	return 0;
554 }
555