From 715e5051c3026655154003799069ae55dc81cf5a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jan 2009 18:11:43 +0100 Subject: Add Speex pre-processing plugin Added Speex pre-processing filter plugin for denoise, AGC, etc. Signed-off-by: Takashi Iwai --- Makefile.am | 5 +- configure.in | 4 +- doc/Makefile.am | 3 +- doc/speexdsp.txt | 54 +++++++++++ speex/Makefile.am | 9 ++ speex/pcm_speex.c | 286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 doc/speexdsp.txt create mode 100644 speex/Makefile.am create mode 100644 speex/pcm_speex.c 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 + * + * 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 +#include +#include + +/* 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); -- cgit