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