Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 1 | // 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 Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 5 | #include <assert.h> |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 6 | #include <getopt.h> |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 7 | #include <stdlib.h> |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 8 | #include <string.h> |
| 9 | #include <sys/types.h> |
| 10 | #include <unistd.h> |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 11 | |
Earl Ou | 5afc8d3 | 2016-12-01 11:30:52 +0800 | [diff] [blame] | 12 | #include "include/binary_client.h" |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 13 | #include "include/common.h" |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 14 | #include "include/evaluator.h" |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 15 | #include "include/generator_player.h" |
| 16 | #include "include/sample_format.h" |
| 17 | #include "include/tone_generators.h" |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 18 | |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 19 | constexpr static const char *short_options = |
| 20 | "a:d:n:o:w:P:f:R:F:r:t:c:C:T:l:hv"; |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 21 | |
| 22 | constexpr static const struct option long_options[] = { |
| 23 | {"active-speaker-channels", 1, NULL, 'a'}, |
| 24 | {"allowed-delay", 1, NULL, 'd'}, |
| 25 | {"fft-size", 1, NULL, 'n'}, |
| 26 | {"confidence-threshold", 1, NULL, 'o'}, |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 27 | {"match-window-size", 1, NULL, 'w'}, |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 28 | {"player-command", 1, NULL, 'P'}, |
| 29 | {"player-fifo", 1, NULL, 'f'}, |
| 30 | {"recorder-command", 1, NULL, 'R'}, |
| 31 | {"recorder-fifo", 1, NULL, 'F'}, |
| 32 | {"sample-rate", 1, NULL, 'r'}, |
| 33 | {"sample-format", 1, NULL, 't'}, |
| 34 | {"num-mic-channels", 1, NULL, 'c'}, |
| 35 | {"num-speaker-channels", 1, NULL, 'C'}, |
| 36 | {"test-rounds", 1, NULL, 'T'}, |
| 37 | {"tone-length", 1, NULL, 'l'}, |
| 38 | |
| 39 | // Other helper args. |
| 40 | {"help", 0, NULL, 'h'}, |
| 41 | {"verbose", 0, NULL, 'v'}, |
| 42 | }; |
| 43 | |
| 44 | // Parse the sample format. The input should be one of the string in |
| 45 | // SampleFormat:Type. |
| 46 | SampleFormat ParseSampleFormat(const char *arg) { |
| 47 | SampleFormat sample_format; |
| 48 | for (int format = SampleFormat::kPcmU8; |
| 49 | format != SampleFormat::kPcmInvalid; |
| 50 | format++) { |
| 51 | sample_format = SampleFormat(SampleFormat::Type(format)); |
| 52 | if (strcmp(sample_format.to_string(), optarg) == 0) { |
| 53 | return sample_format; |
| 54 | } |
| 55 | } |
| 56 | fprintf(stderr, "Unknown sample format %s, using S16 instead.", arg); |
| 57 | return SampleFormat(SampleFormat::kPcmS16); |
| 58 | } |
| 59 | |
| 60 | bool ParseOptions(int argc, char *const argv[], AudioFunTestConfig *config) { |
| 61 | int opt = 0; |
| 62 | int optindex = -1; |
| 63 | |
| 64 | while ((opt = getopt_long(argc, argv, short_options, |
| 65 | long_options, |
| 66 | &optindex)) != -1) { |
| 67 | switch (opt) { |
| 68 | case 'a': |
| 69 | ParseActiveChannels(optarg, &(config->active_speaker_channels)); |
| 70 | break; |
| 71 | case 'd': |
| 72 | config->allowed_delay_sec = atof(optarg); |
| 73 | break; |
| 74 | case 'n': |
| 75 | config->fft_size = atoi(optarg); |
| 76 | if ((config->fft_size == 0) || |
| 77 | (config->fft_size & (config->fft_size - 1))) { |
| 78 | fprintf(stderr, "FFT size needs to be positive & power of 2\n"); |
| 79 | return false; |
| 80 | } |
| 81 | break; |
| 82 | case 'o': |
| 83 | config->confidence_threshold = atof(optarg); |
| 84 | break; |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 85 | case 'w': |
| 86 | config->match_window_size = atoi(optarg); |
| 87 | if (config->match_window_size % 2 == 0) { |
| 88 | fprintf(stderr, "Match window size must be an odd value.\n"); |
| 89 | return false; |
| 90 | } |
| 91 | break; |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 92 | case 'P': |
| 93 | config->player_command = std::string(optarg); |
| 94 | break; |
| 95 | case 'f': |
| 96 | config->player_fifo = std::string(optarg); |
| 97 | break; |
| 98 | case 'R': |
| 99 | config->recorder_command = std::string(optarg); |
| 100 | break; |
| 101 | case 'F': |
| 102 | config->recorder_fifo = std::string(optarg); |
| 103 | break; |
| 104 | case 'r': |
| 105 | config->sample_rate = atoi(optarg); |
| 106 | break; |
| 107 | case 't': |
| 108 | config->sample_format = ParseSampleFormat(optarg); |
| 109 | break; |
| 110 | case 'c': |
| 111 | config->num_mic_channels = atoi(optarg); |
| 112 | break; |
| 113 | case 'C': |
| 114 | config->num_speaker_channels = atoi(optarg); |
| 115 | break; |
| 116 | case 'T': |
| 117 | config->test_rounds = atoi(optarg); |
| 118 | break; |
| 119 | case 'l': |
| 120 | config->tone_length_sec = atof(optarg); |
| 121 | // Avoid overly short tones. |
| 122 | if (config->tone_length_sec < 0.01) { |
| 123 | fprintf(stderr, |
| 124 | "Tone length too short. Must be 0.01s or greater.\n"); |
| 125 | return false; |
| 126 | } |
| 127 | break; |
| 128 | case 'v': |
| 129 | config->verbose = true; |
| 130 | break; |
| 131 | case 'h': |
| 132 | return false; |
| 133 | default: |
| 134 | fprintf(stderr, "Unknown arguments %c\n", opt); |
| 135 | assert(false); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if (config->player_command.empty()) { |
| 140 | fprintf(stderr, "player-command is not set.\n"); |
| 141 | return false; |
| 142 | } |
| 143 | |
| 144 | if (config->recorder_command.empty()) { |
| 145 | fprintf(stderr, "recorder-command is not set.\n"); |
| 146 | return false; |
| 147 | } |
| 148 | |
| 149 | if (config->active_speaker_channels.empty()) { |
| 150 | for (int i = 0; i < config->num_speaker_channels; ++i) { |
| 151 | config->active_speaker_channels.insert(i); |
| 152 | } |
| 153 | } |
| 154 | return true; |
| 155 | } |
| 156 | |
| 157 | void PrintUsage(const char *name, FILE *fd = stderr) { |
| 158 | AudioFunTestConfig default_config; |
| 159 | |
| 160 | fprintf(fd, |
| 161 | "Usage %s -P <player_command> -R <recorder_command> [options]\n", |
| 162 | name); |
| 163 | fprintf(fd, |
| 164 | "\t-a, --active-speaker-channels:\n" |
| 165 | "\t\tComma-separated list of speaker channels to play on. " |
| 166 | "(def all channels)\n"); |
| 167 | fprintf(fd, |
| 168 | "\t-d, --allowed-delay:\n" |
| 169 | "\t\tAllowed latency between player & recorder " |
| 170 | "(def %.4f).\n", default_config.allowed_delay_sec); |
| 171 | fprintf(fd, |
| 172 | "\t-n, --fftsize:\n" |
| 173 | "\t\tLonger fftsize has more carriers but longer latency." |
| 174 | " Also, fftsize needs to be power of 2" |
| 175 | "(def %d)\n", default_config.fft_size); |
| 176 | fprintf(fd, |
| 177 | "\t-o, --confidence-threshold:\n" |
| 178 | "\t\tThreshold of accumulated confidence to pass evaluation " |
| 179 | "(def %.4f)\n", default_config.confidence_threshold); |
| 180 | fprintf(fd, |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 181 | "\t-w, --match-window-size:\n" |
| 182 | "\t\tNumber of bin to be used for calculating matching confidence. " |
| 183 | "Should be an odd number." |
| 184 | "(def %d)\n", default_config.match_window_size); |
| 185 | fprintf(fd, |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 186 | "\t-P, --player-command:\n" |
| 187 | "\t\tThe command used to play sound.\n"); |
| 188 | fprintf(fd, |
| 189 | "\t-f, --player-fifo:\n" |
| 190 | "\t\tThe named pipe used to send wave to the player. If not set, " |
| 191 | "wave is send to player program via its stdin.\n"); |
| 192 | fprintf(fd, |
| 193 | "\t-R, --recorder-command:\n" |
| 194 | "\t\tThe command used to record sound.\n"); |
| 195 | fprintf(fd, |
| 196 | "\t-F, --recorder-fifo:\n" |
| 197 | "\t\tThe named pipe used to read recorded wave from the recorder " |
| 198 | "program. If not set, wave is read from recorder program via " |
| 199 | "its stdout.\n"); |
| 200 | fprintf(fd, |
| 201 | "\t-r, --sample-rate:\n" |
| 202 | "\t\tSample rate of generated wave in HZ " |
| 203 | "(def %d)\n", default_config.sample_rate); |
| 204 | fprintf(fd, |
| 205 | "\t-t, --sample-format:\n" |
| 206 | "\t\tFormat of recording & playing samples, should be one of u8, " |
| 207 | "s16, s24, s32." |
Ting Shen | 95141ed | 2017-01-05 18:41:43 +0800 | [diff] [blame^] | 208 | "(def %s).\n", default_config.sample_format.to_string()); |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 209 | fprintf(fd, |
| 210 | "\t-c, --num-mic-channels:\n" |
| 211 | "\t\tThe number of microphone channels " |
| 212 | "(def %d)\n", default_config.num_mic_channels); |
| 213 | fprintf(fd, |
| 214 | "\t-C, --num-speaker-channels:\n" |
| 215 | "\t\tThe number of speaker channels " |
| 216 | "(def %d)\n", default_config.num_speaker_channels); |
| 217 | fprintf(fd, |
| 218 | "\t-T, --test-rounds:\n" |
| 219 | "\t\tNumber of test rounds " |
| 220 | "(def %d)\n", default_config.test_rounds); |
| 221 | fprintf(fd, |
| 222 | "\t-l, --tone-length:\n" |
| 223 | "\t\tDecimal value of tone length in secs " |
| 224 | "(def %.4f)\n", default_config.tone_length_sec); |
| 225 | fprintf(fd, |
| 226 | "\t-v, --verbose: Show debugging information.\n"); |
| 227 | fprintf(fd, |
| 228 | "\t-h, --help: Show this page.\n"); |
| 229 | } |
| 230 | |
| 231 | void PrintConfig(const AudioFunTestConfig &config, FILE *fd = stdout) { |
| 232 | fprintf(fd, "Config values.\n"); |
| 233 | |
| 234 | fprintf(fd, "\tSpeaker active channels: "); |
| 235 | bool first = true; |
| 236 | for (auto it : config.active_speaker_channels) { |
| 237 | if (!first) |
| 238 | fprintf(fd, ", "); |
| 239 | fprintf(fd, "%d", it); |
| 240 | first = false; |
| 241 | } |
| 242 | fprintf(fd, "\n"); |
| 243 | fprintf(fd, "\tAllowed delay: %.4f(s)\n", config.allowed_delay_sec); |
| 244 | fprintf(fd, "\tFFT size: %d\n", config.fft_size); |
| 245 | fprintf(fd, "\tConfidence threshold: %.4f\n", config.confidence_threshold); |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 246 | fprintf(fd, "\tMatch window size: %d\n", config.match_window_size); |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 247 | fprintf(fd, "\tPlayer parameter: %s\n", config.player_command.c_str()); |
| 248 | fprintf(fd, "\tPlayer FIFO name: %s\n", config.player_fifo.c_str()); |
| 249 | fprintf(fd, "\tRecorder parameter: %s\n", config.recorder_command.c_str()); |
| 250 | fprintf(fd, "\tRecorder FIFO name: %s\n", config.recorder_fifo.c_str()); |
| 251 | fprintf(fd, "\tSample format: %s\n", config.sample_format.to_string()); |
| 252 | fprintf(fd, "\tSample rate: %d\n", config.sample_rate); |
| 253 | fprintf(fd, |
| 254 | "\tNumber of Microphone channels: %d\n", config.num_mic_channels); |
| 255 | fprintf(fd, "\tNumber of Speaker channels: %d\n", |
| 256 | config.num_speaker_channels); |
| 257 | fprintf(fd, "\tNumber of test rounds: %d\n", config.test_rounds); |
| 258 | fprintf(fd, "\tTone length: %.4f(s)\n", config.tone_length_sec); |
| 259 | |
| 260 | if (config.verbose) |
| 261 | fprintf(fd, "\t** Verbose **.\n"); |
| 262 | } |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 263 | |
| 264 | // Randomly picks an integer from the given range [min, max], |
| 265 | // including both end points. |
| 266 | inline int RandomPick(int min, int max) { |
| 267 | if (min > max) { |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 268 | fprintf(stderr, "Range error: min > max\n"); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 269 | assert(false); |
| 270 | } |
| 271 | |
| 272 | static unsigned int seed = time(NULL) + getpid(); |
| 273 | return (rand_r(&seed) % (max - min + 1)) + min; |
| 274 | } |
| 275 | |
| 276 | // Controls the main process of audiofuntest. |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 277 | void ControlLoop(const AudioFunTestConfig &config, |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 278 | Evaluator *evaluator, |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 279 | PlayClient *player, |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 280 | RecordClient *recorder, |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 281 | const int min_frequency = 4000, |
| 282 | const int max_frequency = 10000) { |
| 283 | const double frequency_resolution = |
| 284 | static_cast<double>(config.sample_rate) / config.fft_size; |
| 285 | const int min_bin = min_frequency / frequency_resolution; |
| 286 | const int max_bin = max_frequency / frequency_resolution; |
| 287 | |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 288 | std::vector<int> passes(config.num_mic_channels); |
| 289 | std::vector<bool> single_round_pass(config.num_mic_channels); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 290 | |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 291 | size_t buf_size = config.fft_size * config.num_speaker_channels * |
| 292 | config.sample_format.bytes(); |
| 293 | SineWaveGenerator generator(config.sample_rate, config.tone_length_sec); |
| 294 | GeneratorPlayer generatorPlayer( |
| 295 | buf_size, |
| 296 | config.num_speaker_channels, |
| 297 | config.active_speaker_channels, |
| 298 | config.sample_format, |
| 299 | player); |
| 300 | |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 301 | for (int round = 1; round <= config.test_rounds; ++round) { |
| 302 | std::fill(single_round_pass.begin(), single_round_pass.end(), false); |
| 303 | int bin = RandomPick(min_bin, max_bin); |
| 304 | double frequency = bin * frequency_resolution; |
| 305 | |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 306 | generator.Reset(frequency); |
| 307 | generatorPlayer.Play(&generator); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 308 | |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 309 | evaluator->Evaluate(bin, recorder, &single_round_pass); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 310 | for (int chn = 0; chn < config.num_mic_channels; ++chn) { |
| 311 | if (single_round_pass[chn]) { |
| 312 | ++passes[chn]; |
| 313 | } |
| 314 | } |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 315 | generatorPlayer.Stop(); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 316 | |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 317 | printf("carrier = %d\n", bin); |
| 318 | for (int c = 0; c < config.num_mic_channels; ++c) { |
Earl Ou | 733306b | 2016-11-14 14:26:04 +0800 | [diff] [blame] | 319 | const char *res = single_round_pass[c] ? "O" : "X"; |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 320 | printf("%s: channel = %d, success = %d, fail = %d, rate = %.4f\n", |
| 321 | res, c, passes[c], round - passes[c], 100.0 * passes[c] / round); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 322 | } |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 323 | } |
| 324 | } |
| 325 | |
| 326 | int main(int argc, char *argv[]) { |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 327 | // Parses configuration.ParamConfig |
| 328 | AudioFunTestConfig config; |
| 329 | if (!ParseOptions(argc, argv, &config)) { |
| 330 | PrintUsage(argv[0]); |
| 331 | return 1; |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 332 | } |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 333 | |
| 334 | PrintConfig(config); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 335 | |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 336 | PlayClient player(config); |
Earl Ou | 5afc8d3 | 2016-12-01 11:30:52 +0800 | [diff] [blame] | 337 | player.Start(); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 338 | |
| 339 | RecordClient recorder(config); |
Earl Ou | 5afc8d3 | 2016-12-01 11:30:52 +0800 | [diff] [blame] | 340 | recorder.Start(); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 341 | |
| 342 | Evaluator evaluator(config); |
| 343 | |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 344 | // Starts evaluation. |
Earl Ou | 06c1ad6 | 2016-12-01 17:21:50 +0800 | [diff] [blame] | 345 | ControlLoop(config, &evaluator, &player, &recorder); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 346 | |
| 347 | // Terminates and cleans up. |
| 348 | recorder.Terminate(); |
Amy Lin | 8c768a7 | 2016-08-01 15:44:16 +0800 | [diff] [blame] | 349 | player.Terminate(); |
| 350 | |
| 351 | return 0; |
| 352 | } |