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