blob: 0bda5fd6e9ad9e2758b1fcc0e86b5cf914d3b404 [file] [log] [blame]
Amy Lin8c768a72016-08-01 15:44:16 +08001// Copyright 2016 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Earl Ou06c1ad62016-12-01 17:21:50 +08005#include <assert.h>
Earl Ou7ae955b2016-11-30 16:41:22 +08006#include <getopt.h>
Amy Lin8c768a72016-08-01 15:44:16 +08007#include <stdlib.h>
Earl Ou7ae955b2016-11-30 16:41:22 +08008#include <string.h>
9#include <sys/types.h>
10#include <unistd.h>
Amy Lin8c768a72016-08-01 15:44:16 +080011
Earl Ou5afc8d32016-12-01 11:30:52 +080012#include "include/binary_client.h"
Earl Ou7ae955b2016-11-30 16:41:22 +080013#include "include/common.h"
Amy Lin8c768a72016-08-01 15:44:16 +080014#include "include/evaluator.h"
Earl Ou06c1ad62016-12-01 17:21:50 +080015#include "include/generator_player.h"
16#include "include/sample_format.h"
17#include "include/tone_generators.h"
Amy Lin8c768a72016-08-01 15:44:16 +080018
Earl Ou06c1ad62016-12-01 17:21:50 +080019constexpr static const char *short_options =
cyuehf6167eb2019-08-12 14:25:23 +080020 "a:m:d:n:o:w:P:f:R:F:r:t:c:C:T:l:g:i:x:hv";
Earl Ou7ae955b2016-11-30 16:41:22 +080021
22constexpr static const struct option long_options[] = {
23 {"active-speaker-channels", 1, NULL, 'a'},
Ting Shena2834cb2017-03-07 14:49:05 +080024 {"active-mic-channels", 1, NULL, 'm'},
Earl Ou7ae955b2016-11-30 16:41:22 +080025 {"allowed-delay", 1, NULL, 'd'},
26 {"fft-size", 1, NULL, 'n'},
27 {"confidence-threshold", 1, NULL, 'o'},
Earl Ou06c1ad62016-12-01 17:21:50 +080028 {"match-window-size", 1, NULL, 'w'},
Earl Ou7ae955b2016-11-30 16:41:22 +080029 {"player-command", 1, NULL, 'P'},
30 {"player-fifo", 1, NULL, 'f'},
31 {"recorder-command", 1, NULL, 'R'},
32 {"recorder-fifo", 1, NULL, 'F'},
33 {"sample-rate", 1, NULL, 'r'},
34 {"sample-format", 1, NULL, 't'},
35 {"num-mic-channels", 1, NULL, 'c'},
36 {"num-speaker-channels", 1, NULL, 'C'},
37 {"test-rounds", 1, NULL, 'T'},
38 {"tone-length", 1, NULL, 'l'},
Marco Chen6b3e3312017-09-29 17:02:22 +080039 {"volume-gain", 1, NULL, 'g'},
cyuehf6167eb2019-08-12 14:25:23 +080040 {"min-frequency", 1, NULL, 'i'},
41 {"max-frequency", 1, NULL, 'x'},
Earl Ou7ae955b2016-11-30 16:41:22 +080042
43 // Other helper args.
44 {"help", 0, NULL, 'h'},
45 {"verbose", 0, NULL, 'v'},
46};
47
48// Parse the sample format. The input should be one of the string in
49// SampleFormat:Type.
50SampleFormat ParseSampleFormat(const char *arg) {
51 SampleFormat sample_format;
52 for (int format = SampleFormat::kPcmU8;
53 format != SampleFormat::kPcmInvalid;
54 format++) {
55 sample_format = SampleFormat(SampleFormat::Type(format));
56 if (strcmp(sample_format.to_string(), optarg) == 0) {
57 return sample_format;
58 }
59 }
60 fprintf(stderr, "Unknown sample format %s, using S16 instead.", arg);
61 return SampleFormat(SampleFormat::kPcmS16);
62}
63
64bool ParseOptions(int argc, char *const argv[], AudioFunTestConfig *config) {
65 int opt = 0;
66 int optindex = -1;
67
68 while ((opt = getopt_long(argc, argv, short_options,
69 long_options,
70 &optindex)) != -1) {
71 switch (opt) {
72 case 'a':
73 ParseActiveChannels(optarg, &(config->active_speaker_channels));
74 break;
Ting Shena2834cb2017-03-07 14:49:05 +080075 case 'm':
76 ParseActiveChannels(optarg, &(config->active_mic_channels));
77 break;
Earl Ou7ae955b2016-11-30 16:41:22 +080078 case 'd':
79 config->allowed_delay_sec = atof(optarg);
80 break;
81 case 'n':
82 config->fft_size = atoi(optarg);
83 if ((config->fft_size == 0) ||
84 (config->fft_size & (config->fft_size - 1))) {
85 fprintf(stderr, "FFT size needs to be positive & power of 2\n");
86 return false;
87 }
88 break;
89 case 'o':
90 config->confidence_threshold = atof(optarg);
91 break;
Earl Ou06c1ad62016-12-01 17:21:50 +080092 case 'w':
93 config->match_window_size = atoi(optarg);
94 if (config->match_window_size % 2 == 0) {
95 fprintf(stderr, "Match window size must be an odd value.\n");
96 return false;
97 }
98 break;
Earl Ou7ae955b2016-11-30 16:41:22 +080099 case 'P':
100 config->player_command = std::string(optarg);
101 break;
102 case 'f':
103 config->player_fifo = std::string(optarg);
104 break;
105 case 'R':
106 config->recorder_command = std::string(optarg);
107 break;
108 case 'F':
109 config->recorder_fifo = std::string(optarg);
110 break;
111 case 'r':
112 config->sample_rate = atoi(optarg);
113 break;
114 case 't':
115 config->sample_format = ParseSampleFormat(optarg);
116 break;
117 case 'c':
118 config->num_mic_channels = atoi(optarg);
119 break;
120 case 'C':
121 config->num_speaker_channels = atoi(optarg);
122 break;
123 case 'T':
124 config->test_rounds = atoi(optarg);
125 break;
126 case 'l':
127 config->tone_length_sec = atof(optarg);
128 // Avoid overly short tones.
129 if (config->tone_length_sec < 0.01) {
130 fprintf(stderr,
131 "Tone length too short. Must be 0.01s or greater.\n");
132 return false;
133 }
134 break;
Marco Chen6b3e3312017-09-29 17:02:22 +0800135 case 'g':
136 config->volume_gain = atoi(optarg);
137 if (config->volume_gain < 0 || config->volume_gain > 100) {
138 fprintf(stderr, "Value of volume_gain is out of range.\n");
139 return false;
140 }
cyuehf6167eb2019-08-12 14:25:23 +0800141 break;
142 case 'i':
143 config->min_frequency = atoi(optarg);
144 break;
145 case 'x':
146 config->max_frequency = atoi(optarg);
147 break;
Earl Ou7ae955b2016-11-30 16:41:22 +0800148 case 'v':
149 config->verbose = true;
150 break;
151 case 'h':
152 return false;
153 default:
154 fprintf(stderr, "Unknown arguments %c\n", opt);
155 assert(false);
156 }
157 }
158
159 if (config->player_command.empty()) {
160 fprintf(stderr, "player-command is not set.\n");
161 return false;
162 }
163
164 if (config->recorder_command.empty()) {
165 fprintf(stderr, "recorder-command is not set.\n");
166 return false;
167 }
168
169 if (config->active_speaker_channels.empty()) {
170 for (int i = 0; i < config->num_speaker_channels; ++i) {
171 config->active_speaker_channels.insert(i);
172 }
173 }
Ting Shena2834cb2017-03-07 14:49:05 +0800174
175 if (config->active_mic_channels.empty()) {
176 for (int i = 0; i < config->num_mic_channels; ++i) {
177 config->active_mic_channels.insert(i);
178 }
179 }
cyuehf6167eb2019-08-12 14:25:23 +0800180
181 if (config->min_frequency > config->max_frequency) {
182 fprintf(stderr, "Range error: min_frequency > max_frequency\n");
183 return false;
184 }
185
186 if (config->min_frequency < 0) {
187 fprintf(stderr, "Range error: min_frequency < 0\n");
188 return false;
189 }
Earl Ou7ae955b2016-11-30 16:41:22 +0800190 return true;
191}
192
193void PrintUsage(const char *name, FILE *fd = stderr) {
194 AudioFunTestConfig default_config;
195
196 fprintf(fd,
197 "Usage %s -P <player_command> -R <recorder_command> [options]\n",
198 name);
199 fprintf(fd,
200 "\t-a, --active-speaker-channels:\n"
201 "\t\tComma-separated list of speaker channels to play on. "
202 "(def all channels)\n");
203 fprintf(fd,
Ting Shena2834cb2017-03-07 14:49:05 +0800204 "\t-m, --active-mic-channels:\n"
205 "\t\tComma-separated list of mic channels to test. "
206 "(def all channels)\n");
207 fprintf(fd,
Earl Ou7ae955b2016-11-30 16:41:22 +0800208 "\t-d, --allowed-delay:\n"
209 "\t\tAllowed latency between player & recorder "
210 "(def %.4f).\n", default_config.allowed_delay_sec);
211 fprintf(fd,
212 "\t-n, --fftsize:\n"
213 "\t\tLonger fftsize has more carriers but longer latency."
214 " Also, fftsize needs to be power of 2"
215 "(def %d)\n", default_config.fft_size);
216 fprintf(fd,
217 "\t-o, --confidence-threshold:\n"
218 "\t\tThreshold of accumulated confidence to pass evaluation "
219 "(def %.4f)\n", default_config.confidence_threshold);
220 fprintf(fd,
Earl Ou06c1ad62016-12-01 17:21:50 +0800221 "\t-w, --match-window-size:\n"
222 "\t\tNumber of bin to be used for calculating matching confidence. "
223 "Should be an odd number."
224 "(def %d)\n", default_config.match_window_size);
225 fprintf(fd,
Earl Ou7ae955b2016-11-30 16:41:22 +0800226 "\t-P, --player-command:\n"
227 "\t\tThe command used to play sound.\n");
228 fprintf(fd,
229 "\t-f, --player-fifo:\n"
230 "\t\tThe named pipe used to send wave to the player. If not set, "
231 "wave is send to player program via its stdin.\n");
232 fprintf(fd,
233 "\t-R, --recorder-command:\n"
234 "\t\tThe command used to record sound.\n");
235 fprintf(fd,
236 "\t-F, --recorder-fifo:\n"
237 "\t\tThe named pipe used to read recorded wave from the recorder "
238 "program. If not set, wave is read from recorder program via "
239 "its stdout.\n");
240 fprintf(fd,
241 "\t-r, --sample-rate:\n"
242 "\t\tSample rate of generated wave in HZ "
243 "(def %d)\n", default_config.sample_rate);
244 fprintf(fd,
245 "\t-t, --sample-format:\n"
246 "\t\tFormat of recording & playing samples, should be one of u8, "
247 "s16, s24, s32."
Ting Shen95141ed2017-01-05 18:41:43 +0800248 "(def %s).\n", default_config.sample_format.to_string());
Earl Ou7ae955b2016-11-30 16:41:22 +0800249 fprintf(fd,
250 "\t-c, --num-mic-channels:\n"
251 "\t\tThe number of microphone channels "
252 "(def %d)\n", default_config.num_mic_channels);
253 fprintf(fd,
254 "\t-C, --num-speaker-channels:\n"
255 "\t\tThe number of speaker channels "
256 "(def %d)\n", default_config.num_speaker_channels);
257 fprintf(fd,
258 "\t-T, --test-rounds:\n"
259 "\t\tNumber of test rounds "
260 "(def %d)\n", default_config.test_rounds);
261 fprintf(fd,
262 "\t-l, --tone-length:\n"
263 "\t\tDecimal value of tone length in secs "
264 "(def %.4f)\n", default_config.tone_length_sec);
265 fprintf(fd,
Marco Chen6b3e3312017-09-29 17:02:22 +0800266 "\t-g, --volume-gain\n"
267 "\t\tControl the volume of generated audio frames. The range is from"
cyuehf6167eb2019-08-12 14:25:23 +0800268 " 0 to 100.\n");
269 fprintf(fd,
270 "\t-i, --min-frequency:\n"
271 "\t\tThe minimum frequency of generated audio frames."
272 "(def %d)\n", default_config.min_frequency);
273 fprintf(fd,
274 "\t-x, --max-frequency\n"
275 "\t\tThe maximum frequency of generated audio frames."
276 "(def %d)\n", default_config.max_frequency);
Marco Chen6b3e3312017-09-29 17:02:22 +0800277
278 fprintf(fd,
Earl Ou7ae955b2016-11-30 16:41:22 +0800279 "\t-v, --verbose: Show debugging information.\n");
280 fprintf(fd,
281 "\t-h, --help: Show this page.\n");
282}
283
Ting Shena2834cb2017-03-07 14:49:05 +0800284void PrintSet(const std::set<int> &numbers, FILE *fd = stdout) {
Earl Ou7ae955b2016-11-30 16:41:22 +0800285 bool first = true;
Ting Shena2834cb2017-03-07 14:49:05 +0800286 for (auto it : numbers) {
Earl Ou7ae955b2016-11-30 16:41:22 +0800287 if (!first)
288 fprintf(fd, ", ");
289 fprintf(fd, "%d", it);
290 first = false;
291 }
Ting Shena2834cb2017-03-07 14:49:05 +0800292}
293
294void PrintConfig(const AudioFunTestConfig &config, FILE *fd = stdout) {
295 fprintf(fd, "Config values.\n");
296
297 fprintf(fd, "\tSpeaker active channels: ");
298 PrintSet(config.active_speaker_channels, fd);
299 fprintf(fd, "\n");
300 fprintf(fd, "\tMic active channels: ");
301 PrintSet(config.active_mic_channels, fd);
Earl Ou7ae955b2016-11-30 16:41:22 +0800302 fprintf(fd, "\n");
303 fprintf(fd, "\tAllowed delay: %.4f(s)\n", config.allowed_delay_sec);
304 fprintf(fd, "\tFFT size: %d\n", config.fft_size);
305 fprintf(fd, "\tConfidence threshold: %.4f\n", config.confidence_threshold);
Earl Ou06c1ad62016-12-01 17:21:50 +0800306 fprintf(fd, "\tMatch window size: %d\n", config.match_window_size);
Earl Ou7ae955b2016-11-30 16:41:22 +0800307 fprintf(fd, "\tPlayer parameter: %s\n", config.player_command.c_str());
308 fprintf(fd, "\tPlayer FIFO name: %s\n", config.player_fifo.c_str());
309 fprintf(fd, "\tRecorder parameter: %s\n", config.recorder_command.c_str());
310 fprintf(fd, "\tRecorder FIFO name: %s\n", config.recorder_fifo.c_str());
311 fprintf(fd, "\tSample format: %s\n", config.sample_format.to_string());
312 fprintf(fd, "\tSample rate: %d\n", config.sample_rate);
313 fprintf(fd,
314 "\tNumber of Microphone channels: %d\n", config.num_mic_channels);
315 fprintf(fd, "\tNumber of Speaker channels: %d\n",
316 config.num_speaker_channels);
317 fprintf(fd, "\tNumber of test rounds: %d\n", config.test_rounds);
318 fprintf(fd, "\tTone length: %.4f(s)\n", config.tone_length_sec);
Marco Chen6b3e3312017-09-29 17:02:22 +0800319 fprintf(fd, "\tVolume gain: %d\n", config.volume_gain);
cyuehf6167eb2019-08-12 14:25:23 +0800320 fprintf(fd, "\tMinimum frequency: %d\n", config.min_frequency);
321 fprintf(fd, "\tMaximum frequency: %d\n", config.max_frequency);
Earl Ou7ae955b2016-11-30 16:41:22 +0800322
323 if (config.verbose)
324 fprintf(fd, "\t** Verbose **.\n");
325}
Amy Lin8c768a72016-08-01 15:44:16 +0800326
327// Randomly picks an integer from the given range [min, max],
328// including both end points.
329inline int RandomPick(int min, int max) {
330 if (min > max) {
Earl Ou7ae955b2016-11-30 16:41:22 +0800331 fprintf(stderr, "Range error: min > max\n");
Amy Lin8c768a72016-08-01 15:44:16 +0800332 assert(false);
333 }
334
335 static unsigned int seed = time(NULL) + getpid();
336 return (rand_r(&seed) % (max - min + 1)) + min;
337}
338
339// Controls the main process of audiofuntest.
Earl Ou7ae955b2016-11-30 16:41:22 +0800340void ControlLoop(const AudioFunTestConfig &config,
Amy Lin8c768a72016-08-01 15:44:16 +0800341 Evaluator *evaluator,
Earl Ou06c1ad62016-12-01 17:21:50 +0800342 PlayClient *player,
cyuehf6167eb2019-08-12 14:25:23 +0800343 RecordClient *recorder) {
Amy Lin8c768a72016-08-01 15:44:16 +0800344 const double frequency_resolution =
345 static_cast<double>(config.sample_rate) / config.fft_size;
cyuehf6167eb2019-08-12 14:25:23 +0800346 const int min_bin = config.min_frequency / frequency_resolution;
347 const int max_bin = config.max_frequency / frequency_resolution;
Amy Lin8c768a72016-08-01 15:44:16 +0800348
Earl Ou7ae955b2016-11-30 16:41:22 +0800349 std::vector<int> passes(config.num_mic_channels);
350 std::vector<bool> single_round_pass(config.num_mic_channels);
Amy Lin8c768a72016-08-01 15:44:16 +0800351
Earl Ou06c1ad62016-12-01 17:21:50 +0800352 size_t buf_size = config.fft_size * config.num_speaker_channels *
353 config.sample_format.bytes();
Marco Chen6b3e3312017-09-29 17:02:22 +0800354 SineWaveGenerator generator(
355 config.sample_rate,
356 config.tone_length_sec,
357 config.volume_gain);
Earl Ou06c1ad62016-12-01 17:21:50 +0800358 GeneratorPlayer generatorPlayer(
359 buf_size,
360 config.num_speaker_channels,
361 config.active_speaker_channels,
362 config.sample_format,
363 player);
364
Amy Lin8c768a72016-08-01 15:44:16 +0800365 for (int round = 1; round <= config.test_rounds; ++round) {
366 std::fill(single_round_pass.begin(), single_round_pass.end(), false);
367 int bin = RandomPick(min_bin, max_bin);
368 double frequency = bin * frequency_resolution;
369
Earl Ou06c1ad62016-12-01 17:21:50 +0800370 generator.Reset(frequency);
371 generatorPlayer.Play(&generator);
Amy Lin8c768a72016-08-01 15:44:16 +0800372
Earl Ou06c1ad62016-12-01 17:21:50 +0800373 evaluator->Evaluate(bin, recorder, &single_round_pass);
Amy Lin8c768a72016-08-01 15:44:16 +0800374 for (int chn = 0; chn < config.num_mic_channels; ++chn) {
375 if (single_round_pass[chn]) {
376 ++passes[chn];
377 }
378 }
Earl Ou06c1ad62016-12-01 17:21:50 +0800379 generatorPlayer.Stop();
Amy Lin8c768a72016-08-01 15:44:16 +0800380
Earl Ou7ae955b2016-11-30 16:41:22 +0800381 printf("carrier = %d\n", bin);
Ting Shena2834cb2017-03-07 14:49:05 +0800382 for (auto c : config.active_mic_channels) {
Earl Ou733306b2016-11-14 14:26:04 +0800383 const char *res = single_round_pass[c] ? "O" : "X";
Earl Ou7ae955b2016-11-30 16:41:22 +0800384 printf("%s: channel = %d, success = %d, fail = %d, rate = %.4f\n",
385 res, c, passes[c], round - passes[c], 100.0 * passes[c] / round);
Amy Lin8c768a72016-08-01 15:44:16 +0800386 }
Amy Lin8c768a72016-08-01 15:44:16 +0800387 }
388}
389
390int main(int argc, char *argv[]) {
Earl Ou7ae955b2016-11-30 16:41:22 +0800391 // Parses configuration.ParamConfig
392 AudioFunTestConfig config;
393 if (!ParseOptions(argc, argv, &config)) {
394 PrintUsage(argv[0]);
395 return 1;
Amy Lin8c768a72016-08-01 15:44:16 +0800396 }
Earl Ou7ae955b2016-11-30 16:41:22 +0800397
398 PrintConfig(config);
Amy Lin8c768a72016-08-01 15:44:16 +0800399
Amy Lin8c768a72016-08-01 15:44:16 +0800400 PlayClient player(config);
Earl Ou5afc8d32016-12-01 11:30:52 +0800401 player.Start();
Amy Lin8c768a72016-08-01 15:44:16 +0800402
403 RecordClient recorder(config);
Earl Ou5afc8d32016-12-01 11:30:52 +0800404 recorder.Start();
Amy Lin8c768a72016-08-01 15:44:16 +0800405
406 Evaluator evaluator(config);
407
Amy Lin8c768a72016-08-01 15:44:16 +0800408 // Starts evaluation.
Earl Ou06c1ad62016-12-01 17:21:50 +0800409 ControlLoop(config, &evaluator, &player, &recorder);
Amy Lin8c768a72016-08-01 15:44:16 +0800410
411 // Terminates and cleans up.
412 recorder.Terminate();
Amy Lin8c768a72016-08-01 15:44:16 +0800413 player.Terminate();
414
415 return 0;
416}