Add conditional support for noreturn functions.
[asterisk/asterisk.git] / funcs / func_talkdetect.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2014, Digium, Inc.
5  *
6  * Matt Jordan <mjordan@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Function that raises events when talking is detected on a channel
22  *
23  * \author Matt Jordan <mjordan@digium.com>
24  *
25  * \ingroup functions
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 ASTERISK_REGISTER_FILE()
35
36 #include "asterisk/module.h"
37 #include "asterisk/channel.h"
38 #include "asterisk/pbx.h"
39 #include "asterisk/app.h"
40 #include "asterisk/dsp.h"
41 #include "asterisk/audiohook.h"
42 #include "asterisk/stasis.h"
43 #include "asterisk/stasis_channels.h"
44
45 /*** DOCUMENTATION
46         <function name="TALK_DETECT" language="en_US">
47                 <synopsis>
48                         Raises notifications when Asterisk detects silence or talking on a channel.
49                 </synopsis>
50                 <syntax>
51                         <parameter name="action" required="true">
52                                 <optionlist>
53                                         <option name="remove">
54                                                 <para>W/O. Remove talk detection from the channel.</para>
55                                         </option>
56                                         <option name="set">
57                                                 <para>W/O. Enable TALK_DETECT and/or configure talk detection
58                                                 parameters. Can be called multiple times to change parameters
59                                                 on a channel with talk detection already enabled.</para>
60                                                 <argument name="dsp_silence_threshold" required="false">
61                                                         <para>The time in milliseconds before which a user is considered silent.</para>
62                                                 </argument>
63                                                 <argument name="dsp_talking_threshold" required="false">
64                                                         <para>The time in milliseconds after which a user is considered talking.</para>
65                                                 </argument>
66                                         </option>
67                                 </optionlist>
68                         </parameter>
69                 </syntax>
70                 <description>
71                         <para>The TALK_DETECT function enables events on the channel
72                         it is applied to. These events can be emited over AMI, ARI, and
73                         potentially other Asterisk modules that listen for the internal
74                         notification.</para>
75                         <para>The function has two parameters that can optionally be passed
76                         when <literal>set</literal> on a channel: <replaceable>dsp_talking_threshold</replaceable>
77                         and <replaceable>dsp_silence_threshold</replaceable>.</para>
78                         <para><replaceable>dsp_talking_threshold</replaceable> is the time in milliseconds of sound
79                         above what the dsp has established as base line silence for a user
80                         before a user is considered to be talking. By default, the value of
81                         <replaceable>silencethreshold</replaceable> from <filename>dsp.conf</filename>
82                         is used. If this value is set too tight events may be
83                         falsely triggered by variants in room noise.</para>
84                         <para>Valid values are 1 through 2^31.</para>
85                         <para><replaceable>dsp_silence_threshold</replaceable> is the time in milliseconds of sound
86                         falling within what the dsp has established as baseline silence before
87                         a user is considered be silent. If this value is set too low events
88                         indicating the user has stopped talking may get falsely sent out when
89                         the user briefly pauses during mid sentence.</para>
90                         <para>The best way to approach this option is to set it slightly above
91                         the maximum amount of ms of silence a user may generate during
92                         natural speech.</para>
93                         <para>By default this value is 2500ms. Valid values are 1
94                         through 2^31.</para>
95                         <para>Example:</para>
96                         <para>same => n,Set(TALK_DETECT(set)=)     ; Enable talk detection</para>
97                         <para>same => n,Set(TALK_DETECT(set)=1200) ; Update existing talk detection's silence threshold to 1200 ms</para>
98                         <para>same => n,Set(TALK_DETECT(remove)=)  ; Remove talk detection</para>
99                         <para>same => n,Set(TALK_DETECT(set)=,128) ; Enable and set talk threshold to 128</para>
100                         <para>This function will set the following variables:</para>
101                         <note>
102                                 <para>The TALK_DETECT function uses an audiohook to inspect the
103                                 voice media frames on a channel. Other functions, such as JITTERBUFFER,
104                                 DENOISE, and AGC use a similar mechanism. Audiohooks are processed
105                                 in the order in which they are placed on the channel. As such,
106                                 it typically makes sense to place functions that modify the voice
107                                 media data prior to placing the TALK_DETECT function, as this will
108                                 yield better results.</para>
109                                 <para>Example:</para>
110                                 <para>same => n,Set(DENOISE(rx)=on)    ; Denoise received audio</para>
111                                 <para>same => n,Set(TALK_DETECT(set)=) ; Perform talk detection on the denoised received audio</para>
112                         </note>
113                 </description>
114         </function>
115  ***/
116
117 #define DEFAULT_SILENCE_THRESHOLD 2500
118
119 /*! \brief Private data structure used with the function's datastore */
120 struct talk_detect_params {
121         /*! The audiohook for the function */
122         struct ast_audiohook audiohook;
123         /*! Our threshold above which we consider someone talking */
124         int dsp_talking_threshold;
125         /*! How long we'll wait before we decide someone is silent */
126         int dsp_silence_threshold;
127         /*! Whether or not the user is currently talking */
128         int talking;
129         /*! The time the current burst of talking started */
130         struct timeval talking_start;
131         /*! The DSP used to do the heavy lifting */
132         struct ast_dsp *dsp;
133 };
134
135 /*! \internal \brief Destroy the datastore */
136 static void datastore_destroy_cb(void *data) {
137         struct talk_detect_params *td_params = data;
138
139         ast_audiohook_destroy(&td_params->audiohook);
140
141         if (td_params->dsp) {
142                 ast_dsp_free(td_params->dsp);
143         }
144         ast_free(data);
145 }
146
147 /*! \brief The channel datastore the function uses to store state */
148 static const struct ast_datastore_info talk_detect_datastore = {
149         .type = "talk_detect",
150         .destroy = datastore_destroy_cb
151 };
152
153 /*! \internal \brief An audiohook modification callback
154  *
155  * This processes the read side of a channel's voice data to see if
156  * they are talking
157  *
158  * \note We don't actually modify the audio, so this function always
159  * returns a 'failure' indicating that it didn't modify the data
160  */
161 static int talk_detect_audiohook_cb(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction)
162 {
163         int total_silence;
164         int update_talking = 0;
165         struct ast_datastore *datastore;
166         struct talk_detect_params *td_params;
167         struct stasis_message *message;
168
169         if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE) {
170                 return 1;
171         }
172
173         if (direction != AST_AUDIOHOOK_DIRECTION_READ) {
174                 return 1;
175         }
176
177         if (frame->frametype != AST_FRAME_VOICE) {
178                 return 1;
179         }
180
181         if (!(datastore = ast_channel_datastore_find(chan, &talk_detect_datastore, NULL))) {
182                 return 1;
183         }
184         td_params = datastore->data;
185
186         ast_dsp_silence(td_params->dsp, frame, &total_silence);
187
188         if (total_silence < td_params->dsp_silence_threshold) {
189                 if (!td_params->talking) {
190                         update_talking = 1;
191                         td_params->talking_start = ast_tvnow();
192                 }
193                 td_params->talking = 1;
194         } else {
195                 if (td_params->talking) {
196                         update_talking = 1;
197                 }
198                 td_params->talking = 0;
199         }
200
201         if (update_talking) {
202                 struct ast_json *blob = NULL;
203
204                 if (!td_params->talking) {
205                         int64_t diff_ms = ast_tvdiff_ms(ast_tvnow(), td_params->talking_start);
206                         diff_ms -= td_params->dsp_silence_threshold;
207
208                         blob = ast_json_pack("{s: i}", "duration", diff_ms);
209                         if (!blob) {
210                                 return 1;
211                         }
212                 }
213
214                 ast_verb(4, "%s is now %s\n", ast_channel_name(chan),
215                             td_params->talking ? "talking" : "silent");
216                 message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(chan),
217                                 td_params->talking ? ast_channel_talking_start() : ast_channel_talking_stop(),
218                                 blob);
219                 if (message) {
220                         stasis_publish(ast_channel_topic(chan), message);
221                         ao2_ref(message, -1);
222                 }
223
224                 ast_json_unref(blob);
225         }
226
227         return 1;
228 }
229
230 /*! \internal \brief Disable talk detection on the channel */
231 static int remove_talk_detect(struct ast_channel *chan)
232 {
233         struct ast_datastore *datastore = NULL;
234         struct talk_detect_params *td_params;
235         SCOPED_CHANNELLOCK(chan_lock, chan);
236
237         datastore = ast_channel_datastore_find(chan, &talk_detect_datastore, NULL);
238         if (!datastore) {
239                 ast_log(AST_LOG_WARNING, "Cannot remove TALK_DETECT from %s: TALK_DETECT not currently enabled\n",
240                         ast_channel_name(chan));
241                 return -1;
242         }
243         td_params = datastore->data;
244
245         if (ast_audiohook_remove(chan, &td_params->audiohook)) {
246                 ast_log(AST_LOG_WARNING, "Failed to remove TALK_DETECT audiohook from channel %s\n",
247                         ast_channel_name(chan));
248                 return -1;
249         }
250
251         if (ast_channel_datastore_remove(chan, datastore)) {
252                 ast_log(AST_LOG_WARNING, "Failed to remove TALK_DETECT datastore from channel %s\n",
253                         ast_channel_name(chan));
254                 return -1;
255         }
256         ast_datastore_free(datastore);
257
258         return 0;
259 }
260
261 /*! \internal \brief Enable talk detection on the channel */
262 static int set_talk_detect(struct ast_channel *chan, int dsp_silence_threshold, int dsp_talking_threshold)
263 {
264         struct ast_datastore *datastore = NULL;
265         struct talk_detect_params *td_params;
266         SCOPED_CHANNELLOCK(chan_lock, chan);
267
268         datastore = ast_channel_datastore_find(chan, &talk_detect_datastore, NULL);
269         if (!datastore) {
270                 datastore = ast_datastore_alloc(&talk_detect_datastore, NULL);
271                 if (!datastore) {
272                         return -1;
273                 }
274
275                 td_params = ast_calloc(1, sizeof(*td_params));
276                 if (!td_params) {
277                         ast_datastore_free(datastore);
278                         return -1;
279                 }
280
281                 ast_audiohook_init(&td_params->audiohook,
282                                    AST_AUDIOHOOK_TYPE_MANIPULATE,
283                                    "TALK_DETECT",
284                                    AST_AUDIOHOOK_MANIPULATE_ALL_RATES);
285                 td_params->audiohook.manipulate_callback = talk_detect_audiohook_cb;
286                 ast_set_flag(&td_params->audiohook, AST_AUDIOHOOK_TRIGGER_READ);
287
288                 td_params->dsp = ast_dsp_new_with_rate(ast_format_get_sample_rate(ast_channel_rawreadformat(chan)));
289                 if (!td_params->dsp) {
290                         ast_datastore_free(datastore);
291                         ast_free(td_params);
292                         return -1;
293                 }
294                 datastore->data = td_params;
295
296                 ast_channel_datastore_add(chan, datastore);
297                 ast_audiohook_attach(chan, &td_params->audiohook);
298         } else {
299                 /* Talk detection already enabled; update existing settings */
300                 td_params = datastore->data;
301         }
302
303         td_params->dsp_talking_threshold = dsp_talking_threshold;
304         td_params->dsp_silence_threshold = dsp_silence_threshold;
305
306         ast_dsp_set_threshold(td_params->dsp, td_params->dsp_talking_threshold);
307
308         return 0;
309 }
310
311 /*! \internal \brief TALK_DETECT write function callback */
312 static int talk_detect_fn_write(struct ast_channel *chan, const char *function, char *data, const char *value)
313 {
314         int res;
315
316         if (!chan) {
317                 return -1;
318         }
319
320         if (ast_strlen_zero(data)) {
321                 ast_log(AST_LOG_WARNING, "TALK_DETECT requires an argument\n");
322                 return -1;
323         }
324
325         if (!strcasecmp(data, "set")) {
326                 int dsp_silence_threshold = DEFAULT_SILENCE_THRESHOLD;
327                 int dsp_talking_threshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
328
329                 if (!ast_strlen_zero(value)) {
330                         char *parse = ast_strdupa(value);
331
332                         AST_DECLARE_APP_ARGS(args,
333                                 AST_APP_ARG(silence_threshold);
334                                 AST_APP_ARG(talking_threshold);
335                         );
336
337                         AST_STANDARD_APP_ARGS(args, parse);
338
339                         if (!ast_strlen_zero(args.silence_threshold)) {
340                                 if (sscanf(args.silence_threshold, "%30d", &dsp_silence_threshold) != 1) {
341                                         ast_log(AST_LOG_WARNING, "Failed to parse %s for dsp_silence_threshold\n",
342                                                 args.silence_threshold);
343                                         return -1;
344                                 }
345
346                                 if (dsp_silence_threshold < 1) {
347                                         ast_log(AST_LOG_WARNING, "Invalid value %d for dsp_silence_threshold\n",
348                                                 dsp_silence_threshold);
349                                         return -1;
350                                 }
351                         }
352
353                         if (!ast_strlen_zero(args.talking_threshold)) {
354                                 if (sscanf(args.talking_threshold, "%30d", &dsp_talking_threshold) != 1) {
355                                         ast_log(AST_LOG_WARNING, "Failed to parse %s for dsp_talking_threshold\n",
356                                                 args.talking_threshold);
357                                         return -1;
358                                 }
359
360                                 if (dsp_talking_threshold < 1) {
361                                         ast_log(AST_LOG_WARNING, "Invalid value %d for dsp_talking_threshold\n",
362                                                 dsp_silence_threshold);
363                                         return -1;
364                                 }
365                         }
366                 }
367
368                 res = set_talk_detect(chan, dsp_silence_threshold, dsp_talking_threshold);
369         } else if (!strcasecmp(data, "remove")) {
370                 res = remove_talk_detect(chan);
371         } else {
372                 ast_log(AST_LOG_WARNING, "TALK_DETECT: unknown option %s\n", data);
373                 res = -1;
374         }
375
376         return res;
377 }
378
379 /*! \brief Definition of the TALK_DETECT function */
380 static struct ast_custom_function talk_detect_function = {
381         .name = "TALK_DETECT",
382         .write = talk_detect_fn_write,
383 };
384
385 /*! \internal \brief Unload the module */
386 static int unload_module(void)
387 {
388         int res = 0;
389
390         res |= ast_custom_function_unregister(&talk_detect_function);
391
392         return res;
393 }
394
395 /*! \internal \brief Load the module */
396 static int load_module(void)
397 {
398         int res = 0;
399
400         res |= ast_custom_function_register(&talk_detect_function);
401
402         return res ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS;
403 }
404
405 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Talk detection dialplan function");