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 "aconfig.h" 17 18#include <stdio.h> 19#include <string.h> 20#include <stdbool.h> 21#include <stdlib.h> 22#include <pthread.h> 23#include <errno.h> 24 25#include <tinyalsa/asoundlib.h> 26 27#include "gettext.h" 28 29#include "common.h" 30#include "tinyalsa.h" 31#include "latencytest.h" 32 33struct format_map_table { 34 enum _bat_pcm_format format_bat; 35 enum pcm_format format_tiny; 36}; 37 38static struct format_map_table map_tables[] = { 39 { BAT_PCM_FORMAT_S16_LE, PCM_FORMAT_S16_LE }, 40 { BAT_PCM_FORMAT_S32_LE, PCM_FORMAT_S32_LE }, 41 { BAT_PCM_FORMAT_MAX, }, 42}; 43 44static int format_convert(struct bat *bat, struct pcm_config *config) 45{ 46 struct format_map_table *t = map_tables; 47 48 for (; t->format_bat != BAT_PCM_FORMAT_MAX; t++) { 49 if (t->format_bat == bat->format) { 50 config->format = t->format_tiny; 51 return 0; 52 } 53 } 54 fprintf(bat->err, _("Invalid format!\n")); 55 return -EINVAL; 56} 57 58static int init_config(struct bat *bat, struct pcm_config *config) 59{ 60 config->channels = bat->channels; 61 config->rate = bat->rate; 62 if (bat->period_size > 0) 63 config->period_size = bat->period_size; 64 else 65 config->period_size = TINYALSA_PERIODSIZE; 66 config->period_count = 4; 67 config->start_threshold = 0; 68 config->stop_threshold = 0; 69 config->silence_threshold = 0; 70 71 return format_convert(bat, config); 72} 73 74/** 75 * Called when thread is finished 76 */ 77static void close_handle(void *handle) 78{ 79 struct pcm *pcm = handle; 80 81 if (NULL != pcm) 82 pcm_close(pcm); 83} 84 85/** 86 * Check that a parameter is inside bounds 87 */ 88static int check_param(struct bat *bat, struct pcm_params *params, 89 unsigned int param, unsigned int value, 90 char *param_name, char *param_unit) 91{ 92 unsigned int min; 93 unsigned int max; 94 int ret = 0; 95 96 min = pcm_params_get_min(params, param); 97 if (value < min) { 98 fprintf(bat->err, 99 _("%s is %u%s, device only supports >= %u%s!\n"), 100 param_name, value, param_unit, min, param_unit); 101 ret = -EINVAL; 102 } 103 104 max = pcm_params_get_max(params, param); 105 if (value > max) { 106 fprintf(bat->err, 107 _("%s is %u%s, device only supports <= %u%s!\n"), 108 param_name, value, param_unit, max, param_unit); 109 ret = -EINVAL; 110 } 111 112 return ret; 113} 114 115/** 116 * Check all parameters 117 */ 118static int check_playback_params(struct bat *bat, 119 struct pcm_config *config) 120{ 121 struct pcm_params *params; 122 unsigned int card = bat->playback.card_tiny; 123 unsigned int device = bat->playback.device_tiny; 124 int err = 0; 125 126 params = pcm_params_get(card, device, PCM_OUT); 127 if (params == NULL) { 128 fprintf(bat->err, _("Unable to open PCM device %u!\n"), 129 device); 130 return -EINVAL; 131 } 132 133 err = check_param(bat, params, PCM_PARAM_RATE, 134 config->rate, "Sample rate", "Hz"); 135 if (err < 0) 136 goto exit; 137 err = check_param(bat, params, PCM_PARAM_CHANNELS, 138 config->channels, "Sample", " channels"); 139 if (err < 0) 140 goto exit; 141 err = check_param(bat, params, PCM_PARAM_SAMPLE_BITS, 142 bat->sample_size * 8, "Bitrate", " bits"); 143 if (err < 0) 144 goto exit; 145 err = check_param(bat, params, PCM_PARAM_PERIOD_SIZE, 146 config->period_size, "Period size", "Hz"); 147 if (err < 0) 148 goto exit; 149 err = check_param(bat, params, PCM_PARAM_PERIODS, 150 config->period_count, "Period count", "Hz"); 151 if (err < 0) 152 goto exit; 153 154exit: 155 pcm_params_free(params); 156 157 return err; 158} 159 160/** 161 * Process output data for latency test 162 */ 163static int latencytest_process_output(struct bat *bat, struct pcm *pcm, 164 void *buffer, int bytes) 165{ 166 int err = 0; 167 int frames = bytes / bat->frame_size; 168 169 fprintf(bat->log, _("Play sample with %d frames buffer\n"), frames); 170 171 bat->latency.is_playing = true; 172 173 while (1) { 174 /* generate output data */ 175 err = handleoutput(bat, buffer, bytes, frames); 176 if (err != 0) 177 break; 178 179 err = pcm_write(pcm, buffer, bytes); 180 if (err != 0) 181 break; 182 183 if (bat->latency.state == LATENCY_STATE_COMPLETE_SUCCESS) 184 break; 185 186 bat->periods_played++; 187 } 188 189 bat->latency.is_playing = false; 190 191 return err; 192} 193 194/** 195 * Play sample 196 */ 197static int play_sample(struct bat *bat, struct pcm *pcm, 198 void *buffer, int bytes) 199{ 200 int err = 0; 201 int frames = bytes / bat->frame_size; 202 FILE *fp = NULL; 203 int bytes_total = 0; 204 205 if (bat->debugplay) { 206 fp = fopen(bat->debugplay, "wb"); 207 err = -errno; 208 if (fp == NULL) { 209 fprintf(bat->err, _("Cannot open file: %s %d\n"), 210 bat->debugplay, err); 211 return err; 212 } 213 /* leave space for file header */ 214 if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) { 215 err = -errno; 216 fclose(fp); 217 return err; 218 } 219 } 220 221 while (1) { 222 err = generate_input_data(bat, buffer, bytes, frames); 223 if (err != 0) 224 break; 225 226 if (bat->debugplay) { 227 if (fwrite(buffer, 1, bytes, fp) != bytes) { 228 err = -EIO; 229 break; 230 } 231 bytes_total += bytes; 232 } 233 234 bat->periods_played++; 235 if (bat->period_is_limited 236 && bat->periods_played >= bat->periods_total) 237 break; 238 239 err = pcm_write(pcm, buffer, bytes); 240 if (err != 0) 241 break; 242 } 243 244 if (bat->debugplay) { 245 update_wav_header(bat, fp, bytes_total); 246 fclose(fp); 247 } 248 return err; 249} 250 251static int get_tiny_device(struct bat *bat, char *alsa_device, 252 unsigned int *tiny_card, unsigned int *tiny_device) 253{ 254 char *tmp1, *tmp2, *tmp3; 255 256 if (alsa_device == NULL) 257 goto fail; 258 259 tmp1 = strchr(alsa_device, ':'); 260 if (tmp1 == NULL) 261 goto fail; 262 263 tmp3 = tmp1 + 1; 264 tmp2 = strchr(tmp3, ','); 265 if (tmp2 == NULL) 266 goto fail; 267 268 tmp1 = tmp2 + 1; 269 *tiny_device = atoi(tmp1); 270 *tmp2 = '\0'; 271 *tiny_card = atoi(tmp3); 272 *tmp2 = ','; 273 274 return 0; 275fail: 276 fprintf(bat->err, _("Invalid tiny device: %s\n"), alsa_device); 277 return -EINVAL; 278} 279 280/** 281 * Play 282 */ 283void *playback_tinyalsa(struct bat *bat) 284{ 285 int err = 0; 286 struct pcm_config config; 287 struct pcm *pcm = NULL; 288 void *buffer = NULL; 289 int bufbytes; 290 291 fprintf(bat->log, _("Entering playback thread (tinyalsa).\n")); 292 293 retval_play = 0; 294 295 /* init device */ 296 err = get_tiny_device(bat, bat->playback.device, 297 &bat->playback.card_tiny, 298 &bat->playback.device_tiny); 299 if (err < 0) { 300 retval_play = err; 301 goto exit1; 302 } 303 304 /* init config */ 305 err = init_config(bat, &config); 306 if (err < 0) { 307 retval_play = err; 308 goto exit1; 309 } 310 311 /* check param before open device */ 312 err = check_playback_params(bat, &config); 313 if (err < 0) { 314 retval_play = err; 315 goto exit1; 316 } 317 318 /* open device */ 319 pcm = pcm_open(bat->playback.card_tiny, bat->playback.device_tiny, 320 PCM_OUT, &config); 321 if (!pcm || !pcm_is_ready(pcm)) { 322 fprintf(bat->err, _("Unable to open PCM device %u (%s)!\n"), 323 bat->playback.device_tiny, pcm_get_error(pcm)); 324 retval_play = -EINVAL; 325 goto exit1; 326 } 327 328 /* init buffer */ 329 bufbytes = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); 330 buffer = malloc(bufbytes); 331 if (!buffer) { 332 retval_play = -ENOMEM; 333 goto exit2; 334 } 335 336 /* init playback source */ 337 if (bat->playback.file == NULL) { 338 fprintf(bat->log, _("Playing generated audio sine wave")); 339 bat->sinus_duration == 0 ? 340 fprintf(bat->log, _(" endlessly\n")) : 341 fprintf(bat->log, _("\n")); 342 } else { 343 fprintf(bat->log, _("Playing input audio file: %s\n"), 344 bat->playback.file); 345 bat->fp = fopen(bat->playback.file, "rb"); 346 err = -errno; 347 if (bat->fp == NULL) { 348 fprintf(bat->err, _("Cannot open file: %s %d\n"), 349 bat->playback.file, err); 350 retval_play = err; 351 goto exit3; 352 } 353 /* Skip header */ 354 err = read_wav_header(bat, bat->playback.file, bat->fp, true); 355 if (err != 0) { 356 retval_play = err; 357 goto exit4; 358 } 359 } 360 361 if (bat->roundtriplatency) 362 err = latencytest_process_output(bat, pcm, buffer, bufbytes); 363 else 364 err = play_sample(bat, pcm, buffer, bufbytes); 365 if (err < 0) { 366 retval_play = err; 367 goto exit4; 368 } 369 370exit4: 371 if (bat->playback.file) 372 fclose(bat->fp); 373exit3: 374 free(buffer); 375exit2: 376 pcm_close(pcm); 377exit1: 378 pthread_exit(&retval_play); 379} 380 381/** 382 * Capture sample 383 */ 384static int capture_sample(struct bat *bat, struct pcm *pcm, 385 void *buffer, unsigned int bytes) 386{ 387 int err = 0; 388 FILE *fp = NULL; 389 unsigned int bytes_read = 0; 390 unsigned int bytes_count = bat->frames * bat->frame_size; 391 392 remove(bat->capture.file); 393 fp = fopen(bat->capture.file, "wb"); 394 err = -errno; 395 if (fp == NULL) { 396 fprintf(bat->err, _("Cannot open file: %s %d\n"), 397 bat->capture.file, err); 398 return err; 399 } 400 /* leave space for file header */ 401 if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) { 402 err = -errno; 403 fclose(fp); 404 return err; 405 } 406 407 while (bytes_read < bytes_count && !pcm_read(pcm, buffer, bytes)) { 408 if (fwrite(buffer, 1, bytes, fp) != bytes) 409 break; 410 411 bytes_read += bytes; 412 413 bat->periods_played++; 414 415 if (bat->period_is_limited 416 && bat->periods_played >= bat->periods_total) 417 break; 418 } 419 420 err = update_wav_header(bat, fp, bytes_read); 421 422 fclose(fp); 423 return err; 424} 425 426/** 427 * Process input data for latency test 428 */ 429static int latencytest_process_input(struct bat *bat, struct pcm *pcm, 430 void *buffer, unsigned int bytes) 431{ 432 int err = 0; 433 FILE *fp = NULL; 434 unsigned int bytes_read = 0; 435 unsigned int bytes_count = bat->frames * bat->frame_size; 436 437 remove(bat->capture.file); 438 fp = fopen(bat->capture.file, "wb"); 439 err = -errno; 440 if (fp == NULL) { 441 fprintf(bat->err, _("Cannot open file: %s %d\n"), 442 bat->capture.file, err); 443 return err; 444 } 445 /* leave space for file header */ 446 if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) { 447 err = -errno; 448 fclose(fp); 449 return err; 450 } 451 452 bat->latency.is_capturing = true; 453 454 while (bytes_read < bytes_count && !pcm_read(pcm, buffer, bytes)) { 455 if (fwrite(buffer, 1, bytes, fp) != bytes) 456 break; 457 458 err = handleinput(bat, buffer, bytes / bat->frame_size); 459 if (err != 0) 460 break; 461 462 if (bat->latency.is_playing == false) 463 break; 464 465 bytes_read += bytes; 466 } 467 468 bat->latency.is_capturing = false; 469 470 err = update_wav_header(bat, fp, bytes_read); 471 472 fclose(fp); 473 return err; 474} 475 476/** 477 * Record 478 */ 479void *record_tinyalsa(struct bat *bat) 480{ 481 int err = 0; 482 struct pcm_config config; 483 struct pcm *pcm; 484 void *buffer; 485 unsigned int bufbytes; 486 487 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 488 489 fprintf(bat->log, _("Entering capture thread (tinyalsa).\n")); 490 491 retval_record = 0; 492 493 /* init device */ 494 err = get_tiny_device(bat, bat->capture.device, 495 &bat->capture.card_tiny, 496 &bat->capture.device_tiny); 497 if (err < 0) { 498 retval_record = err; 499 goto exit1; 500 } 501 502 /* init config */ 503 err = init_config(bat, &config); 504 if (err < 0) { 505 retval_record = err; 506 goto exit1; 507 } 508 509 /* open device */ 510 pcm = pcm_open(bat->capture.card_tiny, bat->capture.device_tiny, 511 PCM_IN, &config); 512 if (!pcm || !pcm_is_ready(pcm)) { 513 fprintf(bat->err, _("Unable to open PCM device (%s)!\n"), 514 pcm_get_error(pcm)); 515 retval_record = -EINVAL; 516 goto exit1; 517 } 518 519 /* init buffer */ 520 bufbytes = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); 521 buffer = malloc(bufbytes); 522 if (!buffer) { 523 retval_record = -ENOMEM; 524 goto exit2; 525 } 526 527 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); 528 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); 529 pthread_cleanup_push(close_handle, pcm); 530 pthread_cleanup_push(free, buffer); 531 532 fprintf(bat->log, _("Recording ...\n")); 533 if (bat->roundtriplatency) 534 err = latencytest_process_input(bat, pcm, buffer, bufbytes); 535 else 536 err = capture_sample(bat, pcm, buffer, bufbytes); 537 if (err != 0) { 538 retval_record = err; 539 goto exit3; 540 } 541 542 /* Normally we will never reach this part of code (unless error in 543 * previous call) (before exit3) as this thread will be cancelled 544 * by end of play thread. Except in single line mode. */ 545 pthread_cleanup_pop(0); 546 pthread_cleanup_pop(0); 547 pthread_exit(&retval_record); 548 549exit3: 550 free(buffer); 551exit2: 552 pcm_close(pcm); 553exit1: 554 pthread_exit(&retval_record); 555} 556