2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \brief Meet me conference bridge
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
33 #include <sys/ioctl.h>
35 #include <linux/zaptel.h>
38 #endif /* __linux__ */
42 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/module.h"
50 #include "asterisk/config.h"
51 #include "asterisk/app.h"
52 #include "asterisk/dsp.h"
53 #include "asterisk/musiconhold.h"
54 #include "asterisk/manager.h"
55 #include "asterisk/options.h"
56 #include "asterisk/cli.h"
57 #include "asterisk/say.h"
58 #include "asterisk/utils.h"
59 #include "asterisk/translate.h"
60 #include "asterisk/ulaw.h"
62 static const char *tdesc = "MeetMe conference bridge";
64 static const char *app = "MeetMe";
65 static const char *app2 = "MeetMeCount";
66 static const char *app3 = "MeetMeAdmin";
68 static const char *synopsis = "MeetMe conference bridge";
69 static const char *synopsis2 = "MeetMe participant count";
70 static const char *synopsis3 = "MeetMe conference Administration";
72 static const char *descrip =
73 " MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe conference.\n"
74 "If the conference number is omitted, the user will be prompted to enter\n"
76 "User can exit the conference by hangup, or if the 'p' option is specified, by pressing '#'.\n"
77 "Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!\n\n"
79 "The option string may contain zero or more of the following characters:\n"
80 " 'a' -- set admin mode\n"
81 " 'A' -- set marked mode\n"
82 " 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
83 " Default: conf-background.agi\n"
84 " (Note: This does not work with non-Zap channels in the same conference)\n"
85 " 'c' -- announce user(s) count on joining a conference\n"
86 " 'd' -- dynamically add conference\n"
87 " 'D' -- dynamically add conference, prompting for a PIN\n"
88 " 'e' -- select an empty conference\n"
89 " 'E' -- select an empty pinless conference\n"
90 " 'i' -- announce user join/leave\n"
91 " 'm' -- set monitor only mode (Listen only, no talking)\n"
92 " 'M' -- enable music on hold when the conference has a single caller\n"
93 " 'p' -- allow user to exit the conference by pressing '#'\n"
94 " 'P' -- always prompt for the pin even if it is specified\n"
95 " 'q' -- quiet mode (don't play enter/leave sounds)\n"
96 " 'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
97 " using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
98 " meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is wav.\n"
99 " 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
100 " 't' -- set talk only mode. (Talk only, no listening)\n"
101 " 'T' -- set talker detection (sent to manager interface and meetme list)\n"
102 " 'o' -- set talker optimization - treats talkers who aren't speaking as\n"
103 " being muted, meaning (a) No encode is done on transmission and\n"
104 " (b) Received audio that is not registered as talking is omitted\n"
105 " causing no buildup in background noise\n"
106 " 'v' -- video mode\n"
107 " 'w' -- wait until the marked user enters the conference\n"
108 " 'x' -- close the conference when last marked user exits\n"
109 " 'X' -- allow user to exit the conference by entering a valid single\n"
110 " digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
111 " if that variable is not defined.\n";
113 static const char *descrip2 =
114 " MeetMeCount(confno[|var]): Plays back the number of users in the specified\n"
115 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
116 "will be returned in the variable. Upon app completion, MeetMeCount will hangup the\n"
117 "channel, unless priority n+1 exists, in which case priority progress will continue.\n"
118 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
120 static const char *descrip3 =
121 " MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
122 " 'e' -- Eject last user that joined\n"
123 " 'k' -- Kick one user out of conference\n"
124 " 'K' -- Kick all users out of conference\n"
125 " 'l' -- Unlock conference\n"
126 " 'L' -- Lock conference\n"
127 " 'm' -- Unmute conference\n"
128 " 'M' -- Mute conference\n"
129 " 'n' -- Unmute entire conference (except admin)\n"
130 " 'N' -- Mute entire conference (except admin)\n"
133 #define CONFIG_FILE_NAME "meetme.conf"
139 struct ast_conference {
140 ast_mutex_t playlock; /* Conference specific lock (players) */
141 ast_mutex_t listenlock; /* Conference specific lock (listeners) */
142 char confno[AST_MAX_EXTENSION]; /* Conference */
143 struct ast_channel *chan; /* Announcements channel */
144 struct ast_channel *lchan; /* Listen/Record channel */
145 int fd; /* Announcements fd */
146 int zapconf; /* Zaptel Conf # */
147 int users; /* Number of active users */
148 int markedusers; /* Number of marked users */
149 struct ast_conf_user *firstuser; /* Pointer to the first user struct */
150 struct ast_conf_user *lastuser; /* Pointer to the last user struct */
151 time_t start; /* Start time (s) */
152 int recording; /* recording status */
153 int isdynamic; /* Created on the fly? */
154 int locked; /* Is the conference locked? */
155 pthread_t recordthread; /* thread for recording */
156 pthread_attr_t attr; /* thread attribute */
157 const char *recordingfilename; /* Filename to record the Conference into */
158 const char *recordingformat; /* Format to record the Conference in */
159 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
160 char pinadmin[AST_MAX_EXTENSION]; /* If protected by a admin PIN */
161 struct ast_frame *transframe[32];
162 struct ast_frame *origframe;
163 struct ast_trans_pvt *transpath[32];
164 AST_LIST_ENTRY(ast_conference) list;
167 static AST_LIST_HEAD_STATIC(confs, ast_conference);
170 int desired; /* Desired volume adjustment */
171 int actual; /* Actual volume adjustment (for channels that can't adjust) */
174 struct ast_conf_user {
175 int user_no; /* User Number */
176 struct ast_conf_user *prevuser; /* Pointer to the previous user */
177 struct ast_conf_user *nextuser; /* Pointer to the next user */
178 int userflags; /* Flags as set in the conference */
179 int adminflags; /* Flags set by the Admin */
180 struct ast_channel *chan; /* Connected channel */
181 int talking; /* Is user talking */
182 int zapchannel; /* Is a Zaptel channel */
183 char usrvalue[50]; /* Custom User Value */
184 char namerecloc[AST_MAX_EXTENSION]; /* Name Recorded file Location */
185 time_t jointime; /* Time the user joined the conference */
187 struct volume listen;
190 static int audio_buffers; /* The number of audio buffers to be allocated on pseudo channels
194 #define DEFAULT_AUDIO_BUFFERS 32 /* each buffer is 20ms, so this is 640ms total */
196 #define ADMINFLAG_MUTED (1 << 1) /* User is muted */
197 #define ADMINFLAG_KICKME (1 << 2) /* User is kicked */
198 #define MEETME_DELAYDETECTTALK 300
199 #define MEETME_DELAYDETECTENDTALK 1000
201 #define AST_FRAME_BITS 32
208 static int admin_exec(struct ast_channel *chan, void *data);
210 static void *recordthread(void *args);
218 #define MEETME_RECORD_OFF 0
219 #define MEETME_RECORD_STARTED 1
220 #define MEETME_RECORD_ACTIVE 2
221 #define MEETME_RECORD_TERMINATE 3
223 #define CONF_SIZE 320
225 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
226 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
227 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
228 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user when '*' is pressed */
229 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
230 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
231 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
232 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
233 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when user is alone in conference */
234 #define CONFFLAG_MARKEDEXIT (1 << 10) /* If set the MeetMe will return if all marked with this flag left */
235 #define CONFFLAG_WAITMARKED (1 << 11) /* If set, the MeetMe will wait until a marked user enters */
236 #define CONFFLAG_EXIT_CONTEXT (1 << 12) /* If set, the MeetMe will exit to the specified context */
237 #define CONFFLAG_MARKEDUSER (1 << 13) /* If set, the user will be marked */
238 #define CONFFLAG_INTROUSER (1 << 14) /* If set, user will be ask record name on entry of conference */
239 #define CONFFLAG_RECORDCONF (1<< 15) /* If set, the MeetMe will be recorded */
240 #define CONFFLAG_MONITORTALKER (1 << 16) /* If set, the user will be monitored if the user is talking or not */
241 #define CONFFLAG_DYNAMIC (1 << 17)
242 #define CONFFLAG_DYNAMICPIN (1 << 18)
243 #define CONFFLAG_EMPTY (1 << 19)
244 #define CONFFLAG_EMPTYNOPIN (1 << 20)
245 #define CONFFLAG_ALWAYSPROMPT (1 << 21)
246 #define CONFFLAG_ANNOUNCEUSERCOUNT (1 << 22) /* If set, when user joins the conference, they will be told the number of users that are already in */
247 #define CONFFLAG_OPTIMIZETALKER (1 << 23) /* If set, treats talking users as muted users */
250 AST_APP_OPTIONS(meetme_opts, {
251 AST_APP_OPTION('a', CONFFLAG_ADMIN ),
252 AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
253 AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
254 AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
255 AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
256 AST_APP_OPTION('m', CONFFLAG_MONITOR ),
257 AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
258 AST_APP_OPTION('s', CONFFLAG_STARMENU ),
259 AST_APP_OPTION('t', CONFFLAG_TALKER ),
260 AST_APP_OPTION('q', CONFFLAG_QUIET ),
261 AST_APP_OPTION('M', CONFFLAG_MOH ),
262 AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
263 AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
264 AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
265 AST_APP_OPTION('b', CONFFLAG_AGI ),
266 AST_APP_OPTION('w', CONFFLAG_WAITMARKED ),
267 AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
268 AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
269 AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
270 AST_APP_OPTION('e', CONFFLAG_EMPTY ),
271 AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
272 AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
275 static char *istalking(int x)
280 return "(unmonitored)";
282 return "(not talking)";
285 static int careful_write(int fd, unsigned char *data, int len, int block)
292 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
293 res = ioctl(fd, ZT_IOMUX, &x);
297 res = write(fd, data, len);
299 if (errno != EAGAIN) {
300 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
312 /* Map 'volume' levels from -5 through +5 into
313 decibel (dB) settings for channel drivers
314 Note: these are not a straight linear-to-dB
315 conversion... the numbers have been modified
316 to give the user a better level of adjustability
318 static signed char gain_map[] = {
332 static int set_talk_volume(struct ast_conf_user *user, int volume)
334 signed char gain_adjust;
336 /* attempt to make the adjustment in the channel driver;
337 if successful, don't adjust in the frame reading routine
339 gain_adjust = gain_map[volume + 5];
341 return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
344 static int set_listen_volume(struct ast_conf_user *user, int volume)
346 signed char gain_adjust;
348 /* attempt to make the adjustment in the channel driver;
349 if successful, don't adjust in the frame reading routine
351 gain_adjust = gain_map[volume + 5];
353 return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
356 static void tweak_volume(struct volume *vol, enum volume_action action)
360 switch (vol->desired) {
375 switch (vol->desired) {
391 static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
393 tweak_volume(&user->talk, action);
394 /* attempt to make the adjustment in the channel driver;
395 if successful, don't adjust in the frame reading routine
397 if (!set_talk_volume(user, user->talk.desired))
398 user->talk.actual = 0;
400 user->talk.actual = user->talk.desired;
403 static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
405 tweak_volume(&user->listen, action);
406 /* attempt to make the adjustment in the channel driver;
407 if successful, don't adjust in the frame reading routine
409 if (!set_listen_volume(user, user->listen.desired))
410 user->listen.actual = 0;
412 user->listen.actual = user->listen.desired;
415 static void reset_volumes(struct ast_conf_user *user)
417 signed char zero_volume = 0;
419 ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
420 ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
423 static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int sound)
429 if (!chan->_softhangup)
430 res = ast_autoservice_start(chan);
432 AST_LIST_LOCK(&confs);
448 careful_write(conf->fd, data, len, 1);
451 AST_LIST_UNLOCK(&confs);
454 ast_autoservice_stop(chan);
457 static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic)
459 struct ast_conference *cnf;
460 struct zt_confinfo ztc;
462 AST_LIST_LOCK(&confs);
464 AST_LIST_TRAVERSE(&confs, cnf, list) {
465 if (!strcmp(confno, cnf->confno))
469 if (!cnf && (make || dynamic)) {
471 if ((cnf = ast_calloc(1, sizeof(*cnf)))) {
472 ast_mutex_init(&cnf->playlock);
473 ast_mutex_init(&cnf->listenlock);
474 ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
475 ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
476 ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
477 cnf->markedusers = 0;
478 cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
480 ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
481 ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
482 cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
484 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
485 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
487 ast_log(LOG_WARNING, "Unable to open pseudo device\n");
493 memset(&ztc, 0, sizeof(ztc));
494 /* Setup a new zap conference */
497 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
498 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
499 ast_log(LOG_WARNING, "Error setting conference\n");
501 ast_hangup(cnf->chan);
508 cnf->lchan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
510 ast_set_read_format(cnf->lchan, AST_FORMAT_SLINEAR);
511 ast_set_write_format(cnf->lchan, AST_FORMAT_SLINEAR);
513 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
514 if (ioctl(cnf->lchan->fds[0], ZT_SETCONF, &ztc)) {
515 ast_log(LOG_WARNING, "Error setting conference\n");
516 ast_hangup(cnf->lchan);
520 /* Fill the conference struct */
521 cnf->start = time(NULL);
522 cnf->zapconf = ztc.confno;
523 cnf->isdynamic = dynamic;
524 cnf->firstuser = NULL;
525 cnf->lastuser = NULL;
527 if (option_verbose > 2)
528 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
529 AST_LIST_INSERT_HEAD(&confs, cnf, list);
533 AST_LIST_UNLOCK(&confs);
537 static int confs_show(int fd, int argc, char **argv)
539 ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
541 return RESULT_SUCCESS;
544 static char show_confs_usage[] =
545 "Deprecated! Please use 'meetme' instead.\n";
547 static struct ast_cli_entry cli_show_confs = {
548 { "show", "conferences", NULL }, confs_show,
549 "Show status of conferences", show_confs_usage, NULL };
551 static int conf_cmd(int fd, int argc, char **argv) {
552 /* Process the command */
553 struct ast_conference *cnf;
554 struct ast_conf_user *user;
556 int i = 0, total = 0;
558 char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
559 char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
560 char cmdline[1024] = "";
563 ast_cli(fd, "Invalid Arguments.\n");
564 /* Check for length so no buffer will overflow... */
565 for (i = 0; i < argc; i++) {
566 if (strlen(argv[i]) > 100)
567 ast_cli(fd, "Invalid Arguments.\n");
570 /* 'MeetMe': List all the conferences */
572 if (AST_LIST_EMPTY(&confs)) {
573 ast_cli(fd, "No active MeetMe conferences.\n");
574 return RESULT_SUCCESS;
576 ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
577 AST_LIST_TRAVERSE(&confs, cnf, list) {
578 if (cnf->markedusers == 0)
579 strcpy(cmdline, "N/A ");
581 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
582 hr = (now - cnf->start) / 3600;
583 min = ((now - cnf->start) % 3600) / 60;
584 sec = (now - cnf->start) % 60;
586 ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
590 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
591 return RESULT_SUCCESS;
594 return RESULT_SHOWUSAGE;
595 ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */
596 if (strstr(argv[1], "lock")) {
597 if (strcmp(argv[1], "lock") == 0) {
599 strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
602 strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
604 } else if (strstr(argv[1], "mute")) {
606 return RESULT_SHOWUSAGE;
607 if (strcmp(argv[1], "mute") == 0) {
609 if (strcmp(argv[3], "all") == 0) {
610 strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
612 strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
613 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
617 if (strcmp(argv[3], "all") == 0) {
618 strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
620 strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
621 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
624 } else if (strcmp(argv[1], "kick") == 0) {
626 return RESULT_SHOWUSAGE;
627 if (strcmp(argv[3], "all") == 0) {
629 strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
631 /* Kick a single user */
632 strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
633 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
635 } else if(strcmp(argv[1], "list") == 0) {
636 /* List all the users in a conference */
637 if (AST_LIST_EMPTY(&confs)) {
638 ast_cli(fd, "No active conferences.\n");
639 return RESULT_SUCCESS;
641 /* Find the right conference */
642 AST_LIST_TRAVERSE(&confs, cnf, list) {
643 if (strcmp(cnf->confno, argv[2]) == 0)
647 ast_cli(fd, "No such conference: %s.\n",argv[2]);
648 return RESULT_SUCCESS;
650 /* Show all the users */
651 for (user = cnf->firstuser; user; user = user->nextuser){
653 hr = (now - user->jointime) / 3600;
654 min = ((now - user->jointime) % 3600) / 60;
655 sec = (now - user->jointime) % 60;
658 ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %02d:%02d:%02d\n",
660 user->chan->cid.cid_num ? user->chan->cid.cid_num : "<unknown>",
661 user->chan->cid.cid_name ? user->chan->cid.cid_name : "<no name>",
663 user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
664 user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
665 user->adminflags & ADMINFLAG_MUTED ? "(Admn Muted)" : "",
666 istalking(user->talking), hr, min, sec);
668 ast_cli(fd,"%d users in that conference.\n",cnf->users);
670 return RESULT_SUCCESS;
672 return RESULT_SHOWUSAGE;
673 ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
674 admin_exec(NULL, cmdline);
679 static char *complete_confcmd(const char *line, const char *word, int pos, int state) {
680 #define CONF_COMMANDS 6
681 int which = 0, x = 0;
682 struct ast_conference *cnf = NULL;
683 struct ast_conf_user *usr = NULL;
686 char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
691 for (x = 0;x < CONF_COMMANDS; x++) {
692 if (!strncasecmp(cmds[x], word, strlen(word))) {
693 if (++which > state) {
694 return strdup(cmds[x]);
698 } else if (pos == 2) {
699 /* Conference Number */
700 AST_LIST_LOCK(&confs);
701 AST_LIST_TRAVERSE(&confs, cnf, list) {
702 if (!strncasecmp(word, cnf->confno, strlen(word))) {
707 AST_LIST_UNLOCK(&confs);
708 return cnf ? strdup(cnf->confno) : NULL;
709 } else if (pos == 3) {
710 /* User Number || Conf Command option*/
711 if (strstr(line, "mute") || strstr(line, "kick")) {
712 if ((state == 0) && (strstr(line, "kick") || strstr(line,"mute")) && !(strncasecmp(word, "all", strlen(word)))) {
713 return strdup("all");
716 AST_LIST_LOCK(&confs);
718 /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
719 myline = ast_strdupa(line);
720 if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
721 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
725 AST_LIST_TRAVERSE(&confs, cnf, list) {
726 if (!strcmp(confno, cnf->confno))
731 /* Search for the user */
732 for (usr = cnf->firstuser; usr; usr = usr->nextuser) {
733 snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
734 if (!strncasecmp(word, usrno, strlen(word))) {
740 AST_LIST_UNLOCK(&confs);
741 return usr ? strdup(usrno) : NULL;
748 static char conf_usage[] =
749 "Usage: meetme (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
750 " Executes a command for the conference or on a conferee\n";
752 static struct ast_cli_entry cli_conf = {
753 {"meetme", NULL, NULL }, conf_cmd,
754 "Execute a command on a conference or conferee", conf_usage, complete_confcmd};
756 static void conf_flush(int fd, struct ast_channel *chan)
760 /* read any frames that may be waiting on the channel
766 /* when no frames are available, this will wait
767 for 1 millisecond maximum
769 while (ast_waitfor(chan, 1)) {
776 /* flush any data sitting in the pseudo channel */
778 if (ioctl(fd, ZT_FLUSH, &x))
779 ast_log(LOG_WARNING, "Error flushing channel\n");
783 /* Remove the conference from the list and free it.
784 We assume that this was called while holding conflock. */
785 static int conf_free(struct ast_conference *conf)
789 AST_LIST_REMOVE(&confs, conf, list);
791 if (conf->recording == MEETME_RECORD_ACTIVE) {
792 conf->recording = MEETME_RECORD_TERMINATE;
793 AST_LIST_UNLOCK(&confs);
795 AST_LIST_LOCK(&confs);
796 if (conf->recording == MEETME_RECORD_OFF)
798 AST_LIST_UNLOCK(&confs);
802 for (x=0;x<AST_FRAME_BITS;x++) {
803 if (conf->transframe[x])
804 ast_frfree(conf->transframe[x]);
805 if (conf->transpath[x])
806 ast_translator_free_path(conf->transpath[x]);
809 ast_frfree(conf->origframe);
811 ast_hangup(conf->lchan);
813 ast_hangup(conf->chan);
822 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
824 struct ast_conf_user *user = NULL;
825 struct ast_conf_user *usr = NULL;
827 struct zt_confinfo ztc, ztc_empty;
829 struct ast_channel *c;
841 int currentmarked = 0;
845 int using_pseudo = 0;
849 struct ast_dsp *dsp=NULL;
852 const char *agifiledefault = "conf-background.agi";
853 char meetmesecs[30] = "";
854 char exitcontext[AST_MAX_CONTEXT] = "";
855 char recordingtmp[AST_MAX_EXTENSION] = "";
858 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
859 char *buf = __buf + AST_FRIENDLY_OFFSET;
861 if (!(user = ast_calloc(1, sizeof(*user)))) {
865 if (confflags & CONFFLAG_RECORDCONF) {
866 if (!conf->recordingfilename) {
867 conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
868 if (!conf->recordingfilename) {
869 snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
870 conf->recordingfilename = ast_strdupa(recordingtmp);
872 conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
873 if (!conf->recordingformat) {
874 snprintf(recordingtmp, sizeof(recordingtmp), "wav");
875 conf->recordingformat = ast_strdupa(recordingtmp);
877 ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n",
878 conf->confno, conf->recordingfilename, conf->recordingformat);
882 if ((conf->recording == MEETME_RECORD_OFF) && ((confflags & CONFFLAG_RECORDCONF) || (conf->lchan))) {
883 pthread_attr_init(&conf->attr);
884 pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
885 ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf);
888 time(&user->jointime);
890 if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) {
891 /* Sorry, but this confernce is locked! */
892 if (!ast_streamfile(chan, "conf-locked", chan->language))
893 ast_waitstream(chan, "");
897 if (confflags & CONFFLAG_MARKEDUSER)
900 ast_mutex_lock(&conf->playlock);
901 if (!conf->firstuser) {
902 /* Fill the first new User struct */
904 conf->firstuser = user;
905 conf->lastuser = user;
907 /* Fill the new user struct */
908 user->user_no = conf->lastuser->user_no + 1;
909 user->prevuser = conf->lastuser;
910 if (conf->lastuser->nextuser) {
911 ast_log(LOG_WARNING, "Error in User Management!\n");
912 ast_mutex_unlock(&conf->playlock);
915 conf->lastuser->nextuser = user;
916 conf->lastuser = user;
921 user->userflags = confflags;
922 user->adminflags = 0;
925 ast_mutex_unlock(&conf->playlock);
927 if (confflags & CONFFLAG_EXIT_CONTEXT) {
928 if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
929 ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
930 else if (!ast_strlen_zero(chan->macrocontext))
931 ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
933 ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
936 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
937 snprintf(user->namerecloc, sizeof(user->namerecloc),
938 "%s/meetme/meetme-username-%s-%d", ast_config_AST_SPOOL_DIR,
939 conf->confno, user->user_no);
940 ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
943 if (!(confflags & CONFFLAG_QUIET)) {
944 if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED))
945 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
946 ast_waitstream(chan, "");
947 if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0)
948 if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
949 ast_waitstream(chan, "");
952 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
955 if (conf->users == 2) {
956 if (!ast_streamfile(chan,"conf-onlyone",chan->language)) {
957 res = ast_waitstream(chan, AST_DIGIT_ANY);
964 if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
965 res = ast_waitstream(chan, AST_DIGIT_ANY);
972 res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
978 if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
979 res = ast_waitstream(chan, AST_DIGIT_ANY);
988 ast_indicate(chan, -1);
990 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
991 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
995 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
996 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
1000 retryzap = strcasecmp(chan->tech->type, "Zap");
1001 user->zapchannel = !retryzap;
1004 origfd = chan->fds[0];
1006 fd = open("/dev/zap/pseudo", O_RDWR);
1008 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
1012 /* Make non-blocking */
1013 flags = fcntl(fd, F_GETFL);
1015 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
1019 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
1020 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
1024 /* Setup buffering information */
1025 memset(&bi, 0, sizeof(bi));
1026 bi.bufsize = CONF_SIZE/2;
1027 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
1028 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
1029 bi.numbufs = audio_buffers;
1030 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
1031 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
1036 if (ioctl(fd, ZT_SETLINEAR, &x)) {
1037 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
1043 /* XXX Make sure we're not running on a pseudo channel XXX */
1047 memset(&ztc, 0, sizeof(ztc));
1048 memset(&ztc_empty, 0, sizeof(ztc_empty));
1049 /* Check to see if we're in a conference... */
1051 if (ioctl(fd, ZT_GETCONF, &ztc)) {
1052 ast_log(LOG_WARNING, "Error getting conference\n");
1057 /* Whoa, already in a conference... Retry... */
1059 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
1064 memset(&ztc, 0, sizeof(ztc));
1065 /* Add us to the conference */
1067 ztc.confno = conf->zapconf;
1069 ast_mutex_lock(&conf->playlock);
1071 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
1072 if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) {
1073 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1074 ast_waitstream(conf->chan, "");
1075 if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language))
1076 ast_waitstream(conf->chan, "");
1080 if (confflags & CONFFLAG_MONITOR)
1081 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1082 else if (confflags & CONFFLAG_TALKER)
1083 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1085 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1087 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1088 ast_log(LOG_WARNING, "Error setting conference\n");
1090 ast_mutex_unlock(&conf->playlock);
1093 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
1095 manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
1100 chan->name, chan->uniqueid, conf->confno, user->user_no);
1102 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
1104 if (!(confflags & CONFFLAG_QUIET))
1105 if (!(confflags & CONFFLAG_WAITMARKED) || (conf->markedusers >= 1))
1106 conf_play(chan, conf, ENTER);
1109 ast_mutex_unlock(&conf->playlock);
1111 conf_flush(fd, chan);
1113 if (confflags & CONFFLAG_AGI) {
1114 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
1115 or use default filename of conf-background.agi */
1117 agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
1119 agifile = agifiledefault;
1121 if (user->zapchannel) {
1122 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
1124 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1126 /* Find a pointer to the agi app and execute the script */
1127 app = pbx_findapp("agi");
1129 char *s = ast_strdupa(agifile);
1130 ret = pbx_exec(chan, app, s, 1);
1132 ast_log(LOG_WARNING, "Could not find application (agi)\n");
1135 if (user->zapchannel) {
1136 /* Remove CONFMUTE mode on Zap channel */
1138 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1141 if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
1142 /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
1144 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1146 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) {
1147 ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
1151 int menu_was_active = 0;
1156 /* if we have just exited from the menu, and the user had a channel-driver
1157 volume adjustment, restore it
1159 if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual)
1160 set_talk_volume(user, user->listen.desired);
1162 menu_was_active = menu_active;
1164 currentmarked = conf->markedusers;
1165 if (!(confflags & CONFFLAG_QUIET) &&
1166 (confflags & CONFFLAG_MARKEDUSER) &&
1167 (confflags & CONFFLAG_WAITMARKED) &&
1169 if (currentmarked == 1 && conf->users > 1) {
1170 ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
1171 if (conf->users - 1 == 1) {
1172 if (!ast_streamfile(chan, "conf-userwilljoin", chan->language))
1173 ast_waitstream(chan, "");
1175 if (!ast_streamfile(chan, "conf-userswilljoin", chan->language))
1176 ast_waitstream(chan, "");
1179 if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER))
1180 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
1181 ast_waitstream(chan, "");
1184 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
1187 /* Update the struct with the actual confflags */
1188 user->userflags = confflags;
1190 if (confflags & CONFFLAG_WAITMARKED) {
1191 if(currentmarked == 0) {
1192 if (lastmarked != 0) {
1193 if (!(confflags & CONFFLAG_QUIET))
1194 if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language))
1195 ast_waitstream(chan, "");
1196 if(confflags & CONFFLAG_MARKEDEXIT)
1199 ztc.confmode = ZT_CONF_CONF;
1200 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1201 ast_log(LOG_WARNING, "Error setting conference\n");
1207 if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) {
1208 ast_moh_start(chan, NULL);
1211 ztc.confmode = ZT_CONF_CONF;
1212 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1213 ast_log(LOG_WARNING, "Error setting conference\n");
1218 } else if(currentmarked >= 1 && lastmarked == 0) {
1219 if (confflags & CONFFLAG_MONITOR)
1220 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1221 else if (confflags & CONFFLAG_TALKER)
1222 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1224 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1225 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1226 ast_log(LOG_WARNING, "Error setting conference\n");
1230 if (musiconhold && (confflags & CONFFLAG_MOH)) {
1234 if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) {
1235 if (!ast_streamfile(chan, "conf-placeintoconf", chan->language))
1236 ast_waitstream(chan, "");
1237 conf_play(chan, conf, ENTER);
1242 /* trying to add moh for single person conf */
1243 if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) {
1244 if (conf->users == 1) {
1245 if (musiconhold == 0) {
1246 ast_moh_start(chan, NULL);
1257 /* Leave if the last marked user left */
1258 if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) {
1263 /* Check if the admin changed my modes */
1264 if (user->adminflags) {
1265 /* Set the new modes */
1266 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
1267 ztc.confmode ^= ZT_CONF_TALKER;
1268 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1269 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1274 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1275 ztc.confmode |= ZT_CONF_TALKER;
1276 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1277 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1282 if (user->adminflags & ADMINFLAG_KICKME) {
1283 /* You have been kicked. */
1284 if (!ast_streamfile(chan, "conf-kicked", chan->language))
1285 ast_waitstream(chan, "");
1289 } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1290 ztc.confmode |= ZT_CONF_TALKER;
1291 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1292 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1299 if (c->fds[0] != origfd) {
1301 /* Kill old pseudo */
1305 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
1306 retryzap = strcasecmp(c->tech->type, "Zap");
1307 user->zapchannel = !retryzap;
1310 if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & ADMINFLAG_MUTED))
1311 f = ast_read_noaudio(c);
1316 if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
1317 if (user->talk.actual)
1318 ast_frame_adjust_volume(f, user->talk.actual);
1320 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) {
1323 if (user->talking == -1)
1326 res = ast_dsp_silence(dsp, f, &totalsilence);
1327 if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) {
1329 if (confflags & CONFFLAG_MONITORTALKER)
1330 manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
1335 chan->name, chan->uniqueid, conf->confno, user->user_no);
1337 if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) {
1339 if (confflags & CONFFLAG_MONITORTALKER)
1340 manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking",
1345 chan->name, chan->uniqueid, conf->confno, user->user_no);
1349 /* Absolutely do _not_ use careful_write here...
1350 it is important that we read data from the channel
1351 as fast as it arrives, and feed it into the conference.
1352 The buffering in the pseudo channel will take care of any
1353 timing differences, unless they are so drastic as to lose
1354 audio frames (in which case carefully writing would only
1355 have delayed the audio even further).
1357 /* As it turns out, we do want to use careful write. We just
1358 don't want to block, but we do want to at least *try*
1359 to write out all the samples.
1361 if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER))
1362 careful_write(fd, f->data, f->datalen, 0);
1364 } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
1367 tmp[0] = f->subclass;
1369 if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
1370 ast_log(LOG_DEBUG, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
1373 } else if (option_debug > 1)
1374 ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension %s does not exist in context %s\n", tmp, exitcontext);
1375 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
1378 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
1379 if (ioctl(fd, ZT_SETCONF, &ztc_empty)) {
1380 ast_log(LOG_WARNING, "Error setting conference\n");
1385 /* if we are entering the menu, and the user has a channel-driver
1386 volume adjustment, clear it
1388 if (!menu_active && user->talk.desired && !user->talk.actual)
1389 set_talk_volume(user, 0);
1394 if ((confflags & CONFFLAG_ADMIN)) {
1398 /* Record this sound! */
1399 if (!ast_streamfile(chan, "conf-adminmenu", chan->language))
1400 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1407 case '1': /* Un/Mute */
1409 if (ztc.confmode & ZT_CONF_TALKER) {
1410 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
1411 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
1413 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1414 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
1416 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1417 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1421 if (ztc.confmode & ZT_CONF_TALKER) {
1422 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1423 ast_waitstream(chan, "");
1425 if (!ast_streamfile(chan, "conf-muted", chan->language))
1426 ast_waitstream(chan, "");
1429 case '2': /* Un/Lock the Conference */
1433 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
1434 ast_waitstream(chan, "");
1437 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
1438 ast_waitstream(chan, "");
1441 case '3': /* Eject last user */
1443 usr = conf->lastuser;
1444 if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
1445 if(!ast_streamfile(chan, "conf-errormenu", chan->language))
1446 ast_waitstream(chan, "");
1448 usr->adminflags |= ADMINFLAG_KICKME;
1449 ast_stopstream(chan);
1452 tweak_listen_volume(user, VOL_DOWN);
1455 tweak_listen_volume(user, VOL_UP);
1458 tweak_talk_volume(user, VOL_DOWN);
1464 tweak_talk_volume(user, VOL_UP);
1468 /* Play an error message! */
1469 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1470 ast_waitstream(chan, "");
1478 if (!ast_streamfile(chan, "conf-usermenu", chan->language))
1479 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1486 case '1': /* Un/Mute */
1488 if (ztc.confmode & ZT_CONF_TALKER) {
1489 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
1490 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
1491 } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
1492 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1493 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
1495 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1496 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1500 if (ztc.confmode & ZT_CONF_TALKER) {
1501 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1502 ast_waitstream(chan, "");
1504 if (!ast_streamfile(chan, "conf-muted", chan->language))
1505 ast_waitstream(chan, "");
1509 tweak_listen_volume(user, VOL_DOWN);
1512 tweak_listen_volume(user, VOL_UP);
1515 tweak_talk_volume(user, VOL_DOWN);
1521 tweak_talk_volume(user, VOL_UP);
1525 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1526 ast_waitstream(chan, "");
1532 ast_moh_start(chan, NULL);
1534 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1535 ast_log(LOG_WARNING, "Error setting conference\n");
1537 AST_LIST_UNLOCK(&confs);
1541 conf_flush(fd, chan);
1542 } else if (option_debug) {
1544 "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n",
1545 chan->name, f->frametype, f->subclass);
1548 } else if (outfd > -1) {
1549 res = read(outfd, buf, CONF_SIZE);
1551 memset(&fr, 0, sizeof(fr));
1552 fr.frametype = AST_FRAME_VOICE;
1553 fr.subclass = AST_FORMAT_SLINEAR;
1557 fr.offset = AST_FRIENDLY_OFFSET;
1558 if (!user->listen.actual &&
1559 ((confflags & CONFFLAG_MONITOR) ||
1560 (user->adminflags & ADMINFLAG_MUTED) ||
1561 (user->talking && (confflags & CONFFLAG_OPTIMIZETALKER))
1564 for (index=0;index<AST_FRAME_BITS;index++)
1565 if (chan->rawwriteformat & (1 << index))
1567 if (index >= AST_FRAME_BITS)
1568 goto bailoutandtrynormal;
1569 ast_mutex_lock(&conf->listenlock);
1570 if (!conf->transframe[index]) {
1571 if (conf->origframe) {
1572 if (!conf->transpath[index])
1573 conf->transpath[index] = ast_translator_build_path((1 << index), AST_FORMAT_SLINEAR);
1574 if (conf->transpath[index]) {
1575 conf->transframe[index] = ast_translate(conf->transpath[index], conf->origframe, 0);
1576 if (!conf->transframe[index])
1577 conf->transframe[index] = &ast_null_frame;
1581 if (conf->transframe[index]) {
1582 if (conf->transframe[index]->frametype != AST_FRAME_NULL) {
1583 if (ast_write(chan, conf->transframe[index]))
1584 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
1587 ast_mutex_unlock(&conf->listenlock);
1588 goto bailoutandtrynormal;
1590 ast_mutex_unlock(&conf->listenlock);
1592 bailoutandtrynormal:
1593 if (user->listen.actual)
1594 ast_frame_adjust_volume(&fr, user->listen.actual);
1595 if (ast_write(chan, &fr) < 0) {
1596 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
1600 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
1602 lastmarked = currentmarked;
1608 /* Take out of conference */
1612 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1613 ast_log(LOG_WARNING, "Error setting conference\n");
1617 reset_volumes(user);
1619 AST_LIST_LOCK(&confs);
1620 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
1621 conf_play(chan, conf, LEAVE);
1623 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
1624 if (ast_fileexists(user->namerecloc, NULL, NULL)) {
1625 if ((conf->chan) && (conf->users > 1)) {
1626 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1627 ast_waitstream(conf->chan, "");
1628 if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language))
1629 ast_waitstream(conf->chan, "");
1631 ast_filedelete(user->namerecloc, NULL);
1634 AST_LIST_UNLOCK(&confs);
1637 AST_LIST_LOCK(&confs);
1642 if (user->user_no) { /* Only cleanup users who really joined! */
1644 hr = (now - user->jointime) / 3600;
1645 min = ((now - user->jointime) % 3600) / 60;
1646 sec = (now - user->jointime) % 60;
1648 manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
1655 "Duration: %02d:%02d:%02d\r\n",
1656 chan->name, chan->uniqueid, conf->confno,
1658 user->chan->cid.cid_num ? user->chan->cid.cid_num :
1660 user->chan->cid.cid_name ? user->chan->cid.cid_name :
1661 "<no name>", hr, min, sec);
1664 if (confflags & CONFFLAG_MARKEDUSER)
1665 conf->markedusers--;
1667 /* No more users -- close this one out */
1670 /* Remove the user struct */
1671 if (user == conf->firstuser) {
1672 if (user->nextuser) {
1673 /* There is another entry */
1674 user->nextuser->prevuser = NULL;
1676 /* We are the only entry */
1677 conf->lastuser = NULL;
1679 /* In either case */
1680 conf->firstuser = user->nextuser;
1681 } else if (user == conf->lastuser){
1683 user->prevuser->nextuser = NULL;
1685 ast_log(LOG_ERROR, "Bad bad bad! We're the last, not the first, but nobody before us??\n");
1686 conf->lastuser = user->prevuser;
1689 user->nextuser->prevuser = user->prevuser;
1691 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1693 user->prevuser->nextuser = user->nextuser;
1695 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1698 /* Return the number of seconds the user was in the conf */
1699 snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
1700 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1703 AST_LIST_UNLOCK(&confs);
1708 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin)
1710 struct ast_config *cfg;
1711 struct ast_variable *var;
1712 struct ast_conference *cnf;
1714 AST_DECLARE_APP_ARGS(args,
1715 AST_APP_ARG(confno);
1717 AST_APP_ARG(pinadmin);
1720 /* Check first in the conference list */
1721 AST_LIST_LOCK(&confs);
1722 AST_LIST_TRAVERSE(&confs, cnf, list) {
1723 if (!strcmp(confno, cnf->confno))
1726 AST_LIST_UNLOCK(&confs);
1730 /* No need to parse meetme.conf */
1731 ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1733 if (dynamic_pin[0] == 'q') {
1734 /* Query the user to enter a PIN */
1735 if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0) < 0)
1738 cnf = build_conf(confno, dynamic_pin, "", make, dynamic);
1740 cnf = build_conf(confno, "", "", make, dynamic);
1743 /* Check the config */
1744 cfg = ast_config_load(CONFIG_FILE_NAME);
1746 ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME);
1749 var = ast_variable_browse(cfg, "rooms");
1750 for (; var; var = var->next) {
1751 if (strcasecmp(var->name, "conf"))
1754 if (!(parse = ast_strdupa(var->value)))
1757 AST_STANDARD_APP_ARGS(args, parse);
1758 if (!strcasecmp(args.confno, confno)) {
1759 /* Bingo it's a valid conference */
1762 cnf = build_conf(args.confno, args.pin, args.pinadmin, make, dynamic);
1764 cnf = build_conf(args.confno, args.pin, "", make, dynamic);
1767 cnf = build_conf(args.confno, "", args.pinadmin, make, dynamic);
1769 cnf = build_conf(args.confno, "", "", make, dynamic);
1775 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1777 ast_config_destroy(cfg);
1779 } else if (dynamic_pin) {
1780 /* Correct for the user selecting 'D' instead of 'd' to have
1781 someone join into a conference that has already been created
1783 if (dynamic_pin[0] == 'q')
1784 dynamic_pin[0] = '\0';
1790 /*! \brief The MeetmeCount application */
1791 static int count_exec(struct ast_channel *chan, void *data)
1793 struct localuser *u;
1795 struct ast_conference *conf;
1799 AST_DECLARE_APP_ARGS(args,
1800 AST_APP_ARG(confno);
1801 AST_APP_ARG(varname);
1804 if (ast_strlen_zero(data)) {
1805 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1811 if (!(localdata = ast_strdupa(data))) {
1812 LOCAL_USER_REMOVE(u);
1816 AST_STANDARD_APP_ARGS(args, localdata);
1818 conf = find_conf(chan, args.confno, 0, 0, NULL);
1820 count = conf->users;
1824 if (!ast_strlen_zero(args.varname)){
1825 /* have var so load it and exit */
1826 snprintf(val, sizeof(val), "%d",count);
1827 pbx_builtin_setvar_helper(chan, args.varname, val);
1829 if (chan->_state != AST_STATE_UP)
1831 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1833 LOCAL_USER_REMOVE(u);
1838 /*! \brief The meetme() application */
1839 static int conf_exec(struct ast_channel *chan, void *data)
1842 struct localuser *u;
1843 char confno[AST_MAX_EXTENSION] = "";
1846 struct ast_conference *cnf;
1847 struct ast_flags confflags = {0};
1849 int empty = 0, empty_no_pin = 0;
1850 int always_prompt = 0;
1851 char *notdata, *info, the_pin[AST_MAX_EXTENSION] = "";
1852 AST_DECLARE_APP_ARGS(args,
1853 AST_APP_ARG(confno);
1854 AST_APP_ARG(options);
1860 if (ast_strlen_zero(data)) {
1867 if (chan->_state != AST_STATE_UP)
1870 info = ast_strdupa(notdata);
1872 AST_STANDARD_APP_ARGS(args, info);
1875 ast_copy_string(confno, args.confno, sizeof(confno));
1876 if (ast_strlen_zero(confno)) {
1882 ast_copy_string(the_pin, args.pin, sizeof(the_pin));
1885 ast_app_parse_options(meetme_opts, &confflags, NULL, args.options);
1886 dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN);
1887 if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && !args.pin)
1888 strcpy(the_pin, "q");
1890 empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN);
1891 empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN);
1892 always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT);
1899 int i, map[1024] = { 0, };
1900 struct ast_config *cfg;
1901 struct ast_variable *var;
1904 AST_LIST_LOCK(&confs);
1905 AST_LIST_TRAVERSE(&confs, cnf, list) {
1906 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1907 /* Disqualify in use conference */
1908 if (confno_int >= 0 && confno_int < 1024)
1912 AST_LIST_UNLOCK(&confs);
1914 /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1915 if ((empty_no_pin) || (!dynamic)) {
1916 cfg = ast_config_load(CONFIG_FILE_NAME);
1918 var = ast_variable_browse(cfg, "rooms");
1920 if (!strcasecmp(var->name, "conf")) {
1921 char *stringp = ast_strdupa(var->value);
1923 char *confno_tmp = strsep(&stringp, "|,");
1925 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
1926 if ((confno_int >= 0) && (confno_int < 1024)) {
1927 if (stringp && empty_no_pin) {
1933 /* For static: run through the list and see if this conference is empty */
1934 AST_LIST_LOCK(&confs);
1935 AST_LIST_TRAVERSE(&confs, cnf, list) {
1936 if (!strcmp(confno_tmp, cnf->confno)) {
1937 /* The conference exists, therefore it's not empty */
1942 AST_LIST_UNLOCK(&confs);
1944 /* At this point, we have a confno_tmp (static conference) that is empty */
1945 if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
1946 /* Case 1: empty_no_pin and pin is nonexistent (NULL)
1947 * Case 2: empty_no_pin and pin is blank (but not NULL)
1948 * Case 3: not empty_no_pin
1950 ast_copy_string(confno, confno_tmp, sizeof(confno));
1952 /* XXX the map is not complete (but we do have a confno) */
1960 ast_config_destroy(cfg);
1964 /* Select first conference number not in use */
1965 if (ast_strlen_zero(confno) && dynamic) {
1966 for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
1968 snprintf(confno, sizeof(confno), "%d", i);
1975 if (ast_strlen_zero(confno)) {
1976 res = ast_streamfile(chan, "conf-noempty", chan->language);
1978 ast_waitstream(chan, "");
1980 if (sscanf(confno, "%d", &confno_int) == 1) {
1981 res = ast_streamfile(chan, "conf-enteringno", chan->language);
1983 ast_waitstream(chan, "");
1984 res = ast_say_digits(chan, confno_int, "", chan->language);
1987 ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
1992 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
1993 /* Prompt user for conference number */
1994 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
1996 /* Don't try to validate when we catch an error */
2002 if (!ast_strlen_zero(confno)) {
2003 /* Check the validity of the conference */
2004 cnf = find_conf(chan, confno, 1, dynamic, the_pin);
2006 res = ast_streamfile(chan, "conf-invalid", chan->language);
2008 ast_waitstream(chan, "");
2013 if ((!ast_strlen_zero(cnf->pin) &&
2014 !ast_test_flag(&confflags, CONFFLAG_ADMIN)) ||
2015 (!ast_strlen_zero(cnf->pinadmin) &&
2016 ast_test_flag(&confflags, CONFFLAG_ADMIN))) {
2017 char pin[AST_MAX_EXTENSION]="";
2020 /* Allow the pin to be retried up to 3 times */
2021 for (j = 0; j < 3; j++) {
2022 if (*the_pin && (always_prompt == 0)) {
2023 ast_copy_string(pin, the_pin, sizeof(pin));
2026 /* Prompt user for pin if pin is required */
2027 res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
2030 if (!strcasecmp(pin, cnf->pin) ||
2031 (!ast_strlen_zero(cnf->pinadmin) &&
2032 !strcasecmp(pin, cnf->pinadmin))) {
2035 if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin))
2036 ast_set_flag(&confflags, CONFFLAG_ADMIN);
2037 /* Run the conference */
2038 res = conf_run(chan, cnf, confflags.flags);
2042 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
2044 ast_waitstream(chan, AST_DIGIT_ANY);
2054 /* failed when getting the pin */
2057 /* see if we need to get rid of the conference */
2058 AST_LIST_LOCK(&confs);
2062 AST_LIST_UNLOCK(&confs);
2066 /* Don't retry pin with a static pin */
2067 if (*the_pin && (always_prompt==0)) {
2072 /* No pin required */
2075 /* Run the conference */
2076 res = conf_run(chan, cnf, confflags.flags);
2080 } while (allowretry);
2082 LOCAL_USER_REMOVE(u);
2087 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident)
2089 struct ast_conf_user *user = NULL;
2092 sscanf(callerident, "%i", &cid);
2093 if (conf && callerident) {
2094 user = conf->firstuser;
2096 if (cid == user->user_no)
2098 user = user->nextuser;
2104 /*! \brief The MeetMeadmin application */
2105 /* MeetMeAdmin(confno, command, caller) */
2106 static int admin_exec(struct ast_channel *chan, void *data) {
2108 struct ast_conference *cnf;
2109 struct ast_conf_user *user = NULL;
2110 struct localuser *u;
2111 AST_DECLARE_APP_ARGS(args,
2112 AST_APP_ARG(confno);
2113 AST_APP_ARG(command);
2119 AST_LIST_LOCK(&confs);
2120 /* The param has the conference number the user and the command to execute */
2121 if (!ast_strlen_zero(data)) {
2122 params = ast_strdupa((char *) data);
2124 AST_STANDARD_APP_ARGS(args, params);
2126 if (!args.command) {
2127 ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
2128 AST_LIST_UNLOCK(&confs);
2129 LOCAL_USER_REMOVE(u);
2132 AST_LIST_TRAVERSE(&confs, cnf, list) {
2133 if (!strcmp(cnf->confno, args.confno))
2138 user = find_user(cnf, args.user);
2141 switch((int) (*args.command)) {
2142 case 76: /* L: Lock */
2145 case 108: /* l: Unlock */
2148 case 75: /* K: kick all users*/
2149 user = cnf->firstuser;
2151 user->adminflags |= ADMINFLAG_KICKME;
2152 if (user->nextuser) {
2153 user = user->nextuser;
2159 case 101: /* e: Eject last user*/
2160 user = cnf->lastuser;
2161 if (!(user->userflags & CONFFLAG_ADMIN)) {
2162 user->adminflags |= ADMINFLAG_KICKME;
2165 ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
2167 case 77: /* M: Mute */
2169 user->adminflags |= ADMINFLAG_MUTED;
2171 ast_log(LOG_NOTICE, "Specified User not found!\n");
2174 case 78: /* N: Mute all users */
2175 user = cnf->firstuser;
2177 if (user && !(user->userflags & CONFFLAG_ADMIN))
2178 user->adminflags |= ADMINFLAG_MUTED;
2179 if (user->nextuser) {
2180 user = user->nextuser;
2186 case 109: /* m: Unmute */
2187 if (user && (user->adminflags & ADMINFLAG_MUTED)) {
2188 user->adminflags ^= ADMINFLAG_MUTED;
2190 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!\n");
2193 case 110: /* n: Unmute all users */
2194 user = cnf->firstuser;
2196 if (user && (user-> adminflags & ADMINFLAG_MUTED)) {
2197 user->adminflags ^= ADMINFLAG_MUTED;
2199 if (user->nextuser) {
2200 user = user->nextuser;
2206 case 107: /* k: Kick user */
2208 user->adminflags |= ADMINFLAG_KICKME;
2210 ast_log(LOG_NOTICE, "Specified User not found!");
2215 ast_log(LOG_NOTICE, "Conference Number not found\n");
2218 AST_LIST_UNLOCK(&confs);
2220 LOCAL_USER_REMOVE(u);
2225 static void *recordthread(void *args)
2227 struct ast_conference *cnf = args;
2228 struct ast_frame *f=NULL;
2230 struct ast_filestream *s=NULL;
2233 const char *oldrecordingfilename = NULL;
2235 if (!cnf || !cnf->lchan) {
2239 ast_stopstream(cnf->lchan);
2240 flags = O_CREAT|O_TRUNC|O_WRONLY;
2243 cnf->recording = MEETME_RECORD_ACTIVE;
2244 while (ast_waitfor(cnf->lchan, -1) > -1) {
2245 if (cnf->recording == MEETME_RECORD_TERMINATE) {
2246 AST_LIST_LOCK(&confs);
2247 AST_LIST_UNLOCK(&confs);
2250 if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) {
2251 s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
2252 oldrecordingfilename = cnf->recordingfilename;
2255 f = ast_read(cnf->lchan);
2260 if (f->frametype == AST_FRAME_VOICE) {
2261 ast_mutex_lock(&cnf->listenlock);
2262 for (x=0;x<AST_FRAME_BITS;x++) {
2263 /* Free any translations that have occured */
2264 if (cnf->transframe[x]) {
2265 ast_frfree(cnf->transframe[x]);
2266 cnf->transframe[x] = NULL;
2270 ast_frfree(cnf->origframe);
2272 ast_mutex_unlock(&cnf->listenlock);
2274 res = ast_writestream(s, f);
2282 cnf->recording = MEETME_RECORD_OFF;
2289 static void load_config(void)
2291 struct ast_config *cfg;
2294 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2296 if (!(cfg = ast_config_load(CONFIG_FILE_NAME)))
2299 if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
2300 if ((sscanf(val, "%d", &audio_buffers) != 1)) {
2301 ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val);
2302 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2303 } else if ((audio_buffers < ZT_DEFAULT_NUM_BUFS) || (audio_buffers > ZT_MAX_NUM_BUFS)) {
2304 ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n",
2305 ZT_DEFAULT_NUM_BUFS, ZT_MAX_NUM_BUFS);
2306 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2308 if (audio_buffers != DEFAULT_AUDIO_BUFFERS)
2309 ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers);
2312 ast_config_destroy(cfg);
2315 int unload_module(void)
2319 res = ast_cli_unregister(&cli_show_confs);
2320 res |= ast_cli_unregister(&cli_conf);
2321 res |= ast_unregister_application(app3);
2322 res |= ast_unregister_application(app2);
2323 res |= ast_unregister_application(app);
2325 STANDARD_HANGUP_LOCALUSERS;
2330 int load_module(void)
2336 res = ast_cli_register(&cli_show_confs);
2337 res |= ast_cli_register(&cli_conf);
2338 res |= ast_register_application(app3, admin_exec, synopsis3, descrip3);
2339 res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
2340 res |= ast_register_application(app, conf_exec, synopsis, descrip);
2352 char *description(void)
2354 return (char *) tdesc;
2361 STANDARD_USECOUNT(res);
2368 return ASTERISK_GPL_KEY;