bat.c (18683B)
1 /* 2 * Copyright (C) 2013-2015 Intel Corporation 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 */ 15 16 #include <unistd.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <stdbool.h> 20 #include <string.h> 21 #include <errno.h> 22 #include <pthread.h> 23 #include <getopt.h> 24 #include <math.h> 25 #include <limits.h> 26 #include <locale.h> 27 #include <math.h> 28 29 #include "aconfig.h" 30 #include "gettext.h" 31 #include "version.h" 32 33 #include "common.h" 34 35 #ifdef HAVE_LIBTINYALSA 36 #include "tinyalsa.h" 37 #else 38 #include "alsa.h" 39 #endif 40 #include "convert.h" 41 #ifdef HAVE_LIBFFTW3F 42 #include "analyze.h" 43 #endif 44 #include "latencytest.h" 45 46 /* get snr threshold in dB */ 47 static void get_snr_thd_db(struct bat *bat, char *thd) 48 { 49 int err; 50 float thd_db; 51 char *ptrf; 52 53 thd_db = strtof(thd, &ptrf); 54 err = -errno; 55 if (!snr_is_valid(thd_db)) { 56 fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err); 57 exit(EXIT_FAILURE); 58 } 59 bat->snr_thd_db = thd_db; 60 } 61 62 /* get snr threshold in %, and convert to dB */ 63 static void get_snr_thd_pc(struct bat *bat, char *thd) 64 { 65 int err; 66 float thd_pc; 67 char *ptrf; 68 69 thd_pc = strtof(thd, &ptrf); 70 err = -errno; 71 if (thd_pc <= 0.0 || thd_pc >= 100.0) { 72 fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err); 73 exit(EXIT_FAILURE); 74 } 75 bat->snr_thd_db = 20.0 * log10f(100.0 / thd_pc); 76 } 77 78 static int get_duration(struct bat *bat) 79 { 80 int err; 81 float duration_f; 82 long duration_i; 83 char *ptrf, *ptri; 84 85 duration_f = strtof(bat->narg, &ptrf); 86 err = -errno; 87 if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF 88 || (duration_f == 0.0 && err != 0)) 89 goto err_exit; 90 91 duration_i = strtol(bat->narg, &ptri, 10); 92 if (duration_i == LONG_MAX || duration_i == LONG_MIN) 93 goto err_exit; 94 95 if (*ptrf == 's') 96 bat->frames = duration_f * bat->rate; 97 else if (*ptri == 0) 98 bat->frames = duration_i; 99 else 100 bat->frames = -1; 101 102 if (bat->frames <= 0 || bat->frames > MAX_FRAMES) { 103 fprintf(bat->err, _("Invalid duration. Range: (0, %d(%fs))\n"), 104 MAX_FRAMES, (float)MAX_FRAMES / bat->rate); 105 return -EINVAL; 106 } 107 108 return 0; 109 110 err_exit: 111 fprintf(bat->err, _("Duration overflow/underflow: %d\n"), err); 112 113 return err; 114 } 115 116 static void get_sine_frequencies(struct bat *bat, char *freq) 117 { 118 char *tmp1; 119 120 tmp1 = strchr(freq, ':'); 121 if (tmp1 == NULL) { 122 bat->target_freq[1] = bat->target_freq[0] = atof(optarg); 123 } else { 124 *tmp1 = '\0'; 125 bat->target_freq[0] = atof(optarg); 126 bat->target_freq[1] = atof(tmp1 + 1); 127 } 128 } 129 130 static void get_format(struct bat *bat, char *optarg) 131 { 132 if (strcasecmp(optarg, "cd") == 0) { 133 bat->format = BAT_PCM_FORMAT_S16_LE; 134 bat->rate = 44100; 135 bat->channels = 2; 136 bat->sample_size = 2; 137 } else if (strcasecmp(optarg, "dat") == 0) { 138 bat->format = BAT_PCM_FORMAT_S16_LE; 139 bat->rate = 48000; 140 bat->channels = 2; 141 bat->sample_size = 2; 142 } else if (strcasecmp(optarg, "U8") == 0) { 143 bat->format = BAT_PCM_FORMAT_U8; 144 bat->sample_size = 1; 145 } else if (strcasecmp(optarg, "S16_LE") == 0) { 146 bat->format = BAT_PCM_FORMAT_S16_LE; 147 bat->sample_size = 2; 148 } else if (strcasecmp(optarg, "S24_3LE") == 0) { 149 bat->format = BAT_PCM_FORMAT_S24_3LE; 150 bat->sample_size = 3; 151 } else if (strcasecmp(optarg, "S32_LE") == 0) { 152 bat->format = BAT_PCM_FORMAT_S32_LE; 153 bat->sample_size = 4; 154 } else { 155 bat->format = BAT_PCM_FORMAT_UNKNOWN; 156 fprintf(bat->err, _("wrong extended format '%s'\n"), optarg); 157 exit(EXIT_FAILURE); 158 } 159 } 160 161 static inline int thread_wait_completion(struct bat *bat, 162 pthread_t id, int **val) 163 { 164 int err; 165 166 err = pthread_join(id, (void **) val); 167 if (err) 168 pthread_cancel(id); 169 170 return err; 171 } 172 173 /* loopback test where we play sine wave and capture the same sine wave */ 174 static void test_loopback(struct bat *bat) 175 { 176 pthread_t capture_id, playback_id; 177 int err; 178 int *thread_result_capture, *thread_result_playback; 179 180 /* start playback */ 181 err = pthread_create(&playback_id, NULL, 182 (void *) bat->playback.fct, bat); 183 if (err != 0) { 184 fprintf(bat->err, _("Cannot create playback thread: %d\n"), 185 err); 186 exit(EXIT_FAILURE); 187 } 188 189 /* TODO: use a pipe to signal stream start etc - i.e. to sync threads */ 190 /* Let some time for playing something before capturing */ 191 usleep(CAPTURE_DELAY * 1000); 192 193 /* start capture */ 194 err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat); 195 if (err != 0) { 196 fprintf(bat->err, _("Cannot create capture thread: %d\n"), err); 197 pthread_cancel(playback_id); 198 exit(EXIT_FAILURE); 199 } 200 201 /* wait for playback to complete */ 202 err = thread_wait_completion(bat, playback_id, &thread_result_playback); 203 if (err != 0) { 204 fprintf(bat->err, _("Cannot join playback thread: %d\n"), err); 205 free(thread_result_playback); 206 pthread_cancel(capture_id); 207 exit(EXIT_FAILURE); 208 } 209 210 /* check playback status */ 211 if (*thread_result_playback != 0) { 212 fprintf(bat->err, _("Exit playback thread fail: %d\n"), 213 *thread_result_playback); 214 pthread_cancel(capture_id); 215 exit(EXIT_FAILURE); 216 } else { 217 fprintf(bat->log, _("Playback completed.\n")); 218 } 219 220 /* now stop and wait for capture to finish */ 221 pthread_cancel(capture_id); 222 err = thread_wait_completion(bat, capture_id, &thread_result_capture); 223 if (err != 0) { 224 fprintf(bat->err, _("Cannot join capture thread: %d\n"), err); 225 free(thread_result_capture); 226 exit(EXIT_FAILURE); 227 } 228 229 /* check if capture thread is canceled or not */ 230 if (thread_result_capture == PTHREAD_CANCELED) { 231 fprintf(bat->log, _("Capture canceled.\n")); 232 return; 233 } 234 235 /* check capture status */ 236 if (*thread_result_capture != 0) { 237 fprintf(bat->err, _("Exit capture thread fail: %d\n"), 238 *thread_result_capture); 239 exit(EXIT_FAILURE); 240 } else { 241 fprintf(bat->log, _("Capture completed.\n")); 242 } 243 } 244 245 /* single ended playback only test */ 246 static void test_playback(struct bat *bat) 247 { 248 pthread_t playback_id; 249 int err; 250 int *thread_result; 251 252 /* start playback */ 253 err = pthread_create(&playback_id, NULL, 254 (void *) bat->playback.fct, bat); 255 if (err != 0) { 256 fprintf(bat->err, _("Cannot create playback thread: %d\n"), 257 err); 258 exit(EXIT_FAILURE); 259 } 260 261 /* wait for playback to complete */ 262 err = thread_wait_completion(bat, playback_id, &thread_result); 263 if (err != 0) { 264 fprintf(bat->err, _("Cannot join playback thread: %d\n"), err); 265 free(thread_result); 266 exit(EXIT_FAILURE); 267 } 268 269 /* check playback status */ 270 if (*thread_result != 0) { 271 fprintf(bat->err, _("Exit playback thread fail: %d\n"), 272 *thread_result); 273 exit(EXIT_FAILURE); 274 } else { 275 fprintf(bat->log, _("Playback completed.\n")); 276 } 277 } 278 279 /* single ended capture only test */ 280 static void test_capture(struct bat *bat) 281 { 282 pthread_t capture_id; 283 int err; 284 int *thread_result; 285 286 /* start capture */ 287 err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat); 288 if (err != 0) { 289 fprintf(bat->err, _("Cannot create capture thread: %d\n"), err); 290 exit(EXIT_FAILURE); 291 } 292 293 /* TODO: stop capture */ 294 295 /* wait for capture to complete */ 296 err = thread_wait_completion(bat, capture_id, &thread_result); 297 if (err != 0) { 298 fprintf(bat->err, _("Cannot join capture thread: %d\n"), err); 299 free(thread_result); 300 exit(EXIT_FAILURE); 301 } 302 303 /* check playback status */ 304 if (*thread_result != 0) { 305 fprintf(bat->err, _("Exit capture thread fail: %d\n"), 306 *thread_result); 307 exit(EXIT_FAILURE); 308 } else { 309 fprintf(bat->log, _("Capture completed.\n")); 310 } 311 } 312 313 static void usage(struct bat *bat) 314 { 315 fprintf(bat->log, 316 _("Usage: alsabat [-options]...\n" 317 "\n" 318 " -h, --help this help\n" 319 " -D pcm device for both playback and capture\n" 320 " -P pcm device for playback\n" 321 " -C pcm device for capture\n" 322 " -f sample format\n" 323 " -c number of channels\n" 324 " -r sampling rate\n" 325 " -n frames to playback or capture\n" 326 " -k parameter for frequency detecting threshold\n" 327 " -F target frequency\n" 328 " -p total number of periods to play/capture\n" 329 " -B buffer size in frames\n" 330 " -E period size in frames\n" 331 " --log=# file that both stdout and strerr redirecting to\n" 332 " --file=# file for playback\n" 333 " --saveplay=# file that storing playback content, for debug\n" 334 " --local internal loop, set to bypass pcm hardware devices\n" 335 " --standalone standalone mode, to bypass analysis\n" 336 " --roundtriplatency round trip latency mode\n" 337 " --snr-db=# noise detect threshold, in SNR(dB)\n" 338 " --snr-pc=# noise detect threshold, in noise percentage(%%)\n" 339 )); 340 fprintf(bat->log, _("Recognized sample formats are: ")); 341 fprintf(bat->log, _("U8 S16_LE S24_3LE S32_LE\n")); 342 fprintf(bat->log, _("The available format shotcuts are:\n")); 343 fprintf(bat->log, _("-f cd (16 bit little endian, 44100, stereo)\n")); 344 fprintf(bat->log, _("-f dat (16 bit little endian, 48000, stereo)\n")); 345 } 346 347 static void set_defaults(struct bat *bat) 348 { 349 memset(bat, 0, sizeof(struct bat)); 350 351 /* Set default values */ 352 bat->rate = 44100; 353 bat->frame_size = 2; 354 bat->sample_size = 2; 355 bat->format = BAT_PCM_FORMAT_S16_LE; 356 bat->convert_float_to_sample = convert_float_to_int16; 357 bat->convert_sample_to_float = convert_int16_to_float; 358 bat->frames = bat->rate * 2; 359 bat->target_freq[0] = 997.0; 360 bat->target_freq[1] = 997.0; 361 bat->sigma_k = 3.0; 362 bat->snr_thd_db = SNR_DB_INVALID; 363 bat->playback.device = NULL; 364 bat->capture.device = NULL; 365 bat->buf = NULL; 366 bat->local = false; 367 bat->buffer_size = 0; 368 bat->period_size = 0; 369 bat->roundtriplatency = false; 370 #ifdef HAVE_LIBTINYALSA 371 bat->channels = 2; 372 bat->playback.fct = &playback_tinyalsa; 373 bat->capture.fct = &record_tinyalsa; 374 #else 375 bat->channels = 1; 376 bat->playback.fct = &playback_alsa; 377 bat->capture.fct = &record_alsa; 378 #endif 379 bat->playback.mode = MODE_LOOPBACK; 380 bat->capture.mode = MODE_LOOPBACK; 381 bat->period_is_limited = false; 382 bat->log = stdout; 383 bat->err = stderr; 384 } 385 386 static void parse_arguments(struct bat *bat, int argc, char *argv[]) 387 { 388 int c, option_index, err; 389 static const char short_options[] = "D:P:C:f:n:F:c:r:s:k:p:B:E:lth"; 390 static const struct option long_options[] = { 391 {"help", 0, 0, 'h'}, 392 {"log", 1, 0, OPT_LOG}, 393 {"file", 1, 0, OPT_READFILE}, 394 {"saveplay", 1, 0, OPT_SAVEPLAY}, 395 {"local", 0, 0, OPT_LOCAL}, 396 {"standalone", 0, 0, OPT_STANDALONE}, 397 {"roundtriplatency", 0, 0, OPT_ROUNDTRIPLATENCY}, 398 {"snr-db", 1, 0, OPT_SNRTHD_DB}, 399 {"snr-pc", 1, 0, OPT_SNRTHD_PC}, 400 {0, 0, 0, 0} 401 }; 402 403 while ((c = getopt_long(argc, argv, short_options, long_options, 404 &option_index)) != -1) { 405 switch (c) { 406 case OPT_LOG: 407 bat->logarg = optarg; 408 break; 409 case OPT_READFILE: 410 bat->playback.file = optarg; 411 break; 412 case OPT_SAVEPLAY: 413 bat->debugplay = optarg; 414 break; 415 case OPT_LOCAL: 416 bat->local = true; 417 break; 418 case OPT_STANDALONE: 419 bat->standalone = true; 420 break; 421 case OPT_ROUNDTRIPLATENCY: 422 bat->roundtriplatency = true; 423 break; 424 case OPT_SNRTHD_DB: 425 get_snr_thd_db(bat, optarg); 426 break; 427 case OPT_SNRTHD_PC: 428 get_snr_thd_pc(bat, optarg); 429 break; 430 case 'D': 431 if (bat->playback.device == NULL) 432 bat->playback.device = optarg; 433 if (bat->capture.device == NULL) 434 bat->capture.device = optarg; 435 break; 436 case 'P': 437 if (bat->capture.mode == MODE_SINGLE) 438 bat->capture.mode = MODE_LOOPBACK; 439 else 440 bat->playback.mode = MODE_SINGLE; 441 bat->playback.device = optarg; 442 break; 443 case 'C': 444 if (bat->playback.mode == MODE_SINGLE) 445 bat->playback.mode = MODE_LOOPBACK; 446 else 447 bat->capture.mode = MODE_SINGLE; 448 bat->capture.device = optarg; 449 break; 450 case 'n': 451 bat->narg = optarg; 452 break; 453 case 'F': 454 get_sine_frequencies(bat, optarg); 455 break; 456 case 'c': 457 bat->channels = atoi(optarg); 458 break; 459 case 'r': 460 bat->rate = atoi(optarg); 461 break; 462 case 'f': 463 get_format(bat, optarg); 464 break; 465 case 'k': 466 bat->sigma_k = atof(optarg); 467 break; 468 case 'p': 469 bat->periods_total = atoi(optarg); 470 bat->period_is_limited = true; 471 break; 472 case 'B': 473 err = atoi(optarg); 474 bat->buffer_size = err >= MIN_BUFFERSIZE 475 && err < MAX_BUFFERSIZE ? err : 0; 476 break; 477 case 'E': 478 err = atoi(optarg); 479 bat->period_size = err >= MIN_PERIODSIZE 480 && err < MAX_PERIODSIZE ? err : 0; 481 break; 482 case 'h': 483 default: 484 usage(bat); 485 exit(EXIT_SUCCESS); 486 } 487 } 488 } 489 490 static int validate_options(struct bat *bat) 491 { 492 int c; 493 float freq_low, freq_high; 494 495 /* check if we have an input file for local mode */ 496 if ((bat->local == true) && (bat->capture.file == NULL)) { 497 fprintf(bat->err, _("no input file for local testing\n")); 498 return -EINVAL; 499 } 500 501 /* check supported channels */ 502 if (bat->channels > MAX_CHANNELS || bat->channels < MIN_CHANNELS) { 503 fprintf(bat->err, _("%d channels not supported\n"), 504 bat->channels); 505 return -EINVAL; 506 } 507 508 /* check single ended is in either playback or capture - not both */ 509 if ((bat->playback.mode == MODE_SINGLE) 510 && (bat->capture.mode == MODE_SINGLE)) { 511 fprintf(bat->err, _("single ended mode is simplex\n")); 512 return -EINVAL; 513 } 514 515 /* check sine wave frequency range */ 516 freq_low = DC_THRESHOLD; 517 freq_high = bat->rate * RATE_FACTOR; 518 for (c = 0; c < bat->channels; c++) { 519 if (bat->target_freq[c] < freq_low 520 || bat->target_freq[c] > freq_high) { 521 fprintf(bat->err, _("sine wave frequency out of")); 522 fprintf(bat->err, _(" range: (%.1f, %.1f)\n"), 523 freq_low, freq_high); 524 return -EINVAL; 525 } 526 } 527 528 return 0; 529 } 530 531 static int bat_init(struct bat *bat) 532 { 533 int err = 0; 534 int fd = 0; 535 char name[] = TEMP_RECORD_FILE_NAME; 536 537 /* Determine logging to a file or stdout and stderr */ 538 if (bat->logarg) { 539 bat->log = NULL; 540 bat->log = fopen(bat->logarg, "wb"); 541 err = -errno; 542 if (bat->log == NULL) { 543 fprintf(bat->err, _("Cannot open file: %s %d\n"), 544 bat->logarg, err); 545 return err; 546 } 547 bat->err = bat->log; 548 } 549 550 /* Determine duration of playback and/or capture */ 551 if (bat->narg) { 552 err = get_duration(bat); 553 if (err < 0) 554 return err; 555 } 556 557 /* Set default playback and capture devices */ 558 if (bat->playback.device == NULL && bat->capture.device == NULL) 559 bat->playback.device = bat->capture.device = DEFAULT_DEV_NAME; 560 561 /* Determine capture file */ 562 if (bat->local) { 563 bat->capture.file = bat->playback.file; 564 } else { 565 /* create temp file for sound record and analysis */ 566 fd = mkstemp(name); 567 err = -errno; 568 if (fd == -1) { 569 fprintf(bat->err, _("Fail to create record file: %d\n"), 570 err); 571 return err; 572 } 573 /* store file name which is dynamically created */ 574 bat->capture.file = strdup(name); 575 err = -errno; 576 if (bat->capture.file == NULL) 577 return err; 578 /* close temp file */ 579 close(fd); 580 } 581 582 /* Initial for playback */ 583 if (bat->playback.file == NULL) { 584 /* No input file so we will generate our own sine wave */ 585 if (bat->frames) { 586 if (bat->playback.mode == MODE_SINGLE) { 587 /* Play nb of frames given by -n argument */ 588 bat->sinus_duration = bat->frames; 589 } else { 590 /* Play CAPTURE_DELAY msec + 591 * 150% of the nb of frames to be analyzed */ 592 bat->sinus_duration = bat->rate * 593 CAPTURE_DELAY / 1000; 594 bat->sinus_duration += 595 (bat->frames + bat->frames / 2); 596 } 597 } else { 598 /* Special case where we want to generate a sine wave 599 * endlessly without capturing */ 600 bat->sinus_duration = 0; 601 bat->playback.mode = MODE_SINGLE; 602 } 603 } else { 604 bat->fp = fopen(bat->playback.file, "rb"); 605 err = -errno; 606 if (bat->fp == NULL) { 607 fprintf(bat->err, _("Cannot open file: %s %d\n"), 608 bat->playback.file, err); 609 return err; 610 } 611 err = read_wav_header(bat, bat->playback.file, bat->fp, false); 612 fclose(bat->fp); 613 if (err != 0) 614 return err; 615 } 616 617 bat->frame_size = bat->sample_size * bat->channels; 618 619 /* Set conversion functions */ 620 switch (bat->sample_size) { 621 case 1: 622 bat->convert_float_to_sample = convert_float_to_uint8; 623 bat->convert_sample_to_float = convert_uint8_to_float; 624 break; 625 case 2: 626 bat->convert_float_to_sample = convert_float_to_int16; 627 bat->convert_sample_to_float = convert_int16_to_float; 628 break; 629 case 3: 630 bat->convert_float_to_sample = convert_float_to_int24; 631 bat->convert_sample_to_float = convert_int24_to_float; 632 break; 633 case 4: 634 bat->convert_float_to_sample = convert_float_to_int32; 635 bat->convert_sample_to_float = convert_int32_to_float; 636 break; 637 default: 638 fprintf(bat->err, _("Invalid PCM format: size=%d\n"), 639 bat->sample_size); 640 return -EINVAL; 641 } 642 643 return err; 644 } 645 646 int main(int argc, char *argv[]) 647 { 648 struct bat bat; 649 int err = 0; 650 651 set_defaults(&bat); 652 653 #ifdef ENABLE_NLS 654 setlocale(LC_ALL, ""); 655 textdomain(PACKAGE); 656 #endif 657 658 fprintf(bat.log, _("%s version %s\n\n"), PACKAGE_NAME, PACKAGE_VERSION); 659 660 parse_arguments(&bat, argc, argv); 661 662 err = bat_init(&bat); 663 if (err < 0) 664 goto out; 665 666 err = validate_options(&bat); 667 if (err < 0) 668 goto out; 669 670 /* round trip latency test thread */ 671 if (bat.roundtriplatency) { 672 while (1) { 673 fprintf(bat.log, 674 _("\nStart round trip latency\n")); 675 roundtrip_latency_init(&bat); 676 test_loopback(&bat); 677 678 if (bat.latency.xrun_error == false) 679 break; 680 else { 681 /* Xrun error in playback or capture, 682 increase period size and try again */ 683 bat.period_size += bat.rate / 1000; 684 bat.buffer_size = 685 bat.period_size * DIV_BUFFERSIZE; 686 687 /* terminate the test if period_size is 688 large enough */ 689 if (bat.period_size > bat.rate * 0.2) 690 break; 691 } 692 693 /* Waiting 500ms and start the next round */ 694 usleep(CAPTURE_DELAY * 1000); 695 } 696 goto out; 697 } 698 699 /* single line playback thread: playback only, no capture */ 700 if (bat.playback.mode == MODE_SINGLE) { 701 test_playback(&bat); 702 goto out; 703 } 704 705 /* single line capture thread: capture only, no playback */ 706 if (bat.capture.mode == MODE_SINGLE) { 707 test_capture(&bat); 708 goto analyze; 709 } 710 711 /* loopback thread: playback and capture in a loop */ 712 if (bat.local == false) 713 test_loopback(&bat); 714 715 analyze: 716 #ifdef HAVE_LIBFFTW3F 717 if (!bat.standalone || snr_is_valid(bat.snr_thd_db)) 718 err = analyze_capture(&bat); 719 #else 720 fprintf(bat.log, _("No libfftw3 library. Exit without analysis.\n")); 721 #endif 722 out: 723 fprintf(bat.log, _("\nReturn value is %d\n"), err); 724 725 if (bat.logarg) 726 fclose(bat.log); 727 if (!bat.local) 728 free(bat.capture.file); 729 730 return err; 731 }