Add two new dialplan functions from libspeex for applying audio gain control
[asterisk/asterisk.git] / funcs / func_speex.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2008, Digium, Inc.
5  *
6  * Brian Degenhardt <bmd@digium.com>
7  * Brett Bryant <bbryant@digium.com> 
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*! \file
21  *
22  * \brief Noise reduction and automatic gain control (AGC)
23  *
24  * \author Brian Degenhardt <bmd@digium.com> 
25  * \author Brett Bryant <bbryant@digium.com> 
26  *
27  * \ingroup functions
28  *
29  * \extref The Speex library - http://www.speex.org
30  */
31
32 /*** MODULEINFO
33         <depend>speex</depend>
34  ***/
35
36 #include "asterisk.h"
37
38 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39
40 #include <speex/speex_preprocess.h>
41 #include "asterisk/module.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/audiohook.h"
46
47 #define DEFAULT_AGC_LEVEL 8000.0
48
49 struct speex_direction_info {
50         SpeexPreprocessState *state;    /*!< speex preprocess state object */
51         int agc;                                                /*!< audio gain control is enabled or not */
52         int denoise;                                    /*!< denoise is enabled or not */
53         int samples;                                    /*!< n of 8Khz samples in last frame */
54         float agclevel;                                 /*!< audio gain control level [1.0 - 32768.0] */
55 };
56
57 struct speex_info {
58         struct ast_audiohook audiohook;
59         struct speex_direction_info *tx, *rx;
60 };
61
62 static void destroy_callback(void *data) 
63 {
64         struct speex_info *si = data;
65
66         ast_audiohook_destroy(&si->audiohook);
67
68         if (si->rx && si->rx->state) {
69                 speex_preprocess_state_destroy(si->rx->state);
70         }
71
72         if (si->tx && si->tx->state) {
73                 speex_preprocess_state_destroy(si->tx->state);
74         }
75
76         if (si->rx) {
77                 ast_free(si->rx);
78         }
79
80         if (si->tx) {
81                 ast_free(si->tx);
82         }
83
84         ast_free(data);
85 };
86
87 static const struct ast_datastore_info speex_datastore = {
88         .type = "speex",
89         .destroy = destroy_callback
90 };
91
92 static int speex_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
93 {
94         struct ast_datastore *datastore = NULL;
95         struct speex_direction_info *sdi = NULL;
96         struct speex_info *si = NULL;
97
98         /* If the audiohook is stopping it means the channel is shutting down.... but we let the datastore destroy take care of it */
99         if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || frame->frametype != AST_FRAME_VOICE) {
100                 return 0;
101         }
102         
103         ast_channel_lock(chan);
104         if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
105                 ast_channel_unlock(chan);
106                 return 0;
107         }
108         ast_channel_unlock(chan);
109
110         si = datastore->data;
111
112         sdi = (direction == AST_AUDIOHOOK_DIRECTION_READ) ? si->rx : si->tx;
113
114         if (!sdi) {
115                 return 0;
116         }
117
118         if (sdi->samples != frame->samples) {
119                 if (sdi->state) {
120                         speex_preprocess_state_destroy(sdi->state);
121                 }
122
123                 if (!(sdi->state = speex_preprocess_state_init((sdi->samples = frame->samples), 8000))) {
124                         return -1;
125                 }
126                 
127                 speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC, &sdi->agc);
128
129                 if (sdi->agc) {
130                         speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &sdi->agclevel);
131                 }
132
133                 speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_DENOISE, &sdi->denoise);
134         }
135
136         speex_preprocess(sdi->state, frame->data, NULL);
137
138         return 0;
139 }
140
141 static int speex_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
142 {
143         struct ast_datastore *datastore = NULL;
144         struct speex_info *si = NULL;
145         struct speex_direction_info **sdi = NULL;
146         int is_new = 0;
147
148         ast_channel_lock(chan);
149         if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
150                 ast_channel_unlock(chan);
151
152                 if (!(datastore = ast_channel_datastore_alloc(&speex_datastore, NULL))) {
153                         return 0;
154                 }
155
156                 if (!(si = ast_calloc(1, sizeof(*si)))) {
157                         ast_channel_datastore_free(datastore);
158                         return 0;
159                 }
160
161                 ast_audiohook_init(&si->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "speex");
162                 si->audiohook.manipulate_callback = speex_callback;
163
164                 is_new = 1;
165         } else {
166                 ast_channel_unlock(chan);
167                 si = datastore->data;
168         }
169
170         if (!strcasecmp(data, "rx")) {
171                 sdi = &si->rx;
172         } else if (!strcasecmp(data, "tx")) {
173                 sdi = &si->tx;
174         } else {
175                 ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd);
176
177                 if (is_new) {
178                         ast_channel_datastore_free(datastore);
179                         return -1;
180                 }
181         }
182
183         if (!*sdi) {
184                 if (!(*sdi = ast_calloc(1, sizeof(**sdi)))) {
185                         return 0;
186                 }
187                 /* Right now, the audiohooks API will _only_ provide us 8 kHz slinear
188                  * audio.  When it supports 16 kHz (or any other sample rates, we will
189                  * have to take that into account here. */
190                 (*sdi)->samples = -1;
191         }
192
193         if (!strcasecmp(cmd, "agc")) {
194                 if (!sscanf(value, "%f", &(*sdi)->agclevel))
195                         (*sdi)->agclevel = ast_true(value) ? DEFAULT_AGC_LEVEL : 0.0;
196         
197                 if ((*sdi)->agclevel > 32768.0) {
198                         ast_log(LOG_WARNING, "AGC(%s)=%.01f is greater than 32768... setting to 32768 instead\n", 
199                                         ((*sdi == si->rx) ? "rx" : "tx"), (*sdi)->agclevel);
200                         (*sdi)->agclevel = 32768.0;
201                 }
202         
203                 (*sdi)->agc = !!((*sdi)->agclevel);
204
205                 if ((*sdi)->state) {
206                         speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC, &(*sdi)->agc);
207                         if ((*sdi)->agc) {
208                                 speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &(*sdi)->agclevel);
209                         }
210                 }
211         } else if (!strcasecmp(cmd, "denoise")) {
212                 (*sdi)->denoise = ast_true(value);
213
214                 if ((*sdi)->state) {
215                         speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_DENOISE, &(*sdi)->denoise);
216                 }
217         }
218
219         if (!(*sdi)->agc && !(*sdi)->denoise) {
220                 if ((*sdi)->state)
221                         speex_preprocess_state_destroy((*sdi)->state);
222
223                 ast_free(*sdi);
224                 *sdi = NULL;
225         }
226
227         if (!si->rx && !si->tx) {
228                 if (is_new) {
229                         is_new = 0;
230                 } else {
231                         ast_channel_lock(chan);
232                         ast_channel_datastore_remove(chan, datastore);
233                         ast_channel_unlock(chan);
234                         ast_audiohook_remove(chan, &si->audiohook);
235                         ast_audiohook_detach(&si->audiohook);
236                 }
237                 
238                 ast_channel_datastore_free(datastore);
239         }
240
241         if (is_new) { 
242                 datastore->data = si;
243                 ast_channel_lock(chan);
244                 ast_channel_datastore_add(chan, datastore);
245                 ast_channel_unlock(chan);
246                 ast_audiohook_attach(chan, &si->audiohook);
247         }
248
249         return 0;
250 }
251
252 static struct ast_custom_function agc_function = {
253         .name = "AGC",
254         .synopsis = "Apply automatic gain control to audio on a channel",
255         .desc =
256         "  The AGC function will apply automatic gain control to audio on the channel\n"
257         "that this function is executed on.  Use rx for audio received from the channel\n"
258         "and tx to apply AGC to the audio being sent to the channel.  When using this\n"
259         "function, you set a target audio level.  It is primarily intended for use with\n"
260         "analog lines, but could be useful for other channels, as well.  The target volume\n"
261         "is set with a number between 1 and 32768.  Larger numbers are louder.\n"
262         "  Example Usage:\n"
263         "    Set(AGC(rx)=8000)\n"
264         "    Set(AGC(tx)=8000)\n"
265         "    Set(AGC(rx)=off)\n"
266         "    Set(AGC(tx)=off)\n"
267         "",
268         .write = speex_write,
269 };
270
271 static struct ast_custom_function denoise_function = {
272         .name = "DENOISE",
273         .synopsis = "Apply noise reduction to audio on a channel",
274         .desc =
275         "  The DENOISE function will apply noise reduction to audio on the channel\n"
276         "that this function is executed on.  It is especially useful for noisy analog\n"
277         "lines, especially when adjusting gains or using AGC.  Use rx for audio\n"
278         "received from the channel and tx to apply the filter to the audio being sent\n"
279         "to the channel.\n"
280         "  Example Usage:\n"
281         "    Set(DENOISE(rx)=on)\n"
282         "    Set(DENOISE(tx)=on)\n"
283         "    Set(DENOISE(rx)=off)\n"
284         "    Set(DENOISE(tx)=off)\n"
285         "",
286         .write = speex_write,
287 };
288
289 static int unload_module(void)
290 {
291         ast_custom_function_unregister(&agc_function);
292         ast_custom_function_unregister(&denoise_function);
293         return 0;
294 }
295
296 static int load_module(void)
297 {
298         if (ast_custom_function_register(&agc_function)) {
299                 return AST_MODULE_LOAD_DECLINE;
300         }
301
302         if (ast_custom_function_register(&denoise_function)) {
303                 ast_custom_function_unregister(&denoise_function);
304                 return AST_MODULE_LOAD_DECLINE;
305         }
306
307         return AST_MODULE_LOAD_SUCCESS;
308 }
309
310 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Noise reduction and Automatic Gain Control (AGC)");