Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  pcm-test.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
//
// kselftest for the ALSA PCM API
//
// Original author: Jaroslav Kysela <perex@perex.cz>
// Copyright (c) 2022 Red Hat Inc.

// This test will iterate over all cards detected in the system, exercising
// every PCM device it can find.  This may conflict with other system
// software if there is audio activity so is best run on a system with a
// minimal active userspace.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <pthread.h>

#include "../kselftest.h"
#include "alsa-local.h"

typedef struct timespec timestamp_t;

struct card_data {
 int card;
 snd_ctl_card_info_t *info;
 const char *name;
 pthread_t thread;
 struct card_data *next;
};

struct card_data *card_list = NULL;

struct pcm_data {
 snd_pcm_t *handle;
 int card;
 int device;
 int subdevice;
 const char *card_name;
 snd_pcm_stream_t stream;
 snd_config_t *pcm_config;
 struct pcm_data *next;
};

struct pcm_data *pcm_list = NULL;

int num_missing = 0;
struct pcm_data *pcm_missing = NULL;

snd_config_t *default_pcm_config;

/* Lock while reporting results since kselftest doesn't */
pthread_mutex_t results_lock = PTHREAD_MUTEX_INITIALIZER;

enum test_class {
 TEST_CLASS_DEFAULT,
 TEST_CLASS_SYSTEM,
};

void timestamp_now(timestamp_t *tstamp)
{
 if (clock_gettime(CLOCK_MONOTONIC_RAW, tstamp))
  ksft_exit_fail_msg("clock_get_time\n");
}

long long timestamp_diff_ms(timestamp_t *tstamp)
{
 timestamp_t now, diff;
 timestamp_now(&now);
 if (tstamp->tv_nsec > now.tv_nsec) {
  diff.tv_sec = now.tv_sec - tstamp->tv_sec - 1;
  diff.tv_nsec = (now.tv_nsec + 1000000000L) - tstamp->tv_nsec;
 } else {
  diff.tv_sec = now.tv_sec - tstamp->tv_sec;
  diff.tv_nsec = now.tv_nsec - tstamp->tv_nsec;
 }
 return (diff.tv_sec * 1000) + ((diff.tv_nsec + 500000L) / 1000000L);
}

static long device_from_id(snd_config_t *node)
{
 const char *id;
 char *end;
 long v;

 if (snd_config_get_id(node, &id))
  ksft_exit_fail_msg("snd_config_get_id\n");
 errno = 0;
 v = strtol(id, &end, 10);
 if (errno || *end)
  return -1;
 return v;
}

static void missing_device(int card, int device, int subdevice, snd_pcm_stream_t stream)
{
 struct pcm_data *pcm_data;

 for (pcm_data = pcm_list; pcm_data != NULL; pcm_data = pcm_data->next) {
  if (pcm_data->card != card)
   continue;
  if (pcm_data->device != device)
   continue;
  if (pcm_data->subdevice != subdevice)
   continue;
  if (pcm_data->stream != stream)
   continue;
  return;
 }
 pcm_data = calloc(1, sizeof(*pcm_data));
 if (!pcm_data)
  ksft_exit_fail_msg("Out of memory\n");
 pcm_data->card = card;
 pcm_data->device = device;
 pcm_data->subdevice = subdevice;
 pcm_data->stream = stream;
 pcm_data->next = pcm_missing;
 pcm_missing = pcm_data;
 num_missing++;
}

static void missing_devices(int card, snd_config_t *card_config)
{
 snd_config_t *pcm_config, *node1, *node2;
 snd_config_iterator_t i1, i2, next1, next2;
 int device, subdevice;

 pcm_config = conf_get_subtree(card_config, "pcm", NULL);
 if (!pcm_config)
  return;
 snd_config_for_each(i1, next1, pcm_config) {
  node1 = snd_config_iterator_entry(i1);
  device = device_from_id(node1);
  if (device < 0)
   continue;
  if (snd_config_get_type(node1) != SND_CONFIG_TYPE_COMPOUND)
   continue;
  snd_config_for_each(i2, next2, node1) {
   node2 = snd_config_iterator_entry(i2);
   subdevice = device_from_id(node2);
   if (subdevice < 0)
    continue;
   if (conf_get_subtree(node2, "PLAYBACK", NULL))
    missing_device(card, device, subdevice, SND_PCM_STREAM_PLAYBACK);
   if (conf_get_subtree(node2, "CAPTURE", NULL))
    missing_device(card, device, subdevice, SND_PCM_STREAM_CAPTURE);
  }
 }
}

static void find_pcms(void)
{
 char name[32], key[64];
 char *card_name, *card_longname;
 int card, dev, subdev, count, direction, err;
 snd_pcm_stream_t stream;
 struct pcm_data *pcm_data;
 snd_ctl_t *handle;
 snd_pcm_info_t *pcm_info;
 snd_config_t *config, *card_config, *pcm_config;
 struct card_data *card_data;

 snd_pcm_info_alloca(&pcm_info);

 card = -1;
 if (snd_card_next(&card) < 0 || card < 0)
  return;

 config = get_alsalib_config();

 while (card >= 0) {
  card_data = calloc(1, sizeof(*card_data));
  if (!card_data)
   ksft_exit_fail_msg("Out of memory\n");

  sprintf(name, "hw:%d", card);

  err = snd_ctl_open_lconf(&handle, name, 0, config);
  if (err < 0) {
   ksft_print_msg("Failed to get hctl for card %d: %s\n",
           card, snd_strerror(err));
   goto next_card;
  }

  err = snd_card_get_name(card, &card_name);
  if (err != 0)
   card_name = "Unknown";
  err = snd_card_get_longname(card, &card_longname);
  if (err != 0)
   card_longname = "Unknown";

  err = snd_ctl_card_info_malloc(&card_data->info);
  if (err != 0)
   ksft_exit_fail_msg("Failed to allocate card info: %d\n",
    err);

  err = snd_ctl_card_info(handle, card_data->info);
  if (err == 0) {
   card_data->name = snd_ctl_card_info_get_id(card_data->info);
   if (!card_data->name)
    ksft_print_msg("Failed to get card ID\n");
  } else {
   ksft_print_msg("Failed to get card info: %d\n", err);
  }

  if (!card_data->name)
   card_data->name = "Unknown";

  ksft_print_msg("Card %d/%s - %s (%s)\n", card,
          card_data->name, card_name, card_longname);

  card_config = conf_by_card(card);

  card_data->card = card;
  card_data->next = card_list;
  card_list = card_data;

  dev = -1;
  while (1) {
   if (snd_ctl_pcm_next_device(handle, &dev) < 0)
    ksft_exit_fail_msg("snd_ctl_pcm_next_device\n");
   if (dev < 0)
    break;

   for (direction = 0; direction < 2; direction++) {
    stream = direction ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
    sprintf(key, "pcm.%d.%s", dev, snd_pcm_stream_name(stream));
    pcm_config = conf_get_subtree(card_config, key, NULL);
    if (conf_get_bool(card_config, key, "skip"false)) {
     ksft_print_msg("skipping pcm %d.%d.%s\n", card, dev, snd_pcm_stream_name(stream));
     continue;
    }
    snd_pcm_info_set_device(pcm_info, dev);
    snd_pcm_info_set_subdevice(pcm_info, 0);
    snd_pcm_info_set_stream(pcm_info, stream);
    err = snd_ctl_pcm_info(handle, pcm_info);
    if (err == -ENOENT)
     continue;
    if (err < 0)
     ksft_exit_fail_msg("snd_ctl_pcm_info: %d:%d:%d\n",
          dev, 0, stream);

    ksft_print_msg("%s.0 - %s\n", card_data->name,
            snd_pcm_info_get_id(pcm_info));

    count = snd_pcm_info_get_subdevices_count(pcm_info);
    for (subdev = 0; subdev < count; subdev++) {
     sprintf(key, "pcm.%d.%d.%s", dev, subdev, snd_pcm_stream_name(stream));
     if (conf_get_bool(card_config, key, "skip"false)) {
      ksft_print_msg("skipping pcm %d.%d.%d.%s\n", card, dev,
              subdev, snd_pcm_stream_name(stream));
      continue;
     }
     pcm_data = calloc(1, sizeof(*pcm_data));
     if (!pcm_data)
      ksft_exit_fail_msg("Out of memory\n");
     pcm_data->card = card;
     pcm_data->device = dev;
     pcm_data->subdevice = subdev;
     pcm_data->card_name = card_data->name;
     pcm_data->stream = stream;
     pcm_data->pcm_config = conf_get_subtree(card_config, key, NULL);
     pcm_data->next = pcm_list;
     pcm_list = pcm_data;
    }
   }
  }

  /* check for missing devices */
  missing_devices(card, card_config);

 next_card:
  snd_ctl_close(handle);
  if (snd_card_next(&card) < 0) {
   ksft_print_msg("snd_card_next");
   break;
  }
 }

 snd_config_delete(config);
}

static void test_pcm_time(struct pcm_data *data, enum test_class class,
     const char *test_name, snd_config_t *pcm_cfg)
{
 char name[64], msg[256];
 const int duration_s = 2, margin_ms = 100;
 const int duration_ms = duration_s * 1000;
 const char *cs;
 int i, err;
 snd_pcm_t *handle = NULL;
 snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
 snd_pcm_format_t format, old_format;
 const char *alt_formats[8];
 unsigned char *samples = NULL;
 snd_pcm_sframes_t frames;
 long long ms;
 long rate, channels, period_size, buffer_size;
 unsigned int rrate;
 snd_pcm_uframes_t rperiod_size, rbuffer_size, start_threshold;
 timestamp_t tstamp;
 bool pass = false;
 snd_pcm_hw_params_t *hw_params;
 snd_pcm_sw_params_t *sw_params;
 const char *test_class_name;
 bool skip = true;
 const char *desc;

 switch (class) {
 case TEST_CLASS_DEFAULT:
  test_class_name = "default";
  break;
 case TEST_CLASS_SYSTEM:
  test_class_name = "system";
  break;
 default:
  ksft_exit_fail_msg("Unknown test class %d\n"class);
  break;
 }

 desc = conf_get_string(pcm_cfg, "description", NULL, NULL);
 if (desc)
  ksft_print_msg("%s.%s.%s.%d.%d.%s - %s\n",
          test_class_name, test_name,
          data->card_name, data->device, data->subdevice,
          snd_pcm_stream_name(data->stream),
          desc);


 snd_pcm_hw_params_alloca(&hw_params);
 snd_pcm_sw_params_alloca(&sw_params);

 cs = conf_get_string(pcm_cfg, "format", NULL, "S16_LE");
 format = snd_pcm_format_value(cs);
 if (format == SND_PCM_FORMAT_UNKNOWN)
  ksft_exit_fail_msg("Wrong format '%s'\n", cs);
 conf_get_string_array(pcm_cfg, "alt_formats", NULL,
    alt_formats, ARRAY_SIZE(alt_formats), NULL);
 rate = conf_get_long(pcm_cfg, "rate", NULL, 48000);
 channels = conf_get_long(pcm_cfg, "channels", NULL, 2);
 period_size = conf_get_long(pcm_cfg, "period_size", NULL, 4096);
 buffer_size = conf_get_long(pcm_cfg, "buffer_size", NULL, 16384);

 samples = malloc((rate * channels * snd_pcm_format_physical_width(format)) / 8);
 if (!samples)
  ksft_exit_fail_msg("Out of memory\n");
 snd_pcm_format_set_silence(format, samples, rate * channels);

 sprintf(name, "hw:%d,%d,%d", data->card, data->device, data->subdevice);
 err = snd_pcm_open(&handle, name, data->stream, 0);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "Failed to get pcm handle: %s", snd_strerror(err));
  goto __close;
 }

 err = snd_pcm_hw_params_any(handle, hw_params);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_any: %s", snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate_resample: %s", snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_hw_params_set_access(handle, hw_params, access);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_access %s: %s",
        snd_pcm_access_name(access), snd_strerror(err));
  goto __close;
 }
 i = -1;
__format:
 err = snd_pcm_hw_params_set_format(handle, hw_params, format);
 if (err < 0) {
  i++;
  if (i < ARRAY_SIZE(alt_formats) && alt_formats[i]) {
   old_format = format;
   format = snd_pcm_format_value(alt_formats[i]);
   if (format != SND_PCM_FORMAT_UNKNOWN) {
    ksft_print_msg("%s.%s.%d.%d.%s.%s format %s -> %s\n",
       test_name,
       data->card_name, data->device, data->subdevice,
       snd_pcm_stream_name(data->stream),
       snd_pcm_access_name(access),
       snd_pcm_format_name(old_format),
       snd_pcm_format_name(format));
    samples = realloc(samples, (rate * channels *
           snd_pcm_format_physical_width(format)) / 8);
    if (!samples)
     ksft_exit_fail_msg("Out of memory\n");
    snd_pcm_format_set_silence(format, samples, rate * channels);
    goto __format;
   }
  }
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_format %s: %s",
        snd_pcm_format_name(format), snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_hw_params_set_channels(handle, hw_params, channels);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_channels %ld: %s", channels, snd_strerror(err));
  goto __close;
 }
 rrate = rate;
 err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_rate %ld: %s", rate, snd_strerror(err));
  goto __close;
 }
 if (rrate != rate) {
  snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate);
  goto __close;
 }
 rperiod_size = period_size;
 err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &rperiod_size, 0);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_period_size %ld: %s", period_size, snd_strerror(err));
  goto __close;
 }
 rbuffer_size = buffer_size;
 err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, &rbuffer_size);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params_set_buffer_size %ld: %s", buffer_size, snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_hw_params(handle, hw_params);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_hw_params: %s", snd_strerror(err));
  goto __close;
 }

 err = snd_pcm_sw_params_current(handle, sw_params);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_sw_params_current: %s", snd_strerror(err));
  goto __close;
 }
 if (data->stream == SND_PCM_STREAM_PLAYBACK) {
  start_threshold = (rbuffer_size / rperiod_size) * rperiod_size;
 } else {
  start_threshold = rperiod_size;
 }
 err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, start_threshold);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_start_threshold %ld: %s", (long)start_threshold, snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_sw_params_set_avail_min(handle, sw_params, rperiod_size);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_sw_params_set_avail_min %ld: %s", (long)rperiod_size, snd_strerror(err));
  goto __close;
 }
 err = snd_pcm_sw_params(handle, sw_params);
 if (err < 0) {
  snprintf(msg, sizeof(msg), "snd_pcm_sw_params: %s", snd_strerror(err));
  goto __close;
 }

 ksft_print_msg("%s.%s.%s.%d.%d.%s hw_params.%s.%s.%ld.%ld.%ld.%ld sw_params.%ld\n",
           test_class_name, test_name,
    data->card_name, data->device, data->subdevice,
    snd_pcm_stream_name(data->stream),
    snd_pcm_access_name(access),
    snd_pcm_format_name(format),
    (long)rate, (long)channels,
    (long)rperiod_size, (long)rbuffer_size,
    (long)start_threshold);

 /* Set all the params, actually run the test */
 skip = false;

 timestamp_now(&tstamp);
 for (i = 0; i < duration_s; i++) {
  if (data->stream == SND_PCM_STREAM_PLAYBACK) {
   frames = snd_pcm_writei(handle, samples, rate);
   if (frames < 0) {
    snprintf(msg, sizeof(msg),
      "Write failed: expected %ld, wrote %li", rate, frames);
    goto __close;
   }
   if (frames < rate) {
    snprintf(msg, sizeof(msg),
      "expected %ld, wrote %li", rate, frames);
    goto __close;
   }
  } else {
   frames = snd_pcm_readi(handle, samples, rate);
   if (frames < 0) {
    snprintf(msg, sizeof(msg),
      "expected %ld, wrote %li", rate, frames);
    goto __close;
   }
   if (frames < rate) {
    snprintf(msg, sizeof(msg),
      "expected %ld, wrote %li", rate, frames);
    goto __close;
   }
  }
 }

 snd_pcm_drain(handle);
 ms = timestamp_diff_ms(&tstamp);
 if (ms < duration_ms - margin_ms || ms > duration_ms + margin_ms) {
  snprintf(msg, sizeof(msg), "time mismatch: expected %dms got %lld", duration_ms, ms);
  goto __close;
 }

 msg[0] = '\0';
 pass = true;
__close:
 pthread_mutex_lock(&results_lock);

 switch (class) {
 case TEST_CLASS_SYSTEM:
  test_class_name = "system";
  /*
 * Anything specified as specific to this system
 * should always be supported.
 */

  ksft_test_result(!skip, "%s.%s.%s.%d.%d.%s.params\n",
     test_class_name, test_name,
     data->card_name, data->device,
     data->subdevice,
     snd_pcm_stream_name(data->stream));
  break;
 default:
  break;
 }

 if (!skip)
  ksft_test_result(pass, "%s.%s.%s.%d.%d.%s\n",
     test_class_name, test_name,
     data->card_name, data->device,
     data->subdevice,
     snd_pcm_stream_name(data->stream));
 else
  ksft_test_result_skip("%s.%s.%s.%d.%d.%s\n",
     test_class_name, test_name,
     data->card_name, data->device,
     data->subdevice,
     snd_pcm_stream_name(data->stream));

 if (msg[0])
  ksft_print_msg("%s\n", msg);

 pthread_mutex_unlock(&results_lock);

 free(samples);
 if (handle)
  snd_pcm_close(handle);
}

void run_time_tests(struct pcm_data *pcm, enum test_class class,
      snd_config_t *cfg)
{
 const char *test_name, *test_type;
 snd_config_t *pcm_cfg;
 snd_config_iterator_t i, next;

 if (!cfg)
  return;

 cfg = conf_get_subtree(cfg, "test", NULL);
 if (cfg == NULL)
  return;

 snd_config_for_each(i, next, cfg) {
  pcm_cfg = snd_config_iterator_entry(i);
  if (snd_config_get_id(pcm_cfg, &test_name) < 0)
   ksft_exit_fail_msg("snd_config_get_id\n");
  test_type = conf_get_string(pcm_cfg, "type", NULL, "time");
  if (strcmp(test_type, "time") == 0)
   test_pcm_time(pcm, class, test_name, pcm_cfg);
  else
   ksft_exit_fail_msg("unknown test type '%s'\n", test_type);
 }
}

void *card_thread(void *data)
{
 struct card_data *card = data;
 struct pcm_data *pcm;

 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
  if (pcm->card != card->card)
   continue;

  run_time_tests(pcm, TEST_CLASS_DEFAULT, default_pcm_config);
  run_time_tests(pcm, TEST_CLASS_SYSTEM, pcm->pcm_config);
 }

 return 0;
}

int main(void)
{
 struct card_data *card;
 struct card_cfg_data *conf;
 struct pcm_data *pcm;
 snd_config_t *global_config, *cfg;
 int num_pcm_tests = 0, num_tests, num_std_pcm_tests;
 int ret;
 void *thread_ret;

 ksft_print_header();

 global_config = conf_load_from_file("pcm-test.conf");
 default_pcm_config = conf_get_subtree(global_config, "pcm", NULL);
 if (default_pcm_config == NULL)
  ksft_exit_fail_msg("default pcm test configuration (pcm compound) is missing\n");

 conf_load();

 find_pcms();

 for (conf = conf_cards; conf; conf = conf->next)
  if (conf->card < 0)
   num_missing++;

 num_std_pcm_tests = conf_get_count(default_pcm_config, "test", NULL);

 for (pcm = pcm_list; pcm != NULL; pcm = pcm->next) {
  num_pcm_tests += num_std_pcm_tests;
  cfg = pcm->pcm_config;
  if (cfg == NULL)
   continue;
  /* Setting params is reported as a separate test */
  num_tests = conf_get_count(cfg, "test", NULL) * 2;
  if (num_tests > 0)
   num_pcm_tests += num_tests;
 }

 ksft_set_plan(num_missing + num_pcm_tests);

 for (conf = conf_cards; conf; conf = conf->next)
  if (conf->card < 0)
   ksft_test_result_fail("test.missing.%s.%s\n",
           conf->filename, conf->config_id);

 for (pcm = pcm_missing; pcm != NULL; pcm = pcm->next) {
  ksft_test_result(false"test.missing.%s.%d.%d.%s\n",
     pcm->card_name, pcm->device, pcm->subdevice,
     snd_pcm_stream_name(pcm->stream));
 }

 for (card = card_list; card != NULL; card = card->next) {
  ret = pthread_create(&card->thread, NULL, card_thread, card);
  if (ret != 0) {
   ksft_exit_fail_msg("Failed to create card %d thread: %d (%s)\n",
        card->card, ret,
        strerror(errno));
  }
 }

 for (card = card_list; card != NULL; card = card->next) {
  ret = pthread_join(card->thread, &thread_ret);
  if (ret != 0) {
   ksft_exit_fail_msg("Failed to join card %d thread: %d (%s)\n",
        card->card, ret,
        strerror(errno));
  }
 }

 snd_config_delete(global_config);
 conf_free();

 ksft_exit_pass();

 return 0;
}

Messung V0.5
C=98 H=76 G=87

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge