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 int num_pcms = 0; 35 struct pcm_data *pcm_list = NULL; 36 37 int num_missing = 0; 38 struct pcm_data *pcm_missing = NULL; 39 40 struct time_test_def { 41 const char *cfg_prefix; 42 const char *format; 43 long rate; 44 long channels; 45 long period_size; 46 long buffer_size; 47 }; 48 49 void timestamp_now(timestamp_t *tstamp) 50 { 51 if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp)) 52 ksft_exit_fail_msg("clock_get_time\n"); 53 } 54 55 long long timestamp_diff_ms(timestamp_t *tstamp) 56 { 57 timestamp_t now, diff; 58 timestamp_now(&now); 59 if (tstamp->tv_nsec > now.tv_nsec) { 60 diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1; 61 diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec; 62 } else { 63 diff.tv_sec = now.tv_sec - tstamp->tv_sec; 64 diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec; 65 } 66 return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L); 67 } 68 69 static long device_from_id(snd_config_t *node) 70 { 71 const char *id; 72 char *end; 73 long v; 74 75 if (snd_config_get_id(node, &id)) 76 ksft_exit_fail_msg("snd_config_get_id\n"); 77 errno = 0; 78 v = strtol(id, &end, 10); 79 if (errno || *end) 80 return -1; 81 return v; 82 } 83 84 static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream) 85 { 86 struct pcm_data *pcm_data; 87 88 for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) { 89 if (pcm_data->card != card) 90 continue; 91 if (pcm_data->device != device) 92 continue; 93 if (pcm_data->subdevice != subdevice) 94 continue; 95 if (pcm_data->stream != stream) 96 continue; 97 return; 98 } 99 pcm_data = calloc(1, sizeof(*pcm_data)); 100 if (!pcm_data) 101 ksft_exit_fail_msg("Out of memory\n"); 102 pcm_data->card = card; 103 pcm_data->device = device; 104 pcm_data->subdevice = subdevice; 105 pcm_data->stream = stream; 106 pcm_data->next = pcm_missing; 107 pcm_missing = pcm_data; 108 num_missing++; 109 } 110 111 static void missing_devices(int card, snd_config_t *card_config) 112 { 113 snd_config_t *pcm_config, *node1, *node2; 114 snd_config_iterator_t i1, i2, next1, next2; 115 int device, subdevice; 116 117 pcm_config = conf_get_subtree(card_config, "pcm", NULL); 118 if (!pcm_config) 119 return; 120 snd_config_for_each(i1, next1, pcm_config) { 121 node1 = snd_config_iterator_entry(i1); 122 device = device_from_id(node1); 123 if (device < 0) 124 continue; 125 if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND) 126 continue; 127 snd_config_for_each(i2, next2, node1) { 128 node2 = snd_config_iterator_entry(i2); 129 subdevice = device_from_id(node2); 130 if (subdevice < 0) 131 continue; 132 if (conf_get_subtree(node2, "PLAYBACK", NULL)) 133 missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK); 134 if (conf_get_subtree(node2, "CAPTURE", NULL)) 135 missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE); 136 } 137 } 138 } 139 140 static void find_pcms(void) 141 { 142 char name[32], key[64]; 143 int card, dev, subdev, count, direction, err; 144 snd_pcm_stream_t stream; 145 struct pcm_data *pcm_data; 146 snd_ctl_t *handle; 147 snd_pcm_info_t *pcm_info; 148 snd_config_t *config, *card_config, *pcm_config; 149 150 snd_pcm_info_alloca(&pcm_info); 151 152 card = -1; 153 if (snd_card_next(&card) < 0 || card < 0) 154 return; 155 156 config = get_alsalib_config(); 157 158 while (card >= 0) { 159 sprintf(name, "hw:%d", card); 160 161 err = snd_ctl_open_lconf(&handle, name, 0, config); 162 if (err < 0) { 163 ksft_print_msg("Failed to get hctl for card %d: %s\n", 164 card, snd_strerror(err)); 165 goto next_card; 166 } 167 168 card_config = conf_by_card(card); 169 170 dev = -1; 171 while (1) { 172 if (snd_ctl_pcm_next_device(handle, &dev) < 0) 173 ksft_exit_fail_msg("snd_ctl_pcm_next_device\n"); 174 if (dev < 0) 175 break; 176 177 for (direction = 0; direction < 2; direction++) { 178 stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; 179 sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream)); 180 pcm_config = conf_get_subtree(card_config, key, NULL); 181 if (conf_get_bool(card_config, key, "skip", false)) { 182 ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream)); 183 continue; 184 } 185 snd_pcm_info_set_device(pcm_info, dev); 186 snd_pcm_info_set_subdevice(pcm_info, 0); 187 snd_pcm_info_set_stream(pcm_info, stream); 188 err = snd_ctl_pcm_info(handle, pcm_info); 189 if (err == -ENOENT) 190 continue; 191 if (err < 0) 192 ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n", 193 dev, 0, stream); 194 count = snd_pcm_info_get_subdevices_count(pcm_info); 195 for (subdev = 0; subdev < count; subdev++) { 196 sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream)); 197 if (conf_get_bool(card_config, key, "skip", false)) { 198 ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev, 199 subdev, snd_pcm_stream_name(stream)); 200 continue; 201 } 202 pcm_data = calloc(1, sizeof(*pcm_data)); 203 if (!pcm_data) 204 ksft_exit_fail_msg("Out of memory\n"); 205 pcm_data->card = card; 206 pcm_data->device = dev; 207 pcm_data->subdevice = subdev; 208 pcm_data->stream = stream; 209 pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL); 210 pcm_data->next = pcm_list; 211 pcm_list = pcm_data; 212 num_pcms++; 213 } 214 } 215 } 216 217 /* check for missing devices */ 218 missing_devices(card, card_config); 219 220 next_card: 221 snd_ctl_close(handle); 222 if (snd_card_next(&card) < 0) { 223 ksft_print_msg("snd_card_next"); 224 break; 225 } 226 } 227 228 snd_config_delete(config); 229 } 230 231 static void test_pcm_time1(struct pcm_data *data, 232 const struct time_test_def *test) 233 { 234 char name[64], key[128], msg[256]; 235 const char *cs; 236 int i, err; 237 snd_pcm_t *handle = NULL; 238 snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED; 239 snd_pcm_format_t format; 240 unsigned char *samples = NULL; 241 snd_pcm_sframes_t frames; 242 long long ms; 243 long rate, channels, period_size, buffer_size; 244 unsigned int rchannels; 245 unsigned int rrate; 246 snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold; 247 timestamp_t tstamp; 248 bool pass = false, automatic = true; 249 snd_pcm_hw_params_t *hw_params; 250 snd_pcm_sw_params_t *sw_params; 251 bool skip = false; 252 253 snd_pcm_hw_params_alloca(&hw_params); 254 snd_pcm_sw_params_alloca(&sw_params); 255 256 cs = conf_get_string(data->pcm_config, test->cfg_prefix, "format", test->format); 257 format = snd_pcm_format_value(cs); 258 if (format == SND_PCM_FORMAT_UNKNOWN) 259 ksft_exit_fail_msg("Wrong format '%s'\n", cs); 260 rate = conf_get_long(data->pcm_config, test->cfg_prefix, "rate", test->rate); 261 channels = conf_get_long(data->pcm_config, test->cfg_prefix, "channels", test->channels); 262 period_size = conf_get_long(data->pcm_config, test->cfg_prefix, "period_size", test->period_size); 263 buffer_size = conf_get_long(data->pcm_config, test->cfg_prefix, "buffer_size", test->buffer_size); 264 265 automatic = strcmp(test->format, snd_pcm_format_name(format)) == 0 && 266 test->rate == rate && 267 test->channels == channels && 268 test->period_size == period_size && 269 test->buffer_size == buffer_size; 270 271 samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8); 272 if (!samples) 273 ksft_exit_fail_msg("Out of memory\n"); 274 snd_pcm_format_set_silence(format, samples, rate * channels); 275 276 sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice); 277 err = snd_pcm_open(&handle, name, data->stream, 0); 278 if (err < 0) { 279 snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err)); 280 goto __close; 281 } 282 283 err = snd_pcm_hw_params_any(handle, hw_params); 284 if (err < 0) { 285 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err)); 286 goto __close; 287 } 288 err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); 289 if (err < 0) { 290 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err)); 291 goto __close; 292 } 293 err = snd_pcm_hw_params_set_access(handle, hw_params, access); 294 if (err < 0) { 295 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s", 296 snd_pcm_access_name(access), snd_strerror(err)); 297 goto __close; 298 } 299 __format: 300 err = snd_pcm_hw_params_set_format(handle, hw_params, format); 301 if (err < 0) { 302 if (automatic && format == SND_PCM_FORMAT_S16_LE) { 303 format = SND_PCM_FORMAT_S32_LE; 304 ksft_print_msg("%s.%d.%d.%d.%s.%s format S16_LE -> S32_LE\n", 305 test->cfg_prefix, 306 data->card, data->device, data->subdevice, 307 snd_pcm_stream_name(data->stream), 308 snd_pcm_access_name(access)); 309 } 310 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s", 311 snd_pcm_format_name(format), snd_strerror(err)); 312 goto __close; 313 } 314 rchannels = channels; 315 err = snd_pcm_hw_params_set_channels_near(handle, hw_params, &rchannels); 316 if (err < 0) { 317 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err)); 318 goto __close; 319 } 320 if (rchannels != channels) { 321 snprintf(msg, sizeof(msg), "channels unsupported %ld != %ld", channels, rchannels); 322 skip = true; 323 goto __close; 324 } 325 rrate = rate; 326 err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); 327 if (err < 0) { 328 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err)); 329 goto __close; 330 } 331 if (rrate != rate) { 332 snprintf(msg, sizeof(msg), "rate unsupported %ld != %ld", rate, rrate); 333 skip = true; 334 goto __close; 335 } 336 rperiod_size = period_size; 337 err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0); 338 if (err < 0) { 339 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err)); 340 goto __close; 341 } 342 rbuffer_size = buffer_size; 343 err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size); 344 if (err < 0) { 345 snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err)); 346 goto __close; 347 } 348 err = snd_pcm_hw_params(handle, hw_params); 349 if (err < 0) { 350 snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err)); 351 goto __close; 352 } 353 354 err = snd_pcm_sw_params_current(handle, sw_params); 355 if (err < 0) { 356 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err)); 357 goto __close; 358 } 359 if (data->stream == SND_PCM_STREAM_PLAYBACK) { 360 start_threshold = (rbuffer_size / rperiod_size) * rperiod_size; 361 } else { 362 start_threshold = rperiod_size; 363 } 364 err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold); 365 if (err < 0) { 366 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err)); 367 goto __close; 368 } 369 err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size); 370 if (err < 0) { 371 snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err)); 372 goto __close; 373 } 374 err = snd_pcm_sw_params(handle, sw_params); 375 if (err < 0) { 376 snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err)); 377 goto __close; 378 } 379 380 ksft_print_msg("%s.%d.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n", 381 test->cfg_prefix, 382 data->card, data->device, data->subdevice, 383 snd_pcm_stream_name(data->stream), 384 snd_pcm_access_name(access), 385 snd_pcm_format_name(format), 386 (long)rate, (long)channels, 387 (long)rperiod_size, (long)rbuffer_size, 388 (long)start_threshold); 389 390 timestamp_now(&tstamp); 391 for (i = 0; i < 4; i++) { 392 if (data->stream == SND_PCM_STREAM_PLAYBACK) { 393 frames = snd_pcm_writei(handle, samples, rate); 394 if (frames < 0) { 395 snprintf(msg, sizeof(msg), 396 "Write failed: expected %d, wrote %li", rate, frames); 397 goto __close; 398 } 399 if (frames < rate) { 400 snprintf(msg, sizeof(msg), 401 "expected %d, wrote %li", rate, frames); 402 goto __close; 403 } 404 } else { 405 frames = snd_pcm_readi(handle, samples, rate); 406 if (frames < 0) { 407 snprintf(msg, sizeof(msg), 408 "expected %d, wrote %li", rate, frames); 409 goto __close; 410 } 411 if (frames < rate) { 412 snprintf(msg, sizeof(msg), 413 "expected %d, wrote %li", rate, frames); 414 goto __close; 415 } 416 } 417 } 418 419 snd_pcm_drain(handle); 420 ms = timestamp_diff_ms(&tstamp); 421 if (ms < 3900 || ms > 4100) { 422 snprintf(msg, sizeof(msg), "time mismatch: expected 4000ms got %lld", ms); 423 goto __close; 424 } 425 426 msg[0] = '\0'; 427 pass = true; 428 __close: 429 if (!skip) { 430 ksft_test_result(pass, "%s.%d.%d.%d.%s%s%s\n", 431 test->cfg_prefix, 432 data->card, data->device, data->subdevice, 433 snd_pcm_stream_name(data->stream), 434 msg[0] ? " " : "", msg); 435 } else { 436 ksft_test_result_skip("%s.%d.%d.%d.%s%s%s\n", 437 test->cfg_prefix, 438 data->card, data->device, 439 data->subdevice, 440 snd_pcm_stream_name(data->stream), 441 msg[0] ? " " : "", msg); 442 } 443 free(samples); 444 if (handle) 445 snd_pcm_close(handle); 446 } 447 448 static const struct time_test_def time_tests[] = { 449 /* name format rate chan period buffer */ 450 { "8k.1.big", "S16_LE", 8000, 2, 8000, 32000 }, 451 { "8k.2.big", "S16_LE", 8000, 2, 8000, 32000 }, 452 { "44k1.2.big", "S16_LE", 44100, 2, 22050, 192000 }, 453 { "48k.2.small", "S16_LE", 48000, 2, 512, 4096 }, 454 { "48k.2.big", "S16_LE", 48000, 2, 24000, 192000 }, 455 { "48k.6.big", "S16_LE", 48000, 6, 48000, 576000 }, 456 { "96k.2.big", "S16_LE", 96000, 2, 48000, 192000 }, 457 }; 458 459 int main(void) 460 { 461 struct pcm_data *pcm; 462 int i; 463 464 ksft_print_header(); 465 466 conf_load(); 467 468 find_pcms(); 469 470 ksft_set_plan(num_missing + num_pcms * ARRAY_SIZE(time_tests)); 471 472 for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) { 473 ksft_test_result(false, "test.missing.%d.%d.%d.%s\n", 474 pcm->card, pcm->device, pcm->subdevice, 475 snd_pcm_stream_name(pcm->stream)); 476 } 477 478 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) { 479 for (i = 0; i < ARRAY_SIZE(time_tests); i++) { 480 test_pcm_time1(pcm, &time_tests[i]); 481 } 482 } 483 484 conf_free(); 485 486 ksft_exit_pass(); 487 488 return 0; 489 } 490