diff mbox

[6/6] alsabat: add tinyalsa support

Message ID e228cfd2369678fc831f399b02c38366b1a33315.1458623090.git.han.lu@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

han.lu@intel.com March 22, 2016, 5:10 a.m. UTC
From: "Lu, Han" <han.lu@intel.com>

Alsabat will use tinyalsa instead of ALSA, if tinyalsa is installed
in system. So alsabat can run on tinyalsa platforms such as
IoT/Android devices.
The patch is tested on Ubuntu with libasound.so be replaced by
libtinyalsa.so.

Signed-off-by: Lu, Han <han.lu@intel.com>
diff mbox

Patch

diff --git a/bat/Makefile.am b/bat/Makefile.am
index 712f5bf..24e3eb8 100644
--- a/bat/Makefile.am
+++ b/bat/Makefile.am
@@ -7,13 +7,11 @@  alsabat_SOURCES = \
 	bat.c \
 	common.c \
 	signal.c \
-	convert.c \
-	alsa.c
+	convert.c
 
 noinst_HEADERS = \
 	common.h \
 	bat-signal.h \
-	alsa.h \
 	convert.h
 
 if HAVE_LIBFFTW3
@@ -21,6 +19,14 @@  alsabat_SOURCES += analyze.c
 noinst_HEADERS += analyze.h
 endif
 
+if HAVE_LIBTINYALSA
+alsabat_SOURCES += tinyalsa.c
+noinst_HEADERS += tinyalsa.h
+else
+alsabat_SOURCES += alsa.c
+noinst_HEADERS += alsa.h
+endif
+
 AM_CPPFLAGS = \
 	      -Wall -I$(top_srcdir)/include
 
diff --git a/bat/alsabat.1 b/bat/alsabat.1
index 5f41669..9b969c2 100644
--- a/bat/alsabat.1
+++ b/bat/alsabat.1
@@ -33,6 +33,9 @@  analog loopback :-
 
 https://source.android.com/devices/audio/loopback.html
 
+If tinyalsa lib is installed in system, ALSABAT will use tinyalsa lib instead
+of ALSA lib.
+
 .SH OPTIONS
 .TP
 \fI\-h, \-\-help\fP
diff --git a/bat/bat.c b/bat/bat.c
index f10c647..e824065 100644
--- a/bat/bat.c
+++ b/bat/bat.c
@@ -31,7 +31,11 @@ 
 
 #include "common.h"
 
+#ifdef HAVE_LIBTINYALSA
+#include "tinyalsa.h"
+#else
 #include "alsa.h"
+#endif
 #include "convert.h"
 #ifdef HAVE_LIBFFTW3
 #include "analyze.h"
@@ -301,7 +305,6 @@  static void set_defaults(struct bat *bat)
 
 	/* Set default values */
 	bat->rate = 44100;
-	bat->channels = 1;
 	bat->frame_size = 2;
 	bat->sample_size = 2;
 	bat->format = BAT_PCM_FORMAT_S16_LE;
@@ -315,8 +318,15 @@  static void set_defaults(struct bat *bat)
 	bat->capture.device = NULL;
 	bat->buf = NULL;
 	bat->local = false;
+#ifdef HAVE_LIBTINYALSA
+	bat->channels = 2;
+	bat->playback.fct = &playback_tinyalsa;
+	bat->capture.fct = &record_tinyalsa;
+#else
+	bat->channels = 1;
 	bat->playback.fct = &playback_alsa;
 	bat->capture.fct = &record_alsa;
+#endif
 	bat->playback.mode = MODE_LOOPBACK;
 	bat->capture.mode = MODE_LOOPBACK;
 	bat->period_is_limited = false;
diff --git a/bat/common.h b/bat/common.h
index ed33d51..b789af5 100644
--- a/bat/common.h
+++ b/bat/common.h
@@ -125,6 +125,8 @@  enum _bat_op_mode {
 };
 
 struct pcm {
+	unsigned int card_tiny;
+	unsigned int device_tiny;
 	char *device;
 	char *file;
 	enum _bat_op_mode mode;
diff --git a/bat/tinyalsa.c b/bat/tinyalsa.c
new file mode 100644
index 0000000..ea5f848
--- /dev/null
+++ b/bat/tinyalsa.c
@@ -0,0 +1,460 @@ 
+/*
+ * 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 <stdlib.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 {
+	enum _bat_pcm_format format_bat;
+	enum pcm_format format_tiny;
+};
+
+static struct format_map_table map_tables[] = {
+	{ BAT_PCM_FORMAT_S16_LE, PCM_FORMAT_S16_LE },
+	{ BAT_PCM_FORMAT_S32_LE, PCM_FORMAT_S32_LE },
+	{ BAT_PCM_FORMAT_MAX, },
+};
+
+static int format_convert(struct bat *bat, struct pcm_config *config)
+{
+	struct format_map_table *t = map_tables;
+
+	for (; t->format_bat != BAT_PCM_FORMAT_MAX; t++) {
+		if (t->format_bat == bat->format) {
+			config->format = t->format_tiny;
+			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);
+}
+
+/**
+ * Called when thread is finished
+ */
+static void close_handle(void *handle)
+{
+	struct pcm *pcm = handle;
+
+	if (NULL != pcm)
+		pcm_close(pcm);
+}
+
+/**
+ * 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;
+	int frames = bytes / bat->frame_size;
+	FILE *fp = NULL;
+	int bytes_total = 0;
+
+	if (bat->debugplay) {
+		fp = fopen(bat->debugplay, "wb");
+		err = -errno;
+		if (fp == NULL) {
+			fprintf(bat->err, _("Cannot open file: %s %d\n"),
+					bat->debugplay, err);
+			return err;
+		}
+		/* leave space for file header */
+		if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) {
+			err = -errno;
+			fclose(fp);
+			return err;
+		}
+	}
+
+	while (1) {
+		err = generate_input_data(bat, buffer, bytes, frames);
+		if (err != 0)
+			break;
+
+		if (bat->debugplay) {
+			if (fwrite(buffer, 1, bytes, fp) != bytes) {
+				err = -EIO;
+				break;
+			}
+			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)
+			break;
+	}
+
+	if (bat->debugplay) {
+		update_wav_header(bat, fp, bytes_total);
+		fclose(fp);
+	}
+	return err;
+}
+
+static int get_tiny_device(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 device: %s\n"), alsa_device);
+	return -EINVAL;
+}
+
+/**
+ * Play
+ */
+void *playback_tinyalsa(struct bat *bat)
+{
+	int err = 0;
+	struct pcm_config config;
+	struct pcm *pcm = NULL;
+	void *buffer = NULL;
+	int bufbytes;
+
+	fprintf(bat->log, _("Entering playback thread (tinyalsa).\n"));
+
+	retval_play = 0;
+
+	/* init device */
+	err = get_tiny_device(bat, bat->playback.device,
+			&bat->playback.card_tiny,
+			&bat->playback.device_tiny);
+	if (err < 0) {
+		retval_play = err;
+		goto exit1;
+	}
+
+	/* 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;
+	}
+
+	/* open device */
+	pcm = pcm_open(bat->playback.card_tiny, bat->playback.device_tiny,
+			PCM_OUT, &config);
+	if (!pcm || !pcm_is_ready(pcm)) {
+		fprintf(bat->err, _("Unable to open PCM device %u (%s)!\n"),
+				bat->playback.device_tiny, 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");
+		err = -errno;
+		if (bat->fp == NULL) {
+			fprintf(bat->err, _("Cannot open file: %s %d\n"),
+					bat->playback.file, err);
+			retval_play = err;
+			goto exit3;
+		}
+		/* Skip header */
+		err = read_wav_header(bat, bat->playback.file, bat->fp, true);
+		if (err != 0) {
+			retval_play = err;
+			goto exit4;
+		}
+	}
+
+	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");
+	err = -errno;
+	if (fp == NULL) {
+		fprintf(bat->err, _("Cannot open file: %s %d\n"),
+				bat->capture.file, err);
+		return err;
+	}
+	/* leave space for file header */
+	if (fseek(fp, sizeof(struct wav_container), SEEK_SET) != 0) {
+		err = -errno;
+		fclose(fp);
+		return err;
+	}
+
+	while (bytes_read < bytes_count && !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 device */
+	err = get_tiny_device(bat, bat->capture.device,
+			&bat->capture.card_tiny,
+			&bat->capture.device_tiny);
+	if (err < 0) {
+		retval_record = err;
+		goto exit1;
+	}
+
+	/* init config */
+	err = init_config(bat, &config);
+	if (err < 0) {
+		retval_record = err;
+		goto exit1;
+	}
+
+	/* open 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;
+	}
+
+	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..60f78f7
--- /dev/null
+++ b/bat/tinyalsa.h
@@ -0,0 +1,20 @@ 
+/*
+ * 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;
+
+void *playback_tinyalsa(struct bat *);
+void *record_tinyalsa(struct bat *);
diff --git a/configure.ac b/configure.ac
index d712872..b539fb2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -82,6 +82,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"
@@ -95,6 +98,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=""