[alsa-devel] [PATCH 03/10] alsabat: add tinyalsa support
han.lu at intel.com
han.lu at intel.com
Wed Mar 2 09:53:13 CET 2016
From: "Lu, Han" <han.lu at 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 at 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=""
--
2.5.0
More information about the Alsa-devel
mailing list