From: "Lu, Han" han.lu@intel.com
Add support for alsabat to work on tinyalsa library based platforms such as Android and some Embedded Linux devices. Use option '-t' to select tinyalsa library instead of ALSA library. If '-t' is used while tinyalsa library is not installed in system, alsabat will print error message and quit.
Signed-off-by: Lu, Han han.lu@intel.com
diff --git a/bat/Makefile.am b/bat/Makefile.am index 5646e9a..6101681 100644 --- a/bat/Makefile.am +++ b/bat/Makefile.am @@ -21,6 +21,11 @@ alsabat_SOURCES += analyze.c noinst_HEADERS += analyze.h endif
+if HAVE_LIBTINYALSA +alsabat_SOURCES += tinyalsa.c +noinst_HEADERS += tinyalsa.h +endif + AM_CPPFLAGS = \ -Wall -I$(top_srcdir)/include
diff --git a/bat/alsabat.1 b/bat/alsabat.1 index 5f41669..126232e 100644 --- a/bat/alsabat.1 +++ b/bat/alsabat.1 @@ -106,6 +106,9 @@ Valid range is (DC_THRESHOLD, 40% * Sampling rate). \fI-p\fP Total number of periods to play or capture. .TP +\fI-t\fP +If tinyalsa lib is installed, use tinyalsa lib instead of alsa lib. +.TP \fI--log=#\fP Write stderr and stdout output to this log file. .TP diff --git a/bat/bat.c b/bat/bat.c index 85ec5aa..8db16c0 100644 --- a/bat/bat.c +++ b/bat/bat.c @@ -36,6 +36,9 @@ #ifdef HAVE_LIBFFTW3 #include "analyze.h" #endif +#ifdef HAVE_LIBTINYALSA +#include "tinyalsa.h" +#endif
static int get_duration(struct bat *bat) { @@ -125,6 +128,35 @@ static void get_format(struct bat *bat, char *optarg) } }
+static int get_tiny_format(struct bat *bat, char *alsa_device, + unsigned int *tiny_card, unsigned int *tiny_device) +{ + char *tmp1, *tmp2, *tmp3; + + if (alsa_device == NULL) + goto fail; + + tmp1 = strchr(alsa_device, ':'); + if (tmp1 == NULL) + goto fail; + + tmp3 = tmp1 + 1; + tmp2 = strchr(tmp3, ','); + if (tmp2 == NULL) + goto fail; + + tmp1 = tmp2 + 1; + *tiny_device = atoi(tmp1); + *tmp2 = '\0'; + *tiny_card = atoi(tmp3); + *tmp2 = ','; + + return 0; +fail: + fprintf(bat->err, _("Invalid tiny format!\n")); + return -EINVAL; +} + static inline int thread_wait_completion(struct bat *bat, pthread_t id, int **val) { @@ -287,6 +319,7 @@ _("Usage: alsabat [-options]...\n" " -k parameter for frequency detecting threshold\n" " -F target frequency\n" " -p total number of periods to play/capture\n" +" -t use tinyalsa instead of alsa\n" " --log=# file that both stdout and strerr redirecting to\n" " --file=# file for playback\n" " --saveplay=# file that storing playback content, for debug\n" @@ -330,6 +363,7 @@ static void set_defaults(struct bat *bat) bat->period_is_limited = false; bat->log = stdout; bat->err = stderr; + bat->tinyalsa = false; }
static void parse_arguments(struct bat *bat, int argc, char *argv[]) @@ -406,6 +440,16 @@ static void parse_arguments(struct bat *bat, int argc, char *argv[]) bat->periods_total = atoi(optarg); bat->period_is_limited = true; break; + case 't': +#ifdef HAVE_LIBTINYALSA + bat->playback.fct = &playback_tinyalsa; + bat->capture.fct = &record_tinyalsa; + bat->tinyalsa = true; +#else + fprintf(bat->err, _("tinyalsa lib is not installed\n")); + exit(EXIT_FAILURE); +#endif + break; case 'h': default: usage(bat); @@ -484,6 +528,24 @@ static int bat_init(struct bat *bat) if (bat->playback.device == NULL && bat->capture.device == NULL) bat->playback.device = bat->capture.device = DEFAULT_DEV_NAME;
+ /* Determine tiny device if needed */ + if (bat->tinyalsa == true) { + if (bat->playback.mode != MODE_SINGLE) { + err = get_tiny_format(bat, bat->capture.device, + &bat->capture.card_tiny, + &bat->capture.device_tiny); + if (err < 0) + return err; + } + if (bat->capture.mode != MODE_SINGLE) { + err = get_tiny_format(bat, bat->playback.device, + &bat->playback.card_tiny, + &bat->playback.device_tiny); + if (err < 0) + return err; + } + } + /* Determine capture file */ if (bat->local) { bat->capture.file = bat->playback.file; diff --git a/bat/common.c b/bat/common.c index 798b00b..bbf969e 100644 --- a/bat/common.c +++ b/bat/common.c @@ -18,16 +18,39 @@ #include <stdlib.h> #include <stdbool.h> #include <errno.h> +#include <signal.h>
#include "aconfig.h" #include "gettext.h"
#include "common.h" #include "alsa.h" +#include "bat-signal.h"
int retval_play; int retval_record;
+int is_capturing = 1; +int is_playing = 1; + +/** + * Handling of Ctrl-C for capture + */ +void sigint_handler(int sig) +{ + is_capturing = 0; +} + +/** + * Handling of Ctrl-C for playback + */ +void stream_close(int sig) +{ + /* allow the stream to be closed gracefully */ + signal(sig, SIG_IGN); + is_playing = 0; +} + /* update chunk_fmt data to bat */ static int update_fmt_to_bat(struct bat *bat, struct chunk_fmt *fmt) { @@ -196,3 +219,69 @@ int write_wav_header(FILE *fp, struct wav_container *wav, struct bat *bat)
return 0; } + +/* update wav header when data size changed */ +int update_wav_header(struct bat *bat, FILE *fp, int bytes) +{ + int err = 0; + struct wav_container wav; + + prepare_wav_info(&wav, bat); + wav.chunk.length = bytes; + wav.header.length = (wav.chunk.length) + sizeof(wav.chunk) + + sizeof(wav.format) + sizeof(wav.header) - 8; + rewind(fp); + err = write_wav_header(fp, &wav, bat); + + return err; +} + +/* + * Generate buffer to be played either from input file or from generated data + * Return value + * <0 error + * 0 ok + * >0 break + */ +int generate_input_data0(struct bat *bat, void *buffer, int bytes, int frames) +{ + int err; + static int load; + + if (bat->playback.file != NULL) { + /* From input file */ + load = 0; + + while (1) { + err = fread(buffer + load, 1, bytes - load, bat->fp); + if (0 == err) { + if (feof(bat->fp)) { + fprintf(bat->log, + _("End of playing.\n")); + return 1; + } + } else if (err < bytes - load) { + if (ferror(bat->fp)) { + fprintf(bat->err, _("Read file error")); + fprintf(bat->err, _(": %d\n"), err); + return -EIO; + } + load += err; + } else { + break; + } + } + } else { + /* Generate sine wave */ + if ((bat->sinus_duration) && (load > bat->sinus_duration)) + return 1; + + err = generate_sine_wave(bat, frames, buffer); + if (err != 0) + return err; + + load += frames; + } + + return 0; +} diff --git a/bat/common.h b/bat/common.h index 30e39fc..0d92a8d 100644 --- a/bat/common.h +++ b/bat/common.h @@ -14,6 +14,9 @@ */
#include <alsa/asoundlib.h> +#ifdef HAVE_LIBTINYALSA +#include <tinyalsa/asoundlib.h> +#endif
#define TEMP_RECORD_FILE_NAME "/tmp/bat.wav.XXXXXX" #define DEFAULT_DEV_NAME "default" @@ -119,6 +122,8 @@ enum _bat_op_mode {
struct pcm { char *device; + unsigned int card_tiny; + unsigned int device_tiny; char *file; enum _bat_op_mode mode; void *(*fct)(struct bat *); @@ -171,6 +176,8 @@ struct bat { void *buf; /* PCM Buffer */
bool local; /* true for internal test */ + + bool tinyalsa; /* true to use tinyalsa lib */ };
struct analyze { @@ -180,6 +187,10 @@ struct analyze { double *mag; };
+void sigint_handler(int); +void stream_close(int); void prepare_wav_info(struct wav_container *, struct bat *); int read_wav_header(struct bat *, char *, FILE *, bool); int write_wav_header(FILE *, struct wav_container *, struct bat *); +int update_wav_header(struct bat *, FILE *, int); +int generate_input_data0(struct bat *, void *, int, int); diff --git a/bat/tinyalsa.c b/bat/tinyalsa.c new file mode 100644 index 0000000..ab11247 --- /dev/null +++ b/bat/tinyalsa.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <signal.h> +#include <pthread.h> +#include <errno.h> + +#include <tinyalsa/asoundlib.h> + +#include "aconfig.h" +#include "gettext.h" + +#include "common.h" +#include "tinyalsa.h" + +struct format_map_table { + int sample_bytes; + enum pcm_format format; +}; + +static struct format_map_table map_tables[] = { + { 2, PCM_FORMAT_S16_LE }, + { 4, PCM_FORMAT_S32_LE }, + {} +}; + +/** + * Called when thread is finished + */ +static void close_handle(void *handle) +{ + struct pcm *pcm = handle; + + if (NULL != pcm) + pcm_close(pcm); +} + +static int format_convert(struct bat *bat, struct pcm_config *config) +{ + struct format_map_table *t = map_tables; + + for (; t->sample_bytes; t++) { + if (t->sample_bytes == bat->sample_size) { + config->format = t->format; + return 0; + } + } + + fprintf(bat->err, _("Invalid format!\n")); + return -EINVAL; +} + +static int init_config(struct bat *bat, struct pcm_config *config) +{ + config->channels = bat->channels; + config->rate = bat->rate; + config->period_size = 1024; + config->period_count = 4; + config->start_threshold = 0; + config->stop_threshold = 0; + config->silence_threshold = 0; + + return format_convert(bat, config); +} + +/** + * Check that a parameter is inside bounds + */ +static int check_param(struct bat *bat, struct pcm_params *params, + unsigned int param, unsigned int value, + char *param_name, char *param_unit) +{ + unsigned int min; + unsigned int max; + int ret = 0; + + min = pcm_params_get_min(params, param); + if (value < min) { + fprintf(bat->err, + _("%s is %u%s, device only supports >= %u%s!\n"), + param_name, value, param_unit, min, param_unit); + ret = -EINVAL; + } + + max = pcm_params_get_max(params, param); + if (value > max) { + fprintf(bat->err, + _("%s is %u%s, device only supports <= %u%s!\n"), + param_name, value, param_unit, max, param_unit); + ret = -EINVAL; + } + + return ret; +} + +/** + * Check all parameters + */ +static int check_playback_params(struct bat *bat, + struct pcm_config *config) +{ + struct pcm_params *params; + unsigned int card = bat->playback.card_tiny; + unsigned int device = bat->playback.device_tiny; + int err = 0; + + params = pcm_params_get(card, device, PCM_OUT); + if (params == NULL) { + fprintf(bat->err, _("Unable to open PCM device %u!\n"), + device); + return -EINVAL; + } + + err = check_param(bat, params, PCM_PARAM_RATE, + config->rate, "Sample rate", "Hz"); + if (err < 0) + goto exit; + err = check_param(bat, params, PCM_PARAM_CHANNELS, + config->channels, "Sample", " channels"); + if (err < 0) + goto exit; + err = check_param(bat, params, PCM_PARAM_SAMPLE_BITS, + bat->sample_size * 8, "Bitrate", " bits"); + if (err < 0) + goto exit; + err = check_param(bat, params, PCM_PARAM_PERIOD_SIZE, + config->period_size, "Period size", "Hz"); + if (err < 0) + goto exit; + err = check_param(bat, params, PCM_PARAM_PERIODS, + config->period_count, "Period count", "Hz"); + if (err < 0) + goto exit; + +exit: + pcm_params_free(params); + + return err; +} + +/** + * Play sample + */ +static int play_sample(struct bat *bat, struct pcm *pcm, + void *buffer, int bytes) +{ + int err = 0; + FILE *fp = NULL; + int frames = bytes / bat->frame_size; + int bytes_total = 0; + + if (bat->debugplay) { + fp = fopen(bat->debugplay, "wb"); + if (fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: ")); + fprintf(bat->err, _("%s %d\n"), bat->debugplay, -errno); + return -errno; + } + /* leave space for file header */ + if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) { + fclose(fp); + return -errno; + } + } + + do { + err = generate_input_data0(bat, buffer, bytes, frames); + if (err != 0) + break; + + if (bat->debugplay) { + if (fwrite(buffer, 1, bytes, fp) != bytes) { + update_wav_header(bat, fp, bytes_total); + fclose(fp); + return -EIO; + } + bytes_total += bytes; + } + + bat->periods_played++; + if (bat->period_is_limited + && bat->periods_played >= bat->periods_total) + break; + + err = pcm_write(pcm, buffer, bytes); + if (err != 0) { + fprintf(bat->err, _("Write PCM device error: %d\n"), + err); + break; + } + } while (is_playing); + + if (bat->debugplay) { + err = update_wav_header(bat, fp, bytes_total); + fclose(fp); + } + return err; +} + +/** + * Play + */ +void *playback_tinyalsa(struct bat *bat) +{ + int err = 0; + struct pcm_config config; + struct pcm *pcm = NULL; + void *buffer = NULL; + int bufbytes; + unsigned int card = bat->playback.card_tiny; + unsigned int device = bat->playback.device_tiny; + + fprintf(bat->log, _("Entering playback thread (tinyalsa).\n")); + + retval_play = 0; + + /* init config */ + err = init_config(bat, &config); + if (err < 0) { + retval_play = err; + goto exit1; + } + + /* check param before open device */ + err = check_playback_params(bat, &config); + if (err < 0) { + retval_play = err; + goto exit1; + } + + /* init device */ + pcm = pcm_open(card, device, PCM_OUT, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(bat->err, _("Unable to open PCM device %u (%s)!\n"), + device, pcm_get_error(pcm)); + retval_play = -EINVAL; + goto exit1; + } + + /* init buffer */ + bufbytes = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = malloc(bufbytes); + if (!buffer) { + retval_play = -ENOMEM; + goto exit2; + } + + /* init playback source */ + if (bat->playback.file == NULL) { + fprintf(bat->log, _("Playing generated audio sine wave")); + bat->sinus_duration == 0 ? + fprintf(bat->log, _(" endlessly\n")) : + fprintf(bat->log, _("\n")); + } else { + fprintf(bat->log, _("Playing input audio file: %s\n"), + bat->playback.file); + bat->fp = fopen(bat->playback.file, "rb"); + if (bat->fp == NULL) { + fprintf(bat->err, _("Cannot open file for playback: ")); + fprintf(bat->err, _("%s %d\n"), + bat->playback.file, -errno); + retval_play = -errno; + goto exit3; + } + /* Skip header */ + err = read_wav_header(bat, bat->playback.file, bat->fp, true); + if (err != 0) { + retval_play = err; + goto exit4; + } + } + + /* catch ctrl-c to shutdown cleanly */ + signal(SIGINT, stream_close); + + err = play_sample(bat, pcm, buffer, bufbytes); + if (err < 0) { + retval_play = err; + goto exit4; + } + +exit4: + if (bat->playback.file) + fclose(bat->fp); +exit3: + free(buffer); +exit2: + pcm_close(pcm); +exit1: + pthread_exit(&retval_play); +} + +/** + * Capture sample + */ +static int capture_sample(struct bat *bat, struct pcm *pcm, + void *buffer, unsigned int bytes) +{ + int err = 0; + FILE *fp = NULL; + unsigned int bytes_read = 0; + unsigned int bytes_count = bat->frames * bat->frame_size; + + remove(bat->capture.file); + fp = fopen(bat->capture.file, "wb"); + if (fp == NULL) { + fprintf(bat->err, _("Cannot open file for capture: %s %d\n"), + bat->capture.file, -errno); + return -errno; + } + /* leave space for file header */ + if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) { + fclose(fp); + return -errno; + } + + while (bytes_read < bytes_count && is_capturing + && !pcm_read(pcm, buffer, bytes)) { + if (fwrite(buffer, 1, bytes, fp) != bytes) + break; + + bytes_read += bytes; + + bat->periods_played++; + + if (bat->period_is_limited + && bat->periods_played >= bat->periods_total) + break; + } + + err = update_wav_header(bat, fp, bytes_read); + + fclose(fp); + return err; +} + +/** + * Record + */ +void *record_tinyalsa(struct bat *bat) +{ + int err = 0; + struct pcm_config config; + struct pcm *pcm; + void *buffer; + unsigned int bufbytes; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + fprintf(bat->log, _("Entering capture thread (tinyalsa).\n")); + + retval_record = 0; + + /* init config */ + err = init_config(bat, &config); + if (err < 0) { + retval_record = err; + goto exit1; + } + + /* init device */ + pcm = pcm_open(bat->capture.card_tiny, bat->capture.device_tiny, + PCM_IN, &config); + if (!pcm || !pcm_is_ready(pcm)) { + fprintf(bat->err, _("Unable to open PCM device (%s)!\n"), + pcm_get_error(pcm)); + retval_record = -EINVAL; + goto exit1; + } + + /* init buffer */ + bufbytes = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); + buffer = malloc(bufbytes); + if (!buffer) { + retval_record = -ENOMEM; + goto exit2; + } + + /* install signal handler and begin capturing Ctrl-C */ + signal(SIGINT, sigint_handler); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + pthread_cleanup_push(close_handle, pcm); + pthread_cleanup_push(free, buffer); + + fprintf(bat->log, _("Recording ...\n")); + err = capture_sample(bat, pcm, buffer, bufbytes); + if (err != 0) { + retval_record = err; + goto exit3; + } + + /* Normally we will never reach this part of code (unless error in + * previous call) (before exit3) as this thread will be cancelled + * by end of play thread. Except in single line mode. */ + pthread_cleanup_pop(0); + pthread_cleanup_pop(0); + pthread_exit(&retval_record); + +exit3: + free(buffer); +exit2: + pcm_close(pcm); +exit1: + pthread_exit(&retval_record); +} diff --git a/bat/tinyalsa.h b/bat/tinyalsa.h new file mode 100644 index 0000000..70e4749 --- /dev/null +++ b/bat/tinyalsa.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013-2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +extern int retval_play; +extern int retval_record; + +extern int is_capturing; +extern int is_playing; + +void *playback_tinyalsa(struct bat *); +void *record_tinyalsa(struct bat *); diff --git a/configure.ac b/configure.ac index f6f8103..87dd237 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,9 @@ if test x$bat = xtrue; then dnl Check for libfftw3 have_libfftw3="yes" AC_CHECK_LIB([fftw3], [fftw_malloc], , [have_libfftw3="no"]) + dnl Check for libtinyalsa + have_libtinyalsa="yes" + AC_CHECK_LIB([tinyalsa], [pcm_open], , [have_libtinyalsa="no"]) AC_CHECK_LIB([m], [sqrtf], , [AC_MSG_ERROR([Error: Need sqrtf])]) AC_CHECK_LIB([pthread], [pthread_create], , [AC_MSG_ERROR([Error: need PTHREAD library])]) FFTW_CFLAGS="$CFLAGS" @@ -86,6 +89,7 @@ if test x$bat = xtrue; then
fi AM_CONDITIONAL(HAVE_LIBFFTW3, test "$have_libfftw3" = "yes") +AM_CONDITIONAL(HAVE_LIBTINYALSA, test "$have_libtinyalsa" = "yes")
dnl Check for librt LIBRT=""