tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

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 }