c3893536230b470f4cd3ea32a8f594505c957ddf
[asterisk/asterisk.git] / apps / app_muxmon.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * muxmon Application For Asterisk
5  *
6  * Copyright (C) 2005, Anthony Minessale II
7  *
8  * Anthony Minessale II <anthmct@yahoo.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <asterisk/file.h>
15 #include <asterisk/logger.h>
16 #include <asterisk/channel.h>
17 #include <asterisk/pbx.h>
18 #include <asterisk/module.h>
19 #include <asterisk/lock.h>
20 #include <asterisk/cli.h>
21 #include <asterisk/options.h>
22 #include <asterisk/app.h>
23 #include <asterisk/translate.h>
24 #include <asterisk/slinfactory.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #define get_volfactor(x) x ? ((x > 0) ? (1 << x) : ((1 << abs(x)) * -1)) : 0
29 #define minmax(x,y) x ? (x > y) ? y : ((x < (y * -1)) ? (y * -1) : x) : 0 
30
31 static char *tdesc = "Native Channel Monitoring Module";
32 static char *app = "MuxMon";
33 static char *synopsis = "Record A Call Natively";
34 static char *desc = ""
35 "  MuxMon(<file>.<ext>[|<options>[|<command>]])\n\n"
36 "Records The audio on the current channel to the specified file.\n\n"
37 "Valid Options:\n"
38 " b    - Only save audio to the file while the channel is bridged. Note: does\n"
39 "        not include conferences\n"
40 " a    - Append to the file instead of overwriting it.\n"
41 " v(<x>) - Adjust the heard volume by a factor of <x> (range -4 to 4)\n"        
42 " V(<x>) - Adjust the spoken volume by a factor of <x> (range -4 to 4)\n"       
43 " W(<x>) - Adjust the both heard and spoken volumes by a factor of <x>\n"
44 "         (range -4 to 4)\n\n"  
45 "<command> will be executed when the recording is over\n"
46 "Any strings matching ^{X} will be unescaped to ${X} and \n"
47 "all variables will be evaluated at that time.\n"
48 "The variable MUXMON_FILENAME will contain the filename used to record.\n"
49 "";
50
51 STANDARD_LOCAL_USER;
52
53 LOCAL_USER_DECL;
54
55 struct muxmon {
56         struct ast_channel *chan;
57         char *filename;
58         char *post_process;
59         unsigned int flags;
60         int readvol;
61         int writevol;
62 };
63
64 typedef enum {
65     MUXFLAG_RUNNING = (1 << 0),
66     MUXFLAG_APPEND = (1 << 1),
67     MUXFLAG_BRIDGED = (1 << 2),
68     MUXFLAG_VOLUME = (1 << 3),
69     MUXFLAG_READVOLUME = (1 << 4),
70     MUXFLAG_WRITEVOLUME = (1 << 5)
71 } muxflags;
72
73
74 AST_DECLARE_OPTIONS(muxmon_opts,{
75     ['a'] = { MUXFLAG_APPEND },
76         ['b'] = { MUXFLAG_BRIDGED },
77         ['v'] = { MUXFLAG_READVOLUME, 1 },
78         ['V'] = { MUXFLAG_WRITEVOLUME, 2 },
79         ['W'] = { MUXFLAG_VOLUME, 3 },
80 });
81
82
83 static void stopmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
84 {
85         struct ast_channel_spy *cptr=NULL, *prev=NULL;
86         int count = 0;
87
88         if (chan) {
89                 while(ast_mutex_trylock(&chan->lock)) {
90                         if (chan->spiers == spy) {
91                                 chan->spiers = NULL;
92                                 return;
93                         }
94                         count++;
95                         if (count > 10) {
96                                 return;
97                         }
98                         sched_yield();
99                 }
100                 
101                 for(cptr=chan->spiers; cptr; cptr=cptr->next) {
102                         if (cptr == spy) {
103                                 if (prev) {
104                                         prev->next = cptr->next;
105                                         cptr->next = NULL;
106                                 } else
107                                         chan->spiers = NULL;
108                         }
109                         prev = cptr;
110                 }
111
112                 ast_mutex_unlock(&chan->lock);
113         }
114 }
115
116 static void startmon(struct ast_channel *chan, struct ast_channel_spy *spy) 
117 {
118
119         struct ast_channel_spy *cptr=NULL;
120         struct ast_channel *peer;
121
122         if (chan) {
123                 ast_mutex_lock(&chan->lock);
124                 if (chan->spiers) {
125                         for(cptr=chan->spiers;cptr->next;cptr=cptr->next);
126                         cptr->next = spy;
127                 } else {
128                         chan->spiers = spy;
129                 }
130                 ast_mutex_unlock(&chan->lock);
131                 
132                 if (ast_test_flag(chan, AST_FLAG_NBRIDGE) && (peer = ast_bridged_channel(chan))) {
133                         ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);  
134                 }
135         }
136 }
137
138 static int spy_queue_translate(struct ast_channel_spy *spy,
139                                                            struct ast_slinfactory *slinfactory0,
140                                                            struct ast_slinfactory *slinfactory1)
141 {
142         int res = 0;
143         struct ast_frame *f;
144         
145         ast_mutex_lock(&spy->lock);
146         while((f = spy->queue[0])) {
147                 spy->queue[0] = f->next;
148                 ast_slinfactory_feed(slinfactory0, f);
149                 ast_frfree(f);
150         }
151         ast_mutex_unlock(&spy->lock);
152         ast_mutex_lock(&spy->lock);
153         while((f = spy->queue[1])) {
154                 spy->queue[1] = f->next;
155                 ast_slinfactory_feed(slinfactory1, f);
156                 ast_frfree(f);
157         }
158         ast_mutex_unlock(&spy->lock);
159         return res;
160 }
161
162 static void *muxmon_thread(void *obj) 
163 {
164
165         int len0 = 0, len1 = 0, samp0 = 0, samp1 = 0, framelen, maxsamp = 0, x = 0;
166         short buf0[1280], buf1[1280], buf[1280];
167         struct ast_frame frame;
168         struct muxmon *muxmon = obj;
169         struct ast_channel_spy spy;
170         struct ast_filestream *fs = NULL;
171         char *ext, *name;
172         unsigned int oflags;
173         struct ast_slinfactory slinfactory[2];
174         char post_process[1024] = "";
175         
176         name = ast_strdupa(muxmon->chan->name);
177
178         framelen = 320;
179         frame.frametype = AST_FRAME_VOICE;
180         frame.subclass = AST_FORMAT_SLINEAR;
181         frame.data = buf;
182         ast_set_flag(muxmon, MUXFLAG_RUNNING);
183         oflags = O_CREAT|O_WRONLY;
184         ast_slinfactory_init(&slinfactory[0]);
185         ast_slinfactory_init(&slinfactory[1]);
186         
187
188
189         /* for efficiency, use a flag to bypass volume logic when it's not needed */
190         if (muxmon->readvol || muxmon->writevol) {
191                 ast_set_flag(muxmon, MUXFLAG_VOLUME);
192         }
193
194         if ((ext = strchr(muxmon->filename, '.'))) {
195                 *(ext++) = '\0';
196         } else {
197                 ext = "raw";
198         }
199
200         memset(&spy, 0, sizeof(spy));
201         spy.status = CHANSPY_RUNNING;
202         ast_mutex_init(&spy.lock);
203         startmon(muxmon->chan, &spy);
204         if (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
205                 if (option_verbose > 1) {
206                         ast_verbose(VERBOSE_PREFIX_2 "Begin Muxmon Recording %s\n", name);
207                 }
208
209                 oflags |= ast_test_flag(muxmon, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
210                 
211                 if (!(fs = ast_writefile(muxmon->filename, ext, NULL, oflags, 0, 0644))) {
212                         ast_log(LOG_ERROR, "Cannot open %s\n", muxmon->filename);
213                         spy.status = CHANSPY_DONE;
214                 }  else {
215
216                         if (ast_test_flag(muxmon, MUXFLAG_APPEND)) {
217                                 ast_seekstream(fs, 0, SEEK_END);
218                         }
219
220                         while (ast_test_flag(muxmon, MUXFLAG_RUNNING)) {
221                                 samp0 = samp1 = len0 = len1 = 0;
222
223                                 if (ast_check_hangup(muxmon->chan) || spy.status != CHANSPY_RUNNING) {
224                                         ast_clear_flag(muxmon, MUXFLAG_RUNNING);
225                                         break;
226                                 }
227
228                                 if (ast_test_flag(muxmon, MUXFLAG_BRIDGED) && !ast_bridged_channel(muxmon->chan)) {
229                                         usleep(1000);
230                                         sched_yield();
231                                         continue;
232                                 }
233                                 
234                                 spy_queue_translate(&spy, &slinfactory[0], &slinfactory[1]);
235                                 
236                                 if (slinfactory[0].size < framelen || slinfactory[1].size < framelen) {
237                                         usleep(1000);
238                                         sched_yield();
239                                         continue;
240                                 }
241
242                                 if ((len0 = ast_slinfactory_read(&slinfactory[0], buf0, framelen))) {
243                                         samp0 = len0 / 2;
244                                 }
245                                 if((len1 = ast_slinfactory_read(&slinfactory[1], buf1, framelen))) {
246                                         samp1 = len1 / 2;
247                                 }
248                                 
249                                 if (ast_test_flag(muxmon, MUXFLAG_VOLUME)) {
250                                         if (samp0 && muxmon->readvol > 0) {
251                                                 for(x=0; x < samp0 / 2; x++) {
252                                                         buf0[x] *= muxmon->readvol;
253                                                 }
254                                         } else if (samp0 && muxmon->readvol < 0) {
255                                                 for(x=0; x < samp0 / 2; x++) {
256                                                         buf0[x] /= muxmon->readvol;
257                                                 }
258                                         }
259                                         if (samp1 && muxmon->writevol > 0) {
260                                                 for(x=0; x < samp1 / 2; x++) {
261                                                         buf1[x] *= muxmon->writevol;
262                                                 }
263                                         } else if (muxmon->writevol < 0) {
264                                                 for(x=0; x < samp1 / 2; x++) {
265                                                         buf1[x] /= muxmon->writevol;
266                                                 }
267                                         }
268                                 }
269                                 
270                                 maxsamp = (samp0 > samp1) ? samp0 : samp1;
271
272                                 if (samp0 && samp1) {
273                                         for(x=0; x < maxsamp; x++) {
274                                                 if (x < samp0 && x < samp1) {
275                                                         buf[x] = buf0[x] + buf1[x];
276                                                 } else if (x < samp0) {
277                                                         buf[x] = buf0[x];
278                                                 } else if (x < samp1) {
279                                                         buf[x] = buf1[x];
280                                                 }
281                                         }
282                                 } else if(samp0) {
283                                         memcpy(buf, buf0, len0);
284                                         x = samp0;
285                                 } else if(samp1) {
286                                         memcpy(buf, buf1, len1);
287                                         x = samp1;
288                                 }
289
290                                 frame.samples = x;
291                                 frame.datalen = x * 2;
292                                 ast_writestream(fs, &frame);
293                 
294                                 usleep(1000);
295                                 sched_yield();
296                         }
297                 }
298         }
299
300         if (muxmon->post_process) {
301                 char *p;
302                 for(p = muxmon->post_process; *p ; p++) {
303                         if (*p == '^' && *(p+1) == '{') {
304                                 *p = '$';
305                         }
306                 }
307                 pbx_substitute_variables_helper(muxmon->chan, muxmon->post_process, post_process, sizeof(post_process) - 1);
308                 free(muxmon->post_process);
309                 muxmon->post_process = NULL;
310         }
311
312         stopmon(muxmon->chan, &spy);
313         if (option_verbose > 1) {
314                 ast_verbose(VERBOSE_PREFIX_2 "Finished Recording %s\n", name);
315         }
316         ast_mutex_destroy(&spy.lock);
317         
318         if(fs) {
319                 ast_closestream(fs);
320         }
321         
322         ast_slinfactory_destroy(&slinfactory[0]);
323         ast_slinfactory_destroy(&slinfactory[1]);
324
325         if (muxmon) {
326                 if (muxmon->filename) {
327                         free(muxmon->filename);
328                 }
329                 free(muxmon);
330         }
331
332         if (!ast_strlen_zero(post_process)) {
333                 if (option_verbose > 2) {
334                         ast_verbose(VERBOSE_PREFIX_2 "Executing [%s]\n", post_process);
335                 }
336                 ast_safe_system(post_process);
337         }
338
339         return NULL;
340 }
341
342 static void launch_monitor_thread(struct ast_channel *chan, char *filename, unsigned int flags, int readvol , int writevol, char *post_process) 
343 {
344         pthread_attr_t attr;
345         int result = 0;
346         pthread_t thread;
347         struct muxmon *muxmon;
348
349
350         if (!(muxmon = malloc(sizeof(struct muxmon)))) {
351                 ast_log(LOG_ERROR, "Memory Error!\n");
352                 return;
353         }
354
355         memset(muxmon, 0, sizeof(struct muxmon));
356         muxmon->chan = chan;
357         muxmon->filename = strdup(filename);
358         if(post_process) {
359                 muxmon->post_process = strdup(post_process);
360         }
361         muxmon->readvol = readvol;
362         muxmon->writevol = writevol;
363         muxmon->flags = flags;
364
365         result = pthread_attr_init(&attr);
366         pthread_attr_setschedpolicy(&attr, SCHED_RR);
367         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
368         result = ast_pthread_create(&thread, &attr, muxmon_thread, muxmon);
369         result = pthread_attr_destroy(&attr);
370 }
371
372
373 static int muxmon_exec(struct ast_channel *chan, void *data)
374 {
375         int res = 0, x = 0, readvol = 0, writevol = 0;
376         struct localuser *u;
377         struct ast_flags flags = {0};
378         int argc;
379         char *options = NULL,
380                 *args,
381                 *argv[3],
382                 *filename = NULL,
383                 *post_process = NULL;
384         
385         if (!data) {
386                 ast_log(LOG_WARNING, "muxmon requires an argument\n");
387                 return -1;
388         }
389
390         if (!(args = ast_strdupa(data))) {
391                 ast_log(LOG_WARNING, "Memory Error!\n");
392         return -1;
393         }
394         
395
396         if ((argc = ast_separate_app_args(args, '|', argv, sizeof(argv) / sizeof(argv[0])))) {
397                 filename = argv[0];
398                 if ( argc > 1) {
399                         options = argv[1];
400                 }
401                 if ( argc > 2) {
402                         post_process = argv[2];
403                 }
404         }
405         
406         if (!filename || ast_strlen_zero(filename)) {
407                 ast_log(LOG_WARNING, "Muxmon requires an argument (filename)\n");
408         return -1;
409         }
410
411         LOCAL_USER_ADD(u);
412
413         if (options) {
414                 char *opts[3] = {};
415                 ast_parseoptions(muxmon_opts, &flags, opts, options);
416
417                 if (ast_test_flag(&flags, MUXFLAG_READVOLUME) && opts[0]) {
418                         if (sscanf(opts[0], "%d", &x) != 1)
419                                 ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
420                         else {
421                                 readvol = minmax(x, 4);
422                                 x = get_volfactor(readvol);
423                                 readvol = minmax(x, 16);
424                         }
425                 }
426                 
427                 if (ast_test_flag(&flags, MUXFLAG_WRITEVOLUME) && opts[1]) {
428                         if (sscanf(opts[1], "%d", &x) != 1)
429                                 ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
430                         else {
431                                 writevol = minmax(x, 4);
432                                 x = get_volfactor(writevol);
433                                 writevol = minmax(x, 16);
434                         }
435                 }
436
437                 if (ast_test_flag(&flags, MUXFLAG_VOLUME) && opts[2]) {
438                         if (sscanf(opts[2], "%d", &x) != 1)
439                                 ast_log(LOG_NOTICE, "volume must be a number between -4 and 4\n");
440                         else {
441                                 readvol = writevol = minmax(x, 4);
442                                 x = get_volfactor(readvol);
443                 readvol = minmax(x, 16);
444                                 x = get_volfactor(writevol);
445                 writevol = minmax(x, 16);
446                         }
447                 }
448         }
449                 pbx_builtin_setvar_helper(chan, "MUXMON_FILENAME", filename);
450         launch_monitor_thread(chan, filename, flags.flags, readvol, writevol, post_process);
451
452         LOCAL_USER_REMOVE(u);
453         return res;
454 }
455
456
457 static int muxmon_cli(int fd, int argc, char **argv) 
458 {
459         char *op, *chan_name = NULL, *args = NULL;
460         struct ast_channel *chan;
461
462         if (argc > 2) {
463                 op = argv[1];
464                 chan_name = argv[2];
465
466                 if (argv[3]) {
467                         args = argv[3];
468                 }
469
470                 if (!(chan = ast_get_channel_by_name_prefix_locked(chan_name, strlen(chan_name)))) {
471                         ast_cli(fd, "Invalid Channel!\n");
472                         return -1;
473                 }
474                 if (!strcasecmp(op, "start")) {
475                         muxmon_exec(chan, args);
476                 } else if (!strcasecmp(op, "stop")) {
477                         struct ast_channel_spy *cptr=NULL;
478                         for(cptr=chan->spiers; cptr; cptr=cptr->next) {
479                                 cptr->status = CHANSPY_DONE;
480                         }
481                 }
482                 ast_mutex_unlock(&chan->lock);
483                 return 0;
484         }
485
486         ast_cli(fd, "Usage: muxmon <start|stop> <chan_name> <args>\n");
487         return -1;
488 }
489
490
491 static struct ast_cli_entry cli_muxmon = {
492         { "muxmon", NULL, NULL }, muxmon_cli, 
493         "Execute a monitor command", "muxmon <start|stop> <chan_name> <args>"};
494
495
496 int unload_module(void)
497 {
498         int res;
499
500         res = ast_cli_unregister(&cli_muxmon);
501         res |= ast_unregister_application(app);
502         
503         STANDARD_HANGUP_LOCALUSERS;
504
505         return res;
506 }
507
508 int load_module(void)
509 {
510         int res;
511
512         res = ast_cli_register(&cli_muxmon);
513         res |= ast_register_application(app, muxmon_exec, synopsis, desc);
514
515         return res;
516 }
517
518 char *description(void)
519 {
520         return tdesc;
521 }
522
523 int usecount(void)
524 {
525         int res;
526         STANDARD_USECOUNT(res);
527         return res;
528 }
529
530 char *key()
531 {
532         return ASTERISK_GPL_KEY;
533 }
534