Add two new dialplan functions from libspeex for applying audio gain control
authorBrett Bryant <bbryant@digium.com>
Thu, 1 May 2008 16:57:19 +0000 (16:57 +0000)
committerBrett Bryant <bbryant@digium.com>
Thu, 1 May 2008 16:57:19 +0000 (16:57 +0000)
and denoising to a channel, AGC() and DENOISE(). Also included, is a change
to the audiohook API to add a new function (ast_audiohook_remove) that can
remove an audiohook from a channel before it is detached.

This code is based on a contribution from Switchvox.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@114926 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
funcs/func_speex.c [new file with mode: 0644]
include/asterisk/audiohook.h
main/audiohook.c

diff --git a/CHANGES b/CHANGES
index 253f020..51b0e5a 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,9 @@ Dialplan Functions
  * Added a new dialplan function, AST_CONFIG(), which allows you to access
    variables from an Asterisk configuration file.
  * The JACK_HOOK function now has a c() option to supply a custom client name.
+ * Added two new dialplan functions from libspeex for audio gain control and 
+   denoise, AGC() and DENOISE(). Both functions can be applied to the tx and 
+   rx directions of a channel from the dialplan.
 
 Zaptel channel driver (chan_zap) Changes
 ----------------------------------------
diff --git a/funcs/func_speex.c b/funcs/func_speex.c
new file mode 100644 (file)
index 0000000..33282a1
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008, Digium, Inc.
+ *
+ * Brian Degenhardt <bmd@digium.com>
+ * Brett Bryant <bbryant@digium.com> 
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Noise reduction and automatic gain control (AGC)
+ *
+ * \author Brian Degenhardt <bmd@digium.com> 
+ * \author Brett Bryant <bbryant@digium.com> 
+ *
+ * \ingroup functions
+ *
+ * \extref The Speex library - http://www.speex.org
+ */
+
+/*** MODULEINFO
+       <depend>speex</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <speex/speex_preprocess.h>
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/audiohook.h"
+
+#define DEFAULT_AGC_LEVEL 8000.0
+
+struct speex_direction_info {
+       SpeexPreprocessState *state;    /*!< speex preprocess state object */
+       int agc;                                                /*!< audio gain control is enabled or not */
+       int denoise;                                    /*!< denoise is enabled or not */
+       int samples;                                    /*!< n of 8Khz samples in last frame */
+       float agclevel;                                 /*!< audio gain control level [1.0 - 32768.0] */
+};
+
+struct speex_info {
+       struct ast_audiohook audiohook;
+       struct speex_direction_info *tx, *rx;
+};
+
+static void destroy_callback(void *data) 
+{
+       struct speex_info *si = data;
+
+       ast_audiohook_destroy(&si->audiohook);
+
+       if (si->rx && si->rx->state) {
+               speex_preprocess_state_destroy(si->rx->state);
+       }
+
+       if (si->tx && si->tx->state) {
+               speex_preprocess_state_destroy(si->tx->state);
+       }
+
+       if (si->rx) {
+               ast_free(si->rx);
+       }
+
+       if (si->tx) {
+               ast_free(si->tx);
+       }
+
+       ast_free(data);
+};
+
+static const struct ast_datastore_info speex_datastore = {
+       .type = "speex",
+       .destroy = destroy_callback
+};
+
+static int speex_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
+{
+       struct ast_datastore *datastore = NULL;
+       struct speex_direction_info *sdi = NULL;
+       struct speex_info *si = NULL;
+
+       /* If the audiohook is stopping it means the channel is shutting down.... but we let the datastore destroy take care of it */
+       if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || frame->frametype != AST_FRAME_VOICE) {
+               return 0;
+       }
+       
+       ast_channel_lock(chan);
+       if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
+               ast_channel_unlock(chan);
+               return 0;
+       }
+       ast_channel_unlock(chan);
+
+       si = datastore->data;
+
+       sdi = (direction == AST_AUDIOHOOK_DIRECTION_READ) ? si->rx : si->tx;
+
+       if (!sdi) {
+               return 0;
+       }
+
+       if (sdi->samples != frame->samples) {
+               if (sdi->state) {
+                       speex_preprocess_state_destroy(sdi->state);
+               }
+
+               if (!(sdi->state = speex_preprocess_state_init((sdi->samples = frame->samples), 8000))) {
+                       return -1;
+               }
+               
+               speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC, &sdi->agc);
+
+               if (sdi->agc) {
+                       speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &sdi->agclevel);
+               }
+
+               speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_DENOISE, &sdi->denoise);
+       }
+
+       speex_preprocess(sdi->state, frame->data, NULL);
+
+       return 0;
+}
+
+static int speex_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+       struct ast_datastore *datastore = NULL;
+       struct speex_info *si = NULL;
+       struct speex_direction_info **sdi = NULL;
+       int is_new = 0;
+
+       ast_channel_lock(chan);
+       if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
+               ast_channel_unlock(chan);
+
+               if (!(datastore = ast_channel_datastore_alloc(&speex_datastore, NULL))) {
+                       return 0;
+               }
+
+               if (!(si = ast_calloc(1, sizeof(*si)))) {
+                       ast_channel_datastore_free(datastore);
+                       return 0;
+               }
+
+               ast_audiohook_init(&si->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "speex");
+               si->audiohook.manipulate_callback = speex_callback;
+
+               is_new = 1;
+       } else {
+               ast_channel_unlock(chan);
+               si = datastore->data;
+       }
+
+       if (!strcasecmp(data, "rx")) {
+               sdi = &si->rx;
+       } else if (!strcasecmp(data, "tx")) {
+               sdi = &si->tx;
+       } else {
+               ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd);
+
+               if (is_new) {
+                       ast_channel_datastore_free(datastore);
+                       return -1;
+               }
+       }
+
+       if (!*sdi) {
+               if (!(*sdi = ast_calloc(1, sizeof(**sdi)))) {
+                       return 0;
+               }
+               /* Right now, the audiohooks API will _only_ provide us 8 kHz slinear
+                * audio.  When it supports 16 kHz (or any other sample rates, we will
+                * have to take that into account here. */
+               (*sdi)->samples = -1;
+       }
+
+       if (!strcasecmp(cmd, "agc")) {
+               if (!sscanf(value, "%f", &(*sdi)->agclevel))
+                       (*sdi)->agclevel = ast_true(value) ? DEFAULT_AGC_LEVEL : 0.0;
+       
+               if ((*sdi)->agclevel > 32768.0) {
+                       ast_log(LOG_WARNING, "AGC(%s)=%.01f is greater than 32768... setting to 32768 instead\n", 
+                                       ((*sdi == si->rx) ? "rx" : "tx"), (*sdi)->agclevel);
+                       (*sdi)->agclevel = 32768.0;
+               }
+       
+               (*sdi)->agc = !!((*sdi)->agclevel);
+
+               if ((*sdi)->state) {
+                       speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC, &(*sdi)->agc);
+                       if ((*sdi)->agc) {
+                               speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &(*sdi)->agclevel);
+                       }
+               }
+       } else if (!strcasecmp(cmd, "denoise")) {
+               (*sdi)->denoise = ast_true(value);
+
+               if ((*sdi)->state) {
+                       speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_DENOISE, &(*sdi)->denoise);
+               }
+       }
+
+       if (!(*sdi)->agc && !(*sdi)->denoise) {
+               if ((*sdi)->state)
+                       speex_preprocess_state_destroy((*sdi)->state);
+
+               ast_free(*sdi);
+               *sdi = NULL;
+       }
+
+       if (!si->rx && !si->tx) {
+               if (is_new) {
+                       is_new = 0;
+               } else {
+                       ast_channel_lock(chan);
+                       ast_channel_datastore_remove(chan, datastore);
+                       ast_channel_unlock(chan);
+                       ast_audiohook_remove(chan, &si->audiohook);
+                       ast_audiohook_detach(&si->audiohook);
+               }
+               
+               ast_channel_datastore_free(datastore);
+       }
+
+       if (is_new) { 
+               datastore->data = si;
+               ast_channel_lock(chan);
+               ast_channel_datastore_add(chan, datastore);
+               ast_channel_unlock(chan);
+               ast_audiohook_attach(chan, &si->audiohook);
+       }
+
+       return 0;
+}
+
+static struct ast_custom_function agc_function = {
+       .name = "AGC",
+       .synopsis = "Apply automatic gain control to audio on a channel",
+       .desc =
+       "  The AGC function will apply automatic gain control to audio on the channel\n"
+       "that this function is executed on.  Use rx for audio received from the channel\n"
+       "and tx to apply AGC to the audio being sent to the channel.  When using this\n"
+       "function, you set a target audio level.  It is primarily intended for use with\n"
+       "analog lines, but could be useful for other channels, as well.  The target volume\n"
+       "is set with a number between 1 and 32768.  Larger numbers are louder.\n"
+       "  Example Usage:\n"
+       "    Set(AGC(rx)=8000)\n"
+       "    Set(AGC(tx)=8000)\n"
+       "    Set(AGC(rx)=off)\n"
+       "    Set(AGC(tx)=off)\n"
+       "",
+       .write = speex_write,
+};
+
+static struct ast_custom_function denoise_function = {
+       .name = "DENOISE",
+       .synopsis = "Apply noise reduction to audio on a channel",
+       .desc =
+       "  The DENOISE function will apply noise reduction to audio on the channel\n"
+       "that this function is executed on.  It is especially useful for noisy analog\n"
+       "lines, especially when adjusting gains or using AGC.  Use rx for audio\n"
+       "received from the channel and tx to apply the filter to the audio being sent\n"
+       "to the channel.\n"
+       "  Example Usage:\n"
+       "    Set(DENOISE(rx)=on)\n"
+       "    Set(DENOISE(tx)=on)\n"
+       "    Set(DENOISE(rx)=off)\n"
+       "    Set(DENOISE(tx)=off)\n"
+       "",
+       .write = speex_write,
+};
+
+static int unload_module(void)
+{
+       ast_custom_function_unregister(&agc_function);
+       ast_custom_function_unregister(&denoise_function);
+       return 0;
+}
+
+static int load_module(void)
+{
+       if (ast_custom_function_register(&agc_function)) {
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       if (ast_custom_function_register(&denoise_function)) {
+               ast_custom_function_unregister(&denoise_function);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Noise reduction and Automatic Gain Control (AGC)");
index 4ebd19e..3345c5d 100644 (file)
@@ -160,6 +160,18 @@ int ast_audiohook_detach_list(struct ast_audiohook_list *audiohook_list);
  */
 int ast_audiohook_detach_source(struct ast_channel *chan, const char *source);
 
+/*!
+ * \brief Remove an audiohook from a specified channel
+ *
+ * \param chan Channel to remove from
+ * \param audiohook Audiohook to remove
+ *
+ * \return Returns 0 on success, -1 on failure
+ *
+ * \note The channel does not need to be locked before calling this function
+ */
+int ast_audiohook_remove(struct ast_channel *chan, struct ast_audiohook *audiohook);
+
 /*! \brief Pass a frame off to be handled by the audiohook core
  * \param chan Channel that the list is coming off of
  * \param audiohook_list List of audiohooks
index 1f5bcff..3797017 100644 (file)
@@ -455,6 +455,42 @@ int ast_audiohook_detach_source(struct ast_channel *chan, const char *source)
        return (audiohook ? 0 : -1);
 }
 
+/*!
+ * \brief Remove an audiohook from a specified channel
+ *
+ * \param chan Channel to remove from
+ * \param audiohook Audiohook to remove
+ *
+ * \return Returns 0 on success, -1 on failure
+ *
+ * \note The channel does not need to be locked before calling this function
+ */
+int ast_audiohook_remove(struct ast_channel *chan, struct ast_audiohook *audiohook)
+{
+       ast_channel_lock(chan);
+
+       if (!chan->audiohooks) {
+               ast_channel_unlock(chan);
+               return -1;
+       }
+
+       if (audiohook->type == AST_AUDIOHOOK_TYPE_SPY)
+               AST_LIST_REMOVE(&chan->audiohooks->spy_list, audiohook, list);
+       else if (audiohook->type == AST_AUDIOHOOK_TYPE_WHISPER)
+               AST_LIST_REMOVE(&chan->audiohooks->whisper_list, audiohook, list);
+       else if (audiohook->type == AST_AUDIOHOOK_TYPE_MANIPULATE)
+               AST_LIST_REMOVE(&chan->audiohooks->manipulate_list, audiohook, list);
+
+       ast_audiohook_lock(audiohook);
+       audiohook->status = AST_AUDIOHOOK_STATUS_DONE;
+       ast_cond_signal(&audiohook->trigger);
+       ast_audiohook_unlock(audiohook);
+
+       ast_channel_unlock(chan);
+
+       return 0;
+}
+
 /*! \brief Pass a DTMF frame off to be handled by the audiohook core
  * \param chan Channel that the list is coming off of
  * \param audiohook_list List of audiohooks