[alsa-devel] [PATCH BAT V1 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 loopback. It then compares the captured stream to the original to determine if the testcase 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.
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 | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 ++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 ++++++++++++++++++ bat/common.h | 169 ++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 88 ++++++++ configure.ac | 19 +- 13 files changed, 2202 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
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..24c74e8 --- /dev/null +++ b/bat/bat.c @@ -0,0 +1,608 @@ +/* + * 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->sinf_func = sinf_sign; + 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 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; + bat->sinf_func = sinf_unsign; + break; + case 2: + bat->convert_float_to_sample = convert_float_to_int16; + bat->convert_sample_to_double = convert_int16_to_double; + bat->sinf_func = sinf_sign; + break; + case 3: + bat->convert_float_to_sample = convert_float_to_int24; + bat->convert_sample_to_double = convert_int24_to_double; + bat->sinf_func = sinf_sign; + break; + case 4: + bat->convert_float_to_sample = convert_float_to_int32; + bat->convert_sample_to_double = convert_int32_to_double; + bat->sinf_func = sinf_sign; + 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; +}
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..4e773cc --- /dev/null +++ b/bat/common.h @@ -0,0 +1,169 @@ +/* + * 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 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 (*sinf_func)(struct bat *, float *, int, int, int *, float *); + + 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 *); + +void sinf_sign(struct bat *, float *, int, int, int *, float *); +void sinf_unsign(struct bat *, float *, int, int, int *, float *); +int generate_sine_wave(struct bat *, int, void *, int);
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..de543ed --- /dev/null +++ b/bat/alsa.c @@ -0,0 +1,618 @@ +/* + * 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" + +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; + void *buf; + int max, samples = 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; + + switch (bat->sample_size) { + case 1: + buf = (uint8_t *) sndpcm->buffer; + max = UINT8_MAX; + break; + case 2: + buf = (int16_t *) sndpcm->buffer; + max = INT16_MAX; + break; + case 3: + buf = (int8_t *) sndpcm->buffer; + max = (1 << 23) - 1; + break; + case 4: + buf = (int32_t *) sndpcm->buffer; + max = INT32_MAX; + break; + default: + fprintf(bat->err, _("Invalid PCM format: size=%d\n"), + bat->sample_size); + return -EINVAL; + } + + err = generate_sine_wave(bat, samples, buf, max); + if (err != 0) + return err; + + load += samples; + } + + bat->periods_played++; + + 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; + } + + 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
diff --git a/bat/signal.c b/bat/signal.c new file mode 100644 index 0000000..1efb81e --- /dev/null +++ b/bat/signal.c @@ -0,0 +1,88 @@ +/* + * 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 <math.h> +#include <stdbool.h> + +#include "gettext.h" +#include "common.h" + +void sinf_sign(struct bat *bat, + float *out, int length, int max, int *phase, float *val) +{ + int i = *phase, k, c, idx; + float factor = max * RANGE_FACTOR; + + for (k = 0; k < length; k++) { + for (c = 0; c < bat->channels; c++) { + idx = k * bat->channels + c; + out[idx] = sinf(val[c] * i) * factor; + } + i++; + if (i == bat->rate) + i = 0; /* Restart from 0 after one sine wave period */ + } + *phase = i; +} + +void sinf_unsign(struct bat *bat, + float *out, int length, int max, int *phase, float *val) +{ + int i = *phase, k, c, idx; + float factor = max * RANGE_FACTOR / 2.0; + float offset = max * (1 - RANGE_FACTOR) / 2.0; + + for (k = 0; k < length; k++) { + for (c = 0; c < bat->channels; c++) { + idx = k * bat->channels + c; + out[idx] = (sinf(val[c] * i) + 1.0) * factor + + offset; + } + i++; + if (i == bat->rate) + i = 0; /* Restart from 0 after one sine wave period */ + } + *phase = i; +} + +int generate_sine_wave(struct bat *bat, int length, void *buf, int max) +{ + static int i; + int c, buf_bytes; + float sin_val[MAX_CHANNELS]; + float *sinus_f = NULL; + + buf_bytes = bat->channels * length; + sinus_f = (float *) malloc(buf_bytes * sizeof(float)); + if (sinus_f == NULL) { + fprintf(bat->err, _("Not enough memory.\n")); + return -ENOMEM; + } + + for (c = 0; c < bat->channels; c++) + sin_val[c] = 2.0 * M_PI * bat->target_freq[c] + / (float) bat->rate; + + bat->sinf_func(bat, sinus_f, length, max, &i, sin_val); + + bat->convert_float_to_sample(sinus_f, buf, length, bat->channels); + + free(sinus_f); + + return 0; +}
Hi Han, Liam, all,
It looks like you have run into the old problem that the math library actually stinks at generating sine waves right :-)
for (k = 0; k < length; k++) {
for (c = 0; c < bat->channels; c++) {
idx = k * bat->channels + c;
out[idx] = sinf(val[c] * i) * factor;
}
i++;
if (i == bat->rate)
i = 0; /* Restart from 0 after one sine wave period */
}
If I'm reading this right, you're resetting the argument to the sinf function. This can generate unwanted distortion in the sine wave if the frequency isn't an exact fraction of the sample rate.
Here's a little library (along with a test program) I just whipped up using a phasor as the sine wave generator. The test program runs it for a simulated 3 days, then prints a python script to stdout that you can then run and plot to see the generated wave.
This will work for any frequency sine wave, and as far as I know, it's the best way to generate sine waves. (Not for calculating the sin of a value though!)
Here is the generator and test program. Sorry If this isn't the right way to submit a file/patch, but I have no idea how to do it properly :-/
BTW, this isn't actually integrated into the bat program, it's just the little library for generating the sine waves.
Author: Caleb Crome caleb@signalessence.com Date: Fri Sep 18 09:45:43 2015 -0700
added sin_generator
diff --git a/bat/sin_generator.c b/bat/sin_generator.c new file mode 100644 index 0000000..e67f376 --- /dev/null +++ b/bat/sin_generator.c @@ -0,0 +1,120 @@ +#include "math.h" +#include "sin_generator.h" +/* + * Copyright (C) 2015 Caleb Crome + * + * 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 + */ + + +/* 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 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 sr; // return the input value so sine wave starts at + // exactly 0.0 +} +/* 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); + } +} + +#ifdef TEST_PHASOR +#define TESTSIZE (48000/1000) +// compile with this command to create executable that will +// auto-generate a self plotting plot :-) + +// gcc -g sin_generator.c -o sin_generator -lm -DTEST_PHASOR + +#include <stdio.h> + +void dumpbuffer(const char *name, float *buffer, int n) +{ + int i; + printf("%s = [\n", name); + for (i = 0; i < n; i++) { + printf(" %f,\n", buffer[i]); + } + printf("]\n"); + printf("plot(%s)\n", name); +} + +int main(int argc, char *argv[]) +{ + float buffer[TESTSIZE]; + long int i; + struct sin_generator sg; + + // test the first few. + sin_generator_init (&sg, 3.0, 1000, 48000); + sin_generator_vfill(&sg, buffer, TESTSIZE); + printf("from pylab import *\n"); + dumpbuffer("s", buffer, TESTSIZE); + + // now wind the phasor for 3 days worth of samples to see + // if magnitude drifts. + for (i = 0; i < (3L*24*60*60*48000); i++) + sin_generator_next_sample(&sg); + + sin_generator_vfill(&sg, buffer, TESTSIZE); + // and let's see what that one looks like + dumpbuffer("o", buffer, TESTSIZE); + printf("show()\n"); + + return 0; +} +#endif diff --git a/bat/sin_generator.h b/bat/sin_generator.h new file mode 100644 index 0000000..2ab7561 --- /dev/null +++ b/bat/sin_generator.h @@ -0,0 +1,23 @@ +/* + * 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. I was + */ + +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; +}; + +int sin_generator_init(struct sin_generator *sg, float magnitude, float frequency, float sample_rate); +float sin_generator_next_sample(struct sin_generator *sg); +void sin_generator_vfill(struct sin_generator *sg, float *buf, int n);
On Fri, 2015-09-18 at 09:50 -0700, Caleb Crome wrote:
Hi Han, Liam, all,
It looks like you have run into the old problem that the math library actually stinks at generating sine waves right :-)
for (k = 0; k < length; k++) {
for (c = 0; c < bat->channels; c++) {
idx = k * bat->channels + c;
out[idx] = sinf(val[c] * i) * factor;
}
i++;
if (i == bat->rate)
i = 0; /* Restart from 0 after one sine wave period */
}
If I'm reading this right, you're resetting the argument to the sinf function. This can generate unwanted distortion in the sine wave if the frequency isn't an exact fraction of the sample rate.
Here's a little library (along with a test program) I just whipped up using a phasor as the sine wave generator. The test program runs it for a simulated 3 days, then prints a python script to stdout that you can then run and plot to see the generated wave.
This will work for any frequency sine wave, and as far as I know, it's the best way to generate sine waves. (Not for calculating the sin of a value though!)
Here is the generator and test program. Sorry If this isn't the right way to submit a file/patch, but I have no idea how to do it properly :-/
No worries, I dont think Han has put a link to his public git repo so it will be a bit more difficult to create proper patches.
BTW, this isn't actually integrated into the bat program, it's just the little library for generating the sine waves.
Thanks, we can integrate and add to the codebase for resending V2 patches. :)
Liam
Author: Caleb Crome caleb@signalessence.com Date: Fri Sep 18 09:45:43 2015 -0700
added sin_generator
diff --git a/bat/sin_generator.c b/bat/sin_generator.c new file mode 100644 index 0000000..e67f376 --- /dev/null +++ b/bat/sin_generator.c @@ -0,0 +1,120 @@ +#include "math.h" +#include "sin_generator.h" +/*
- Copyright (C) 2015 Caleb Crome
- 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
- */
+/* 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 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 sr; // return the input value so sine wave starts at
// exactly 0.0
+} +/* 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);
- }
+}
+#ifdef TEST_PHASOR +#define TESTSIZE (48000/1000) +// compile with this command to create executable that will +// auto-generate a self plotting plot :-)
+// gcc -g sin_generator.c -o sin_generator -lm -DTEST_PHASOR
+#include <stdio.h>
+void dumpbuffer(const char *name, float *buffer, int n) +{
- int i;
- printf("%s = [\n", name);
- for (i = 0; i < n; i++) {
printf(" %f,\n", buffer[i]);
- }
- printf("]\n");
- printf("plot(%s)\n", name);
+}
+int main(int argc, char *argv[]) +{
- float buffer[TESTSIZE];
- long int i;
- struct sin_generator sg;
- // test the first few.
- sin_generator_init (&sg, 3.0, 1000, 48000);
- sin_generator_vfill(&sg, buffer, TESTSIZE);
- printf("from pylab import *\n");
- dumpbuffer("s", buffer, TESTSIZE);
- // now wind the phasor for 3 days worth of samples to see
- // if magnitude drifts.
- for (i = 0; i < (3L*24*60*60*48000); i++)
sin_generator_next_sample(&sg);
- sin_generator_vfill(&sg, buffer, TESTSIZE);
- // and let's see what that one looks like
- dumpbuffer("o", buffer, TESTSIZE);
- printf("show()\n");
- return 0;
+} +#endif diff --git a/bat/sin_generator.h b/bat/sin_generator.h new file mode 100644 index 0000000..2ab7561 --- /dev/null +++ b/bat/sin_generator.h @@ -0,0 +1,23 @@ +/*
- 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. I was
- */
+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;
+};
+int sin_generator_init(struct sin_generator *sg, float magnitude, float frequency, float sample_rate); +float sin_generator_next_sample(struct sin_generator *sg); +void sin_generator_vfill(struct sin_generator *sg, float *buf, int n);
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..ff04838 --- /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 analyzes 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 \
Hi Han, This is great. Audio testing is something near and dear to my heart :-) Well, if not heart, at least my profession. One test that we've been using a long time (that I got from the Audio Anecdotes books) is to do a ramp test. In fact, we strongly suggest our customers (almost require them) to complete the test successfully before trying to integrate our software (high end mic array and acoustic echo cancellation software).
So, the test is pretty simple: Set up a hardware (digital) loopback between output and input. Then send out a 'ramp', i.e. just keep a counter that increments each frame, and send out the counter value out to the audio port and watch the values that come back. In our work it's quite important that every sample come back bit-perfect with no samples added or dropped.
Once this test is up and running, you then bang on the system to ensure the audio stream doesn't break even under heavy load.
After the test has been up and running for some days without a bit lost, then you can be pretty sure it's safe to integrate the AEC software.
The benefit of the ramp test is that it's a 100% bit-perfect test. No analysis required, simple pass or fail The drawback is that it doesn't work with analog loopback, which your test will.
For analog testing there may be other tests that could be useful too, like an audio PLL plus comparator to test in the time domain or a million other possibilities, but it depends on what failure modes actually happen.
What kind of failure modes do we see in linux? Do all failures end up with underrun or overrun, or are there more subtle errors, like frames lost or gained without the software knowing about it? Or perhaps whole blocks that get duplicated or deleted?
i.e. is the user level always notified of an error?
From one of our customers that runs on Windows, it seems that there
are million ways Windows messes with audio and can wreck the audio without telling you, but I haven't seen too many (or any) like that with Linux?
On Tue, Sep 15, 2015 at 12:00 AM, 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 loopback. It then compares the captured stream to the original to determine if the testcase 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.
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 | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 ++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 ++++++++++++++++++ bat/common.h | 169 ++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 88 ++++++++ configure.ac | 19 +- 13 files changed, 2202 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
-- 1.9.1
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
+ Mark and fix Takashi's email address
On Tue, 2015-09-15 at 20:23 -0700, Caleb Crome wrote:
Hi Han, This is great. Audio testing is something near and dear to my heart :-) Well, if not heart, at least my profession. One test that we've been using a long time (that I got from the Audio Anecdotes books) is to do a ramp test. In fact, we strongly suggest our customers (almost require them) to complete the test successfully before trying to integrate our software (high end mic array and acoustic echo cancellation software).
So, the test is pretty simple: Set up a hardware (digital) loopback between output and input. Then send out a 'ramp', i.e. just keep a counter that increments each frame, and send out the counter value out to the audio port and watch the values that come back. In our work it's quite important that every sample come back bit-perfect with no samples added or dropped.
Once this test is up and running, you then bang on the system to ensure the audio stream doesn't break even under heavy load.
After the test has been up and running for some days without a bit lost, then you can be pretty sure it's safe to integrate the AEC software.
The benefit of the ramp test is that it's a 100% bit-perfect test. No analysis required, simple pass or fail The drawback is that it doesn't work with analog loopback, which your test will.
But this would be a good test to add into BAT too :) There are many DSPs and codecs that can do digital loopback so we should look at adding this to BAT too.
For analog testing there may be other tests that could be useful too, like an audio PLL plus comparator to test in the time domain or a million other possibilities, but it depends on what failure modes actually happen.
We also have some plans for testing gain changes and channel mappings. Fwiw, during our testing it did pick up some driver bugs with incorrect BIAS setting (signal went to DC after 10 hours of testing) and filter settings (test failed at certain sine wave frequencies). We have also used BAT for stress testing audio over D0 -> D3 -> D0 transitions.
What kind of failure modes do we see in linux? Do all failures end up with underrun or overrun, or are there more subtle errors, like frames lost or gained without the software knowing about it? Or perhaps whole blocks that get duplicated or deleted?
i.e. is the user level always notified of an error?
No, not for every error. Userspace usually does not know about FIFO underruns/overrruns. It does know about DMA overrruns/underruns though. Userspace mainly has some difficulty with errors caused in the analog domain.
From one of our customers that runs on Windows, it seems that there are million ways Windows messes with audio and can wreck the audio without telling you, but I haven't seen too many (or any) like that with Linux?
I'm sure there are ways for us to do this in Linux too ;)
Liam
On Tue, Sep 15, 2015 at 12:00 AM, 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 loopback. It then compares the captured stream to the original to determine if the testcase 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.
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 | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 ++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 ++++++++++++++++++ bat/common.h | 169 ++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 88 ++++++++ configure.ac | 19 +- 13 files changed, 2202 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
-- 1.9.1
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
;; This buffer is for notes you don't want to save, and for Lisp evaluation. ;; If you want to create a file, visit that file with C-x C-f, ;; then enter the text in that file's own buffer.
So, the test is pretty simple: Set up a hardware (digital) loopback between output and input. Then send out a 'ramp', i.e. just keep a counter that increments each frame, and send out the counter value out to the audio port and watch the values that come back. In our work it's quite important that every sample come back bit-perfect with no samples added or dropped.
Once this test is up and running, you then bang on the system to ensure the audio stream doesn't break even under heavy load.
The benefit of the ramp test is that it's a 100% bit-perfect test.
But this would be a good test to add into BAT too :) There are many DSPs and codecs that can do digital loopback so we should look at adding this to BAT too.
Yep, many don't as well. In those cases we recommend that there is a resistor stuffing option on the PCB to allow for digital loopback.
We also have some plans for testing gain changes and channel mappings. Fwiw, during our testing it did pick up some driver bugs with incorrect BIAS setting (signal went to DC after 10 hours of testing)
Ah Ha! So I'm not crazy asking our customers to run for days! :-) I knew there was good reason for that.
We have also used BAT for stress testing audio over D0 -> D3 -> D0 transitions.
Sorry, I'm not familiar with that terminology. Are those the different power states?
What kind of failure modes do we see in linux? Do all failures end up with underrun or overrun, or are there more subtle errors, like frames lost or gained without the software knowing about it? Or perhaps whole blocks that get duplicated or deleted?
i.e. is the user level always notified of an error?
No, not for every error. Userspace usually does not know about FIFO underruns/overrruns. It does know about DMA overrruns/underruns though. Userspace mainly has some difficulty with errors caused in the analog domain.
So, it sounds to me like there are definitely several tests that would be useful, both for digital loopback testing and analog loopback testing.
Since we run microphone arrays, it's imperative that all the ADC channels are perfectly time-aligned. It's definitely non-trivial to do that on some codecs, and impossible on others. It's definitely not good enough to just 'start the codecs at the same time' because that's not well defined on some codecs.
Here are some of the tests I can think of:
Digital Loopback --------------------------------- Setup: Either configure the serial port or PCB for digital loopback form out->in.
Test: Ramp test. This will do bit-perfect, very simple, very reliable test that when passed will guarantee: * No samples lost/added/duplicated. * Show latency from out -> in. This should be the SAME latency at every startup under all load conditions. * This is essentially the gold standard test for Linux+Driver+serial port configuration. * Does not test codec startup/phase alignment.
Analog Loopback -------------------------------- Setup: Loop back one output to all inputs (up to 32 on my board!) in the analog domain.
Test: Impulse response. There are many ways to measure impulse responses. I can help with this :-) It's something I do quite often. The impulse response test will let us gather: * Frequency response of analog system, and compare channel-to-channel component matching. * Phase response of the analog system, which will let us see any component matching issues, but more importantly, codec startup/phase issues. * Can also show latency from out->in * does test codec phase alignment
Test: Time domain testy thingy. * This is one that we don't normally do because its not needed if you do the digital ramp test, but would be useful for finding single/few sample defects in systems that can't do digital loopback. * Run a sine wave out * use a (software) PLL to lock on to the received sine wave. This PLL will lock on to frequency, phase, and magnitude (essentially a 2 tap adaptive phasor). Then you simply use the PLL output to subtract from the input signal, and you should end up with no signal, but just noise. If you get any pops or samples higher than the expected noise floor, then you know you have a problem. * Definitely an imperfect test, especially if the Fs/2 is much higher than the analog bandwidth. (i.e. 20kHz analog bandwidth, but 96kHz sampling), but it's not terrible.
If you're unfamiliar with the codec time alignment problem, the issue is that codecs don't use the WCLK as the sample-time source, but rather some number of MCLKs from when they turn on. In my system I have to turn on and configure up to 16 stereo codecs, but if MCLK is running during configuration, there is literally no way to get all the codecs to start at once because they are turned on via I2C. So, on the TLV320AIC3x series of parts the way to do it is: Make sure MCLK is turned off, then configure and enable all the codecs. Then, and only then, do you turn the MCLK clock on. That way all codecs start in synchrony and are cycle-locked together.
If you wish to use a PLL on the AIC3x, the problem gets even trickier -- you can't use the PLL for multiple codecs unless you run the source clock in on GPIO2, and the PLL output (MCLK) out on GPIO1, then to all the codecs in the system (include the PLL). It takes a bit of time staring at the datasheet to work that out :-/
On the CS53L30 (4 channel ADC), there is a dedicated signal to synchronize multiple codec chips, so the startup is much easier, but still requires thought.
I've seen at least one codec with 3 stereo ADCs on-chip with literally no way to synchronize them. The vendor's response was... "use another part". Unfortunately, I can't remember which part it was that had the problem. Just beware if you're doing multi-channel arrays!
Anyway, in summary, the tests I'm thinking of are: * Ramp test: the gold standard for driver/SoC testing. * Impulse response test: the gold standard for phase/alignment issues. * Time domain testy thingy: a less good ramp test if you can't manage digital loopback in your system.
In addition, maybe there should be a 'load' parameter in the test tool to add system load until things start breaking.
I wonder, will these tests capture all failure modes?
-Caleb
On Wed, 2015-09-16 at 09:43 -0700, Caleb Crome wrote:
;; This buffer is for notes you don't want to save, and for Lisp evaluation. ;; If you want to create a file, visit that file with C-x C-f, ;; then enter the text in that file's own buffer.
So, the test is pretty simple: Set up a hardware (digital) loopback between output and input. Then send out a 'ramp', i.e. just keep a counter that increments each frame, and send out the counter value out to the audio port and watch the values that come back. In our work it's quite important that every sample come back bit-perfect with no samples added or dropped.
Once this test is up and running, you then bang on the system to ensure the audio stream doesn't break even under heavy load.
The benefit of the ramp test is that it's a 100% bit-perfect test.
But this would be a good test to add into BAT too :) There are many DSPs and codecs that can do digital loopback so we should look at adding this to BAT too.
Yep, many don't as well. In those cases we recommend that there is a resistor stuffing option on the PCB to allow for digital loopback.
We also have some plans for testing gain changes and channel mappings. Fwiw, during our testing it did pick up some driver bugs with incorrect BIAS setting (signal went to DC after 10 hours of testing)
Ah Ha! So I'm not crazy asking our customers to run for days! :-) I knew there was good reason for that.
We have also used BAT for stress testing audio over D0 -> D3 -> D0 transitions.
Sorry, I'm not familiar with that terminology. Are those the different power states?
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
What kind of failure modes do we see in linux? Do all failures end up with underrun or overrun, or are there more subtle errors, like frames lost or gained without the software knowing about it? Or perhaps whole blocks that get duplicated or deleted?
i.e. is the user level always notified of an error?
No, not for every error. Userspace usually does not know about FIFO underruns/overrruns. It does know about DMA overrruns/underruns though. Userspace mainly has some difficulty with errors caused in the analog domain.
So, it sounds to me like there are definitely several tests that would be useful, both for digital loopback testing and analog loopback testing.
Since we run microphone arrays, it's imperative that all the ADC channels are perfectly time-aligned. It's definitely non-trivial to do that on some codecs, and impossible on others. It's definitely not good enough to just 'start the codecs at the same time' because that's not well defined on some codecs.
Here are some of the tests I can think of:
Digital Loopback
Setup: Either configure the serial port or PCB for digital loopback form out->in.
Test: Ramp test. This will do bit-perfect, very simple, very reliable test that when passed will guarantee:
- No samples lost/added/duplicated.
- Show latency from out -> in. This should be the SAME latency at every startup under all load conditions.
- This is essentially the gold standard test for Linux+Driver+serial port configuration.
- Does not test codec startup/phase alignment.
Analog Loopback
Setup: Loop back one output to all inputs (up to 32 on my board!) in the analog domain.
Test: Impulse response. There are many ways to measure impulse responses. I can help with this :-) It's something I do quite often.
Thanks, any help to add new tests is appreciated !
The impulse response test will let us gather:
- Frequency response of analog system, and compare channel-to-channel component matching.
- Phase response of the analog system, which will let us see any component matching issues, but more importantly, codec
startup/phase issues.
- Can also show latency from out->in
- does test codec phase alignment
Test: Time domain testy thingy.
- This is one that we don't normally do because its not needed if you do the digital ramp test, but would be useful for finding single/few sample defects in systems that can't do digital loopback.
- Run a sine wave out
- use a (software) PLL to lock on to the received sine wave. This PLL will lock on to frequency, phase, and magnitude (essentially a 2 tap adaptive phasor). Then you simply use the PLL output to subtract from the input signal, and you should end up with no signal, but just noise. If you get any pops or samples higher than the expected noise floor, then you know you have a problem.
- Definitely an imperfect test, especially if the Fs/2 is much higher than the analog bandwidth. (i.e. 20kHz analog bandwidth, but 96kHz sampling), but it's not terrible.
If you're unfamiliar with the codec time alignment problem, the issue is that codecs don't use the WCLK as the sample-time source, but rather some number of MCLKs from when they turn on. In my system I have to turn on and configure up to 16 stereo codecs, but if MCLK is running during configuration, there is literally no way to get all the codecs to start at once because they are turned on via I2C. So, on the TLV320AIC3x series of parts the way to do it is: Make sure MCLK is turned off, then configure and enable all the codecs. Then, and only then, do you turn the MCLK clock on. That way all codecs start in synchrony and are cycle-locked together.
If you wish to use a PLL on the AIC3x, the problem gets even trickier -- you can't use the PLL for multiple codecs unless you run the source clock in on GPIO2, and the PLL output (MCLK) out on GPIO1, then to all the codecs in the system (include the PLL). It takes a bit of time staring at the datasheet to work that out :-/
On the CS53L30 (4 channel ADC), there is a dedicated signal to synchronize multiple codec chips, so the startup is much easier, but still requires thought.
I've seen at least one codec with 3 stereo ADCs on-chip with literally no way to synchronize them. The vendor's response was... "use another part". Unfortunately, I can't remember which part it was that had the problem. Just beware if you're doing multi-channel arrays!
Anyway, in summary, the tests I'm thinking of are:
- Ramp test: the gold standard for driver/SoC testing.
- Impulse response test: the gold standard for phase/alignment issues.
- Time domain testy thingy: a less good ramp test if you can't manage digital loopback in your system.
This sounds great. Lets get the first part upstream and then we can incrementally add the other tests. Btw, are you going to the audio mini conf at ELCE in Dublin ? It would be good to discuss this in person !
In addition, maybe there should be a 'load' parameter in the test tool to add system load until things start breaking.
That's a good idea too. Ideally we would want to stress both IO load and CPU load independently and together.
I wonder, will these tests capture all failure modes?
There may always be a few corner cases that are missed, but I would imagine that most cases would be covered.
Liam
-Caleb _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
We have also used BAT for stress testing audio over D0 -> D3 -> D0 transitions.
Sorry, I'm not familiar with that terminology. Are those the different power states?
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
That seems particularly likely to result in multiple codecs getting out of phase alignment. i.e. when restarting, you want to be sure to follow the same startup procedure as the first time to make sure all codecs are aligned.
Anyway, in summary, the tests I'm thinking of are:
- Ramp test: the gold standard for driver/SoC testing.
- Impulse response test: the gold standard for phase/alignment issues.
- Time domain testy thingy: a less good ramp test if you can't manage digital loopback in your system.
This sounds great. Lets get the first part upstream and then we can incrementally add the other tests. Btw, are you going to the audio mini conf at ELCE in Dublin ? It would be good to discuss this in person !
No, not planning to go to Dublin, though it's not a bad idea. I'm not sure I can justify the trip there (I'm in California). My day job is to be a DSP/Acoustical/Electrical engineer, and I'm only hacking on the kernel because we need to get it working :-). Which I guess is also my day job now too. Make that DSP/Acoustical/Electrical/Hack engineer.
We can always chat via Skype or phone, though I agree, there's nothing like talking face to face.
Liam
-Caleb
On Thu, Sep 17, 2015 at 06:29:58AM -0700, Caleb Crome wrote:
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
That seems particularly likely to result in multiple codecs getting out of phase alignment. i.e. when restarting, you want to be sure to follow the same startup procedure as the first time to make sure all codecs are aligned.
Why should resume be any different from initial system power up?
On Thu, Sep 17, 2015 at 10:42 AM, Mark Brown broonie@kernel.org wrote:
On Thu, Sep 17, 2015 at 06:29:58AM -0700, Caleb Crome wrote:
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
That seems particularly likely to result in multiple codecs getting out of phase alignment. i.e. when restarting, you want to be sure to follow the same startup procedure as the first time to make sure all codecs are aligned.
Why should resume be any different from initial system power up?
Heh, perhaps only because I don't understand how it all works :-)
The example I was thinking of is if the card gets partially powered down. For example, start recording on 8 channels (4 codecs, called 0, 1, 2, 3 in this example). At this point, all's well, and all 4 codecs are in perfect phase sync.
Then switch to playing 2 channel mode with codec 0 only. Since codecs 1, 2, 3 are no longer needed, then they could get powered down.
Then, switch back to 8 channel mode -- codec 0 is up and running, and now you want to turn on codecs 1, 2, 3. At this point it's literally impossible to resync them with codec 0 without powering down codec 0 and starting from scratch. This is true of the TLV320AIC seris of codecs. The CS53L30 has a dedicated sync pin on the other hand, so could perhaps recover gracefully in this situation.
-Caleb
The more I think of this channel synchronization issue, the thorinier it gets.
Basically, with the TLV320AIC series of codecs, whenever any of the DAC or ADC channels are powered up on any codec, the MCLK needs to be stopped, all DACs/ADCs turned off, then configuration can happen, then all DACs/ADCs can be turned back on, then MCLK starts again. That's the only way to ensure phase/delay accuracy among several codecs.
So, going from play-only to play+record requires MCLK be stopped, then restarted.
Maybe it's not too terrible. Any thoughts?
-Caleb
On Thu, Sep 17, 2015 at 11:50 AM, Caleb Crome caleb@crome.org wrote:
On Thu, Sep 17, 2015 at 10:42 AM, Mark Brown broonie@kernel.org wrote:
On Thu, Sep 17, 2015 at 06:29:58AM -0700, Caleb Crome wrote:
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
That seems particularly likely to result in multiple codecs getting out of phase alignment. i.e. when restarting, you want to be sure to follow the same startup procedure as the first time to make sure all codecs are aligned.
Why should resume be any different from initial system power up?
Heh, perhaps only because I don't understand how it all works :-)
The example I was thinking of is if the card gets partially powered down. For example, start recording on 8 channels (4 codecs, called 0, 1, 2, 3 in this example). At this point, all's well, and all 4 codecs are in perfect phase sync.
Then switch to playing 2 channel mode with codec 0 only. Since codecs 1, 2, 3 are no longer needed, then they could get powered down.
Then, switch back to 8 channel mode -- codec 0 is up and running, and now you want to turn on codecs 1, 2, 3. At this point it's literally impossible to resync them with codec 0 without powering down codec 0 and starting from scratch. This is true of the TLV320AIC seris of codecs. The CS53L30 has a dedicated sync pin on the other hand, so could perhaps recover gracefully in this situation.
-Caleb
On Thu, Sep 17, 2015 at 03:40:33PM -0700, Caleb Crome wrote:
The more I think of this channel synchronization issue, the thorinier it gets.
Basically, with the TLV320AIC series of codecs, whenever any of the DAC or ADC channels are powered up on any codec, the MCLK needs to be stopped, all DACs/ADCs turned off, then configuration can happen, then all DACs/ADCs can be turned back on, then MCLK starts again. That's the only way to ensure phase/delay accuracy among several codecs.
So, going from play-only to play+record requires MCLK be stopped, then restarted.
Aye. Lots of clocks in lots of domains. Also pay penalty of any delays needed to stabilise ADCs.
Same general issue with many single codecs though, if capture vs playback synchronisation required.
Maybe it's not too terrible. Any thoughts?
In this scenario my preference would be to always configure the codecs and DMA for playback and capture, and do any gating higher in the stack; e.g. send zero samples if there is no playback stream, and discard samples if there is no capture stream.
On Thu, Sep 17, 2015 at 4:00 PM, James Cameron quozl@laptop.org wrote:
On Thu, Sep 17, 2015 at 03:40:33PM -0700, Caleb Crome wrote:
The more I think of this channel synchronization issue, the thorinier it gets.
Basically, with the TLV320AIC series of codecs, whenever any of the DAC or ADC channels are powered up on any codec, the MCLK needs to be stopped, all DACs/ADCs turned off, then configuration can happen, then all DACs/ADCs can be turned back on, then MCLK starts again. That's the only way to ensure phase/delay accuracy among several codecs.
So, going from play-only to play+record requires MCLK be stopped, then restarted.
Aye. Lots of clocks in lots of domains. Also pay penalty of any delays needed to stabilise ADCs.
Same general issue with many single codecs though, if capture vs playback synchronisation required.
Maybe it's not too terrible. Any thoughts?
In this scenario my preference would be to always configure the codecs and DMA for playback and capture, and do any gating higher in the stack; e.g. send zero samples if there is no playback stream, and discard samples if there is no capture stream.
Yes, this makes sense to me. This is for this sort of special case that I have where we have many synchronized codecs on the same bus. It's really just 1 clock that needs to be gated -- the MCLK.
Is there any way for the machine driver to handle this? Currently, the AIC3x driver uses a dapm control to turn the ADCs and DACs on and off. Can the machine driver currently get some sort of pre and post callback whenever particular controls are enabled?
I think to get through the day, I need to remove the SND_SOC_DAPM_ADC("Left ADC", ...) SND_SOC_DAPM_ADC("Right ADC", ...) SND_SOC_DAPM_DAC("Left ...") SND_SOC_DAPM_DAC("Right" lines in tlv320aic3x, and just hard-wire them somewhere so they are all always turned on. Then I can make sure to turn on the clocks only after all the codecs have been powered up and alsa thinks they are configured. I guess in the machine driver's trigger callback?
-Caleb
-- James Cameron http://quozl.linux.org.au/
On Thu, 2015-09-17 at 06:29 -0700, Caleb Crome wrote:
We have also used BAT for stress testing audio over D0 -> D3 -> D0 transitions.
Sorry, I'm not familiar with that terminology. Are those the different power states?
Yes, PM suspend and resume of device. We have some scripts (to be upstreamed later) that would play audio, suspend the device, resume the device and then retest the audio.
That seems particularly likely to result in multiple codecs getting out of phase alignment. i.e. when restarting, you want to be sure to follow the same startup procedure as the first time to make sure all codecs are aligned.
This was more for testing our DSP and codec context was properly saved/restored and also our codec was properly powered down/up during PM state changes.
Liam
On Tue, Sep 15, 2015 at 03:00:16PM +0800, han.lu@intel.com wrote:
BAT works by playing an audio stream and capturing the same stream in either a digital or analog loopback. It then compares the captured stream to the original to determine if the testcase passes or fails.
This is really cool, sounds like there's some overlap with the tool David wrote to verify TLV information? Actually in that vein one thing I started looking at but parked until this got posted is something that goes through and tries to stress test control value settings (trying to set out of bounds values, make sure all valid settings can be used and so on). That's probably better kept as a separate tool I think given that it doesn't really overlap with audio streaming.
On Wed, 2015-09-16 at 17:37 +0100, Mark Brown wrote:
On Tue, Sep 15, 2015 at 03:00:16PM +0800, han.lu@intel.com wrote:
BAT works by playing an audio stream and capturing the same stream in either a digital or analog loopback. It then compares the captured stream to the original to determine if the testcase passes or fails.
This is really cool, sounds like there's some overlap with the tool David wrote to verify TLV information? Actually in that vein one thing I started looking at but parked until this got posted is something that goes through and tries to stress test control value settings (trying to set out of bounds values, make sure all valid settings can be used and so on). That's probably better kept as a separate tool I think given that it doesn't really overlap with audio streaming.
I didn't think of bounds testing the controls :) Especially useful for DSPs where we could overflow or crash FW if certain combinations of enumerated controls are used.
We were looking at testing the gains too, to make sure that a 6dB control gain really does mean a 6dB gain in signal. It's in the TODO list. Most of the code is already there to do this, but we were looking to upstream first and then incrementally add features. I think Han plans to upstream the tinyalsa support after the initial code is upstream though.
Liam
On Wed, Sep 16, 2015 at 05:57:00PM +0100, Liam Girdwood wrote:
I didn't think of bounds testing the controls :) Especially useful for DSPs where we could overflow or crash FW if certain combinations of enumerated controls are used.
I'll try to get onto it again in my copious free time. :/
We were looking at testing the gains too, to make sure that a 6dB control gain really does mean a 6dB gain in signal. It's in the TODO list. Most of the code is already there to do this, but we were looking to upstream first and then incrementally add features. I think Han plans to upstream the tinyalsa support after the initial code is upstream though.
Yeah, definitely not something needed for initial release.
Hi,
I'm currently traveling on vacation and have little time to check through the patchset and join the discussion. I'll take a closer look in the next week.
thanks,
Takashi
On Tue, 15 Sep 2015 09:00:16 +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 loopback. It then compares the captured stream to the original to determine if the testcase 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.
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 | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 ++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 ++++++++++++++++++ bat/common.h | 169 ++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 88 ++++++++ configure.ac | 19 +- 13 files changed, 2202 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
-- 1.9.1
On Sat, 2015-09-19 at 18:40 +0200, Takashi Iwai wrote:
Hi,
I'm currently traveling on vacation and have little time to check through the patchset and join the discussion. I'll take a closer look in the next week.
No worries. This gives us time to merge in Caleb's sine wave generator patch and we can then send a V2 series.
Thanks
Liam
thanks,
Takashi
On Tue, 15 Sep 2015 09:00:16 +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 loopback. It then compares the captured stream to the original to determine if the testcase 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.
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 | 618 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/alsa.h | 20 ++ bat/analyze.c | 314 ++++++++++++++++++++++++++++ bat/analyze.h | 16 ++ bat/bat.c | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bat/common.c | 198 ++++++++++++++++++ bat/common.h | 169 ++++++++++++++++ bat/convert.c | 113 +++++++++++ bat/convert.h | 23 +++ bat/signal.c | 88 ++++++++ configure.ac | 19 +- 13 files changed, 2202 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
-- 1.9.1
--------------------------------------------------------------------- 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.
participants (7)
-
Caleb Crome
-
han.lu@intel.com
-
James Cameron
-
Liam Girdwood
-
Liam Girdwood
-
Mark Brown
-
Takashi Iwai