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 
33 struct format_map_table {
34 	enum _bat_pcm_format format_bat;
35 	enum pcm_format format_tiny;
36 };
37 
38 static 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 
format_convert(struct bat *bat, struct pcm_config *config)44 static 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 
init_config(struct bat *bat, struct pcm_config *config)58 static 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  */
close_handle(void *handle)77 static 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  */
check_param(struct bat *bat, struct pcm_params *params, unsigned int param, unsigned int value, char *param_name, char *param_unit)88 static 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  */
check_playback_params(struct bat *bat, struct pcm_config *config)118 static 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 
154 exit:
155 	pcm_params_free(params);
156 
157 	return err;
158 }
159 
160 /**
161  * Process output data for latency test
162  */
latencytest_process_output(struct bat *bat, struct pcm *pcm, void *buffer, int bytes)163 static 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  */
play_sample(struct bat *bat, struct pcm *pcm, void *buffer, int bytes)197 static 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 
get_tiny_device(struct bat *bat, char *alsa_device, unsigned int *tiny_card, unsigned int *tiny_device)251 static 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;
275 fail:
276 	fprintf(bat->err, _("Invalid tiny device: %s\n"), alsa_device);
277 	return -EINVAL;
278 }
279 
280 /**
281  * Play
282  */
playback_tinyalsa(struct bat *bat)283 void *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 
370 exit4:
371 	if (bat->playback.file)
372 		fclose(bat->fp);
373 exit3:
374 	free(buffer);
375 exit2:
376 	pcm_close(pcm);
377 exit1:
378 	pthread_exit(&retval_play);
379 }
380 
381 /**
382  * Capture sample
383  */
capture_sample(struct bat *bat, struct pcm *pcm, void *buffer, unsigned int bytes)384 static 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  */
latencytest_process_input(struct bat *bat, struct pcm *pcm, void *buffer, unsigned int bytes)429 static 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  */
record_tinyalsa(struct bat *bat)479 void *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 
549 exit3:
550 	free(buffer);
551 exit2:
552 	pcm_close(pcm);
553 exit1:
554 	pthread_exit(&retval_record);
555 }
556