Path: blob/master/tools/testing/selftests/alsa/pcm-test.c
26285 views
// SPDX-License-Identifier: GPL-2.01//2// kselftest for the ALSA PCM API3//4// Original author: Jaroslav Kysela <[email protected]>5// Copyright (c) 2022 Red Hat Inc.67// This test will iterate over all cards detected in the system, exercising8// every PCM device it can find. This may conflict with other system9// software if there is audio activity so is best run on a system with a10// minimal active userspace.1112#include <stdio.h>13#include <stdlib.h>14#include <stdbool.h>15#include <errno.h>16#include <assert.h>17#include <pthread.h>1819#include "../kselftest.h"20#include "alsa-local.h"2122typedef struct timespec timestamp_t;2324struct card_data {25int card;26snd_ctl_card_info_t *info;27const char *name;28pthread_t thread;29struct card_data *next;30};3132struct card_data *card_list = NULL;3334struct pcm_data {35snd_pcm_t *handle;36int card;37int device;38int subdevice;39const char *card_name;40snd_pcm_stream_t stream;41snd_config_t *pcm_config;42struct pcm_data *next;43};4445struct pcm_data *pcm_list = NULL;4647int num_missing = 0;48struct pcm_data *pcm_missing = NULL;4950snd_config_t *default_pcm_config;5152/* Lock while reporting results since kselftest doesn't */53pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;5455enum test_class {56TEST_CLASS_DEFAULT,57TEST_CLASS_SYSTEM,58};5960void timestamp_now(timestamp_t *tstamp)61{62if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))63ksft_exit_fail_msg("clock_get_time\n");64}6566long long timestamp_diff_ms(timestamp_t *tstamp)67{68timestamp_t now, diff;69timestamp_now(&now);70if (tstamp->tv_nsec > now.tv_nsec) {71diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;72diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;73} else {74diff.tv_sec = now.tv_sec - tstamp->tv_sec;75diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;76}77return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);78}7980static long device_from_id(snd_config_t *node)81{82const char *id;83char *end;84long v;8586if (snd_config_get_id(node, &id))87ksft_exit_fail_msg("snd_config_get_id\n");88errno = 0;89v = strtol(id, &end, 10);90if (errno || *end)91return -1;92return v;93}9495static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)96{97struct pcm_data *pcm_data;9899for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {100if (pcm_data->card != card)101continue;102if (pcm_data->device != device)103continue;104if (pcm_data->subdevice != subdevice)105continue;106if (pcm_data->stream != stream)107continue;108return;109}110pcm_data = calloc(1, sizeof(*pcm_data));111if (!pcm_data)112ksft_exit_fail_msg("Out of memory\n");113pcm_data->card = card;114pcm_data->device = device;115pcm_data->subdevice = subdevice;116pcm_data->stream = stream;117pcm_data->next = pcm_missing;118pcm_missing = pcm_data;119num_missing++;120}121122static void missing_devices(int card, snd_config_t *card_config)123{124snd_config_t *pcm_config, *node1, *node2;125snd_config_iterator_t i1, i2, next1, next2;126int device, subdevice;127128pcm_config = conf_get_subtree(card_config, "pcm", NULL);129if (!pcm_config)130return;131snd_config_for_each(i1, next1, pcm_config) {132node1 = snd_config_iterator_entry(i1);133device = device_from_id(node1);134if (device < 0)135continue;136if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)137continue;138snd_config_for_each(i2, next2, node1) {139node2 = snd_config_iterator_entry(i2);140subdevice = device_from_id(node2);141if (subdevice < 0)142continue;143if (conf_get_subtree(node2, "PLAYBACK", NULL))144missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);145if (conf_get_subtree(node2, "CAPTURE", NULL))146missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);147}148}149}150151static void find_pcms(void)152{153char name[32], key[64];154char *card_name, *card_longname;155int card, dev, subdev, count, direction, err;156snd_pcm_stream_t stream;157struct pcm_data *pcm_data;158snd_ctl_t *handle;159snd_pcm_info_t *pcm_info;160snd_config_t *config, *card_config, *pcm_config;161struct card_data *card_data;162163snd_pcm_info_alloca(&pcm_info);164165card = -1;166if (snd_card_next(&card) < 0 || card < 0)167return;168169config = get_alsalib_config();170171while (card >= 0) {172card_data = calloc(1, sizeof(*card_data));173if (!card_data)174ksft_exit_fail_msg("Out of memory\n");175176sprintf(name, "hw:%d", card);177178err = snd_ctl_open_lconf(&handle, name, 0, config);179if (err < 0) {180ksft_print_msg("Failed to get hctl for card %d: %s\n",181card, snd_strerror(err));182goto next_card;183}184185err = snd_card_get_name(card, &card_name);186if (err != 0)187card_name = "Unknown";188err = snd_card_get_longname(card, &card_longname);189if (err != 0)190card_longname = "Unknown";191192err = snd_ctl_card_info_malloc(&card_data->info);193if (err != 0)194ksft_exit_fail_msg("Failed to allocate card info: %d\n",195err);196197err = snd_ctl_card_info(handle, card_data->info);198if (err == 0) {199card_data->name = snd_ctl_card_info_get_id(card_data->info);200if (!card_data->name)201ksft_print_msg("Failed to get card ID\n");202} else {203ksft_print_msg("Failed to get card info: %d\n", err);204}205206if (!card_data->name)207card_data->name = "Unknown";208209ksft_print_msg("Card %d/%s - %s (%s)\n", card,210card_data->name, card_name, card_longname);211212card_config = conf_by_card(card);213214card_data->card = card;215card_data->next = card_list;216card_list = card_data;217218dev = -1;219while (1) {220if (snd_ctl_pcm_next_device(handle, &dev) < 0)221ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");222if (dev < 0)223break;224225for (direction = 0; direction < 2; direction++) {226stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;227sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));228pcm_config = conf_get_subtree(card_config, key, NULL);229if (conf_get_bool(card_config, key, "skip", false)) {230ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));231continue;232}233snd_pcm_info_set_device(pcm_info, dev);234snd_pcm_info_set_subdevice(pcm_info, 0);235snd_pcm_info_set_stream(pcm_info, stream);236err = snd_ctl_pcm_info(handle, pcm_info);237if (err == -ENOENT)238continue;239if (err < 0)240ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",241dev, 0, stream);242243ksft_print_msg("%s.0 - %s\n", card_data->name,244snd_pcm_info_get_id(pcm_info));245246count = snd_pcm_info_get_subdevices_count(pcm_info);247for (subdev = 0; subdev < count; subdev++) {248sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));249if (conf_get_bool(card_config, key, "skip", false)) {250ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,251subdev, snd_pcm_stream_name(stream));252continue;253}254pcm_data = calloc(1, sizeof(*pcm_data));255if (!pcm_data)256ksft_exit_fail_msg("Out of memory\n");257pcm_data->card = card;258pcm_data->device = dev;259pcm_data->subdevice = subdev;260pcm_data->card_name = card_data->name;261pcm_data->stream = stream;262pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);263pcm_data->next = pcm_list;264pcm_list = pcm_data;265}266}267}268269/* check for missing devices */270missing_devices(card, card_config);271272next_card:273snd_ctl_close(handle);274if (snd_card_next(&card) < 0) {275ksft_print_msg("snd_card_next");276break;277}278}279280snd_config_delete(config);281}282283static void test_pcm_time(struct pcm_data *data, enum test_class class,284const char *test_name, snd_config_t *pcm_cfg)285{286char name[64], msg[256];287const int duration_s = 2, margin_ms = 100;288const int duration_ms = duration_s * 1000;289const char *cs;290int i, err;291snd_pcm_t *handle = NULL;292snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;293snd_pcm_format_t format, old_format;294const char *alt_formats[8];295unsigned char *samples = NULL;296snd_pcm_sframes_t frames;297long long ms;298long rate, channels, period_size, buffer_size;299unsigned int rrate;300snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;301timestamp_t tstamp;302bool pass = false;303snd_pcm_hw_params_t *hw_params;304snd_pcm_sw_params_t *sw_params;305const char *test_class_name;306bool skip = true;307const char *desc;308309switch (class) {310case TEST_CLASS_DEFAULT:311test_class_name = "default";312break;313case TEST_CLASS_SYSTEM:314test_class_name = "system";315break;316default:317ksft_exit_fail_msg("Unknown test class %d\n", class);318break;319}320321desc = conf_get_string(pcm_cfg, "description", NULL, NULL);322if (desc)323ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n",324test_class_name, test_name,325data->card_name, data->device, data->subdevice,326snd_pcm_stream_name(data->stream),327desc);328329330snd_pcm_hw_params_alloca(&hw_params);331snd_pcm_sw_params_alloca(&sw_params);332333cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");334format = snd_pcm_format_value(cs);335if (format == SND_PCM_FORMAT_UNKNOWN)336ksft_exit_fail_msg("Wrong format '%s'\n", cs);337conf_get_string_array(pcm_cfg, "alt_formats", NULL,338alt_formats, ARRAY_SIZE(alt_formats), NULL);339rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);340channels = conf_get_long(pcm_cfg, "channels", NULL, 2);341period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);342buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);343344samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);345if (!samples)346ksft_exit_fail_msg("Out of memory\n");347snd_pcm_format_set_silence(format, samples, rate * channels);348349sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);350err = snd_pcm_open(&handle, name, data->stream, 0);351if (err < 0) {352snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));353goto __close;354}355356err = snd_pcm_hw_params_any(handle, hw_params);357if (err < 0) {358snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));359goto __close;360}361err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);362if (err < 0) {363snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));364goto __close;365}366err = snd_pcm_hw_params_set_access(handle, hw_params, access);367if (err < 0) {368snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",369snd_pcm_access_name(access), snd_strerror(err));370goto __close;371}372i = -1;373__format:374err = snd_pcm_hw_params_set_format(handle, hw_params, format);375if (err < 0) {376i++;377if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {378old_format = format;379format = snd_pcm_format_value(alt_formats[i]);380if (format != SND_PCM_FORMAT_UNKNOWN) {381ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n",382test_name,383data->card_name, data->device, data->subdevice,384snd_pcm_stream_name(data->stream),385snd_pcm_access_name(access),386snd_pcm_format_name(old_format),387snd_pcm_format_name(format));388samples = realloc(samples, (rate * channels *389snd_pcm_format_physical_width(format)) / 8);390if (!samples)391ksft_exit_fail_msg("Out of memory\n");392snd_pcm_format_set_silence(format, samples, rate * channels);393goto __format;394}395}396snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",397snd_pcm_format_name(format), snd_strerror(err));398goto __close;399}400err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);401if (err < 0) {402snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));403goto __close;404}405rrate = rate;406err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);407if (err < 0) {408snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));409goto __close;410}411if (rrate != rate) {412snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate);413goto __close;414}415rperiod_size = period_size;416err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);417if (err < 0) {418snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));419goto __close;420}421rbuffer_size = buffer_size;422err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);423if (err < 0) {424snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));425goto __close;426}427err = snd_pcm_hw_params(handle, hw_params);428if (err < 0) {429snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));430goto __close;431}432433err = snd_pcm_sw_params_current(handle, sw_params);434if (err < 0) {435snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));436goto __close;437}438if (data->stream == SND_PCM_STREAM_PLAYBACK) {439start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;440} else {441start_threshold = rperiod_size;442}443err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);444if (err < 0) {445snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));446goto __close;447}448err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);449if (err < 0) {450snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));451goto __close;452}453err = snd_pcm_sw_params(handle, sw_params);454if (err < 0) {455snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));456goto __close;457}458459ksft_print_msg("%s.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",460test_class_name, test_name,461data->card_name, data->device, data->subdevice,462snd_pcm_stream_name(data->stream),463snd_pcm_access_name(access),464snd_pcm_format_name(format),465(long)rate, (long)channels,466(long)rperiod_size, (long)rbuffer_size,467(long)start_threshold);468469/* Set all the params, actually run the test */470skip = false;471472timestamp_now(&tstamp);473for (i = 0; i < duration_s; i++) {474if (data->stream == SND_PCM_STREAM_PLAYBACK) {475frames = snd_pcm_writei(handle, samples, rate);476if (frames < 0) {477snprintf(msg, sizeof(msg),478"Write failed: expected %ld, wrote %li", rate, frames);479goto __close;480}481if (frames < rate) {482snprintf(msg, sizeof(msg),483"expected %ld, wrote %li", rate, frames);484goto __close;485}486} else {487frames = snd_pcm_readi(handle, samples, rate);488if (frames < 0) {489snprintf(msg, sizeof(msg),490"expected %ld, wrote %li", rate, frames);491goto __close;492}493if (frames < rate) {494snprintf(msg, sizeof(msg),495"expected %ld, wrote %li", rate, frames);496goto __close;497}498}499}500501snd_pcm_drain(handle);502ms = timestamp_diff_ms(&tstamp);503if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {504snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);505goto __close;506}507508msg[0] = '\0';509pass = true;510__close:511pthread_mutex_lock(&results_lock);512513switch (class) {514case TEST_CLASS_SYSTEM:515test_class_name = "system";516/*517* Anything specified as specific to this system518* should always be supported.519*/520ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n",521test_class_name, test_name,522data->card_name, data->device,523data->subdevice,524snd_pcm_stream_name(data->stream));525break;526default:527break;528}529530if (!skip)531ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n",532test_class_name, test_name,533data->card_name, data->device,534data->subdevice,535snd_pcm_stream_name(data->stream));536else537ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n",538test_class_name, test_name,539data->card_name, data->device,540data->subdevice,541snd_pcm_stream_name(data->stream));542543if (msg[0])544ksft_print_msg("%s\n", msg);545546pthread_mutex_unlock(&results_lock);547548free(samples);549if (handle)550snd_pcm_close(handle);551}552553void run_time_tests(struct pcm_data *pcm, enum test_class class,554snd_config_t *cfg)555{556const char *test_name, *test_type;557snd_config_t *pcm_cfg;558snd_config_iterator_t i, next;559560if (!cfg)561return;562563cfg = conf_get_subtree(cfg, "test", NULL);564if (cfg == NULL)565return;566567snd_config_for_each(i, next, cfg) {568pcm_cfg = snd_config_iterator_entry(i);569if (snd_config_get_id(pcm_cfg, &test_name) < 0)570ksft_exit_fail_msg("snd_config_get_id\n");571test_type = conf_get_string(pcm_cfg, "type", NULL, "time");572if (strcmp(test_type, "time") == 0)573test_pcm_time(pcm, class, test_name, pcm_cfg);574else575ksft_exit_fail_msg("unknown test type '%s'\n", test_type);576}577}578579void *card_thread(void *data)580{581struct card_data *card = data;582struct pcm_data *pcm;583584for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {585if (pcm->card != card->card)586continue;587588run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);589run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);590}591592return 0;593}594595int main(void)596{597struct card_data *card;598struct card_cfg_data *conf;599struct pcm_data *pcm;600snd_config_t *global_config, *cfg;601int num_pcm_tests = 0, num_tests, num_std_pcm_tests;602int ret;603void *thread_ret;604605ksft_print_header();606607global_config = conf_load_from_file("pcm-test.conf");608default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);609if (default_pcm_config == NULL)610ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");611612conf_load();613614find_pcms();615616for (conf = conf_cards; conf; conf = conf->next)617if (conf->card < 0)618num_missing++;619620num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);621622for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {623num_pcm_tests += num_std_pcm_tests;624cfg = pcm->pcm_config;625if (cfg == NULL)626continue;627/* Setting params is reported as a separate test */628num_tests = conf_get_count(cfg, "test", NULL) * 2;629if (num_tests > 0)630num_pcm_tests += num_tests;631}632633ksft_set_plan(num_missing + num_pcm_tests);634635for (conf = conf_cards; conf; conf = conf->next)636if (conf->card < 0)637ksft_test_result_fail("test.missing.%s.%s\n",638conf->filename, conf->config_id);639640for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {641ksft_test_result(false, "test.missing.%s.%d.%d.%s\n",642pcm->card_name, pcm->device, pcm->subdevice,643snd_pcm_stream_name(pcm->stream));644}645646for (card = card_list; card != NULL; card = card->next) {647ret = pthread_create(&card->thread, NULL, card_thread, card);648if (ret != 0) {649ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",650card->card, ret,651strerror(errno));652}653}654655for (card = card_list; card != NULL; card = card->next) {656ret = pthread_join(card->thread, &thread_ret);657if (ret != 0) {658ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",659card->card, ret,660strerror(errno));661}662}663664snd_config_delete(global_config);665conf_free();666667ksft_exit_pass();668669return 0;670}671672673