a2cceb8a417868855635fe5b85cab7d97cb1ea05
[asterisk/asterisk.git] / indications.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Tone Management
5  * 
6  * Copyright (C) 2002, Pauline Middelink
7  *
8  * Pauline Middelink <middelink@polyware.nl>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  *
13  * This set of function allow us to play a list of tones on a channel.
14  * Each element has two frequencies, which are mixed together and a
15  * duration. For silence both frequencies can be set to 0.
16  * The playtones can be given as a comma seperated string.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <pthread.h>
22 #include <string.h>
23 #include <math.h>                       /* For PI */
24 #include <asterisk/indications.h>
25 #include <asterisk/frame.h>
26 #include <asterisk/options.h>
27 #include <asterisk/channel.h>
28 #include <asterisk/logger.h>
29
30 #define PTHREAD_MUTEX_LOCK(a) ast_pthread_mutex_lock(a)
31 #define PTHREAD_MUTEX_UNLOCK(a) ast_pthread_mutex_unlock(a)
32
33 struct playtones_item {
34         int freq1;
35         int freq2;
36         int duration;
37 };
38
39 struct playtones_def {
40         int vol;
41         int reppos;
42         int nitems;
43         struct playtones_item *items;
44 };
45
46 struct playtones_state {
47         int vol;
48         int reppos;
49         int nitems;
50         struct playtones_item *items;
51         int npos;
52         int pos;
53         int origwfmt;
54         struct ast_frame f;
55         short data[4000];
56 };
57
58 static void playtones_release(struct ast_channel *chan, void *params)
59 {
60         struct playtones_state *ps = params;
61         if (chan) {
62                 ast_set_write_format(chan, ps->origwfmt);
63         }
64         if (ps->items) free(ps->items);
65         free(ps);
66 }
67
68 static void * playtones_alloc(struct ast_channel *chan, void *params)
69 {
70         struct playtones_def *pd = params;
71         struct playtones_state *ps = malloc(sizeof(struct playtones_state));
72         if (!ps)
73                 return NULL;
74         memset(ps, 0, sizeof(struct playtones_state));
75         ps->origwfmt = chan->writeformat;
76         if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
77                 ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format (write)\n", chan->name);
78                 playtones_release(NULL, ps);
79                 ps = NULL;
80         } else {
81                 ps->vol = pd->vol;
82                 ps->reppos = pd->reppos;
83                 ps->nitems = pd->nitems;
84                 ps->items = pd->items;
85         }
86         /* Let interrupts interrupt :) */
87         chan->writeinterrupt = 1;
88         return ps;
89 }
90
91 static int playtones_generator(struct ast_channel *chan, void *data, int len, int samples)
92 {
93         struct playtones_state *ps = data;
94         struct playtones_item *pi;
95         int x;
96         /* we need to prepare a frame with 16 * timelen samples as we're 
97          * generating SLIN audio
98          */
99         len = samples * 2;
100         if (len > sizeof(ps->data) / 2 - 1) {
101                 ast_log(LOG_WARNING, "Can't generate that much data!\n");
102                 return -1;
103         }
104         memset(&ps->f, 0, sizeof(ps->f));
105
106         pi = &ps->items[ps->npos];
107         for (x=0;x<len/2;x++) {
108                 ps->data[x] = ps->vol * (
109                                 sin((pi->freq1 * 2.0 * M_PI / 8000.0) * (ps->pos + x)) +
110                                 sin((pi->freq2 * 2.0 * M_PI / 8000.0) * (ps->pos + x))
111                         );
112         }
113         ps->f.frametype = AST_FRAME_VOICE;
114         ps->f.subclass = AST_FORMAT_SLINEAR;
115         ps->f.datalen = len;
116         ps->f.samples = samples;
117         ps->f.offset = AST_FRIENDLY_OFFSET;
118         ps->f.data = ps->data;
119         ast_write(chan, &ps->f);
120
121         ps->pos += x;
122         if (pi->duration && ps->pos >= pi->duration * 8) {      /* item finished? */
123                 ps->pos = 0;                                    /* start new item */
124                 ps->npos++;
125                 if (ps->npos >= ps->nitems) {                   /* last item? */
126                         if (ps->reppos == -1)                   /* repeat set? */
127                                 return -1;
128                         ps->npos = ps->reppos;                  /* redo from top */
129                 }
130         }
131         return 0;
132 }
133
134 static struct ast_generator playtones = {
135         alloc: playtones_alloc,
136         release: playtones_release,
137         generate: playtones_generator,
138 };
139
140 int ast_playtones_start(struct ast_channel *chan, int vol, const char *playlst)
141 {
142         char *s, *data = strdupa(playlst); /* cute */
143         struct playtones_def d = { vol, -1, 0, NULL};
144         char *stringp=NULL;
145         if (!data)
146                 return -1;
147         if (vol < 1)
148                 d.vol = 8192;
149
150         stringp=data;
151         s = strsep(&stringp,",");
152         while(s && *s) {
153                 int freq1, freq2, time;
154
155                 if (s[0]=='!')
156                         s++;
157                 else if (d.reppos == -1)
158                         d.reppos = d.nitems;
159
160                 if (sscanf(s, "%d+%d/%d", &freq1, &freq2, &time) == 3) {
161                         /* f1+f2/time format */
162                 } else if (sscanf(s, "%d+%d", &freq1, &freq2) == 2) {
163                         /* f1+f2 format */
164                         time = 0;
165                 } else if (sscanf(s, "%d/%d", &freq1, &time) == 2) {
166                         /* f1/time format */
167                         freq2 = 0;
168                 } else if (sscanf(s, "%d", &freq1) == 1) {
169                         /* f1 format */
170                         freq2 = 0;
171                         time = 0;
172                 } else {
173                         ast_log(LOG_WARNING,"%s: tone component '%s' of '%s' is no good\n",chan->name,s,playlst);
174                         return -1;
175                 }
176
177                 d.items = realloc(d.items,(d.nitems+1)*sizeof(struct playtones_item));
178                 if (d.items == NULL)
179                         return -1;
180                 d.items[d.nitems].freq1    = freq1;
181                 d.items[d.nitems].freq2    = freq2;
182                 d.items[d.nitems].duration = time;
183                 d.nitems++;
184
185                 s = strsep(&stringp,",");
186         }
187
188         if (ast_activate_generator(chan, &playtones, &d)) {
189                 free(d.items);
190                 return -1;
191         }
192         return 0;
193 }
194
195 void ast_playtones_stop(struct ast_channel *chan)
196 {
197         ast_deactivate_generator(chan);
198 }
199
200 /*--------------------------------------------*/
201
202 struct tone_zone *tone_zones;
203 static struct tone_zone *current_tonezone;
204
205 /* Protect the tone_zones list (highly unlikely that two things would change
206  * it at the same time, but still! */
207 pthread_mutex_t tzlock = AST_MUTEX_INITIALIZER;
208
209 /* Set global indication country */
210 int ast_set_indication_country(const char *country)
211 {
212         if (country) {
213                 struct tone_zone *z = ast_get_indication_zone(country);
214                 if (z) {
215                         ast_verbose(VERBOSE_PREFIX_3 "Setting default indication country to '%s'\n",country);
216                         current_tonezone = z;
217                         return 0;
218                 }
219         }
220         return 1; /* not found */
221 }
222
223 /* locate tone_zone, given the country. if country == NULL, use the default country */
224 struct tone_zone *ast_get_indication_zone(const char *country)
225 {
226         struct tone_zone *tz;
227         int alias_loop = 0;
228
229         /* we need some tonezone, pick the first */
230         if (country == NULL && current_tonezone)
231                 return current_tonezone;        /* default country? */
232         if (country == NULL && tone_zones)
233                 return tone_zones;              /* any country? */
234         if (country == NULL)
235                 return 0;       /* not a single country insight */
236
237         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
238                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
239                 return 0;
240         }
241         do {
242                 for (tz=tone_zones; tz; tz=tz->next) {
243                         if (strcasecmp(country,tz->country)==0) {
244                                 /* tone_zone found */
245                                 if (tz->alias && tz->alias[0]) {
246                                         country = tz->alias;
247                                         break;
248                                 }
249                                 PTHREAD_MUTEX_UNLOCK(&tzlock);
250                                 return tz;
251                         }
252                 }
253         } while (++alias_loop<20 && tz);
254         PTHREAD_MUTEX_UNLOCK(&tzlock);
255         if (alias_loop==20)
256                 ast_log(LOG_NOTICE,"Alias loop for '%s' forcefull broken\n",country);
257         /* nothing found, sorry */
258         return 0;
259 }
260
261 /* locate a tone_zone_sound, given the tone_zone. if tone_zone == NULL, use the default tone_zone */
262 struct tone_zone_sound *ast_get_indication_tone(const struct tone_zone *zone, const char *indication)
263 {
264         struct tone_zone_sound *ts;
265
266         /* we need some tonezone, pick the first */
267         if (zone == NULL && current_tonezone)
268                 zone = current_tonezone;        /* default country? */
269         if (zone == NULL && tone_zones)
270                 zone = tone_zones;              /* any country? */
271         if (zone == NULL)
272                 return 0;       /* not a single country insight */
273
274         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
275                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
276                 return 0;
277         }
278         for (ts=zone->tones; ts; ts=ts->next) {
279                 if (strcasecmp(indication,ts->name)==0) {
280                         /* found indication! */
281                         PTHREAD_MUTEX_UNLOCK(&tzlock);
282                         return ts;
283                 }
284         }
285         /* nothing found, sorry */
286         PTHREAD_MUTEX_UNLOCK(&tzlock);
287         return 0;
288 }
289
290 /* helper function to delete a tone_zone in its entirety */
291 static inline void free_zone(struct tone_zone* zone)
292 {
293         while (zone->tones) {
294                 struct tone_zone_sound *tmp = zone->tones->next;
295                 free((void*)zone->tones->name);
296                 free((void*)zone->tones->data);
297                 free(zone->tones);
298                 zone->tones = tmp;
299         }
300         free(zone);
301 }
302
303 /*--------------------------------------------*/
304
305 /* add a new country, if country exists, it will be replaced. */
306 int ast_register_indication_country(struct tone_zone *zone)
307 {
308         struct tone_zone *tz,*pz;
309
310         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
311                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
312                 return -1;
313         }
314         for (pz=NULL,tz=tone_zones; tz; pz=tz,tz=tz->next) {
315                 if (strcasecmp(zone->country,tz->country)==0) {
316                         /* tone_zone already there, replace */
317                         zone->next = tz->next;
318                         if (pz)
319                                 pz->next = zone;
320                         else
321                                 tone_zones = zone;
322                         /* if we are replacing the default zone, re-point it */
323                         if (tz == current_tonezone)
324                                 current_tonezone = zone;
325                         /* now free the previous zone */
326                         free_zone(tz);
327                         PTHREAD_MUTEX_UNLOCK(&tzlock);
328                         return 0;
329                 }
330         }
331         /* country not there, add */
332         zone->next = NULL;
333         if (pz)
334                 pz->next = zone;
335         else
336                 tone_zones = zone;
337         PTHREAD_MUTEX_UNLOCK(&tzlock);
338
339         ast_verbose(VERBOSE_PREFIX_3 "Registered indication country '%s'\n",zone->country);
340         return 0;
341 }
342
343 /* remove an existing country and all its indications, country must exist.
344  * Also, all countries which are an alias for the specified country are removed. */
345 int ast_unregister_indication_country(const char *country)
346 {
347         struct tone_zone *tz, *pz = NULL, *tmp;
348         int res = -1;
349
350         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
351                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
352                 return -1;
353         }
354         tz = tone_zones;
355         while (tz) {
356                 if (country==NULL ||
357                     (strcasecmp(country, tz->country)==0 ||
358                      strcasecmp(country, tz->alias)==0)) {
359                         /* tone_zone found, remove */
360                         tmp = tz->next;
361                         if (pz)
362                                 pz->next = tmp;
363                         else
364                                 tone_zones = tmp;
365                         /* if we are unregistering the default country, w'll notice */
366                         if (tz == current_tonezone) {
367                                 ast_log(LOG_NOTICE,"Removed default indication country '%s'\n",tz->country);
368                                 current_tonezone = NULL;
369                         }
370                         ast_verbose(VERBOSE_PREFIX_3 "Unregistered indication country '%s'\n",tz->country);
371                         free_zone(tz);
372                         tz = tmp;
373                         res = 0;
374                 }
375                 else {
376                         /* next zone please */
377                         pz = tz;
378                         tz = tz->next;
379                 }
380         }
381         PTHREAD_MUTEX_UNLOCK(&tzlock);
382         return res;
383 }
384
385 /* add a new indication to a tone_zone. tone_zone must exist. if the indication already
386  * exists, it will be replaced. */
387 int ast_register_indication(struct tone_zone *zone, const char *indication, const char *tonelist)
388 {
389         struct tone_zone_sound *ts,*ps;
390
391         /* is it an alias? stop */
392         if (zone->alias[0])
393                 return -1;
394
395         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
396                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
397                 return -2;
398         }
399         for (ps=NULL,ts=zone->tones; ts; ps=ts,ts=ts->next) {
400                 if (strcasecmp(indication,ts->name)==0) {
401                         /* indication already there, replace */
402                         free((void*)ts->name);
403                         free((void*)ts->data);
404                         break;
405                 }
406         }
407         if (!ts) {
408                 /* not there, we have to add */
409                 ts = malloc(sizeof(struct tone_zone_sound));
410                 if (!ts) {
411                         ast_log(LOG_WARNING, "Out of memory\n");
412                         PTHREAD_MUTEX_UNLOCK(&tzlock);
413                         return -2;
414                 }
415                 ts->next = NULL;
416         }
417         ts->name = strdup(indication);
418         ts->data = strdup(tonelist);
419         if (ts->name==NULL || ts->data==NULL) {
420                 ast_log(LOG_WARNING, "Out of memory\n");
421                 PTHREAD_MUTEX_UNLOCK(&tzlock);
422                 return -2;
423         }
424         if (ps)
425                 ps->next = ts;
426         else
427                 zone->tones = ts;
428         PTHREAD_MUTEX_UNLOCK(&tzlock);
429         return 0;
430 }
431
432 /* remove an existing country's indication. Both country and indication must exist */
433 int ast_unregister_indication(struct tone_zone *zone, const char *indication)
434 {
435         struct tone_zone_sound *ts,*ps = NULL, *tmp;
436         int res = -1;
437
438         /* is it an alias? stop */
439         if (zone->alias[0])
440                 return -1;
441
442         if (PTHREAD_MUTEX_LOCK(&tzlock)) {
443                 ast_log(LOG_WARNING, "Unable to lock tone_zones list\n");
444                 return -1;
445         }
446         ts = zone->tones;
447         while (ts) {
448                 if (strcasecmp(indication,ts->name)==0) {
449                         /* indication found */
450                         tmp = ts->next;
451                         if (ps)
452                                 ps->next = tmp;
453                         else
454                                 zone->tones = tmp;
455                         free((void*)ts->name);
456                         free((void*)ts->data);
457                         free(ts);
458                         ts = tmp;
459                         res = 0;
460                 }
461                 else {
462                         /* next zone please */
463                         ps = ts;
464                         ts = ts->next;
465                 }
466         }
467         /* indication not found, goodbye */
468         PTHREAD_MUTEX_UNLOCK(&tzlock);
469         return res;
470 }