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