blob: 0bda5fd6e9ad9e2758b1fcc0e86b5cf914d3b404 [file] [log] [blame]
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "include/binary_client.h"
#include "include/common.h"
#include "include/evaluator.h"
#include "include/generator_player.h"
#include "include/sample_format.h"
#include "include/tone_generators.h"
constexpr static const char *short_options =
"a:m:d:n:o:w:P:f:R:F:r:t:c:C:T:l:g:i:x:hv";
constexpr static const struct option long_options[] = {
{"active-speaker-channels", 1, NULL, 'a'},
{"active-mic-channels", 1, NULL, 'm'},
{"allowed-delay", 1, NULL, 'd'},
{"fft-size", 1, NULL, 'n'},
{"confidence-threshold", 1, NULL, 'o'},
{"match-window-size", 1, NULL, 'w'},
{"player-command", 1, NULL, 'P'},
{"player-fifo", 1, NULL, 'f'},
{"recorder-command", 1, NULL, 'R'},
{"recorder-fifo", 1, NULL, 'F'},
{"sample-rate", 1, NULL, 'r'},
{"sample-format", 1, NULL, 't'},
{"num-mic-channels", 1, NULL, 'c'},
{"num-speaker-channels", 1, NULL, 'C'},
{"test-rounds", 1, NULL, 'T'},
{"tone-length", 1, NULL, 'l'},
{"volume-gain", 1, NULL, 'g'},
{"min-frequency", 1, NULL, 'i'},
{"max-frequency", 1, NULL, 'x'},
// Other helper args.
{"help", 0, NULL, 'h'},
{"verbose", 0, NULL, 'v'},
};
// Parse the sample format. The input should be one of the string in
// SampleFormat:Type.
SampleFormat ParseSampleFormat(const char *arg) {
SampleFormat sample_format;
for (int format = SampleFormat::kPcmU8;
format != SampleFormat::kPcmInvalid;
format++) {
sample_format = SampleFormat(SampleFormat::Type(format));
if (strcmp(sample_format.to_string(), optarg) == 0) {
return sample_format;
}
}
fprintf(stderr, "Unknown sample format %s, using S16 instead.", arg);
return SampleFormat(SampleFormat::kPcmS16);
}
bool ParseOptions(int argc, char *const argv[], AudioFunTestConfig *config) {
int opt = 0;
int optindex = -1;
while ((opt = getopt_long(argc, argv, short_options,
long_options,
&optindex)) != -1) {
switch (opt) {
case 'a':
ParseActiveChannels(optarg, &(config->active_speaker_channels));
break;
case 'm':
ParseActiveChannels(optarg, &(config->active_mic_channels));
break;
case 'd':
config->allowed_delay_sec = atof(optarg);
break;
case 'n':
config->fft_size = atoi(optarg);
if ((config->fft_size == 0) ||
(config->fft_size & (config->fft_size - 1))) {
fprintf(stderr, "FFT size needs to be positive & power of 2\n");
return false;
}
break;
case 'o':
config->confidence_threshold = atof(optarg);
break;
case 'w':
config->match_window_size = atoi(optarg);
if (config->match_window_size % 2 == 0) {
fprintf(stderr, "Match window size must be an odd value.\n");
return false;
}
break;
case 'P':
config->player_command = std::string(optarg);
break;
case 'f':
config->player_fifo = std::string(optarg);
break;
case 'R':
config->recorder_command = std::string(optarg);
break;
case 'F':
config->recorder_fifo = std::string(optarg);
break;
case 'r':
config->sample_rate = atoi(optarg);
break;
case 't':
config->sample_format = ParseSampleFormat(optarg);
break;
case 'c':
config->num_mic_channels = atoi(optarg);
break;
case 'C':
config->num_speaker_channels = atoi(optarg);
break;
case 'T':
config->test_rounds = atoi(optarg);
break;
case 'l':
config->tone_length_sec = atof(optarg);
// Avoid overly short tones.
if (config->tone_length_sec < 0.01) {
fprintf(stderr,
"Tone length too short. Must be 0.01s or greater.\n");
return false;
}
break;
case 'g':
config->volume_gain = atoi(optarg);
if (config->volume_gain < 0 || config->volume_gain > 100) {
fprintf(stderr, "Value of volume_gain is out of range.\n");
return false;
}
break;
case 'i':
config->min_frequency = atoi(optarg);
break;
case 'x':
config->max_frequency = atoi(optarg);
break;
case 'v':
config->verbose = true;
break;
case 'h':
return false;
default:
fprintf(stderr, "Unknown arguments %c\n", opt);
assert(false);
}
}
if (config->player_command.empty()) {
fprintf(stderr, "player-command is not set.\n");
return false;
}
if (config->recorder_command.empty()) {
fprintf(stderr, "recorder-command is not set.\n");
return false;
}
if (config->active_speaker_channels.empty()) {
for (int i = 0; i < config->num_speaker_channels; ++i) {
config->active_speaker_channels.insert(i);
}
}
if (config->active_mic_channels.empty()) {
for (int i = 0; i < config->num_mic_channels; ++i) {
config->active_mic_channels.insert(i);
}
}
if (config->min_frequency > config->max_frequency) {
fprintf(stderr, "Range error: min_frequency > max_frequency\n");
return false;
}
if (config->min_frequency < 0) {
fprintf(stderr, "Range error: min_frequency < 0\n");
return false;
}
return true;
}
void PrintUsage(const char *name, FILE *fd = stderr) {
AudioFunTestConfig default_config;
fprintf(fd,
"Usage %s -P <player_command> -R <recorder_command> [options]\n",
name);
fprintf(fd,
"\t-a, --active-speaker-channels:\n"
"\t\tComma-separated list of speaker channels to play on. "
"(def all channels)\n");
fprintf(fd,
"\t-m, --active-mic-channels:\n"
"\t\tComma-separated list of mic channels to test. "
"(def all channels)\n");
fprintf(fd,
"\t-d, --allowed-delay:\n"
"\t\tAllowed latency between player & recorder "
"(def %.4f).\n", default_config.allowed_delay_sec);
fprintf(fd,
"\t-n, --fftsize:\n"
"\t\tLonger fftsize has more carriers but longer latency."
" Also, fftsize needs to be power of 2"
"(def %d)\n", default_config.fft_size);
fprintf(fd,
"\t-o, --confidence-threshold:\n"
"\t\tThreshold of accumulated confidence to pass evaluation "
"(def %.4f)\n", default_config.confidence_threshold);
fprintf(fd,
"\t-w, --match-window-size:\n"
"\t\tNumber of bin to be used for calculating matching confidence. "
"Should be an odd number."
"(def %d)\n", default_config.match_window_size);
fprintf(fd,
"\t-P, --player-command:\n"
"\t\tThe command used to play sound.\n");
fprintf(fd,
"\t-f, --player-fifo:\n"
"\t\tThe named pipe used to send wave to the player. If not set, "
"wave is send to player program via its stdin.\n");
fprintf(fd,
"\t-R, --recorder-command:\n"
"\t\tThe command used to record sound.\n");
fprintf(fd,
"\t-F, --recorder-fifo:\n"
"\t\tThe named pipe used to read recorded wave from the recorder "
"program. If not set, wave is read from recorder program via "
"its stdout.\n");
fprintf(fd,
"\t-r, --sample-rate:\n"
"\t\tSample rate of generated wave in HZ "
"(def %d)\n", default_config.sample_rate);
fprintf(fd,
"\t-t, --sample-format:\n"
"\t\tFormat of recording & playing samples, should be one of u8, "
"s16, s24, s32."
"(def %s).\n", default_config.sample_format.to_string());
fprintf(fd,
"\t-c, --num-mic-channels:\n"
"\t\tThe number of microphone channels "
"(def %d)\n", default_config.num_mic_channels);
fprintf(fd,
"\t-C, --num-speaker-channels:\n"
"\t\tThe number of speaker channels "
"(def %d)\n", default_config.num_speaker_channels);
fprintf(fd,
"\t-T, --test-rounds:\n"
"\t\tNumber of test rounds "
"(def %d)\n", default_config.test_rounds);
fprintf(fd,
"\t-l, --tone-length:\n"
"\t\tDecimal value of tone length in secs "
"(def %.4f)\n", default_config.tone_length_sec);
fprintf(fd,
"\t-g, --volume-gain\n"
"\t\tControl the volume of generated audio frames. The range is from"
" 0 to 100.\n");
fprintf(fd,
"\t-i, --min-frequency:\n"
"\t\tThe minimum frequency of generated audio frames."
"(def %d)\n", default_config.min_frequency);
fprintf(fd,
"\t-x, --max-frequency\n"
"\t\tThe maximum frequency of generated audio frames."
"(def %d)\n", default_config.max_frequency);
fprintf(fd,
"\t-v, --verbose: Show debugging information.\n");
fprintf(fd,
"\t-h, --help: Show this page.\n");
}
void PrintSet(const std::set<int> &numbers, FILE *fd = stdout) {
bool first = true;
for (auto it : numbers) {
if (!first)
fprintf(fd, ", ");
fprintf(fd, "%d", it);
first = false;
}
}
void PrintConfig(const AudioFunTestConfig &config, FILE *fd = stdout) {
fprintf(fd, "Config values.\n");
fprintf(fd, "\tSpeaker active channels: ");
PrintSet(config.active_speaker_channels, fd);
fprintf(fd, "\n");
fprintf(fd, "\tMic active channels: ");
PrintSet(config.active_mic_channels, fd);
fprintf(fd, "\n");
fprintf(fd, "\tAllowed delay: %.4f(s)\n", config.allowed_delay_sec);
fprintf(fd, "\tFFT size: %d\n", config.fft_size);
fprintf(fd, "\tConfidence threshold: %.4f\n", config.confidence_threshold);
fprintf(fd, "\tMatch window size: %d\n", config.match_window_size);
fprintf(fd, "\tPlayer parameter: %s\n", config.player_command.c_str());
fprintf(fd, "\tPlayer FIFO name: %s\n", config.player_fifo.c_str());
fprintf(fd, "\tRecorder parameter: %s\n", config.recorder_command.c_str());
fprintf(fd, "\tRecorder FIFO name: %s\n", config.recorder_fifo.c_str());
fprintf(fd, "\tSample format: %s\n", config.sample_format.to_string());
fprintf(fd, "\tSample rate: %d\n", config.sample_rate);
fprintf(fd,
"\tNumber of Microphone channels: %d\n", config.num_mic_channels);
fprintf(fd, "\tNumber of Speaker channels: %d\n",
config.num_speaker_channels);
fprintf(fd, "\tNumber of test rounds: %d\n", config.test_rounds);
fprintf(fd, "\tTone length: %.4f(s)\n", config.tone_length_sec);
fprintf(fd, "\tVolume gain: %d\n", config.volume_gain);
fprintf(fd, "\tMinimum frequency: %d\n", config.min_frequency);
fprintf(fd, "\tMaximum frequency: %d\n", config.max_frequency);
if (config.verbose)
fprintf(fd, "\t** Verbose **.\n");
}
// Randomly picks an integer from the given range [min, max],
// including both end points.
inline int RandomPick(int min, int max) {
if (min > max) {
fprintf(stderr, "Range error: min > max\n");
assert(false);
}
static unsigned int seed = time(NULL) + getpid();
return (rand_r(&seed) % (max - min + 1)) + min;
}
// Controls the main process of audiofuntest.
void ControlLoop(const AudioFunTestConfig &config,
Evaluator *evaluator,
PlayClient *player,
RecordClient *recorder) {
const double frequency_resolution =
static_cast<double>(config.sample_rate) / config.fft_size;
const int min_bin = config.min_frequency / frequency_resolution;
const int max_bin = config.max_frequency / frequency_resolution;
std::vector<int> passes(config.num_mic_channels);
std::vector<bool> single_round_pass(config.num_mic_channels);
size_t buf_size = config.fft_size * config.num_speaker_channels *
config.sample_format.bytes();
SineWaveGenerator generator(
config.sample_rate,
config.tone_length_sec,
config.volume_gain);
GeneratorPlayer generatorPlayer(
buf_size,
config.num_speaker_channels,
config.active_speaker_channels,
config.sample_format,
player);
for (int round = 1; round <= config.test_rounds; ++round) {
std::fill(single_round_pass.begin(), single_round_pass.end(), false);
int bin = RandomPick(min_bin, max_bin);
double frequency = bin * frequency_resolution;
generator.Reset(frequency);
generatorPlayer.Play(&generator);
evaluator->Evaluate(bin, recorder, &single_round_pass);
for (int chn = 0; chn < config.num_mic_channels; ++chn) {
if (single_round_pass[chn]) {
++passes[chn];
}
}
generatorPlayer.Stop();
printf("carrier = %d\n", bin);
for (auto c : config.active_mic_channels) {
const char *res = single_round_pass[c] ? "O" : "X";
printf("%s: channel = %d, success = %d, fail = %d, rate = %.4f\n",
res, c, passes[c], round - passes[c], 100.0 * passes[c] / round);
}
}
}
int main(int argc, char *argv[]) {
// Parses configuration.ParamConfig
AudioFunTestConfig config;
if (!ParseOptions(argc, argv, &config)) {
PrintUsage(argv[0]);
return 1;
}
PrintConfig(config);
PlayClient player(config);
player.Start();
RecordClient recorder(config);
recorder.Start();
Evaluator evaluator(config);
// Starts evaluation.
ControlLoop(config, &evaluator, &player, &recorder);
// Terminates and cleans up.
recorder.Terminate();
player.Terminate();
return 0;
}