• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
format_convert(struct bat * bat,struct pcm_config * config)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 
init_config(struct bat * bat,struct pcm_config * config)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  */
close_handle(void * handle)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  */
check_param(struct bat * bat,struct pcm_params * params,unsigned int param,unsigned int value,char * param_name,char * param_unit)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  */
check_playback_params(struct bat * bat,struct pcm_config * config)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  */
latencytest_process_output(struct bat * bat,struct pcm * pcm,void * buffer,int bytes)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  */
play_sample(struct bat * bat,struct pcm * pcm,void * buffer,int bytes)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 
get_tiny_device(struct bat * bat,char * alsa_device,unsigned int * tiny_card,unsigned int * tiny_device)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  */
playback_tinyalsa(struct bat * bat)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  */
capture_sample(struct bat * bat,struct pcm * pcm,void * buffer,unsigned int bytes)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  */
latencytest_process_input(struct bat * bat,struct pcm * pcm,void * buffer,unsigned int bytes)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  */
record_tinyalsa(struct bat * bat)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 }
555