Fri Jun 19 12:09:45 2009

Asterisk developer's documentation


func_speex.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2008, Digium, Inc.
00005  *
00006  * Brian Degenhardt <bmd@digium.com>
00007  * Brett Bryant <bbryant@digium.com> 
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*! \file
00021  *
00022  * \brief Noise reduction and automatic gain control (AGC)
00023  *
00024  * \author Brian Degenhardt <bmd@digium.com> 
00025  * \author Brett Bryant <bbryant@digium.com> 
00026  *
00027  * \ingroup functions
00028  *
00029  * \extref The Speex library - http://www.speex.org
00030  */
00031 
00032 /*** MODULEINFO
00033    <depend>speex</depend>
00034    <depend>speex_preprocess</depend>
00035    <use>speexdsp</use>
00036  ***/
00037 
00038 #include "asterisk.h"
00039 
00040 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 135680 $")
00041 
00042 #include <speex/speex_preprocess.h>
00043 #include "asterisk/module.h"
00044 #include "asterisk/channel.h"
00045 #include "asterisk/pbx.h"
00046 #include "asterisk/utils.h"
00047 #include "asterisk/audiohook.h"
00048 
00049 #define DEFAULT_AGC_LEVEL 8000.0
00050 
00051 struct speex_direction_info {
00052    SpeexPreprocessState *state;  /*!< speex preprocess state object */
00053    int agc;                /*!< audio gain control is enabled or not */
00054    int denoise;               /*!< denoise is enabled or not */
00055    int samples;               /*!< n of 8Khz samples in last frame */
00056    float agclevel;               /*!< audio gain control level [1.0 - 32768.0] */
00057 };
00058 
00059 struct speex_info {
00060    struct ast_audiohook audiohook;
00061    struct speex_direction_info *tx, *rx;
00062 };
00063 
00064 static void destroy_callback(void *data) 
00065 {
00066    struct speex_info *si = data;
00067 
00068    ast_audiohook_destroy(&si->audiohook);
00069 
00070    if (si->rx && si->rx->state) {
00071       speex_preprocess_state_destroy(si->rx->state);
00072    }
00073 
00074    if (si->tx && si->tx->state) {
00075       speex_preprocess_state_destroy(si->tx->state);
00076    }
00077 
00078    if (si->rx) {
00079       ast_free(si->rx);
00080    }
00081 
00082    if (si->tx) {
00083       ast_free(si->tx);
00084    }
00085 
00086    ast_free(data);
00087 };
00088 
00089 static const struct ast_datastore_info speex_datastore = {
00090    .type = "speex",
00091    .destroy = destroy_callback
00092 };
00093 
00094 static int speex_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
00095 {
00096    struct ast_datastore *datastore = NULL;
00097    struct speex_direction_info *sdi = NULL;
00098    struct speex_info *si = NULL;
00099 
00100    /* If the audiohook is stopping it means the channel is shutting down.... but we let the datastore destroy take care of it */
00101    if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || frame->frametype != AST_FRAME_VOICE) {
00102       return 0;
00103    }
00104    
00105    ast_channel_lock(chan);
00106    if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
00107       ast_channel_unlock(chan);
00108       return 0;
00109    }
00110    ast_channel_unlock(chan);
00111 
00112    si = datastore->data;
00113 
00114    sdi = (direction == AST_AUDIOHOOK_DIRECTION_READ) ? si->rx : si->tx;
00115 
00116    if (!sdi) {
00117       return 0;
00118    }
00119 
00120    if (sdi->samples != frame->samples) {
00121       if (sdi->state) {
00122          speex_preprocess_state_destroy(sdi->state);
00123       }
00124 
00125       if (!(sdi->state = speex_preprocess_state_init((sdi->samples = frame->samples), 8000))) {
00126          return -1;
00127       }
00128       
00129       speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC, &sdi->agc);
00130 
00131       if (sdi->agc) {
00132          speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &sdi->agclevel);
00133       }
00134 
00135       speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_DENOISE, &sdi->denoise);
00136    }
00137 
00138    speex_preprocess(sdi->state, frame->data.ptr, NULL);
00139 
00140    return 0;
00141 }
00142 
00143 static int speex_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00144 {
00145    struct ast_datastore *datastore = NULL;
00146    struct speex_info *si = NULL;
00147    struct speex_direction_info **sdi = NULL;
00148    int is_new = 0;
00149 
00150    ast_channel_lock(chan);
00151    if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
00152       ast_channel_unlock(chan);
00153 
00154       if (!(datastore = ast_datastore_alloc(&speex_datastore, NULL))) {
00155          return 0;
00156       }
00157 
00158       if (!(si = ast_calloc(1, sizeof(*si)))) {
00159          ast_datastore_free(datastore);
00160          return 0;
00161       }
00162 
00163       ast_audiohook_init(&si->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "speex");
00164       si->audiohook.manipulate_callback = speex_callback;
00165 
00166       is_new = 1;
00167    } else {
00168       ast_channel_unlock(chan);
00169       si = datastore->data;
00170    }
00171 
00172    if (!strcasecmp(data, "rx")) {
00173       sdi = &si->rx;
00174    } else if (!strcasecmp(data, "tx")) {
00175       sdi = &si->tx;
00176    } else {
00177       ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd);
00178 
00179       if (is_new) {
00180          ast_datastore_free(datastore);
00181          return -1;
00182       }
00183    }
00184 
00185    if (!*sdi) {
00186       if (!(*sdi = ast_calloc(1, sizeof(**sdi)))) {
00187          return 0;
00188       }
00189       /* Right now, the audiohooks API will _only_ provide us 8 kHz slinear
00190        * audio.  When it supports 16 kHz (or any other sample rates, we will
00191        * have to take that into account here. */
00192       (*sdi)->samples = -1;
00193    }
00194 
00195    if (!strcasecmp(cmd, "agc")) {
00196       if (!sscanf(value, "%f", &(*sdi)->agclevel))
00197          (*sdi)->agclevel = ast_true(value) ? DEFAULT_AGC_LEVEL : 0.0;
00198    
00199       if ((*sdi)->agclevel > 32768.0) {
00200          ast_log(LOG_WARNING, "AGC(%s)=%.01f is greater than 32768... setting to 32768 instead\n", 
00201                ((*sdi == si->rx) ? "rx" : "tx"), (*sdi)->agclevel);
00202          (*sdi)->agclevel = 32768.0;
00203       }
00204    
00205       (*sdi)->agc = !!((*sdi)->agclevel);
00206 
00207       if ((*sdi)->state) {
00208          speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC, &(*sdi)->agc);
00209          if ((*sdi)->agc) {
00210             speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &(*sdi)->agclevel);
00211          }
00212       }
00213    } else if (!strcasecmp(cmd, "denoise")) {
00214       (*sdi)->denoise = (ast_true(value) != 0);
00215 
00216       if ((*sdi)->state) {
00217          speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_DENOISE, &(*sdi)->denoise);
00218       }
00219    }
00220 
00221    if (!(*sdi)->agc && !(*sdi)->denoise) {
00222       if ((*sdi)->state)
00223          speex_preprocess_state_destroy((*sdi)->state);
00224 
00225       ast_free(*sdi);
00226       *sdi = NULL;
00227    }
00228 
00229    if (!si->rx && !si->tx) {
00230       if (is_new) {
00231          is_new = 0;
00232       } else {
00233          ast_channel_lock(chan);
00234          ast_channel_datastore_remove(chan, datastore);
00235          ast_channel_unlock(chan);
00236          ast_audiohook_remove(chan, &si->audiohook);
00237          ast_audiohook_detach(&si->audiohook);
00238       }
00239       
00240       ast_datastore_free(datastore);
00241    }
00242 
00243    if (is_new) { 
00244       datastore->data = si;
00245       ast_channel_lock(chan);
00246       ast_channel_datastore_add(chan, datastore);
00247       ast_channel_unlock(chan);
00248       ast_audiohook_attach(chan, &si->audiohook);
00249    }
00250 
00251    return 0;
00252 }
00253 
00254 static int speex_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00255 {
00256    struct ast_datastore *datastore = NULL;
00257    struct speex_info *si = NULL;
00258    struct speex_direction_info *sdi = NULL;
00259 
00260    if (!chan) {
00261       ast_log(LOG_ERROR, "%s cannot be used without a channel!\n", cmd);
00262       return -1;
00263    }
00264 
00265    ast_channel_lock(chan);
00266    if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
00267       ast_channel_unlock(chan);
00268       return -1;
00269    }
00270    ast_channel_unlock(chan);
00271 
00272    si = datastore->data;
00273 
00274    if (!strcasecmp(data, "tx"))
00275       sdi = si->tx;
00276    else if (!strcasecmp(data, "rx"))
00277       sdi = si->rx;
00278    else {
00279       ast_log(LOG_ERROR, "%s(%s) must either \"tx\" or \"rx\"\n", cmd, data);
00280       return -1;
00281    }
00282 
00283    if (!strcasecmp(cmd, "agc"))
00284       snprintf(buf, len, "%.01f", sdi ? sdi->agclevel : 0.0);
00285    else
00286       snprintf(buf, len, "%d", sdi ? sdi->denoise : 0);
00287 
00288    return 0;
00289 }
00290 
00291 static struct ast_custom_function agc_function = {
00292    .name = "AGC",
00293    .synopsis = "Apply automatic gain control to audio on a channel",
00294    .desc =
00295    "  The AGC function will apply automatic gain control to audio on the channel\n"
00296    "that this function is executed on.  Use rx for audio received from the channel\n"
00297    "and tx to apply AGC to the audio being sent to the channel.  When using this\n"
00298    "function, you set a target audio level.  It is primarily intended for use with\n"
00299    "analog lines, but could be useful for other channels, as well.  The target volume\n"
00300    "is set with a number between 1 and 32768.  Larger numbers are louder.\n"
00301    "  Example Usage:\n"
00302    "    Set(AGC(rx)=8000)\n"
00303    "    Set(AGC(tx)=8000)\n"
00304    "    Set(AGC(rx)=off)\n"
00305    "    Set(AGC(tx)=off)\n"
00306    "",
00307    .write = speex_write,
00308    .read = speex_read
00309 };
00310 
00311 static struct ast_custom_function denoise_function = {
00312    .name = "DENOISE",
00313    .synopsis = "Apply noise reduction to audio on a channel",
00314    .desc =
00315    "  The DENOISE function will apply noise reduction to audio on the channel\n"
00316    "that this function is executed on.  It is especially useful for noisy analog\n"
00317    "lines, especially when adjusting gains or using AGC.  Use rx for audio\n"
00318    "received from the channel and tx to apply the filter to the audio being sent\n"
00319    "to the channel.\n"
00320    "  Example Usage:\n"
00321    "    Set(DENOISE(rx)=on)\n"
00322    "    Set(DENOISE(tx)=on)\n"
00323    "    Set(DENOISE(rx)=off)\n"
00324    "    Set(DENOISE(tx)=off)\n"
00325    "",
00326    .write = speex_write,
00327    .read = speex_read
00328 };
00329 
00330 static int unload_module(void)
00331 {
00332    ast_custom_function_unregister(&agc_function);
00333    ast_custom_function_unregister(&denoise_function);
00334    return 0;
00335 }
00336 
00337 static int load_module(void)
00338 {
00339    if (ast_custom_function_register(&agc_function)) {
00340       return AST_MODULE_LOAD_DECLINE;
00341    }
00342 
00343    if (ast_custom_function_register(&denoise_function)) {
00344       ast_custom_function_unregister(&agc_function);
00345       return AST_MODULE_LOAD_DECLINE;
00346    }
00347 
00348    return AST_MODULE_LOAD_SUCCESS;
00349 }
00350 
00351 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Noise reduction and Automatic Gain Control (AGC)");

Generated on Fri Jun 19 12:09:45 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7