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