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