xref: /third_party/alsa-utils/bat/tinyalsa.c (revision c72fcc34)
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