[alsa-devel] [PATCH V2 0/7] BAT: Add Basic Audio Tester command line tool
From: "Lu, Han" han.lu@intel.com
BAT (Basic Audio Tester) is a simple command line utility intended to automate audio driver and sound server QA testing with no human interaction.
BAT works by playing an audio stream and capturing the same stream in either a digital or analog loop back. It then compares the captured stream to the original to determine if the test case passes or fails.
The main idea of frequency detecting is: The analysis function reads data from WAV file, run FFT against the data to get magnitude of frequency vectors, and then calculates the average value and standard deviation of frequency vectors. After that, we define a threshold: threshold = 3 * standard_deviation + average_value Frequencies with amplitude larger than threshold will be recognized as a peak, and the frequency with largest peak value will be recognized as a detected frequency. BAT then compares the detected frequency to target frequency, to decide if the detecting passes or fails.
BAT supports 4 working modes: 1. Single line playback; 2. Single line capture and analysis; 3. Playback and capture in loop, and analyze captured data; 4. Local analyze without actual playback or capture. BAT will check devices input by user to decide which mode to use. BAT will create threads for playback and record, and will run spectrum analysis for mode 2, 3 or 4.
Changes on V2: 1. Adopt Caleb Crome's method to generate sine signal to avoids unwanted distortion on waveform.
Lu, Han (7): BAT: Add initial functions BAT: Add common definitions and functions BAT: Add playback and record functions BAT: Add signal generator BAT: Add converting functions BAT: Add spectrum analysis functions BAT: Add Makefile and configures
Makefile.am | 3 + bat/Makefile.am | 14 ++ bat/alsa.c | 594 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 +++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 603 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 +++++++++++++++++++ bat/common.h | 176 +++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 182 +++++++++++++++++ bat/signal.h | 30 +++ configure.ac | 19 +- 14 files changed, 2304 insertions(+), 1 deletion(-) create mode 100644 bat/Makefile.am create mode 100644 bat/alsa.c create mode 100644 bat/alsa.h create mode 100644 bat/analyze.c create mode 100644 bat/analyze.h create mode 100644 bat/bat.c create mode 100644 bat/common.c create mode 100644 bat/common.h create mode 100644 bat/convert.c create mode 100644 bat/convert.h create mode 100644 bat/signal.c create mode 100644 bat/signal.h
From: "Lu, Han" han.lu@intel.com
Add main entrance, command line parsing, parameter initiating and thread initiating functions for BAT.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/bat.c b/bat/bat.c new file mode 100644 index 0000000..54fe1ec --- /dev/null +++ b/bat/bat.c @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <getopt.h> +#include <math.h> +#include <limits.h> + +#include "aconfig.h" +#include "gettext.h" +#include "version.h" + +#include "common.h" + +#include "alsa.h" +#include "convert.h" +#include "analyze.h" + +static int get_duration(struct bat *bat) +{ + float duration_f; + long duration_i; + char *ptrf, *ptri; + + duration_f = strtof(bat->narg, &ptrf); + if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF) { + fprintf(bat->err, _("duration float overflow: %f %d\n"), + duration_f, -errno); + return -errno; + } else if (duration_f == 0.0 && errno != 0) { + fprintf(bat->err, _("duration float underflow: %f %d\n"), + duration_f, -errno); + return -errno; + } + + duration_i = strtol(bat->narg, &ptri, 10); + if (duration_i == LONG_MAX) { + fprintf(bat->err, _("duration long overflow: %ld %d\n"), + duration_i, -errno); + return -errno; + } else if (duration_i == LONG_MIN) { + fprintf(bat->err, _("duration long underflow: %ld %d\n"), + duration_i, -errno); + return -errno; + } + + if (*ptrf == 's') { + bat->frames = duration_f * bat->rate; + } else if (*ptri == 0) { + bat->frames = duration_i; + } else { + fprintf(bat->err, _("invalid duration: %s\n"), bat->narg); + return -EINVAL; + } + + if (bat->frames <= 0 || bat->frames > MAX_FRAMES) { + fprintf(bat->err, _("duration out of range: (0, %d(%ds))\n"), + MAX_FRAMES, (bat->frames / bat->rate)); + return -EINVAL; + } + + return 0; +} + +static void get_sine_frequencies(struct bat *bat, char *freq) +{ + char *tmp1; + + tmp1 = strchr(freq, ','); + if (tmp1 == NULL) { + bat->target_freq[1] = bat->target_freq[0] = atof(optarg); + } else { + *tmp1 = '\0'; + bat->target_freq[0] = atof(optarg); + bat->target_freq[1] = atof(tmp1 + 1); + } +} + +static void get_format(struct bat *bat, char *optarg) +{ + if (strcasecmp(optarg, "cd") == 0) { + bat->format = SND_PCM_FORMAT_S16_LE; + bat->rate = 44100; + bat->channels = 2; + } else if (strcasecmp(optarg, "dat") == 0) { + bat->format = SND_PCM_FORMAT_S16_LE; + bat->rate = 48000; + bat->channels = 2; + } else { + bat->format = snd_pcm_format_value(optarg); + if (bat->format == SND_PCM_FORMAT_UNKNOWN) { + fprintf(bat->err, _("wrong extended format '%s'\n"), + optarg); + exit(EXIT_FAILURE); + } + } + + switch (bat->format) { + case SND_PCM_FORMAT_U8: + bat->sample_size = 1; + break; + case SND_PCM_FORMAT_S16_LE: + bat->sample_size = 2; + break; + case SND_PCM_FORMAT_S24_3LE: + bat->sample_size = 3; + break; + case SND_PCM_FORMAT_S32_LE: + bat->sample_size = 4; + break; + default: + fprintf(bat->err, _("unsupported format: %d\n"), bat->format); + exit(EXIT_FAILURE); + } +} + +static inline int thread_wait_completion(struct bat *bat, + pthread_t id, int **val) +{ + int err; + + err = pthread_join(id, (void **) val); + if (err) + pthread_cancel(id); + + return err; +} + +/* loopback test where we play sine wave and capture the same sine wave */ +static void test_loopback(struct bat *bat) +{ + pthread_t capture_id, playback_id; + int err; + int *thread_result_capture, *thread_result_playback; + + /* start playback */ + err = pthread_create(&playback_id, NULL, + (void *) bat->playback.fct, bat); + if (err != 0) { + fprintf(bat->err, _("Cannot create playback thread: %d\n"), + err); + exit(EXIT_FAILURE); + } + + /* TODO: use a pipe to signal stream start etc - i.e. to sync threads */ + /* Let some time for playing something before capturing */ + usleep(CAPTURE_DELAY * 1000); + + /* start capture */ + err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat); + if (err != 0) { + fprintf(bat->err, _("Cannot create capture thread: %d\n"), err); + pthread_cancel(playback_id); + exit(EXIT_FAILURE); + } + + /* wait for playback to complete */ + err = thread_wait_completion(bat, playback_id, &thread_result_playback); + if (err != 0) { + fprintf(bat->err, _("Cannot join playback thread: %d\n"), err); + free(thread_result_playback); + pthread_cancel(capture_id); + exit(EXIT_FAILURE); + } + + /* check playback status */ + if (*thread_result_playback != 0) { + fprintf(bat->err, _("Exit playback thread fail: %d\n"), + *thread_result_playback); + pthread_cancel(capture_id); + exit(EXIT_FAILURE); + } else { + fprintf(bat->log, _("Playback completed.\n")); + } + + /* now stop and wait for capture to finish */ + pthread_cancel(capture_id); + err = thread_wait_completion(bat, capture_id, &thread_result_capture); + if (err != 0) { + fprintf(bat->err, _("Cannot join capture thread: %d\n"), err); + free(thread_result_capture); + exit(EXIT_FAILURE); + } + + /* check capture status */ + if (*thread_result_capture != 0) { + fprintf(bat->err, _("Exit capture thread fail: %d\n"), + *thread_result_capture); + exit(EXIT_FAILURE); + } else { + fprintf(bat->log, _("Capture completed.\n")); + } +} + +/* single ended playback only test */ +static void test_playback(struct bat *bat) +{ + pthread_t playback_id; + int err; + int *thread_result; + + /* start playback */ + err = pthread_create(&playback_id, NULL, + (void *) bat->playback.fct, bat); + if (err != 0) { + fprintf(bat->err, _("Cannot create playback thread: %d\n"), + err); + exit(EXIT_FAILURE); + } + + /* wait for playback to complete */ + err = thread_wait_completion(bat, playback_id, &thread_result); + if (err != 0) { + fprintf(bat->err, _("Cannot join playback thread: %d\n"), err); + free(thread_result); + exit(EXIT_FAILURE); + } + + /* check playback status */ + if (*thread_result != 0) { + fprintf(bat->err, _("Exit playback thread fail: %d\n"), + *thread_result); + exit(EXIT_FAILURE); + } else { + fprintf(bat->log, _("Playback completed.\n")); + } +} + +/* single ended capture only test */ +static void test_capture(struct bat *bat) +{ + pthread_t capture_id; + int err; + int *thread_result; + + /* start capture */ + err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat); + if (err != 0) { + fprintf(bat->err, _("Cannot create capture thread: %d\n"), err); + exit(EXIT_FAILURE); + } + + /* TODO: stop capture */ + + /* wait for capture to complete */ + err = thread_wait_completion(bat, capture_id, &thread_result); + if (err != 0) { + fprintf(bat->err, _("Cannot join capture thread: %d\n"), err); + free(thread_result); + exit(EXIT_FAILURE); + } + + /* check playback status */ + if (*thread_result != 0) { + fprintf(bat->err, _("Exit capture thread fail: %d\n"), + *thread_result); + exit(EXIT_FAILURE); + } else { + fprintf(bat->log, _("Capture completed.\n")); + } +} + +static void usage(struct bat *bat, char *argv[]) +{ + fprintf(bat->log, +_("Usage:%s [Option]...\n" +"\n" +"-h, --help help\n" +"-D sound card\n" +"-P playback pcm\n" +"-C capture pcm\n" +"-f sample size\n" +"-c number of channels\n" +"-r sampling rate\n" +"-n frames to capture\n" +"-k sigma k\n" +"-F target frequency\n" +"-p total number of periods to play/capture\n" +" --log=# path of log file. if not set, logs be put to stdout,\n" +" and errors be put to stderr.\n" +" --file=# input file\n" +" --saveplay=# save playback content to target file, for debug\n" +" --local internal loop, bypass hardware\n" +), argv[0]); + fprintf(bat->log, _("Recognized sample formats are: %s %s %s %s\n"), + snd_pcm_format_name(SND_PCM_FORMAT_U8), + snd_pcm_format_name(SND_PCM_FORMAT_S16_LE), + snd_pcm_format_name(SND_PCM_FORMAT_S24_3LE), + snd_pcm_format_name(SND_PCM_FORMAT_S32_LE)); + fprintf(bat->log, _("The available format shotcuts are:\n")); + fprintf(bat->log, _("-f cd (16 bit little endian, 44100, stereo)\n")); + fprintf(bat->log, _("-f dat (16 bit little endian, 48000, stereo)\n")); +} + +static void set_defaults(struct bat *bat) +{ + memset(bat, 0, sizeof(struct bat)); + + /* Set default values */ + bat->rate = 44100; + bat->channels = 1; + bat->frame_size = 2; + bat->sample_size = 2; + bat->format = SND_PCM_FORMAT_S16_LE; + bat->convert_float_to_sample = convert_float_to_int16; + bat->convert_sample_to_double = convert_int16_to_double; + bat->frames = bat->rate * 2; + bat->target_freq[0] = 997.0; + bat->target_freq[1] = 997.0; + bat->sigma_k = 3.0; + bat->playback.device = NULL; + bat->capture.device = NULL; + bat->buf = NULL; + bat->local = false; + bat->playback.fct = &playback_alsa; + bat->capture.fct = &record_alsa; + bat->playback.mode = MODE_LOOPBACK; + bat->capture.mode = MODE_LOOPBACK; + bat->period_is_limited = false; + bat->log = stdout; + bat->err = stderr; +} + +static void parse_arguments(struct bat *bat, int argc, char *argv[]) +{ + int c, option_index; + static const char short_options[] = "D:P:C:f:n:F:c:r:s:k:p:lth"; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"log", 1, 0, OPT_LOG}, + {"file", 1, 0, OPT_READFILE}, + {"saveplay", 1, 0, OPT_SAVEPLAY}, + {"local", 0, 0, OPT_LOCAL}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, short_options, long_options, + &option_index)) != -1) { + switch (c) { + case OPT_LOG: + bat->logarg = optarg; + break; + case OPT_READFILE: + bat->playback.file = optarg; + break; + case OPT_SAVEPLAY: + bat->debugplay = optarg; + break; + case OPT_LOCAL: + bat->local = true; + break; + case 'D': + if (bat->playback.device == NULL) + bat->playback.device = optarg; + if (bat->capture.device == NULL) + bat->capture.device = optarg; + break; + case 'P': + if (bat->capture.mode == MODE_SINGLE) + bat->capture.mode = MODE_LOOPBACK; + else + bat->playback.mode = MODE_SINGLE; + bat->playback.device = optarg; + break; + case 'C': + if (bat->playback.mode == MODE_SINGLE) + bat->playback.mode = MODE_LOOPBACK; + else + bat->capture.mode = MODE_SINGLE; + bat->capture.device = optarg; + break; + case 'n': + bat->narg = optarg; + break; + case 'F': + get_sine_frequencies(bat, optarg); + break; + case 'c': + bat->channels = atoi(optarg); + break; + case 'r': + bat->rate = atoi(optarg); + break; + case 'f': + get_format(bat, optarg); + break; + case 'k': + bat->sigma_k = atof(optarg); + break; + case 'p': + bat->periods_total = atoi(optarg); + bat->period_is_limited = true; + break; + case 'h': + default: + usage(bat, argv); + exit(EXIT_SUCCESS); + } + } +} + +static int validate_options(struct bat *bat) +{ + int c; + float freq_low, freq_high; + + /* check if we have an input file for local mode */ + if ((bat->local == true) && (bat->capture.file == NULL)) { + fprintf(bat->err, _("no input file for local testing\n")); + return -EINVAL; + } + + /* check supported channels */ + if (bat->channels > MAX_CHANNELS || bat->channels < MIN_CHANNELS) { + fprintf(bat->err, _("%d channels not supported\n"), + bat->channels); + return -EINVAL; + } + + /* check single ended is in either playback or capture - not both */ + if ((bat->playback.mode == MODE_SINGLE) + && (bat->capture.mode == MODE_SINGLE)) { + fprintf(bat->err, _("single ended mode is simplex\n")); + return -EINVAL; + } + + /* check sine wave frequency range */ + freq_low = DC_THRESHOLD; + freq_high = bat->rate * RATE_FACTOR; + for (c = 0; c < bat->channels; c++) { + if (bat->target_freq[c] < freq_low + || bat->target_freq[c] > freq_high) { + fprintf(bat->err, _("sine wave frequency out of")); + fprintf(bat->err, _(" range: (%.1f, %.1f)\n"), + freq_low, freq_high); + return -EINVAL; + } + } + + return 0; +} + +static int bat_init(struct bat *bat) +{ + int err = 0; + + /* Determine logging to a file or stdout and stderr */ + if (bat->logarg) { + bat->log = NULL; + bat->log = fopen(bat->logarg, "wb"); + if (bat->log == NULL) { + fprintf(bat->err, _("Cannot open file for capture:")); + fprintf(bat->err, _(" %s %d\n"), + bat->logarg, -errno); + return -errno; + } + bat->err = bat->log; + } + + /* Determine duration of playback and/or capture */ + if (bat->narg) { + err = get_duration(bat); + if (err < 0) + return err; + } + + /* Determine capture file */ + if (bat->local) + bat->capture.file = bat->playback.file; + else + bat->capture.file = TEMP_RECORD_FILE_NAME; + + /* Initial for playback */ + if (bat->playback.file == NULL) { + /* No input file so we will generate our own sine wave */ + if (bat->frames) { + if (bat->playback.mode == MODE_SINGLE) { + /* Play nb of frames given by -n argument */ + bat->sinus_duration = bat->frames; + } else { + /* Play CAPTURE_DELAY msec + + * 150% of the nb of frames to be analyzed */ + bat->sinus_duration = bat->rate * + CAPTURE_DELAY / 1000; + bat->sinus_duration += + (bat->frames + bat->frames / 2); + } + } else { + /* Special case where we want to generate a sine wave + * endlessly without capturing */ + bat->sinus_duration = 0; + bat->playback.mode = MODE_SINGLE; + } + } else { + bat->fp = fopen(bat->playback.file, "rb"); + if (bat->fp == NULL) { + fprintf(bat->err, _("Cannot open file for playback:")); + fprintf(bat->err, _(" %s %d\n"), + bat->playback.file, -errno); + return -errno; + } + err = read_wav_header(bat, bat->playback.file, bat->fp, false); + fclose(bat->fp); + if (err != 0) + return err; + } + + bat->frame_size = bat->sample_size * bat->channels; + + /* Set conversion functions */ + switch (bat->sample_size) { + case 1: + bat->convert_float_to_sample = convert_float_to_uint8; + bat->convert_sample_to_double = convert_uint8_to_double; + break; + case 2: + bat->convert_float_to_sample = convert_float_to_int16; + bat->convert_sample_to_double = convert_int16_to_double; + break; + case 3: + bat->convert_float_to_sample = convert_float_to_int24; + bat->convert_sample_to_double = convert_int24_to_double; + break; + case 4: + bat->convert_float_to_sample = convert_float_to_int32; + bat->convert_sample_to_double = convert_int32_to_double; + break; + default: + fprintf(bat->err, _("Invalid PCM format: size=%d\n"), + bat->sample_size); + return -EINVAL; + } + + return err; +} + +int main(int argc, char *argv[]) +{ + struct bat bat; + int err = 0; + + set_defaults(&bat); + +#ifdef ENABLE_NLS + setlocale(LC_ALL, ""); + textdomain(PACKAGE); +#endif + + fprintf(bat.log, _("%s version %s\n\n"), PACKAGE_NAME, PACKAGE_VERSION); + + parse_arguments(&bat, argc, argv); + + err = bat_init(&bat); + if (err < 0) + goto out; + + err = validate_options(&bat); + if (err < 0) + goto out; + + /* single line playback thread: playback only, no capture */ + if (bat.playback.mode == MODE_SINGLE) { + test_playback(&bat); + goto out; + } + + /* single line capture thread: capture only, no playback */ + if (bat.capture.mode == MODE_SINGLE) { + test_capture(&bat); + goto analyze; + } + + /* loopback thread: playback and capture in a loop */ + if (bat.local == false) + test_loopback(&bat); + +analyze: + err = analyze_capture(&bat); +out: + fprintf(bat.log, _("\nReturn value is %d\n"), err); + if (bat.logarg) + fclose(bat.log); + + return err; +}
On Wed, 23 Sep 2015 09:48:49 +0200, han.lu@intel.com wrote:
From: "Lu, Han" han.lu@intel.com
Add main entrance, command line parsing, parameter initiating and thread initiating functions for BAT.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/bat.c b/bat/bat.c new file mode 100644 index 0000000..54fe1ec --- /dev/null +++ b/bat/bat.c @@ -0,0 +1,603 @@ +/*
- Copyright (C) 2013-2015 Intel Corporation
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <getopt.h> +#include <math.h> +#include <limits.h>
+#include "aconfig.h" +#include "gettext.h" +#include "version.h"
+#include "common.h"
+#include "alsa.h" +#include "convert.h" +#include "analyze.h"
+static int get_duration(struct bat *bat) +{
- float duration_f;
- long duration_i;
- char *ptrf, *ptri;
- duration_f = strtof(bat->narg, &ptrf);
- if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF) {
fprintf(bat->err, _("duration float overflow: %f %d\n"),
duration_f, -errno);
return -errno;
- } else if (duration_f == 0.0 && errno != 0) {
fprintf(bat->err, _("duration float underflow: %f %d\n"),
duration_f, -errno);
return -errno;
- }
- duration_i = strtol(bat->narg, &ptri, 10);
- if (duration_i == LONG_MAX) {
fprintf(bat->err, _("duration long overflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- } else if (duration_i == LONG_MIN) {
fprintf(bat->err, _("duration long underflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- }
- if (*ptrf == 's') {
bat->frames = duration_f * bat->rate;
- } else if (*ptri == 0) {
bat->frames = duration_i;
- } else {
fprintf(bat->err, _("invalid duration: %s\n"), bat->narg);
return -EINVAL;
- }
- if (bat->frames <= 0 || bat->frames > MAX_FRAMES) {
fprintf(bat->err, _("duration out of range: (0, %d(%ds))\n"),
MAX_FRAMES, (bat->frames / bat->rate));
The messages strings could be unified to a single error message. Otherwise you'll annoy translators, as they will need to work on each different variant.
return -EINVAL;
- }
- return 0;
+}
+static void get_sine_frequencies(struct bat *bat, char *freq) +{
- char *tmp1;
- tmp1 = strchr(freq, ',');
- if (tmp1 == NULL) {
bat->target_freq[1] = bat->target_freq[0] = atof(optarg);
- } else {
*tmp1 = '\0';
bat->target_freq[0] = atof(optarg);
bat->target_freq[1] = atof(tmp1 + 1);
- }
Note that parsing a floting point string is tricky. For example, if you run it in a locale that handles comma as the decimal point, this gets messy. Did you check it, e.g. with LANG=en_GB or such?
+static void usage(struct bat *bat, char *argv[]) +{
- fprintf(bat->log,
+_("Usage:%s [Option]...\n" +"\n" +"-h, --help help\n" +"-D sound card\n" +"-P playback pcm\n" +"-C capture pcm\n" +"-f sample size\n" +"-c number of channels\n" +"-r sampling rate\n" +"-n frames to capture\n" +"-k sigma k\n" +"-F target frequency\n" +"-p total number of periods to play/capture\n" +" --log=# path of log file. if not set, logs be put to stdout,\n" +" and errors be put to stderr.\n" +" --file=# input file\n" +" --saveplay=# save playback content to target file, for debug\n" +" --local internal loop, bypass hardware\n"
Why there are options with and without indentation?
+), argv[0]);
The program name should be a const string.
Takashi
Hi Takashi,
-----Original Message----- From: Takashi Iwai [mailto:tiwai@suse.de] Sent: Friday, October 2, 2015 6:56 PM To: Lu, Han Cc: Girdwood, Liam R; Gautier, Bernard; caleb@crome.org; alsa-devel@alsa- project.org Subject: Re: [PATCH V2 1/7] BAT: Add initial functions
On Wed, 23 Sep 2015 09:48:49 +0200, han.lu@intel.com wrote:
From: "Lu, Han" han.lu@intel.com
Add main entrance, command line parsing, parameter initiating and thread initiating functions for BAT.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/bat.c b/bat/bat.c new file mode 100644 index 0000000..54fe1ec --- /dev/null +++ b/bat/bat.c @@ -0,0 +1,603 @@ +/*
- Copyright (C) 2013-2015 Intel Corporation
- This program is free software; you can redistribute it and/or
+modify
- it under the terms of the GNU General Public License as published
+by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <getopt.h> +#include <math.h> +#include <limits.h>
+#include "aconfig.h" +#include "gettext.h" +#include "version.h"
+#include "common.h"
+#include "alsa.h" +#include "convert.h" +#include "analyze.h"
+static int get_duration(struct bat *bat) {
- float duration_f;
- long duration_i;
- char *ptrf, *ptri;
- duration_f = strtof(bat->narg, &ptrf);
- if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF) {
fprintf(bat->err, _("duration float overflow: %f %d\n"),
duration_f, -errno);
return -errno;
- } else if (duration_f == 0.0 && errno != 0) {
fprintf(bat->err, _("duration float underflow: %f %d\n"),
duration_f, -errno);
return -errno;
- }
- duration_i = strtol(bat->narg, &ptri, 10);
- if (duration_i == LONG_MAX) {
fprintf(bat->err, _("duration long overflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- } else if (duration_i == LONG_MIN) {
fprintf(bat->err, _("duration long underflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- }
- if (*ptrf == 's') {
bat->frames = duration_f * bat->rate;
- } else if (*ptri == 0) {
bat->frames = duration_i;
- } else {
fprintf(bat->err, _("invalid duration: %s\n"), bat->narg);
return -EINVAL;
- }
- if (bat->frames <= 0 || bat->frames > MAX_FRAMES) {
fprintf(bat->err, _("duration out of range: (0, %d(%ds))\n"),
MAX_FRAMES, (bat->frames / bat->rate));
The messages strings could be unified to a single error message. Otherwise you'll annoy translators, as they will need to work on each different variant.
[Han] may I just change the strings to " duration overflow ..." " duration underflow ..." " duration overflow ..." " duration underflow ..." " invalid duration ..." " duration out of range ..." Or change the sequence and keep 2 or 3 strings like: "duration overflow/underflow ..." "invalid duration ..." "duration out of range ..." Since they would be clearer to developer/user?
return -EINVAL;
- }
- return 0;
+}
+static void get_sine_frequencies(struct bat *bat, char *freq) {
- char *tmp1;
- tmp1 = strchr(freq, ',');
- if (tmp1 == NULL) {
bat->target_freq[1] = bat->target_freq[0] = atof(optarg);
- } else {
*tmp1 = '\0';
bat->target_freq[0] = atof(optarg);
bat->target_freq[1] = atof(tmp1 + 1);
- }
Note that parsing a floting point string is tricky. For example, if you run it in a locale that handles comma as the decimal point, this gets messy. Did you check it, e.g. with LANG=en_GB or such?
[Han] OK, I'll check the implementation.
+static void usage(struct bat *bat, char *argv[]) {
- fprintf(bat->log,
+_("Usage:%s [Option]...\n" +"\n" +"-h, --help help\n" +"-D sound card\n" +"-P playback pcm\n" +"-C capture pcm\n" +"-f sample size\n" +"-c number of channels\n" +"-r sampling rate\n" +"-n frames to capture\n" +"-k sigma k\n" +"-F target frequency\n" +"-p total number of periods to play/capture\n" +" --log=# path of log file. if not set, logs be put to stdout,\n" +" and errors be put to stderr.\n" +" --file=# input file\n" +" --saveplay=# save playback content to target file, for debug\n" +" --local internal loop, bypass hardware\n"
Why there are options with and without indentation?
+), argv[0]);
The program name should be a const string.
[Han] OK. I followed the indentation style of aplay, to align with multiple-character options such as "--log=#". Shall I remove indentation for these options?
Takashi
BR, Han Lu
On Mon, 2015-10-12 at 08:56 +0000, Lu, Han wrote:
Hi Takashi,
-----Original Message----- From: Takashi Iwai [mailto:tiwai@suse.de] Sent: Friday, October 2, 2015 6:56 PM To: Lu, Han Cc: Girdwood, Liam R; Gautier, Bernard; caleb@crome.org; alsa-devel@alsa- project.org Subject: Re: [PATCH V2 1/7] BAT: Add initial functions
On Wed, 23 Sep 2015 09:48:49 +0200, han.lu@intel.com wrote:
From: "Lu, Han" han.lu@intel.com
Add main entrance, command line parsing, parameter initiating and thread initiating functions for BAT.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/bat.c b/bat/bat.c new file mode 100644 index 0000000..54fe1ec --- /dev/null +++ b/bat/bat.c @@ -0,0 +1,603 @@ +/*
- Copyright (C) 2013-2015 Intel Corporation
- This program is free software; you can redistribute it and/or
+modify
- it under the terms of the GNU General Public License as published
+by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> +#include <getopt.h> +#include <math.h> +#include <limits.h>
+#include "aconfig.h" +#include "gettext.h" +#include "version.h"
+#include "common.h"
+#include "alsa.h" +#include "convert.h" +#include "analyze.h"
+static int get_duration(struct bat *bat) {
- float duration_f;
- long duration_i;
- char *ptrf, *ptri;
- duration_f = strtof(bat->narg, &ptrf);
- if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF) {
fprintf(bat->err, _("duration float overflow: %f %d\n"),
duration_f, -errno);
return -errno;
- } else if (duration_f == 0.0 && errno != 0) {
fprintf(bat->err, _("duration float underflow: %f %d\n"),
duration_f, -errno);
return -errno;
- }
- duration_i = strtol(bat->narg, &ptri, 10);
- if (duration_i == LONG_MAX) {
fprintf(bat->err, _("duration long overflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- } else if (duration_i == LONG_MIN) {
fprintf(bat->err, _("duration long underflow: %ld %d\n"),
duration_i, -errno);
return -errno;
- }
- if (*ptrf == 's') {
bat->frames = duration_f * bat->rate;
- } else if (*ptri == 0) {
bat->frames = duration_i;
- } else {
fprintf(bat->err, _("invalid duration: %s\n"), bat->narg);
return -EINVAL;
- }
- if (bat->frames <= 0 || bat->frames > MAX_FRAMES) {
fprintf(bat->err, _("duration out of range: (0, %d(%ds))\n"),
MAX_FRAMES, (bat->frames / bat->rate));
The messages strings could be unified to a single error message. Otherwise you'll annoy translators, as they will need to work on each different variant.
[Han] may I just change the strings to " duration overflow ..." " duration underflow ..." " duration overflow ..." " duration underflow ..." " invalid duration ..." " duration out of range ..." Or change the sequence and keep 2 or 3 strings like: "duration overflow/underflow ..." "invalid duration ..." "duration out of range ..." Since they would be clearer to developer/user?
I would probably just have "duration overflow/underflow" and "invalid duration". That cuts down 6 strings to only 2. Likewise for other strings.
Remember to also include variables in the error output that will help identify the error cause (since we wont be able to determine the exact error line by string text).
return -EINVAL;
- }
- return 0;
+}
+static void get_sine_frequencies(struct bat *bat, char *freq) {
- char *tmp1;
- tmp1 = strchr(freq, ',');
- if (tmp1 == NULL) {
bat->target_freq[1] = bat->target_freq[0] = atof(optarg);
- } else {
*tmp1 = '\0';
bat->target_freq[0] = atof(optarg);
bat->target_freq[1] = atof(tmp1 + 1);
- }
Note that parsing a floting point string is tricky. For example, if you run it in a locale that handles comma as the decimal point, this gets messy. Did you check it, e.g. with LANG=en_GB or such?
[Han] OK, I'll check the implementation.
You could even try using a : or similar (instead of commas) to deliminate the input string. e.g. -values 1.2, 4.5 would become -values 1.2 : 4.5
Liam
+static void usage(struct bat *bat, char *argv[]) {
- fprintf(bat->log,
+_("Usage:%s [Option]...\n" +"\n" +"-h, --help help\n" +"-D sound card\n" +"-P playback pcm\n" +"-C capture pcm\n" +"-f sample size\n" +"-c number of channels\n" +"-r sampling rate\n" +"-n frames to capture\n" +"-k sigma k\n" +"-F target frequency\n" +"-p total number of periods to play/capture\n" +" --log=# path of log file. if not set, logs be put to stdout,\n" +" and errors be put to stderr.\n" +" --file=# input file\n" +" --saveplay=# save playback content to target file, for debug\n" +" --local internal loop, bypass hardware\n"
Why there are options with and without indentation?
+), argv[0]);
The program name should be a const string.
[Han] OK. I followed the indentation style of aplay, to align with multiple-character options such as "--log=#". Shall I remove indentation for these options?
Takashi
BR, Han Lu _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
--------------------------------------------------------------------- Intel Corporation (UK) Limited Registered No. 1134945 (England) Registered Office: Pipers Way, Swindon SN3 1RJ VAT No: 860 2173 47
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.
From: "Lu, Han" han.lu@intel.com
Add common definitions of macros and data structures; Add functions that used by multiple components, such as wav file reading and writing.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/common.c b/bat/common.c new file mode 100644 index 0000000..798b00b --- /dev/null +++ b/bat/common.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdbool.h> +#include <errno.h> + +#include "aconfig.h" +#include "gettext.h" + +#include "common.h" +#include "alsa.h" + +int retval_play; +int retval_record; + +/* update chunk_fmt data to bat */ +static int update_fmt_to_bat(struct bat *bat, struct chunk_fmt *fmt) +{ + bat->channels = fmt->channels; + bat->rate = fmt->sample_rate; + bat->sample_size = fmt->sample_length / 8; + if (bat->sample_size > 4) { + fprintf(bat->err, _("Invalid format: sample size=%d\n"), + bat->sample_size); + return -EINVAL; + } + bat->frame_size = fmt->blocks_align; + + return 0; +} + +/* calculate frames and update to bat */ +static int update_frames_to_bat(struct bat *bat, + struct wav_chunk_header *header, FILE *fp) +{ + /* The number of analyzed captured frames is arbitrarily set to half of + the number of frames of the wav file or the number of frames of the + wav file when doing direct analysis (--local) */ + bat->frames = header->length / bat->frame_size; + if (!bat->local) + bat->frames /= 2; + + return 0; +} + +static int read_chunk_fmt(struct bat *bat, char *file, FILE *fp, bool skip, + struct wav_chunk_header *header) +{ + size_t err; + int header_skip; + struct chunk_fmt chunk_fmt; + + err = fread(&chunk_fmt, sizeof(chunk_fmt), 1, fp); + if (err != 1) { + fprintf(bat->err, _("Read chunk fmt error: %s:%zd\n"), + file, err); + return -EIO; + } + /* If the format header is larger, skip the rest */ + header_skip = header->length - sizeof(chunk_fmt); + if (header_skip > 0) { + err = fseek(fp, header_skip, SEEK_CUR); + if (err == -1) { + fprintf(bat->err, _("Seek fmt header error: %s:%zd\n"), + file, err); + return -EINVAL; + } + } + /* If the file is opened for playback, update BAT data; + If the file is opened for analysis, no update */ + if (skip == false) { + err = update_fmt_to_bat(bat, &chunk_fmt); + if (err != 0) + return err; + } + + return 0; +} + +int read_wav_header(struct bat *bat, char *file, FILE *fp, bool skip) +{ + struct wav_header riff_wave_header; + struct wav_chunk_header chunk_header; + int more_chunks = 1; + size_t err; + + /* Read header of RIFF wav file */ + err = fread(&riff_wave_header, sizeof(riff_wave_header), 1, fp); + if (err != 1) { + fprintf(bat->err, _("Read header error: %s:%zd\n"), file, err); + return -EIO; + } + if ((riff_wave_header.magic != WAV_RIFF) + || (riff_wave_header.type != WAV_WAVE)) { + fprintf(bat->err, _("%s is not a riff/wave file\n"), file); + return -EINVAL; + } + + /* Read chunks in RIFF wav file */ + do { + err = fread(&chunk_header, sizeof(chunk_header), 1, fp); + if (err != 1) { + fprintf(bat->err, _("Read chunk header error: ")); + fprintf(bat->err, _("%s:%zd\n"), file, err); + return -EIO; + } + + switch (chunk_header.type) { + case WAV_FMT: + /* WAV_FMT chunk, read and analyze */ + err = read_chunk_fmt(bat, file, fp, skip, + &chunk_header); + if (err != 0) + return err; + break; + case WAV_DATA: + /* WAV_DATA chunk, break looping */ + /* If the file is opened for playback, update BAT data; + If the file is opened for analysis, no update */ + if (skip == false) { + err = update_frames_to_bat(bat, &chunk_header, + fp); + if (err != 0) + return err; + } + /* Stop looking for chunks */ + more_chunks = 0; + break; + default: + /* Unknown chunk, skip bytes */ + err = fseek(fp, chunk_header.length, SEEK_CUR); + if (err == -1) { + fprintf(bat->err, _("Fail to skip unknown")); + fprintf(bat->err, _(" chunk of %s:%zd\n"), + file, err); + return -EINVAL; + } + } + } while (more_chunks); + + return 0; +} + +void prepare_wav_info(struct wav_container *wav, struct bat *bat) +{ + wav->header.magic = WAV_RIFF; + wav->header.type = WAV_WAVE; + wav->format.magic = WAV_FMT; + wav->format.fmt_size = 16; + wav->format.format = WAV_FORMAT_PCM; + wav->format.channels = bat->channels; + wav->format.sample_rate = bat->rate; + wav->format.sample_length = bat->sample_size * 8; + wav->format.blocks_align = bat->channels * bat->sample_size; + wav->format.bytes_p_second = wav->format.blocks_align * bat->rate; + wav->chunk.length = bat->frames * bat->frame_size; + wav->chunk.type = WAV_DATA; + wav->header.length = (wav->chunk.length) + sizeof(wav->chunk) + + sizeof(wav->format) + sizeof(wav->header) - 8; +} + +int write_wav_header(FILE *fp, struct wav_container *wav, struct bat *bat) +{ + int err = 0; + + err = fwrite(&wav->header, 1, sizeof(wav->header), fp); + if (err != sizeof(wav->header)) { + fprintf(bat->err, _("Write file error: header %d\n"), err); + return -EIO; + } + err = fwrite(&wav->format, 1, sizeof(wav->format), fp); + if (err != sizeof(wav->format)) { + fprintf(bat->err, _("Write file error: format %d\n"), err); + return -EIO; + } + err = fwrite(&wav->chunk, 1, sizeof(wav->chunk), fp); + if (err != sizeof(wav->chunk)) { + fprintf(bat->err, _("Write file error: chunk %d\n"), err); + return -EIO; + } + + return 0; +} diff --git a/bat/common.h b/bat/common.h new file mode 100644 index 0000000..e6ff7f1 --- /dev/null +++ b/bat/common.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <alsa/asoundlib.h> + +#define TEMP_RECORD_FILE_NAME "/tmp/bat.wav" + +#define OPT_BASE 300 +#define OPT_LOG (OPT_BASE + 1) +#define OPT_READFILE (OPT_BASE + 2) +#define OPT_SAVEPLAY (OPT_BASE + 3) +#define OPT_LOCAL (OPT_BASE + 4) + +#define COMPOSE(a, b, c, d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) +#define WAV_RIFF COMPOSE('R', 'I', 'F', 'F') +#define WAV_WAVE COMPOSE('W', 'A', 'V', 'E') +#define WAV_FMT COMPOSE('f', 'm', 't', ' ') +#define WAV_DATA COMPOSE('d', 'a', 't', 'a') +#define WAV_FORMAT_PCM 1 /* PCM WAVE file encoding */ + +#define MAX_CHANNELS 2 +#define MIN_CHANNELS 1 +#define MAX_PEAKS 10 +#define MAX_FRAMES (10 * 1024 * 1024) +/* Given in ms */ +#define CAPTURE_DELAY 500 +/* signal frequency should be less than samplerate * RATE_FACTOR */ +#define RATE_FACTOR 0.4 +/* valid range of samplerate: (1 - RATE_RANGE, 1 + RATE_RANGE) * samplerate */ +#define RATE_RANGE 0.05 +/* Given in us */ +#define MAX_BUFFERTIME 500000 +/* devide factor, was 4, changed to 8 to remove reduce capture overrun */ +#define DIV_BUFFERTIME 8 +/* margin to avoid sign inversion when generate sine wav */ +#define RANGE_FACTOR 0.95 + +#define EBATBASE 1000 +#define ENOPEAK (EBATBASE + 1) +#define EONLYDC (EBATBASE + 2) +#define EBADPEAK (EBATBASE + 3) + +#define DC_THRESHOLD 7.01 + +/* tolerance of detected peak = max (DELTA_HZ, DELTA_RATE * target_freq). + * If DELTA_RATE is too high, BAT may not be able to recognize negative result; + * if too low, BAT may be too sensitive and results in uncecessary failure. */ +#define DELTA_RATE 0.005 +#define DELTA_HZ 1 + +#define FOUND_DC (1<<1) +#define FOUND_WRONG_PEAK (1<<0) + +struct wav_header { + unsigned int magic; /* 'RIFF' */ + unsigned int length; /* file len */ + unsigned int type; /* 'WAVE' */ +}; + +struct wav_chunk_header { + unsigned int type; /* 'data' */ + unsigned int length; /* sample count */ +}; + +struct wav_fmt { + unsigned int magic; /* 'FMT '*/ + unsigned int fmt_size; /* 16 or 18 */ + unsigned short format; /* see WAV_FMT_* */ + unsigned short channels; + unsigned int sample_rate; /* Frequency of sample */ + unsigned int bytes_p_second; + unsigned short blocks_align; /* sample size; 1 or 2 bytes */ + unsigned short sample_length; /* 8, 12 or 16 bit */ +}; + +struct chunk_fmt { + unsigned short format; /* see WAV_FMT_* */ + unsigned short channels; + unsigned int sample_rate; /* Frequency of sample */ + unsigned int bytes_p_second; + unsigned short blocks_align; /* sample size; 1 or 2 bytes */ + unsigned short sample_length; /* 8, 12 or 16 bit */ +}; + +struct wav_container { + struct wav_header header; + struct wav_fmt format; + struct wav_chunk_header chunk; +}; + +struct bat; + +enum _bat_op_mode { + MODE_UNKNOWN = -1, + MODE_SINGLE = 0, + MODE_LOOPBACK, + MODE_LAST +}; + +struct pcm { + char *device; + char *file; + enum _bat_op_mode mode; + void *(*fct)(struct bat *); +}; + +struct sin_generator; + +struct sin_generator { + double state_real; + double state_imag; + double phasor_real; + double phasor_imag; + float frequency; + float sample_rate; + float magnitude; +}; + +struct bat { + unsigned int rate; /* sampling rate */ + int channels; /* nb of channels */ + int frames; /* nb of frames */ + int frame_size; /* size of frame */ + int sample_size; /* size of sample */ + snd_pcm_format_t format; /* PCM format */ + + float sigma_k; /* threshold for peak detection */ + float target_freq[MAX_CHANNELS]; + + int sinus_duration; /* number of frames for playback */ + char *narg; /* argument string of duration */ + char *logarg; /* path name of log file */ + char *debugplay; /* path name to store playback signal */ + + struct pcm playback; + struct pcm capture; + + unsigned int periods_played; + unsigned int periods_total; + bool period_is_limited; + + FILE *fp; + + FILE *log; + FILE *err; + + void (*convert_sample_to_double)(void *, double *, int); + void (*convert_float_to_sample)(float *, void *, int, int); + + void *buf; /* PCM Buffer */ + + bool local; /* true for internal test */ +}; + +struct analyze { + void *buf; + double *in; + double *out; + double *mag; +}; + +void prepare_wav_info(struct wav_container *, struct bat *); +int read_wav_header(struct bat *, char *, FILE *, bool); +int write_wav_header(FILE *, struct wav_container *, struct bat *);
On Wed, 23 Sep 2015 09:48:50 +0200, han.lu@intel.com wrote:
+#define TEMP_RECORD_FILE_NAME "/tmp/bat.wav"
Using a fixed temporary file path on /tmp is bad in security. You'd need to create a temp file dynamically, or use only the passed file name by user.
Takashi
From: "Lu, Han" han.lu@intel.com
Add functions as main loop of playback thread and record thread. The functions access pcm hardware through ALSA APIs.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/alsa.c b/bat/alsa.c new file mode 100644 index 0000000..582c604 --- /dev/null +++ b/bat/alsa.c @@ -0,0 +1,594 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <math.h> +#include <stdint.h> +#include <pthread.h> + +#include <alsa/asoundlib.h> + +#include "aconfig.h" +#include "gettext.h" + +#include "common.h" +#include "alsa.h" +#include "signal.h" + +struct pcm_container { + snd_pcm_t *handle; + snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_size; + snd_pcm_format_t format; + unsigned short channels; + size_t period_bytes; + size_t sample_bits; + size_t frame_bits; + char *buffer; +}; + +static int set_snd_pcm_params(struct bat *bat, struct pcm_container *sndpcm) +{ + snd_pcm_hw_params_t *params; + unsigned int buffer_time = 0; + unsigned int period_time = 0; + unsigned int rate; + int err; + const char *device_name = snd_pcm_name(sndpcm->handle); + + /* Allocate a hardware parameters object. */ + snd_pcm_hw_params_alloca(¶ms); + + /* Fill it in with default values. */ + err = snd_pcm_hw_params_any(sndpcm->handle, params); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("default params: %s: %s(%d)\n"), + device_name, snd_strerror(err), err); + return err; + } + + /* Set access mode */ + err = snd_pcm_hw_params_set_access(sndpcm->handle, params, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("access type: %s: %s(%d)\n"), + device_name, snd_strerror(err), err); + return err; + } + + /* Set format */ + err = snd_pcm_hw_params_set_format(sndpcm->handle, params, bat->format); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("PCM format: %d %s: %s(%d)\n"), + bat->format, + device_name, snd_strerror(err), err); + return err; + } + + /* Set channels */ + err = snd_pcm_hw_params_set_channels(sndpcm->handle, + params, bat->channels); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("channel number: %d %s: %s(%d)\n"), + bat->channels, + device_name, snd_strerror(err), err); + return err; + } + + /* Set sampling rate */ + rate = bat->rate; + err = snd_pcm_hw_params_set_rate_near(sndpcm->handle, + params, &bat->rate, + 0); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("sample rate: %d %s: %s(%d)\n"), + bat->rate, + device_name, snd_strerror(err), err); + return err; + } + if ((float) rate * (1 + RATE_RANGE) < bat->rate + || (float) rate * (1 - RATE_RANGE) > bat->rate) { + fprintf(bat->err, _("Invalid parameters: sample rate: ")); + fprintf(bat->err, _("requested %dHz, got %dHz\n"), + rate, bat->rate); + return -EINVAL; + } + + if (snd_pcm_hw_params_get_buffer_time_max(params, + &buffer_time, 0) < 0) { + fprintf(bat->err, _("Get parameter from device error: ")); + fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"), + buffer_time, + device_name, snd_strerror(err), err); + return -EINVAL; + } + + if (buffer_time > MAX_BUFFERTIME) + buffer_time = MAX_BUFFERTIME; + + period_time = buffer_time / DIV_BUFFERTIME; + + /* Set buffer time and period time */ + err = snd_pcm_hw_params_set_buffer_time_near(sndpcm->handle, params, + &buffer_time, 0); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("buffer time: %d %s: %s(%d)\n"), + buffer_time, + device_name, snd_strerror(err), err); + return err; + } + + err = snd_pcm_hw_params_set_period_time_near(sndpcm->handle, params, + &period_time, 0); + if (err < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("period time: %d %s: %s(%d)\n"), + period_time, + device_name, snd_strerror(err), err); + return err; + } + + /* Write the parameters to the driver */ + if (snd_pcm_hw_params(sndpcm->handle, params) < 0) { + fprintf(bat->err, _("Set parameter to device error: ")); + fprintf(bat->err, _("hw params: %s: %s(%d)\n"), + device_name, snd_strerror(err), err); + return -EINVAL; + } + + err = snd_pcm_hw_params_get_period_size(params, + &sndpcm->period_size, 0); + if (err < 0) { + fprintf(bat->err, _("Get parameter from device error: ")); + fprintf(bat->err, _("period size: %zd %s: %s(%d)\n"), + sndpcm->period_size, + device_name, snd_strerror(err), err); + return err; + } + + err = snd_pcm_hw_params_get_buffer_size(params, &sndpcm->buffer_size); + if (err < 0) { + fprintf(bat->err, _("Get parameter from device error: ")); + fprintf(bat->err, _("buffer size: %zd %s: %s(%d)\n"), + sndpcm->buffer_size, + device_name, snd_strerror(err), err); + return err; + } + + if (sndpcm->period_size == sndpcm->buffer_size) { + fprintf(bat->err, _("Invalid parameters: can't use period ")); + fprintf(bat->err, _("equal to buffer size (%zd)\n"), + sndpcm->period_size); + return -EINVAL; + } + + err = snd_pcm_format_physical_width(bat->format); + if (err < 0) { + fprintf(bat->err, _("Invalid parameters: ")); + fprintf(bat->err, _("snd_pcm_format_physical_width: %d\n"), + err); + return err; + } + sndpcm->sample_bits = err; + + sndpcm->frame_bits = sndpcm->sample_bits * bat->channels; + + /* Calculate the period bytes */ + sndpcm->period_bytes = sndpcm->period_size * sndpcm->frame_bits / 8; + sndpcm->buffer = (char *) malloc(sndpcm->period_bytes); + if (sndpcm->buffer == NULL) { + fprintf(bat->err, _("Not enough memory: size=%zd\n"), + sndpcm->period_bytes); + return -ENOMEM; + } + + return 0; +} + +/* + * Generate buffer to be played either from input file or from generated data + * Return value + * <0 error + * 0 ok + * >0 break + */ +static int generate_input_data(struct pcm_container *sndpcm, int bytes, + struct bat *bat) +{ + int err; + static int load; + int frames = bytes * 8 / sndpcm->frame_bits; + + if (bat->playback.file != NULL) { + /* From input file */ + load = 0; + + while (1) { + err = fread(sndpcm->buffer + load, 1, + bytes - load, bat->fp); + if (0 == err) { + if (feof(bat->fp)) { + fprintf(bat->log, + _("End of playing.\n")); + return 1; + } + } else if (err < bytes - load) { + if (ferror(bat->fp)) { + fprintf(bat->err, _("Read file error")); + fprintf(bat->err, _(": %d\n"), err); + return -EIO; + } + load += err; + } else { + break; + } + } + } else { + /* Generate sine wave */ + if ((bat->sinus_duration) && (load > bat->sinus_duration)) + return 1; + + err = generate_sine_wave(bat, frames, (void *)sndpcm->buffer); + if (err != 0) + return err; + + load += frames; + } + + return 0; +} + +static int write_to_pcm(const struct pcm_container *sndpcm, + int frames, struct bat *bat) +{ + int err; + int offset = 0; + int remain = frames; + + while (remain > 0) { + err = snd_pcm_writei(sndpcm->handle, sndpcm->buffer + offset, + remain); + if (err == -EAGAIN || (err >= 0 && err < frames)) { + snd_pcm_wait(sndpcm->handle, 500); + } else if (err == -EPIPE) { + fprintf(bat->err, _("Underrun: %s(%d)\n"), + snd_strerror(err), err); + snd_pcm_prepare(sndpcm->handle); + } else if (err < 0) { + fprintf(bat->err, _("Write PCM device error: %s(%d)\n"), + snd_strerror(err), err); + return err; + } + + if (err > 0) { + remain -= err; + offset += err * sndpcm->frame_bits / 8; + } + } + + return 0; +} + +static int write_to_pcm_loop(struct pcm_container *sndpcm, struct bat *bat) +{ + int err; + int bytes = sndpcm->period_bytes; /* playback buffer size */ + int frames = bytes * 8 / sndpcm->frame_bits; /* frame count */ + FILE *fp = NULL; + struct wav_container wav; + int bytes_total = 0; + + if (bat->debugplay) { + fp = fopen(bat->debugplay, "wb"); + if (fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: ")); + fprintf(bat->err, _("%s %d\n"), bat->debugplay, -errno); + return -errno; + } + /* leave space for wav header */ + err = fseek(fp, sizeof(wav), SEEK_SET); + if (err != 0) { + fprintf(bat->err, _("Seek file error: %d %d\n"), + err, -errno); + return -errno; + } + } + + while (1) { + err = generate_input_data(sndpcm, bytes, bat); + if (err < 0) + return err; + else if (err > 0) + break; + + if (bat->debugplay) { + err = fwrite(sndpcm->buffer, 1, bytes, fp); + if (err != bytes) { + fprintf(bat->err, _("Write file error: ")); + fprintf(bat->err, _("%s(%d)\n"), + snd_strerror(err), err); + return -EIO; + } + bytes_total += bytes; + } + + bat->periods_played++; + if (bat->period_is_limited + && bat->periods_played >= bat->periods_total) + break; + + err = write_to_pcm(sndpcm, frames, bat); + if (err != 0) + return err; + } + + if (bat->debugplay) { + /* update wav header */ + prepare_wav_info(&wav, bat); + wav.chunk.length = bytes_total; + wav.header.length = (wav.chunk.length) + sizeof(wav.chunk) + + sizeof(wav.format) + sizeof(wav.header) - 8; + + rewind(fp); + err = write_wav_header(fp, &wav, bat); + if (err != 0) { + fprintf(bat->err, _("Write file error: %s %s(%d)\n"), + bat->debugplay, snd_strerror(err), err); + return err; + } + fclose(fp); + } + + snd_pcm_drain(sndpcm->handle); + + return 0; +} + +/** + * Play + */ +void *playback_alsa(struct bat *bat) +{ + int err = 0; + struct pcm_container sndpcm; + + fprintf(bat->log, _("Entering playback thread (ALSA).\n")); + + retval_play = 0; + memset(&sndpcm, 0, sizeof(sndpcm)); + + if (bat->playback.device == NULL) { + fprintf(bat->err, _("No PCM device for playback: exit\n")); + retval_play = 1; + goto exit1; + } + + err = snd_pcm_open(&sndpcm.handle, bat->playback.device, + SND_PCM_STREAM_PLAYBACK, 0); + if (err != 0) { + fprintf(bat->err, _("Cannot open PCM playback device: ")); + fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err); + retval_play = 1; + goto exit1; + } + + err = set_snd_pcm_params(bat, &sndpcm); + if (err != 0) { + retval_play = 1; + goto exit2; + } + + if (bat->playback.file == NULL) { + fprintf(bat->log, _("Playing generated audio sine wave")); + bat->sinus_duration == 0 ? + fprintf(bat->log, _(" endlessly\n")) : + fprintf(bat->log, _("\n")); + } else { + fprintf(bat->log, _("Playing input audio file: %s\n"), + bat->playback.file); + bat->fp = fopen(bat->playback.file, "rb"); + if (bat->fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: ")); + fprintf(bat->err, _("%s %d\n"), + bat->playback.file, -errno); + retval_play = 1; + goto exit3; + } + /* Skip header */ + err = read_wav_header(bat, bat->playback.file, bat->fp, true); + if (err != 0) { + retval_play = 1; + goto exit4; + } + } + + err = write_to_pcm_loop(&sndpcm, bat); + if (err != 0) { + retval_play = 1; + goto exit4; + } + +exit4: + if (bat->playback.file) + fclose(bat->fp); +exit3: + free(sndpcm.buffer); +exit2: + snd_pcm_close(sndpcm.handle); +exit1: + pthread_exit(&retval_play); +} + +static int read_from_pcm(struct pcm_container *sndpcm, + int frames, struct bat *bat) +{ + int err = 0; + int offset = 0; + int remain = frames; + + while (remain > 0) { + err = snd_pcm_readi(sndpcm->handle, + sndpcm->buffer + offset, remain); + if (err == -EAGAIN || (err >= 0 && err < remain)) { + snd_pcm_wait(sndpcm->handle, 500); + } else if (err == -EPIPE) { + snd_pcm_prepare(sndpcm->handle); + fprintf(bat->err, _("Overrun: %s(%d)\n"), + snd_strerror(err), err); + } else if (err < 0) { + fprintf(bat->err, _("Read PCM device error: %s(%d)\n"), + snd_strerror(err), err); + return err; + } + + if (err > 0) { + remain -= err; + offset += err * sndpcm->frame_bits / 8; + } + } + + return 0; +} + +static int read_from_pcm_loop(FILE *fp, int count, + struct pcm_container *sndpcm, struct bat *bat) +{ + int err = 0; + int size, frames; + int remain = count; + + while (remain > 0) { + size = (remain <= sndpcm->period_bytes) ? + remain : sndpcm->period_bytes; + frames = size * 8 / sndpcm->frame_bits; + + /* read a chunk from pcm device */ + err = read_from_pcm(sndpcm, frames, bat); + if (err != 0) + return err; + + /* write the chunk to file */ + err = fwrite(sndpcm->buffer, 1, size, fp); + if (err != size) { + fprintf(bat->err, _("Write file error: %s(%d)\n"), + snd_strerror(err), err); + return -EIO; + } + remain -= size; + bat->periods_played++; + + if (bat->period_is_limited + && bat->periods_played >= bat->periods_total) + break; + } + + return 0; +} + +/** + * Record + */ +void *record_alsa(struct bat *bat) +{ + int err = 0; + FILE *fp = NULL; + struct pcm_container sndpcm; + struct wav_container wav; + int count; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + fprintf(bat->log, _("Entering capture thread (ALSA).\n")); + + retval_record = 0; + memset(&sndpcm, 0, sizeof(sndpcm)); + + if (bat->capture.device == NULL) { + fprintf(bat->err, _("No PCM device for capture: exit\n")); + retval_record = 1; + goto exit1; + } + + err = snd_pcm_open(&sndpcm.handle, bat->capture.device, + SND_PCM_STREAM_CAPTURE, 0); + if (err != 0) { + fprintf(bat->err, _("Cannot open PCM capture device: ")); + fprintf(bat->err, _("%s(%d)\n"), snd_strerror(err), err); + retval_record = 1; + goto exit1; + } + + err = set_snd_pcm_params(bat, &sndpcm); + if (err != 0) { + retval_record = 1; + goto exit2; + } + + remove(bat->capture.file); + fp = fopen(bat->capture.file, "w+"); + if (fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: %s %d\n"), + bat->capture.file, -errno); + retval_record = 1; + goto exit3; + } + + prepare_wav_info(&wav, bat); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + pthread_cleanup_push(snd_pcm_close, sndpcm.handle); + pthread_cleanup_push(free, sndpcm.buffer); + pthread_cleanup_push(fclose, fp); + + err = write_wav_header(fp, &wav, bat); + if (err != 0) { + retval_record = 1; + goto exit4; + } + + count = wav.chunk.length; + fprintf(bat->log, _("Recording ...\n")); + err = read_from_pcm_loop(fp, count, &sndpcm, bat); + if (err != 0) { + retval_record = 1; + goto exit4; + } + + /* Normally we will never reach this part of code (before fail_exit) as + this thread will be cancelled by end of play thread. */ + pthread_cleanup_pop(0); + pthread_cleanup_pop(0); + pthread_cleanup_pop(0); + + snd_pcm_drain(sndpcm.handle); + +exit4: + fclose(fp); +exit3: + free(sndpcm.buffer); +exit2: + snd_pcm_close(sndpcm.handle); +exit1: + pthread_exit(&retval_record); +} diff --git a/bat/alsa.h b/bat/alsa.h new file mode 100644 index 0000000..d5c9972 --- /dev/null +++ b/bat/alsa.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +extern int retval_play; +extern int retval_record; + +void *playback_alsa(struct bat *); +void *record_alsa(struct bat *);
From: "Lu, Han" han.lu@intel.com
Add function that generates sine waveform through math lib. The waveform can be used as source for playback or analysis.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com Signed-off-by: Caleb Crome caleb@crome.org
diff --git a/bat/signal.c b/bat/signal.c new file mode 100644 index 0000000..595f76b --- /dev/null +++ b/bat/signal.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2015 Caleb Crome + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * This is a general purpose sine wave generator that will stay stable + * for a long time, and with a little renormalization, could stay stay + * stable indefinitely + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <math.h> +#include <stdint.h> +#include <stdbool.h> + +#include "gettext.h" +#include "common.h" +#include "signal.h" + +/* + * Initialize the sine wave generator. + * sin_generator: gets initialized by this call. + * frequency: the frequency for the sine wave. must be < 0.5*sample_rate + * sample_rate: the sample rate... + * returns 0 on success, -1 on error. + */ +int sin_generator_init(struct sin_generator *sg, float magnitude, + float frequency, float sample_rate) +{ + /* angular frequency: cycles/sec / (samp/sec) * rad/cycle = rad/samp */ + float w = frequency / sample_rate * 2 * M_PI; + if (frequency >= sample_rate / 2) + return -1; + sg->phasor_real = cos(w); + sg->phasor_imag = sin(w); + sg->magnitude = magnitude; + sg->state_real = 0.0; + sg->state_imag = magnitude; + sg->frequency = frequency; + sg->sample_rate = sample_rate; + return 0; +} + +/* + * Generates the next sample in the sine wave. + * should be much faster than calling a sin function + * if it's inlined and optimized. + * + * returns the next value. no possibility of error. + */ +float sin_generator_next_sample(struct sin_generator *sg) +{ + /* get shorthand to pointers */ + const double pr = sg->phasor_real; + const double pi = sg->phasor_imag; + const double sr = sg->state_real; + const double si = sg->state_imag; + /* step the phasor -- complex multiply */ + sg->state_real = sr * pr - si * pi; + sg->state_imag = sr * pi + pr * si; + /* return the input value so sine wave starts at exactly 0.0 */ + return sr; +} + +/* fills a vector with a sine wave */ +void sin_generator_vfill(struct sin_generator *sg, float *buf, int n) +{ + int i; + for (i = 0; i < n; i++) + *buf++ = sin_generator_next_sample(sg); +} + +static int reorder(struct bat *bat, float *val, int frames) +{ + float *new_buf = NULL; + int i, c, bytes; + + bytes = frames * bat->channels * sizeof(float); + + new_buf = (float *) malloc(bytes); + if (new_buf == NULL) { + fprintf(bat->err, _("Not enough memory.\n")); + return -ENOMEM; + } + + memcpy(new_buf, val, bytes); + for (i = 0; i < frames; i++) + for (c = 0; c < bat->channels; c++) + val[i * bat->channels + c] = + new_buf[c * frames + i]; + free(new_buf); + + return 0; +} + +static int adjust_waveform(struct bat *bat, float *val, int frames) +{ + int i, nsamples, max; + float factor, offset = 0.0; + + switch (bat->format) { + case SND_PCM_FORMAT_U8: + max = INT8_MAX; + offset = max; /* shift for unsigned format */ + break; + case SND_PCM_FORMAT_S16_LE: + max = INT16_MAX; + break; + case SND_PCM_FORMAT_S24_3LE: + max = (1 << 23) - 1; + break; + case SND_PCM_FORMAT_S32_LE: + max = INT32_MAX; + break; + default: + fprintf(bat->err, _("Invalid PCM format: %s\n"), + snd_pcm_format_name(bat->format)); + return -EINVAL; + } + + factor = max * RANGE_FACTOR; + nsamples = bat->channels * frames; + + for (i = 0; i < nsamples; i++) + val[i] = val[i] * factor + offset; + + return 0; +} + +int generate_sine_wave(struct bat *bat, int frames, void *buf) +{ + int err = 0; + int c, nsamples; + float *sinus_f = NULL; + static struct sin_generator sg[MAX_CHANNELS]; + + nsamples = bat->channels * frames; + sinus_f = (float *) malloc(nsamples * sizeof(float)); + if (sinus_f == NULL) { + fprintf(bat->err, _("Not enough memory.\n")); + return -ENOMEM; + } + + for (c = 0; c < bat->channels; c++) { + /* initialize static struct at the first time */ + if (sg[c].frequency != bat->target_freq[c]) + sin_generator_init(&sg[c], 1.0, bat->target_freq[c], + bat->rate); + /* fill buffer for each channel */ + sin_generator_vfill(&sg[c], sinus_f + c * frames, frames); + } + + /* reorder samples to interleaved mode */ + err = reorder(bat, sinus_f, frames); + if (err != 0) + return err; + + /* adjust amplitude and offset of waveform */ + err = adjust_waveform(bat, sinus_f, frames); + if (err != 0) + return err; + + bat->convert_float_to_sample(sinus_f, buf, frames, bat->channels); + + free(sinus_f); + + return 0; +} diff --git a/bat/signal.h b/bat/signal.h new file mode 100644 index 0000000..a295517 --- /dev/null +++ b/bat/signal.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Caleb Crome + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Here's a generic sine wave generator that will work indefinitely + * for any frequency. + * + * Note: the state & phasor are stored as doubles (and updated as + * doubles) because after a million samples the magnitude drifts a + * bit. If we really need floats, it can be done with periodic + * renormalization of the state_real+state_imag magnitudes. + */ + +int sin_generator_init(struct sin_generator *, float, float, float); +float sin_generator_next_sample(struct sin_generator *); +void sin_generator_vfill(struct sin_generator *, float *, int); +int generate_sine_wave(struct bat *, int, void *);
From: "Lu, Han" han.lu@intel.com
Add functions that converting audio samples to double data for analysis, and functions that converting float data to audio samples for playback.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/convert.c b/bat/convert.c new file mode 100644 index 0000000..dcbe912 --- /dev/null +++ b/bat/convert.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> + +void convert_uint8_to_double(void *buf, double *val, int samples) +{ + int i; + + for (i = 0; i < samples; i++) + val[i] = ((uint8_t *) buf)[i]; +} + +void convert_int16_to_double(void *buf, double *val, int samples) +{ + int i; + + for (i = 0; i < samples; i++) + val[i] = ((int16_t *) buf)[i]; +} + +void convert_int24_to_double(void *buf, double *val, int samples) +{ + int i; + int32_t tmp; + + for (i = 0; i < samples; i++) { + tmp = ((uint8_t *) buf)[i * 3 + 2] << 24; + tmp |= ((uint8_t *) buf)[i * 3 + 1] << 16; + tmp |= ((uint8_t *) buf)[i * 3] << 8; + tmp >>= 8; + val[i] = tmp; + } +} + +void convert_int32_to_double(void *buf, double *val, int samples) +{ + int i; + + for (i = 0; i < samples; i++) + val[i] = ((int32_t *) buf)[i]; +} + +void convert_float_to_uint8(float *val, void *buf, int samples, int channels) +{ + int i, c, idx; + + for (i = 0; i < samples; i++) { + for (c = 0; c < channels; c++) { + idx = i * channels + c; + ((uint8_t *) buf)[idx] = (uint8_t) val[idx]; + } + } +} + +void convert_float_to_int16(float *val, void *buf, int samples, int channels) +{ + int i, c, idx; + + for (i = 0; i < samples; i++) { + for (c = 0; c < channels; c++) { + idx = i * channels + c; + ((int16_t *) buf)[idx] = (int16_t) val[idx]; + } + } +} + +void convert_float_to_int24(float *val, void *buf, int samples, int channels) +{ + int i, c, idx_f, idx_i; + int32_t val_f_i; + + for (i = 0; i < samples; i++) { + for (c = 0; c < channels; c++) { + idx_f = i * channels + c; + idx_i = 3 * idx_f; + val_f_i = (int32_t) val[idx_f]; + ((int8_t *) buf)[idx_i + 0] = + (int8_t) (val_f_i & 0xff); + ((int8_t *) buf)[idx_i + 1] = + (int8_t) ((val_f_i >> 8) & 0xff); + ((int8_t *) buf)[idx_i + 2] = + (int8_t) ((val_f_i >> 16) & 0xff); + } + } +} + +void convert_float_to_int32(float *val, void *buf, int samples, int channels) +{ + int i, c, idx; + + for (i = 0; i < samples; i++) { + for (c = 0; c < channels; c++) { + idx = i * channels + c; + ((int32_t *) buf)[idx] = (int32_t) val[idx]; + } + } +} diff --git a/bat/convert.h b/bat/convert.h new file mode 100644 index 0000000..28828ba --- /dev/null +++ b/bat/convert.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +void convert_uint8_to_double(void *, double *, int); +void convert_int16_to_double(void *, double *, int); +void convert_int24_to_double(void *, double *, int); +void convert_int32_to_double(void *, double *, int); +void convert_float_to_uint8(float *, void *, int, int); +void convert_float_to_int16(float *, void *, int, int); +void convert_float_to_int24(float *, void *, int, int); +void convert_float_to_int32(float *, void *, int, int);
From: "Lu, Han" han.lu@intel.com
Add functions that detecting signal frequency through spectrum analyzing.
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/bat/analyze.c b/bat/analyze.c new file mode 100644 index 0000000..60e2d1c --- /dev/null +++ b/bat/analyze.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> + +#include <math.h> +#include <fftw3.h> + +#include "aconfig.h" +#include "gettext.h" + +#include "common.h" + +static void check_amplitude(struct bat *bat, double *buf) +{ + double sum, average, amplitude; + int i, percent; + + /* calculate average value */ + for (i = 0, sum = 0.0; i < bat->frames; i++) + sum += buf[i]; + average = sum / bat->frames; + + /* calculate peak-to-average amplitude */ + for (i = 0, sum = 0.0; i < bat->frames; i++) + sum += abs(buf[i] - average); + amplitude = sum / bat->frames * M_PI / 2.0; + + /* calculate amplitude percentage against full range */ + percent = amplitude * 100 / ((1 << ((bat->sample_size << 3) - 1)) - 1); + + fprintf(bat->log, _("Amplitude: %.1f; Percentage: [%d]\n"), + amplitude, percent); + if (percent < 0) + fprintf(bat->err, _("ERROR: Amplitude can't be negative!\n")); + else if (percent < 1) + fprintf(bat->err, _("WARNING: Signal too weak!\n")); + else if (percent > 100) + fprintf(bat->err, _("WARNING: Signal overflow!\n")); +} + +/** + * + * @return 0 if peak detected at right frequency, + * 1 if peak detected somewhere else + * 2 if DC detected + */ +int check_peak(struct bat *bat, struct analyze *a, int end, int peak, float hz, + float mean, float p, int channel, int start) +{ + int err; + float hz_peak = (float) (peak) * hz; + float delta_rate = DELTA_RATE * bat->target_freq[channel]; + float delta_HZ = DELTA_HZ; + float tolerance = (delta_rate > delta_HZ) ? delta_rate : delta_HZ; + + fprintf(bat->log, _("Detected peak at %2.2f Hz of %2.2f dB\n"), hz_peak, + 10.0 * log10(a->mag[peak] / mean)); + fprintf(bat->log, _(" Total %3.1f dB from %2.2f to %2.2f Hz\n"), + 10.0 * log10(p / mean), start * hz, end * hz); + + if (hz_peak < DC_THRESHOLD) { + fprintf(bat->err, _(" WARNING: Found low peak %2.2f Hz,"), + hz_peak); + fprintf(bat->err, _(" very close to DC\n")); + err = FOUND_DC; + } else if (hz_peak < bat->target_freq[channel] - tolerance) { + fprintf(bat->err, _(" FAIL: Peak freq too low %2.2f Hz\n"), + hz_peak); + err = FOUND_WRONG_PEAK; + } else if (hz_peak > bat->target_freq[channel] + tolerance) { + fprintf(bat->err, _(" FAIL: Peak freq too high %2.2f Hz\n"), + hz_peak); + err = FOUND_WRONG_PEAK; + } else { + fprintf(bat->log, _(" PASS: Peak detected")); + fprintf(bat->log, _(" at target frequency\n")); + err = 0; + } + + return err; +} + +/** + * Search for main frequencies in fft results and compare it to target + */ +static int check(struct bat *bat, struct analyze *a, int channel) +{ + float hz = 1.0 / ((float) bat->frames / (float) bat->rate); + float mean = 0.0, t, sigma = 0.0, p = 0.0; + int i, start = -1, end = -1, peak = 0, signals = 0; + int err = 0, N = bat->frames / 2; + + /* calculate mean */ + for (i = 0; i < N; i++) + mean += a->mag[i]; + mean /= (float) N; + + /* calculate standard deviation */ + for (i = 0; i < N; i++) { + t = a->mag[i] - mean; + t *= t; + sigma += t; + } + sigma /= (float) N; + sigma = sqrtf(sigma); + + /* clip any data less than k sigma + mean */ + for (i = 0; i < N; i++) { + if (a->mag[i] > mean + bat->sigma_k * sigma) { + + /* find peak start points */ + if (start == -1) { + start = peak = end = i; + signals++; + } else { + if (a->mag[i] > a->mag[peak]) + peak = i; + end = i; + } + p += a->mag[i]; + } else if (start != -1) { + /* Check if peak is as expected */ + err |= check_peak(bat, a, end, peak, hz, mean, + p, channel, start); + end = start = -1; + if (signals == MAX_PEAKS) + break; + } + } + if (signals == 0) + err = -ENOPEAK; /* No peak detected */ + else if ((err == FOUND_DC) && (signals == 1)) + err = -EONLYDC; /* Only DC detected */ + else if ((err & FOUND_WRONG_PEAK) == FOUND_WRONG_PEAK) + err = -EBADPEAK; /* Bad peak detected */ + else + err = 0; /* Correct peak detected */ + + fprintf(bat->log, _("Detected at least %d signal(s) in total\n"), + signals); + + return err; +} + +static void calc_magnitude(struct bat *bat, struct analyze *a, int N) +{ + double r2, i2; + int i; + + for (i = 1; i < N / 2; i++) { + r2 = a->out[i] * a->out[i]; + i2 = a->out[N - i] * a->out[N - i]; + + a->mag[i] = sqrtf(r2 + i2); + } + a->mag[0] = 0.0; +} + +static int find_and_check_harmonics(struct bat *bat, struct analyze *a, + int channel) +{ + fftw_plan p; + int err = -ENOMEM, N = bat->frames; + + /* Allocate FFT buffers */ + a->in = (double *) fftw_malloc(sizeof(double) * bat->frames); + if (a->in == NULL) + goto out1; + + a->out = (double *) fftw_malloc(sizeof(double) * bat->frames); + if (a->out == NULL) + goto out2; + + a->mag = (double *) fftw_malloc(sizeof(double) * bat->frames); + if (a->mag == NULL) + goto out3; + + /* create FFT plan */ + p = fftw_plan_r2r_1d(N, a->in, a->out, FFTW_R2HC, + FFTW_MEASURE | FFTW_PRESERVE_INPUT); + if (p == NULL) + goto out4; + + /* convert source PCM to doubles */ + bat->convert_sample_to_double(a->buf, a->in, bat->frames); + + /* check amplitude */ + check_amplitude(bat, a->in); + + /* run FFT */ + fftw_execute(p); + + /* FFT out is real and imaginary numbers - calc magnitude for each */ + calc_magnitude(bat, a, N); + + /* check data */ + err = check(bat, a, channel); + + fftw_destroy_plan(p); + +out4: + fftw_free(a->mag); +out3: + fftw_free(a->out); +out2: + fftw_free(a->in); +out1: + return err; +} + +/** + * Convert interleaved samples from channels in samples from a single channel + */ +static int reorder_data(struct bat *bat) +{ + char *p, *new_bat_buf; + int ch, i, j; + + if (bat->channels == 1) + return 0; /* No need for reordering */ + + p = malloc(bat->frames * bat->frame_size); + new_bat_buf = p; + if (p == NULL) + return -ENOMEM; + + for (ch = 0; ch < bat->channels; ch++) { + for (j = 0; j < bat->frames; j++) { + for (i = 0; i < bat->sample_size; i++) { + *p++ = ((char *) (bat->buf))[j * bat->frame_size + + ch * bat->sample_size + i]; + } + } + } + + free(bat->buf); + bat->buf = new_bat_buf; + + return 0; +} + +int analyze_capture(struct bat *bat) +{ + int err = 0; + size_t items; + int c; + struct analyze a; + + fprintf(bat->log, _("\nBAT analysis: signal has %d frames at %d Hz,"), + bat->frames, bat->rate); + fprintf(bat->log, _(" %d channels, %d bytes per sample.\n"), + bat->channels, bat->sample_size); + + bat->buf = malloc(bat->frames * bat->frame_size); + if (bat->buf == NULL) + return -ENOMEM; + + bat->fp = fopen(bat->capture.file, "rb"); + if (bat->fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: %s %d\n"), + bat->capture.file, -errno); + err = -errno; + goto exit1; + } + + /* Skip header */ + err = read_wav_header(bat, bat->capture.file, bat->fp, true); + if (err != 0) + goto exit2; + + items = fread(bat->buf, bat->frame_size, bat->frames, bat->fp); + if (items != bat->frames) { + err = -EIO; + goto exit2; + } + + err = reorder_data(bat); + if (err != 0) + goto exit2; + + for (c = 0; c < bat->channels; c++) { + fprintf(bat->log, _("\nChannel %i - "), c + 1); + fprintf(bat->log, _("Checking for target frequency %2.2f Hz\n"), + bat->target_freq[c]); + a.buf = bat->buf + + c * bat->frames * bat->frame_size + / bat->channels; + err = find_and_check_harmonics(bat, &a, c); + } + +exit2: + fclose(bat->fp); +exit1: + free(bat->buf); + + return err; +} diff --git a/bat/analyze.h b/bat/analyze.h new file mode 100644 index 0000000..3fd03d4 --- /dev/null +++ b/bat/analyze.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +int analyze_capture(struct bat *);
From: "Lu, Han" han.lu@intel.com
Add Makefile and configures that enable BAT on alsa-utils
Signed-off-by: Lu, Han han.lu@intel.com Signed-off-by: Liam Girdwood liam.r.girdwood@intel.com Signed-off-by: Bernard Gautier bernard.gautier@intel.com
diff --git a/Makefile.am b/Makefile.am index 613f62d..3d24b87 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,9 @@ SUBDIRS += aplay iecset speaker-test if ALSALOOP SUBDIRS += alsaloop endif +if BAT +SUBDIRS += bat +endif endif if HAVE_SEQ SUBDIRS += seq diff --git a/bat/Makefile.am b/bat/Makefile.am new file mode 100644 index 0000000..0849d0d --- /dev/null +++ b/bat/Makefile.am @@ -0,0 +1,14 @@ +bin_PROGRAMS = bat + +bat_SOURCES = \ + bat.c \ + common.c \ + analyze.c \ + signal.c \ + convert.c \ + alsa.c + +AM_CPPFLAGS = \ + -Wall -I$(top_srcdir)/include + +bat_LDADD = -lasound diff --git a/configure.ac b/configure.ac index 4c279a9..8c2d1a5 100644 --- a/configure.ac +++ b/configure.ac @@ -63,6 +63,23 @@ AM_CONDITIONAL(HAVE_UCM, test "$have_ucm" = "yes") AM_CONDITIONAL(HAVE_TOPOLOGY, test "$have_topology" = "yes") AM_CONDITIONAL(HAVE_SAMPLERATE, test "$have_samplerate" = "yes")
+AC_CHECK_LIB([fftw3], [fftw_malloc], , [AC_MSG_ERROR([Error: need FFTW3 library])]) +AC_CHECK_LIB([m], [sqrtf], , [AC_MSG_ERROR([Error: Need sqrtf])]) +AC_CHECK_LIB([pthread], [pthread_create], , [AC_MSG_ERROR([Error: need PTHREAD library])]) + +dnl Disable bat +bat= +if test "$have_pcm" = "yes"; then +AC_ARG_ENABLE(bat, + AS_HELP_STRING([--disable-bat], [Disable bat compilation]), + [case "${enableval}" in + yes) bat=true ;; + no) bat=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-bat) ;; + esac],[bat=true]) +fi +AM_CONDITIONAL(BAT, test x$bat = xtrue) + dnl Check for librt LIBRT="" AC_MSG_CHECKING(for librt) @@ -361,7 +378,7 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \ m4/Makefile po/Makefile.in \ alsaconf/alsaconf alsaconf/Makefile \ alsaconf/po/Makefile \ - alsaucm/Makefile topology/Makefile \ + alsaucm/Makefile topology/Makefile bat/Makefile \ aplay/Makefile include/Makefile iecset/Makefile utils/Makefile \ utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \ seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \
On Wed, 23 Sep 2015 09:48:48 +0200, han.lu@intel.com wrote:
From: "Lu, Han" han.lu@intel.com
BAT (Basic Audio Tester) is a simple command line utility intended to automate audio driver and sound server QA testing with no human interaction.
BAT works by playing an audio stream and capturing the same stream in either a digital or analog loop back. It then compares the captured stream to the original to determine if the test case passes or fails.
The main idea of frequency detecting is: The analysis function reads data from WAV file, run FFT against the data to get magnitude of frequency vectors, and then calculates the average value and standard deviation of frequency vectors. After that, we define a threshold: threshold = 3 * standard_deviation + average_value Frequencies with amplitude larger than threshold will be recognized as a peak, and the frequency with largest peak value will be recognized as a detected frequency. BAT then compares the detected frequency to target frequency, to decide if the detecting passes or fails.
BAT supports 4 working modes:
- Single line playback;
- Single line capture and analysis;
- Playback and capture in loop, and analyze captured data;
- Local analyze without actual playback or capture.
BAT will check devices input by user to decide which mode to use. BAT will create threads for playback and record, and will run spectrum analysis for mode 2, 3 or 4.
Changes on V2:
- Adopt Caleb Crome's method to generate sine signal to avoids
unwanted distortion on waveform.
Lu, Han (7): BAT: Add initial functions BAT: Add common definitions and functions BAT: Add playback and record functions BAT: Add signal generator BAT: Add converting functions BAT: Add spectrum analysis functions BAT: Add Makefile and configures
Thanks, I applied all patches now for making the further development easier. But in general, please give a good documentation describing how to use it, at best, in a form of man page.
I'll give some comments in each patch later.
Takashi
participants (4)
-
han.lu@intel.com
-
Liam Girdwood
-
Lu, Han
-
Takashi Iwai