summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2009-01-21 18:11:43 +0100
committerTakashi Iwai <tiwai@suse.de>2009-01-21 18:11:43 +0100
commit715e5051c3026655154003799069ae55dc81cf5a (patch)
tree05a41f37a160f3be648ca6859a4f026d29d9c23d
parentba7cd8e0a04798479f851741ec29fef97889aa39 (diff)
Add Speex pre-processing plugin
Added Speex pre-processing filter plugin for denoise, AGC, etc. Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--Makefile.am5
-rw-r--r--configure.in4
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/speexdsp.txt54
-rw-r--r--speex/Makefile.am9
-rw-r--r--speex/pcm_speex.c286
6 files changed, 358 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index b32f1f5..aba3ef6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -17,8 +17,11 @@ endif
if HAVE_PPH
PPHDIR = pph
endif
+if HAVE_SPEEXDSP
+SPEEXDIR = speex
+endif
-SUBDIRS = oss mix $(PPHDIR) $(JACKDIR) $(PULSEDIR) $(SAMPLERATEDIR) $(A52DIR) $(LAVCRATEDIR) $(MAEMODIR) usb_stream doc
+SUBDIRS = oss mix $(PPHDIR) $(JACKDIR) $(PULSEDIR) $(SAMPLERATEDIR) $(A52DIR) $(LAVCRATEDIR) $(MAEMODIR) $(SPEEXDIR) usb_stream doc
EXTRA_DIST = gitcompile version COPYING.GPL m4/attributes.m4
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
diff --git a/configure.in b/configure.in
index ce95b5d..12c6f54 100644
--- a/configure.in
+++ b/configure.in
@@ -166,6 +166,7 @@ AC_OUTPUT([
maemo/Makefile
doc/Makefile
usb_stream/Makefile
+ speex/Makefile
])
dnl Show the build conditions
@@ -198,7 +199,8 @@ if test "$HAVE_AVCODEC" = "yes"; then
echo " AVCODEC_HEADER: $AVCODEC_HEADER"
fi
echo "Speex rate plugin: $PPH"
-if test "$PPH" = "lib"; then
+echo "Speex preprocess plugin: $HAVE_SPEEXDSP"
+if test "$HAVE_SPEEX" = "yes"; then
echo " speexdsp_CFLAGS: $speexdsp_CFLAGS"
echo " speexdsp_LIBS: $speexdsp_LIBS"
fi
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 41a7ebe..3e89be8 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,3 +1,4 @@
EXTRA_DIST = README-pcm-oss README-jack README-pulse README-maemo \
upmix.txt vdownmix.txt samplerate.txt a52.txt lavcrate.txt \
- speexrate.txt
+ speexrate.txt speexdsp.txt
+
diff --git a/doc/speexdsp.txt b/doc/speexdsp.txt
new file mode 100644
index 0000000..875fc19
--- /dev/null
+++ b/doc/speexdsp.txt
@@ -0,0 +1,54 @@
+Speex Preprocessing Plugin
+==========================
+
+This plugin provides a pre-processing of a mono stream like denoise
+using libspeex DSP API. You can use the plugin with the plugin type
+"speex" like below:
+
+ pcm.my_pcm {
+ type speex
+ slave.pcm "default"
+ }
+
+Then record like
+
+ % arecord -fdat -c1 -Dplug:speex foo.wav
+
+so that you'll get 48kHz mono stream with the denoising effect.
+
+Right now, the plugin supports only a mono stream.
+The accepted format is only S16.
+
+The following parameters can be set optionally:
+
+* frames
+
+ This controls the frames of the intermediate buffer. This
+ corresponds to the latency of the filter. As default it's 64.
+
+* denoise
+
+ A boolean value to enable/disable the denoise function. Default is
+ yes.
+
+* agc
+
+ A boolean value to enable/disable the auto-gain control function.
+ Default is no.
+
+* agc_level
+
+ A float value for the automatic gain-control level. Default is 8000.
+
+* dereverb
+
+ A boolean value to enable/disable dereverb function. Default is no.
+
+For example, you can enable agc like
+
+ pcm.my_pcm {
+ type speex
+ slave.pcm "default"
+ agc 1
+ agc_level 8000
+ }
diff --git a/speex/Makefile.am b/speex/Makefile.am
new file mode 100644
index 0000000..7d84190
--- /dev/null
+++ b/speex/Makefile.am
@@ -0,0 +1,9 @@
+asound_module_pcm_speex_LTLIBRARIES = libasound_module_pcm_speex.la
+
+asound_module_pcm_speexdir = @ALSA_PLUGIN_DIR@
+
+AM_CFLAGS = -Wall -g @ALSA_CFLAGS@ @speexdsp_CFLAGS@
+AM_LDFLAGS = -module -avoid-version -export-dynamic -no-undefined $(LDFLAGS_NOUNDEFINED)
+
+libasound_module_pcm_speex_la_SOURCES = pcm_speex.c
+libasound_module_pcm_speex_la_LIBADD = @ALSA_LIBS@ @speexdsp_LIBS@
diff --git a/speex/pcm_speex.c b/speex/pcm_speex.c
new file mode 100644
index 0000000..7bb9213
--- /dev/null
+++ b/speex/pcm_speex.c
@@ -0,0 +1,286 @@
+/*
+ * Speex preprocess plugin
+ *
+ * Copyright (c) 2009 by Takashi Iwai <tiwai@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <alsa/asoundlib.h>
+#include <alsa/pcm_external.h>
+#include <speex/speex_preprocess.h>
+
+/* preprocessing parameters */
+struct spx_parms {
+ int frames;
+ int denoise;
+ int agc;
+ float agc_level;
+ int dereverb;
+ float dereverb_decay;
+ float dereverb_level;
+};
+
+typedef struct {
+ snd_pcm_extplug_t ext;
+ struct spx_parms parms;
+ /* instance and intermedate buffer */
+ SpeexPreprocessState *state;
+ short *buf;
+ /* running states */
+ unsigned int filled;
+ unsigned int processed;
+} snd_pcm_speex_t;
+
+
+static inline void *area_addr(const snd_pcm_channel_area_t *area,
+ snd_pcm_uframes_t offset)
+{
+ unsigned int bitofs = area->first + area->step * offset;
+ return (char *) area->addr + bitofs / 8;
+}
+
+static snd_pcm_sframes_t
+spx_transfer(snd_pcm_extplug_t *ext,
+ const snd_pcm_channel_area_t *dst_areas,
+ snd_pcm_uframes_t dst_offset,
+ const snd_pcm_channel_area_t *src_areas,
+ snd_pcm_uframes_t src_offset,
+ snd_pcm_uframes_t size)
+{
+ snd_pcm_speex_t *spx = (snd_pcm_speex_t *)ext;
+ short *src = area_addr(src_areas, src_offset);
+ short *dst = area_addr(dst_areas, dst_offset);
+ unsigned int count = size;
+
+ while (count > 0) {
+ unsigned int chunk;
+ if (spx->filled + count > spx->parms.frames)
+ chunk = spx->parms.frames - spx->filled;
+ else
+ chunk = count;
+ if (spx->processed)
+ memcpy(dst, spx->buf + spx->filled, chunk * 2);
+ else
+ memset(dst, 0, chunk * 2);
+ dst += chunk;
+ memcpy(spx->buf + spx->filled, src, chunk * 2);
+ spx->filled += chunk;
+ if (spx->filled == spx->parms.frames) {
+ speex_preprocess_run(spx->state, spx->buf);
+ spx->processed = 1;
+ spx->filled = 0;
+ }
+ src += chunk;
+ count -= chunk;
+ }
+
+ return size;
+}
+
+static int spx_init(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_speex_t *spx = (snd_pcm_speex_t *)ext;
+
+ if (!spx->buf) {
+ spx->buf = malloc(spx->parms.frames * 2);
+ if (!spx->buf)
+ return -ENOMEM;
+ }
+ memset(spx->buf, 0, spx->parms.frames * 2);
+
+ if (spx->state)
+ speex_preprocess_state_destroy(spx->state);
+ spx->state = speex_preprocess_state_init(spx->parms.frames,
+ spx->ext.rate);
+ if (!spx->state)
+ return -EIO;
+
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_DENOISE,
+ &spx->parms.denoise);
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_AGC,
+ &spx->parms.agc);
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_AGC_LEVEL,
+ &spx->parms.agc_level);
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_DEREVERB,
+ &spx->parms.dereverb);
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_DEREVERB_DECAY,
+ &spx->parms.dereverb_decay);
+ speex_preprocess_ctl(spx->state, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL,
+ &spx->parms.dereverb_level);
+
+ spx->filled = 0;
+ spx->processed = 0;
+ return 0;
+}
+
+static int spx_close(snd_pcm_extplug_t *ext)
+{
+ snd_pcm_speex_t *spx = (snd_pcm_speex_t *)ext;
+ free(spx->buf);
+ if (spx->state)
+ speex_preprocess_state_destroy(spx->state);
+ return 0;
+}
+
+static const snd_pcm_extplug_callback_t speex_callback = {
+ .transfer = spx_transfer,
+ .init = spx_init,
+ .close = spx_close,
+};
+
+static int get_bool_parm(snd_config_t *n, const char *id, const char *str,
+ int *val_ret)
+{
+ int val;
+ if (strcmp(id, str))
+ return 0;
+
+ val = snd_config_get_bool(n);
+ if (val < 0) {
+ SNDERR("Invalid value for %s", id);
+ return val;
+ }
+ *val_ret = val;
+ return 1;
+}
+
+static int get_int_parm(snd_config_t *n, const char *id, const char *str,
+ int *val_ret)
+{
+ long val;
+ int err;
+
+ if (strcmp(id, str))
+ return 0;
+ err = snd_config_get_integer(n, &val);
+ if (err < 0) {
+ SNDERR("Invalid value for %s parameter", id);
+ return err;
+ }
+ *val_ret = val;
+ return 1;
+}
+
+static int get_float_parm(snd_config_t *n, const char *id, const char *str,
+ float *val_ret)
+{
+ double val;
+ int err;
+
+ if (strcmp(id, str))
+ return 0;
+ err = snd_config_get_ireal(n, &val);
+ if (err < 0) {
+ SNDERR("Invalid value for %s", id);
+ return err;
+ }
+ *val_ret = val;
+ return 1;
+}
+
+SND_PCM_PLUGIN_DEFINE_FUNC(speex)
+{
+ snd_config_iterator_t i, next;
+ snd_pcm_speex_t *spx;
+ snd_config_t *sconf = NULL;
+ int err;
+ struct spx_parms parms = {
+ .frames = 64,
+ .denoise = 1,
+ .agc = 0,
+ .agc_level = 8000,
+ .dereverb = 0,
+ .dereverb_decay = 0,
+ .dereverb_level = 0,
+ };
+
+ snd_config_for_each(i, next, conf) {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 ||
+ strcmp(id, "hint") == 0)
+ continue;
+ if (strcmp(id, "slave") == 0) {
+ sconf = n;
+ continue;
+ }
+ err = get_int_parm(n, id, "frames", &parms.frames);
+ if (err)
+ goto ok;
+ err = get_bool_parm(n, id, "denoise", &parms.denoise);
+ if (err)
+ goto ok;
+ err = get_bool_parm(n, id, "agc", &parms.agc);
+ if (err)
+ goto ok;
+ err = get_float_parm(n, id, "agc_level", &parms.agc_level);
+ if (err)
+ goto ok;
+ err = get_bool_parm(n, id, "dereverb", &parms.dereverb);
+ if (err)
+ goto ok;
+ err = get_float_parm(n, id, "dereverb_decay",
+ &parms.dereverb_decay);
+ if (err)
+ goto ok;
+ err = get_float_parm(n, id, "dereverb_level",
+ &parms.dereverb_level);
+ if (err)
+ goto ok;
+ SNDERR("Unknown field %s", id);
+ err = -EINVAL;
+ ok:
+ if (err < 0)
+ return err;
+ }
+
+ if (!sconf) {
+ SNDERR("No slave configuration for speex pcm");
+ return -EINVAL;
+ }
+
+ spx = calloc(1, sizeof(*spx));
+ if (!spx)
+ return -ENOMEM;
+
+ spx->ext.version = SND_PCM_EXTPLUG_VERSION;
+ spx->ext.name = "Speex Denoise Plugin";
+ spx->ext.callback = &speex_callback;
+ spx->ext.private_data = spx;
+ spx->parms = parms;
+
+ err = snd_pcm_extplug_create(&spx->ext, name, root, sconf,
+ stream, mode);
+ if (err < 0) {
+ free(spx);
+ return err;
+ }
+
+ snd_pcm_extplug_set_param(&spx->ext, SND_PCM_EXTPLUG_HW_CHANNELS, 1);
+ snd_pcm_extplug_set_slave_param(&spx->ext,
+ SND_PCM_EXTPLUG_HW_CHANNELS, 1);
+ snd_pcm_extplug_set_param(&spx->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+ snd_pcm_extplug_set_slave_param(&spx->ext, SND_PCM_EXTPLUG_HW_FORMAT,
+ SND_PCM_FORMAT_S16);
+
+ *pcmp = spx->ext.pcm;
+ return 0;
+}
+
+SND_PCM_PLUGIN_SYMBOL(speex);