7b24840101f332d94d0075ebe31a9cdd80a90094
[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         <depend>speex_preprocess</depend>
35         <use>speexdsp</use>
36  ***/
37
38 #include "asterisk.h"
39
40 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
41
42 #include <speex/speex_preprocess.h>
43 #include "asterisk/module.h"
44 #include "asterisk/channel.h"
45 #include "asterisk/pbx.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/audiohook.h"
48
49 #define DEFAULT_AGC_LEVEL 8000.0
50
51 struct speex_direction_info {
52         SpeexPreprocessState *state;    /*!< speex preprocess state object */
53         int agc;                                                /*!< audio gain control is enabled or not */
54         int denoise;                                    /*!< denoise is enabled or not */
55         int samples;                                    /*!< n of 8Khz samples in last frame */
56         float agclevel;                                 /*!< audio gain control level [1.0 - 32768.0] */
57 };
58
59 struct speex_info {
60         struct ast_audiohook audiohook;
61         struct speex_direction_info *tx, *rx;
62 };
63
64 static void destroy_callback(void *data) 
65 {
66         struct speex_info *si = data;
67
68         ast_audiohook_destroy(&si->audiohook);
69
70         if (si->rx && si->rx->state) {
71                 speex_preprocess_state_destroy(si->rx->state);
72         }
73
74         if (si->tx && si->tx->state) {
75                 speex_preprocess_state_destroy(si->tx->state);
76         }
77
78         if (si->rx) {
79                 ast_free(si->rx);
80         }
81
82         if (si->tx) {
83                 ast_free(si->tx);
84         }
85
86         ast_free(data);
87 };
88
89 static const struct ast_datastore_info speex_datastore = {
90         .type = "speex",
91         .destroy = destroy_callback
92 };
93
94 static int speex_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
95 {
96         struct ast_datastore *datastore = NULL;
97         struct speex_direction_info *sdi = NULL;
98         struct speex_info *si = NULL;
99
100         /* If the audiohook is stopping it means the channel is shutting down.... but we let the datastore destroy take care of it */
101         if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || frame->frametype != AST_FRAME_VOICE) {
102                 return 0;
103         }
104         
105         ast_channel_lock(chan);
106         if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
107                 ast_channel_unlock(chan);
108                 return 0;
109         }
110         ast_channel_unlock(chan);
111
112         si = datastore->data;
113
114         sdi = (direction == AST_AUDIOHOOK_DIRECTION_READ) ? si->rx : si->tx;
115
116         if (!sdi) {
117                 return 0;
118         }
119
120         if (sdi->samples != frame->samples) {
121                 if (sdi->state) {
122                         speex_preprocess_state_destroy(sdi->state);
123                 }
124
125                 if (!(sdi->state = speex_preprocess_state_init((sdi->samples = frame->samples), 8000))) {
126                         return -1;
127                 }
128                 
129                 speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC, &sdi->agc);
130
131                 if (sdi->agc) {
132                         speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &sdi->agclevel);
133                 }
134
135                 speex_preprocess_ctl(sdi->state, SPEEX_PREPROCESS_SET_DENOISE, &sdi->denoise);
136         }
137
138         speex_preprocess(sdi->state, frame->data.ptr, NULL);
139
140         return 0;
141 }
142
143 static int speex_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
144 {
145         struct ast_datastore *datastore = NULL;
146         struct speex_info *si = NULL;
147         struct speex_direction_info **sdi = NULL;
148         int is_new = 0;
149
150         ast_channel_lock(chan);
151         if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
152                 ast_channel_unlock(chan);
153
154                 if (!(datastore = ast_channel_datastore_alloc(&speex_datastore, NULL))) {
155                         return 0;
156                 }
157
158                 if (!(si = ast_calloc(1, sizeof(*si)))) {
159                         ast_channel_datastore_free(datastore);
160                         return 0;
161                 }
162
163                 ast_audiohook_init(&si->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "speex");
164                 si->audiohook.manipulate_callback = speex_callback;
165
166                 is_new = 1;
167         } else {
168                 ast_channel_unlock(chan);
169                 si = datastore->data;
170         }
171
172         if (!strcasecmp(data, "rx")) {
173                 sdi = &si->rx;
174         } else if (!strcasecmp(data, "tx")) {
175                 sdi = &si->tx;
176         } else {
177                 ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd);
178
179                 if (is_new) {
180                         ast_channel_datastore_free(datastore);
181                         return -1;
182                 }
183         }
184
185         if (!*sdi) {
186                 if (!(*sdi = ast_calloc(1, sizeof(**sdi)))) {
187                         return 0;
188                 }
189                 /* Right now, the audiohooks API will _only_ provide us 8 kHz slinear
190                  * audio.  When it supports 16 kHz (or any other sample rates, we will
191                  * have to take that into account here. */
192                 (*sdi)->samples = -1;
193         }
194
195         if (!strcasecmp(cmd, "agc")) {
196                 if (!sscanf(value, "%f", &(*sdi)->agclevel))
197                         (*sdi)->agclevel = ast_true(value) ? DEFAULT_AGC_LEVEL : 0.0;
198         
199                 if ((*sdi)->agclevel > 32768.0) {
200                         ast_log(LOG_WARNING, "AGC(%s)=%.01f is greater than 32768... setting to 32768 instead\n", 
201                                         ((*sdi == si->rx) ? "rx" : "tx"), (*sdi)->agclevel);
202                         (*sdi)->agclevel = 32768.0;
203                 }
204         
205                 (*sdi)->agc = !!((*sdi)->agclevel);
206
207                 if ((*sdi)->state) {
208                         speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC, &(*sdi)->agc);
209                         if ((*sdi)->agc) {
210                                 speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &(*sdi)->agclevel);
211                         }
212                 }
213         } else if (!strcasecmp(cmd, "denoise")) {
214                 (*sdi)->denoise = (ast_true(value) != 0);
215
216                 if ((*sdi)->state) {
217                         speex_preprocess_ctl((*sdi)->state, SPEEX_PREPROCESS_SET_DENOISE, &(*sdi)->denoise);
218                 }
219         }
220
221         if (!(*sdi)->agc && !(*sdi)->denoise) {
222                 if ((*sdi)->state)
223                         speex_preprocess_state_destroy((*sdi)->state);
224
225                 ast_free(*sdi);
226                 *sdi = NULL;
227         }
228
229         if (!si->rx && !si->tx) {
230                 if (is_new) {
231                         is_new = 0;
232                 } else {
233                         ast_channel_lock(chan);
234                         ast_channel_datastore_remove(chan, datastore);
235                         ast_channel_unlock(chan);
236                         ast_audiohook_remove(chan, &si->audiohook);
237                         ast_audiohook_detach(&si->audiohook);
238                 }
239                 
240                 ast_channel_datastore_free(datastore);
241         }
242
243         if (is_new) { 
244                 datastore->data = si;
245                 ast_channel_lock(chan);
246                 ast_channel_datastore_add(chan, datastore);
247                 ast_channel_unlock(chan);
248                 ast_audiohook_attach(chan, &si->audiohook);
249         }
250
251         return 0;
252 }
253
254 static int speex_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
255 {
256         struct ast_datastore *datastore = NULL;
257         struct speex_info *si = NULL;
258         struct speex_direction_info *sdi = NULL;
259
260         if (!chan) {
261                 ast_log(LOG_ERROR, "%s cannot be used without a channel!\n", cmd);
262                 return -1;
263         }
264
265         ast_channel_lock(chan);
266         if (!(datastore = ast_channel_datastore_find(chan, &speex_datastore, NULL))) {
267                 ast_channel_unlock(chan);
268                 return -1;
269         }
270         ast_channel_unlock(chan);
271
272         si = datastore->data;
273
274         if (!strcasecmp(data, "tx"))
275                 sdi = si->tx;
276         else if (!strcasecmp(data, "rx"))
277                 sdi = si->rx;
278         else {
279                 ast_log(LOG_ERROR, "%s(%s) must either \"tx\" or \"rx\"\n", cmd, data);
280                 return -1;
281         }
282
283         if (!strcasecmp(cmd, "agc"))
284                 snprintf(buf, len, "%.01f", sdi ? sdi->agclevel : 0.0);
285         else
286                 snprintf(buf, len, "%d", sdi ? sdi->denoise : 0);
287
288         return 0;
289 }
290
291 static struct ast_custom_function agc_function = {
292         .name = "AGC",
293         .synopsis = "Apply automatic gain control to audio on a channel",
294         .desc =
295         "  The AGC function will apply automatic gain control to audio on the channel\n"
296         "that this function is executed on.  Use rx for audio received from the channel\n"
297         "and tx to apply AGC to the audio being sent to the channel.  When using this\n"
298         "function, you set a target audio level.  It is primarily intended for use with\n"
299         "analog lines, but could be useful for other channels, as well.  The target volume\n"
300         "is set with a number between 1 and 32768.  Larger numbers are louder.\n"
301         "  Example Usage:\n"
302         "    Set(AGC(rx)=8000)\n"
303         "    Set(AGC(tx)=8000)\n"
304         "    Set(AGC(rx)=off)\n"
305         "    Set(AGC(tx)=off)\n"
306         "",
307         .write = speex_write,
308         .read = speex_read
309 };
310
311 static struct ast_custom_function denoise_function = {
312         .name = "DENOISE",
313         .synopsis = "Apply noise reduction to audio on a channel",
314         .desc =
315         "  The DENOISE function will apply noise reduction to audio on the channel\n"
316         "that this function is executed on.  It is especially useful for noisy analog\n"
317         "lines, especially when adjusting gains or using AGC.  Use rx for audio\n"
318         "received from the channel and tx to apply the filter to the audio being sent\n"
319         "to the channel.\n"
320         "  Example Usage:\n"
321         "    Set(DENOISE(rx)=on)\n"
322         "    Set(DENOISE(tx)=on)\n"
323         "    Set(DENOISE(rx)=off)\n"
324         "    Set(DENOISE(tx)=off)\n"
325         "",
326         .write = speex_write,
327         .read = speex_read
328 };
329
330 static int unload_module(void)
331 {
332         ast_custom_function_unregister(&agc_function);
333         ast_custom_function_unregister(&denoise_function);
334         return 0;
335 }
336
337 static int load_module(void)
338 {
339         if (ast_custom_function_register(&agc_function)) {
340                 return AST_MODULE_LOAD_DECLINE;
341         }
342
343         if (ast_custom_function_register(&denoise_function)) {
344                 ast_custom_function_unregister(&agc_function);
345                 return AST_MODULE_LOAD_DECLINE;
346         }
347
348         return AST_MODULE_LOAD_SUCCESS;
349 }
350
351 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Noise reduction and Automatic Gain Control (AGC)");