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