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"
137 struct ast_conference {
138 ast_mutex_t playlock; /* Conference specific lock (players) */
139 ast_mutex_t listenlock; /* Conference specific lock (listeners) */
140 char confno[AST_MAX_EXTENSION]; /* Conference */
141 struct ast_channel *chan; /* Announcements channel */
142 struct ast_channel *lchan; /* Listen/Record channel */
143 int fd; /* Announcements fd */
144 int zapconf; /* Zaptel Conf # */
145 int users; /* Number of active users */
146 int markedusers; /* Number of marked users */
147 struct ast_conf_user *firstuser; /* Pointer to the first user struct */
148 struct ast_conf_user *lastuser; /* Pointer to the last user struct */
149 time_t start; /* Start time (s) */
150 int recording; /* recording status */
151 int isdynamic; /* Created on the fly? */
152 int locked; /* Is the conference locked? */
153 pthread_t recordthread; /* thread for recording */
154 pthread_attr_t attr; /* thread attribute */
155 const char *recordingfilename; /* Filename to record the Conference into */
156 const char *recordingformat; /* Format to record the Conference in */
157 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
158 char pinadmin[AST_MAX_EXTENSION]; /* If protected by a admin PIN */
159 struct ast_frame *transframe[32];
160 struct ast_frame *origframe;
161 struct ast_trans_pvt *transpath[32];
162 AST_LIST_ENTRY(ast_conference) list;
165 static AST_LIST_HEAD_STATIC(confs, ast_conference);
168 int desired; /* Desired volume adjustment */
169 int actual; /* Actual volume adjustment (for channels that can't adjust) */
172 struct ast_conf_user {
173 int user_no; /* User Number */
174 struct ast_conf_user *prevuser; /* Pointer to the previous user */
175 struct ast_conf_user *nextuser; /* Pointer to the next user */
176 int userflags; /* Flags as set in the conference */
177 int adminflags; /* Flags set by the Admin */
178 struct ast_channel *chan; /* Connected channel */
179 int talking; /* Is user talking */
180 int zapchannel; /* Is a Zaptel channel */
181 char usrvalue[50]; /* Custom User Value */
182 char namerecloc[AST_MAX_EXTENSION]; /* Name Recorded file Location */
183 time_t jointime; /* Time the user joined the conference */
185 struct volume listen;
188 static int audio_buffers; /* The number of audio buffers to be allocated on pseudo channels
192 #define DEFAULT_AUDIO_BUFFERS 32 /* each buffer is 20ms, so this is 640ms total */
194 #define ADMINFLAG_MUTED (1 << 1) /* User is muted */
195 #define ADMINFLAG_KICKME (1 << 2) /* User is kicked */
196 #define MEETME_DELAYDETECTTALK 300
197 #define MEETME_DELAYDETECTENDTALK 1000
199 #define AST_FRAME_BITS 32
206 static int admin_exec(struct ast_channel *chan, void *data);
208 static void *recordthread(void *args);
216 #define MEETME_RECORD_OFF 0
217 #define MEETME_RECORD_STARTED 1
218 #define MEETME_RECORD_ACTIVE 2
219 #define MEETME_RECORD_TERMINATE 3
221 #define CONF_SIZE 320
223 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
224 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
225 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
226 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user when '*' is pressed */
227 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
228 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
229 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
230 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
231 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when user is alone in conference */
232 #define CONFFLAG_MARKEDEXIT (1 << 10) /* If set the MeetMe will return if all marked with this flag left */
233 #define CONFFLAG_WAITMARKED (1 << 11) /* If set, the MeetMe will wait until a marked user enters */
234 #define CONFFLAG_EXIT_CONTEXT (1 << 12) /* If set, the MeetMe will exit to the specified context */
235 #define CONFFLAG_MARKEDUSER (1 << 13) /* If set, the user will be marked */
236 #define CONFFLAG_INTROUSER (1 << 14) /* If set, user will be ask record name on entry of conference */
237 #define CONFFLAG_RECORDCONF (1<< 15) /* If set, the MeetMe will be recorded */
238 #define CONFFLAG_MONITORTALKER (1 << 16) /* If set, the user will be monitored if the user is talking or not */
239 #define CONFFLAG_DYNAMIC (1 << 17)
240 #define CONFFLAG_DYNAMICPIN (1 << 18)
241 #define CONFFLAG_EMPTY (1 << 19)
242 #define CONFFLAG_EMPTYNOPIN (1 << 20)
243 #define CONFFLAG_ALWAYSPROMPT (1 << 21)
244 #define CONFFLAG_ANNOUNCEUSERCOUNT (1 << 22) /* If set, when user joins the conference, they will be told the number of users that are already in */
245 #define CONFFLAG_OPTIMIZETALKER (1 << 23) /* If set, treats talking users as muted users */
248 AST_APP_OPTIONS(meetme_opts, {
249 AST_APP_OPTION('a', CONFFLAG_ADMIN ),
250 AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
251 AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
252 AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
253 AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
254 AST_APP_OPTION('m', CONFFLAG_MONITOR ),
255 AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
256 AST_APP_OPTION('s', CONFFLAG_STARMENU ),
257 AST_APP_OPTION('t', CONFFLAG_TALKER ),
258 AST_APP_OPTION('q', CONFFLAG_QUIET ),
259 AST_APP_OPTION('M', CONFFLAG_MOH ),
260 AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
261 AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
262 AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
263 AST_APP_OPTION('b', CONFFLAG_AGI ),
264 AST_APP_OPTION('w', CONFFLAG_WAITMARKED ),
265 AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
266 AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
267 AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
268 AST_APP_OPTION('e', CONFFLAG_EMPTY ),
269 AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
270 AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
273 static char *istalking(int x)
278 return "(unmonitored)";
280 return "(not talking)";
283 static int careful_write(int fd, unsigned char *data, int len, int block)
290 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
291 res = ioctl(fd, ZT_IOMUX, &x);
295 res = write(fd, data, len);
297 if (errno != EAGAIN) {
298 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
310 /* Map 'volume' levels from -5 through +5 into
311 decibel (dB) settings for channel drivers
312 Note: these are not a straight linear-to-dB
313 conversion... the numbers have been modified
314 to give the user a better level of adjustability
316 static signed char gain_map[] = {
330 static int set_talk_volume(struct ast_conf_user *user, int volume)
332 signed char gain_adjust;
334 /* attempt to make the adjustment in the channel driver;
335 if successful, don't adjust in the frame reading routine
337 gain_adjust = gain_map[volume + 5];
339 return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
342 static int set_listen_volume(struct ast_conf_user *user, int volume)
344 signed char gain_adjust;
346 /* attempt to make the adjustment in the channel driver;
347 if successful, don't adjust in the frame reading routine
349 gain_adjust = gain_map[volume + 5];
351 return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
354 static void tweak_volume(struct volume *vol, enum volume_action action)
358 switch (vol->desired) {
373 switch (vol->desired) {
389 static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
391 tweak_volume(&user->talk, action);
392 /* attempt to make the adjustment in the channel driver;
393 if successful, don't adjust in the frame reading routine
395 if (!set_talk_volume(user, user->talk.desired))
396 user->talk.actual = 0;
398 user->talk.actual = user->talk.desired;
401 static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
403 tweak_volume(&user->listen, action);
404 /* attempt to make the adjustment in the channel driver;
405 if successful, don't adjust in the frame reading routine
407 if (!set_listen_volume(user, user->listen.desired))
408 user->listen.actual = 0;
410 user->listen.actual = user->listen.desired;
413 static void reset_volumes(struct ast_conf_user *user)
415 signed char zero_volume = 0;
417 ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
418 ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
421 static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int sound)
427 if (!chan->_softhangup)
428 res = ast_autoservice_start(chan);
430 AST_LIST_LOCK(&confs);
446 careful_write(conf->fd, data, len, 1);
449 AST_LIST_UNLOCK(&confs);
452 ast_autoservice_stop(chan);
455 static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic)
457 struct ast_conference *cnf;
458 struct zt_confinfo ztc;
460 AST_LIST_LOCK(&confs);
462 AST_LIST_TRAVERSE(&confs, cnf, list) {
463 if (!strcmp(confno, cnf->confno))
467 if (!cnf && (make || dynamic)) {
469 if ((cnf = ast_calloc(1, sizeof(*cnf)))) {
470 ast_mutex_init(&cnf->playlock);
471 ast_mutex_init(&cnf->listenlock);
472 ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
473 ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
474 ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
475 cnf->markedusers = 0;
476 cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
478 ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
479 ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
480 cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
482 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
483 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
485 ast_log(LOG_WARNING, "Unable to open pseudo device\n");
491 memset(&ztc, 0, sizeof(ztc));
492 /* Setup a new zap conference */
495 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
496 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
497 ast_log(LOG_WARNING, "Error setting conference\n");
499 ast_hangup(cnf->chan);
506 cnf->lchan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
508 ast_set_read_format(cnf->lchan, AST_FORMAT_SLINEAR);
509 ast_set_write_format(cnf->lchan, AST_FORMAT_SLINEAR);
511 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
512 if (ioctl(cnf->lchan->fds[0], ZT_SETCONF, &ztc)) {
513 ast_log(LOG_WARNING, "Error setting conference\n");
514 ast_hangup(cnf->lchan);
518 /* Fill the conference struct */
519 cnf->start = time(NULL);
520 cnf->zapconf = ztc.confno;
521 cnf->isdynamic = dynamic;
522 cnf->firstuser = NULL;
523 cnf->lastuser = NULL;
525 if (option_verbose > 2)
526 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
527 AST_LIST_INSERT_HEAD(&confs, cnf, list);
531 AST_LIST_UNLOCK(&confs);
535 static int confs_show(int fd, int argc, char **argv)
537 ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
539 return RESULT_SUCCESS;
542 static char show_confs_usage[] =
543 "Deprecated! Please use 'meetme' instead.\n";
545 static struct ast_cli_entry cli_show_confs = {
546 { "show", "conferences", NULL }, confs_show,
547 "Show status of conferences", show_confs_usage, NULL };
549 static int conf_cmd(int fd, int argc, char **argv) {
550 /* Process the command */
551 struct ast_conference *cnf;
552 struct ast_conf_user *user;
554 int i = 0, total = 0;
556 char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
557 char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
558 char cmdline[1024] = "";
561 ast_cli(fd, "Invalid Arguments.\n");
562 /* Check for length so no buffer will overflow... */
563 for (i = 0; i < argc; i++) {
564 if (strlen(argv[i]) > 100)
565 ast_cli(fd, "Invalid Arguments.\n");
568 /* 'MeetMe': List all the conferences */
570 if (AST_LIST_EMPTY(&confs)) {
571 ast_cli(fd, "No active MeetMe conferences.\n");
572 return RESULT_SUCCESS;
574 ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
575 AST_LIST_TRAVERSE(&confs, cnf, list) {
576 if (cnf->markedusers == 0)
577 strcpy(cmdline, "N/A ");
579 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
580 hr = (now - cnf->start) / 3600;
581 min = ((now - cnf->start) % 3600) / 60;
582 sec = (now - cnf->start) % 60;
584 ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
588 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
589 return RESULT_SUCCESS;
592 return RESULT_SHOWUSAGE;
593 ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */
594 if (strstr(argv[1], "lock")) {
595 if (strcmp(argv[1], "lock") == 0) {
597 strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
600 strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
602 } else if (strstr(argv[1], "mute")) {
604 return RESULT_SHOWUSAGE;
605 if (strcmp(argv[1], "mute") == 0) {
607 if (strcmp(argv[3], "all") == 0) {
608 strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
610 strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
611 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
615 if (strcmp(argv[3], "all") == 0) {
616 strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
618 strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
619 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
622 } else if (strcmp(argv[1], "kick") == 0) {
624 return RESULT_SHOWUSAGE;
625 if (strcmp(argv[3], "all") == 0) {
627 strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
629 /* Kick a single user */
630 strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
631 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
633 } else if(strcmp(argv[1], "list") == 0) {
634 /* List all the users in a conference */
635 if (AST_LIST_EMPTY(&confs)) {
636 ast_cli(fd, "No active conferences.\n");
637 return RESULT_SUCCESS;
639 /* Find the right conference */
640 AST_LIST_TRAVERSE(&confs, cnf, list) {
641 if (strcmp(cnf->confno, argv[2]) == 0)
645 ast_cli(fd, "No such conference: %s.\n",argv[2]);
646 return RESULT_SUCCESS;
648 /* Show all the users */
649 for (user = cnf->firstuser; user; user = user->nextuser){
651 hr = (now - user->jointime) / 3600;
652 min = ((now - user->jointime) % 3600) / 60;
653 sec = (now - user->jointime) % 60;
656 ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %02d:%02d:%02d\n",
658 user->chan->cid.cid_num ? user->chan->cid.cid_num : "<unknown>",
659 user->chan->cid.cid_name ? user->chan->cid.cid_name : "<no name>",
661 user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
662 user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
663 user->adminflags & ADMINFLAG_MUTED ? "(Admn Muted)" : "",
664 istalking(user->talking), hr, min, sec);
666 ast_cli(fd,"%d users in that conference.\n",cnf->users);
668 return RESULT_SUCCESS;
670 return RESULT_SHOWUSAGE;
671 ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
672 admin_exec(NULL, cmdline);
677 static char *complete_confcmd(const char *line, const char *word, int pos, int state) {
678 #define CONF_COMMANDS 6
679 int which = 0, x = 0;
680 struct ast_conference *cnf = NULL;
681 struct ast_conf_user *usr = NULL;
684 char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
689 for (x = 0;x < CONF_COMMANDS; x++) {
690 if (!strncasecmp(cmds[x], word, strlen(word))) {
691 if (++which > state) {
692 return strdup(cmds[x]);
696 } else if (pos == 2) {
697 /* Conference Number */
698 AST_LIST_LOCK(&confs);
699 AST_LIST_TRAVERSE(&confs, cnf, list) {
700 if (!strncasecmp(word, cnf->confno, strlen(word))) {
705 AST_LIST_UNLOCK(&confs);
706 return cnf ? strdup(cnf->confno) : NULL;
707 } else if (pos == 3) {
708 /* User Number || Conf Command option*/
709 if (strstr(line, "mute") || strstr(line, "kick")) {
710 if ((state == 0) && (strstr(line, "kick") || strstr(line,"mute")) && !(strncasecmp(word, "all", strlen(word)))) {
711 return strdup("all");
714 AST_LIST_LOCK(&confs);
716 /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
717 myline = ast_strdupa(line);
718 if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
719 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
723 AST_LIST_TRAVERSE(&confs, cnf, list) {
724 if (!strcmp(confno, cnf->confno))
729 /* Search for the user */
730 for (usr = cnf->firstuser; usr; usr = usr->nextuser) {
731 snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
732 if (!strncasecmp(word, usrno, strlen(word))) {
738 AST_LIST_UNLOCK(&confs);
739 return usr ? strdup(usrno) : NULL;
746 static char conf_usage[] =
747 "Usage: meetme (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
748 " Executes a command for the conference or on a conferee\n";
750 static struct ast_cli_entry cli_conf = {
751 {"meetme", NULL, NULL }, conf_cmd,
752 "Execute a command on a conference or conferee", conf_usage, complete_confcmd};
754 static void conf_flush(int fd, struct ast_channel *chan)
758 /* read any frames that may be waiting on the channel
764 /* when no frames are available, this will wait
765 for 1 millisecond maximum
767 while (ast_waitfor(chan, 1)) {
774 /* flush any data sitting in the pseudo channel */
776 if (ioctl(fd, ZT_FLUSH, &x))
777 ast_log(LOG_WARNING, "Error flushing channel\n");
781 /* Remove the conference from the list and free it.
782 We assume that this was called while holding conflock. */
783 static int conf_free(struct ast_conference *conf)
787 AST_LIST_REMOVE(&confs, conf, list);
789 if (conf->recording == MEETME_RECORD_ACTIVE) {
790 conf->recording = MEETME_RECORD_TERMINATE;
791 AST_LIST_UNLOCK(&confs);
793 AST_LIST_LOCK(&confs);
794 if (conf->recording == MEETME_RECORD_OFF)
796 AST_LIST_UNLOCK(&confs);
800 for (x=0;x<AST_FRAME_BITS;x++) {
801 if (conf->transframe[x])
802 ast_frfree(conf->transframe[x]);
803 if (conf->transpath[x])
804 ast_translator_free_path(conf->transpath[x]);
807 ast_frfree(conf->origframe);
809 ast_hangup(conf->lchan);
811 ast_hangup(conf->chan);
820 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
822 struct ast_conf_user *user = NULL;
823 struct ast_conf_user *usr = NULL;
825 struct zt_confinfo ztc, ztc_empty;
827 struct ast_channel *c;
839 int currentmarked = 0;
843 int using_pseudo = 0;
847 struct ast_dsp *dsp=NULL;
850 const char *agifiledefault = "conf-background.agi";
851 char meetmesecs[30] = "";
852 char exitcontext[AST_MAX_CONTEXT] = "";
853 char recordingtmp[AST_MAX_EXTENSION] = "";
856 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
857 char *buf = __buf + AST_FRIENDLY_OFFSET;
859 if (!(user = ast_calloc(1, sizeof(*user)))) {
863 if (confflags & CONFFLAG_RECORDCONF) {
864 if (!conf->recordingfilename) {
865 conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
866 if (!conf->recordingfilename) {
867 snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
868 conf->recordingfilename = ast_strdupa(recordingtmp);
870 conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
871 if (!conf->recordingformat) {
872 snprintf(recordingtmp, sizeof(recordingtmp), "wav");
873 conf->recordingformat = ast_strdupa(recordingtmp);
875 ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n",
876 conf->confno, conf->recordingfilename, conf->recordingformat);
880 if ((conf->recording == MEETME_RECORD_OFF) && ((confflags & CONFFLAG_RECORDCONF) || (conf->lchan))) {
881 pthread_attr_init(&conf->attr);
882 pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
883 ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf);
886 time(&user->jointime);
888 if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) {
889 /* Sorry, but this confernce is locked! */
890 if (!ast_streamfile(chan, "conf-locked", chan->language))
891 ast_waitstream(chan, "");
895 if (confflags & CONFFLAG_MARKEDUSER)
898 ast_mutex_lock(&conf->playlock);
899 if (!conf->firstuser) {
900 /* Fill the first new User struct */
902 conf->firstuser = user;
903 conf->lastuser = user;
905 /* Fill the new user struct */
906 user->user_no = conf->lastuser->user_no + 1;
907 user->prevuser = conf->lastuser;
908 if (conf->lastuser->nextuser) {
909 ast_log(LOG_WARNING, "Error in User Management!\n");
910 ast_mutex_unlock(&conf->playlock);
913 conf->lastuser->nextuser = user;
914 conf->lastuser = user;
919 user->userflags = confflags;
920 user->adminflags = 0;
923 ast_mutex_unlock(&conf->playlock);
925 if (confflags & CONFFLAG_EXIT_CONTEXT) {
926 if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
927 ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
928 else if (!ast_strlen_zero(chan->macrocontext))
929 ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
931 ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
934 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
935 snprintf(user->namerecloc, sizeof(user->namerecloc),
936 "%s/meetme/meetme-username-%s-%d", ast_config_AST_SPOOL_DIR,
937 conf->confno, user->user_no);
938 ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
941 if (!(confflags & CONFFLAG_QUIET)) {
942 if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED))
943 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
944 ast_waitstream(chan, "");
945 if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0)
946 if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
947 ast_waitstream(chan, "");
950 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
953 if (conf->users == 2) {
954 if (!ast_streamfile(chan,"conf-onlyone",chan->language)) {
955 res = ast_waitstream(chan, AST_DIGIT_ANY);
962 if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
963 res = ast_waitstream(chan, AST_DIGIT_ANY);
970 res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
976 if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
977 res = ast_waitstream(chan, AST_DIGIT_ANY);
986 ast_indicate(chan, -1);
988 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
989 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
993 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
994 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
998 retryzap = strcasecmp(chan->tech->type, "Zap");
999 user->zapchannel = !retryzap;
1002 origfd = chan->fds[0];
1004 fd = open("/dev/zap/pseudo", O_RDWR);
1006 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
1010 /* Make non-blocking */
1011 flags = fcntl(fd, F_GETFL);
1013 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
1017 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
1018 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
1022 /* Setup buffering information */
1023 memset(&bi, 0, sizeof(bi));
1024 bi.bufsize = CONF_SIZE/2;
1025 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
1026 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
1027 bi.numbufs = audio_buffers;
1028 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
1029 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
1034 if (ioctl(fd, ZT_SETLINEAR, &x)) {
1035 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
1041 /* XXX Make sure we're not running on a pseudo channel XXX */
1045 memset(&ztc, 0, sizeof(ztc));
1046 memset(&ztc_empty, 0, sizeof(ztc_empty));
1047 /* Check to see if we're in a conference... */
1049 if (ioctl(fd, ZT_GETCONF, &ztc)) {
1050 ast_log(LOG_WARNING, "Error getting conference\n");
1055 /* Whoa, already in a conference... Retry... */
1057 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
1062 memset(&ztc, 0, sizeof(ztc));
1063 /* Add us to the conference */
1065 ztc.confno = conf->zapconf;
1067 ast_mutex_lock(&conf->playlock);
1069 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
1070 if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) {
1071 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1072 ast_waitstream(conf->chan, "");
1073 if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language))
1074 ast_waitstream(conf->chan, "");
1078 if (confflags & CONFFLAG_MONITOR)
1079 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1080 else if (confflags & CONFFLAG_TALKER)
1081 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1083 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1085 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1086 ast_log(LOG_WARNING, "Error setting conference\n");
1088 ast_mutex_unlock(&conf->playlock);
1091 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
1093 manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
1098 chan->name, chan->uniqueid, conf->confno, user->user_no);
1100 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
1102 if (!(confflags & CONFFLAG_QUIET))
1103 if (!(confflags & CONFFLAG_WAITMARKED) || (conf->markedusers >= 1))
1104 conf_play(chan, conf, ENTER);
1107 ast_mutex_unlock(&conf->playlock);
1109 conf_flush(fd, chan);
1111 if (confflags & CONFFLAG_AGI) {
1112 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
1113 or use default filename of conf-background.agi */
1115 agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
1117 agifile = agifiledefault;
1119 if (user->zapchannel) {
1120 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
1122 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1124 /* Find a pointer to the agi app and execute the script */
1125 app = pbx_findapp("agi");
1127 char *s = ast_strdupa(agifile);
1128 ret = pbx_exec(chan, app, s, 1);
1130 ast_log(LOG_WARNING, "Could not find application (agi)\n");
1133 if (user->zapchannel) {
1134 /* Remove CONFMUTE mode on Zap channel */
1136 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1139 if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
1140 /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
1142 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1144 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) {
1145 ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
1149 int menu_was_active = 0;
1154 /* if we have just exited from the menu, and the user had a channel-driver
1155 volume adjustment, restore it
1157 if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual)
1158 set_talk_volume(user, user->listen.desired);
1160 menu_was_active = menu_active;
1162 currentmarked = conf->markedusers;
1163 if (!(confflags & CONFFLAG_QUIET) &&
1164 (confflags & CONFFLAG_MARKEDUSER) &&
1165 (confflags & CONFFLAG_WAITMARKED) &&
1167 if (currentmarked == 1 && conf->users > 1) {
1168 ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
1169 if (conf->users - 1 == 1) {
1170 if (!ast_streamfile(chan, "conf-userwilljoin", chan->language))
1171 ast_waitstream(chan, "");
1173 if (!ast_streamfile(chan, "conf-userswilljoin", chan->language))
1174 ast_waitstream(chan, "");
1177 if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER))
1178 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
1179 ast_waitstream(chan, "");
1182 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
1185 /* Update the struct with the actual confflags */
1186 user->userflags = confflags;
1188 if (confflags & CONFFLAG_WAITMARKED) {
1189 if(currentmarked == 0) {
1190 if (lastmarked != 0) {
1191 if (!(confflags & CONFFLAG_QUIET))
1192 if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language))
1193 ast_waitstream(chan, "");
1194 if(confflags & CONFFLAG_MARKEDEXIT)
1197 ztc.confmode = ZT_CONF_CONF;
1198 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1199 ast_log(LOG_WARNING, "Error setting conference\n");
1205 if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) {
1206 ast_moh_start(chan, NULL);
1209 ztc.confmode = ZT_CONF_CONF;
1210 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1211 ast_log(LOG_WARNING, "Error setting conference\n");
1216 } else if(currentmarked >= 1 && lastmarked == 0) {
1217 if (confflags & CONFFLAG_MONITOR)
1218 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1219 else if (confflags & CONFFLAG_TALKER)
1220 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1222 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1223 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1224 ast_log(LOG_WARNING, "Error setting conference\n");
1228 if (musiconhold && (confflags & CONFFLAG_MOH)) {
1232 if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) {
1233 if (!ast_streamfile(chan, "conf-placeintoconf", chan->language))
1234 ast_waitstream(chan, "");
1235 conf_play(chan, conf, ENTER);
1240 /* trying to add moh for single person conf */
1241 if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) {
1242 if (conf->users == 1) {
1243 if (musiconhold == 0) {
1244 ast_moh_start(chan, NULL);
1255 /* Leave if the last marked user left */
1256 if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) {
1261 /* Check if the admin changed my modes */
1262 if (user->adminflags) {
1263 /* Set the new modes */
1264 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
1265 ztc.confmode ^= ZT_CONF_TALKER;
1266 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1267 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1272 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1273 ztc.confmode |= ZT_CONF_TALKER;
1274 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1275 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1280 if (user->adminflags & ADMINFLAG_KICKME) {
1281 /* You have been kicked. */
1282 if (!ast_streamfile(chan, "conf-kicked", chan->language))
1283 ast_waitstream(chan, "");
1287 } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1288 ztc.confmode |= ZT_CONF_TALKER;
1289 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1290 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1297 if (c->fds[0] != origfd) {
1299 /* Kill old pseudo */
1303 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
1304 retryzap = strcasecmp(c->tech->type, "Zap");
1305 user->zapchannel = !retryzap;
1308 if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & ADMINFLAG_MUTED))
1309 f = ast_read_noaudio(c);
1314 if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
1315 if (user->talk.actual)
1316 ast_frame_adjust_volume(f, user->talk.actual);
1318 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) {
1321 if (user->talking == -1)
1324 res = ast_dsp_silence(dsp, f, &totalsilence);
1325 if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) {
1327 if (confflags & CONFFLAG_MONITORTALKER)
1328 manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
1333 chan->name, chan->uniqueid, conf->confno, user->user_no);
1335 if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) {
1337 if (confflags & CONFFLAG_MONITORTALKER)
1338 manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking",
1343 chan->name, chan->uniqueid, conf->confno, user->user_no);
1347 /* Absolutely do _not_ use careful_write here...
1348 it is important that we read data from the channel
1349 as fast as it arrives, and feed it into the conference.
1350 The buffering in the pseudo channel will take care of any
1351 timing differences, unless they are so drastic as to lose
1352 audio frames (in which case carefully writing would only
1353 have delayed the audio even further).
1355 /* As it turns out, we do want to use careful write. We just
1356 don't want to block, but we do want to at least *try*
1357 to write out all the samples.
1359 if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER))
1360 careful_write(fd, f->data, f->datalen, 0);
1362 } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
1365 tmp[0] = f->subclass;
1367 if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
1368 ast_log(LOG_DEBUG, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
1371 } else if (option_debug > 1)
1372 ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension %s does not exist in context %s\n", tmp, exitcontext);
1373 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
1376 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
1377 if (ioctl(fd, ZT_SETCONF, &ztc_empty)) {
1378 ast_log(LOG_WARNING, "Error setting conference\n");
1383 /* if we are entering the menu, and the user has a channel-driver
1384 volume adjustment, clear it
1386 if (!menu_active && user->talk.desired && !user->talk.actual)
1387 set_talk_volume(user, 0);
1392 if ((confflags & CONFFLAG_ADMIN)) {
1396 /* Record this sound! */
1397 if (!ast_streamfile(chan, "conf-adminmenu", chan->language)) {
1398 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1399 ast_stopstream(chan);
1406 case '1': /* Un/Mute */
1408 if (ztc.confmode & ZT_CONF_TALKER) {
1409 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
1410 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
1412 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1413 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
1415 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1416 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1420 if (ztc.confmode & ZT_CONF_TALKER) {
1421 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1422 ast_waitstream(chan, "");
1424 if (!ast_streamfile(chan, "conf-muted", chan->language))
1425 ast_waitstream(chan, "");
1428 case '2': /* Un/Lock the Conference */
1432 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
1433 ast_waitstream(chan, "");
1436 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
1437 ast_waitstream(chan, "");
1440 case '3': /* Eject last user */
1442 usr = conf->lastuser;
1443 if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
1444 if(!ast_streamfile(chan, "conf-errormenu", chan->language))
1445 ast_waitstream(chan, "");
1447 usr->adminflags |= ADMINFLAG_KICKME;
1448 ast_stopstream(chan);
1451 tweak_listen_volume(user, VOL_DOWN);
1454 tweak_listen_volume(user, VOL_UP);
1457 tweak_talk_volume(user, VOL_DOWN);
1463 tweak_talk_volume(user, VOL_UP);
1467 /* Play an error message! */
1468 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1469 ast_waitstream(chan, "");
1477 if (!ast_streamfile(chan, "conf-usermenu", chan->language)) {
1478 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1479 ast_stopstream(chan);
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;
1612 /* Take out of conference */
1616 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1617 ast_log(LOG_WARNING, "Error setting conference\n");
1621 reset_volumes(user);
1623 AST_LIST_LOCK(&confs);
1624 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
1625 conf_play(chan, conf, LEAVE);
1627 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
1628 if (ast_fileexists(user->namerecloc, NULL, NULL)) {
1629 if ((conf->chan) && (conf->users > 1)) {
1630 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1631 ast_waitstream(conf->chan, "");
1632 if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language))
1633 ast_waitstream(conf->chan, "");
1635 ast_filedelete(user->namerecloc, NULL);
1638 AST_LIST_UNLOCK(&confs);
1641 AST_LIST_LOCK(&confs);
1646 if (user->user_no) { /* Only cleanup users who really joined! */
1648 hr = (now - user->jointime) / 3600;
1649 min = ((now - user->jointime) % 3600) / 60;
1650 sec = (now - user->jointime) % 60;
1652 manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
1659 "Duration: %02d:%02d:%02d\r\n",
1660 chan->name, chan->uniqueid, conf->confno,
1662 user->chan->cid.cid_num ? user->chan->cid.cid_num :
1664 user->chan->cid.cid_name ? user->chan->cid.cid_name :
1665 "<no name>", hr, min, sec);
1668 if (confflags & CONFFLAG_MARKEDUSER)
1669 conf->markedusers--;
1671 /* No more users -- close this one out */
1674 /* Remove the user struct */
1675 if (user == conf->firstuser) {
1676 if (user->nextuser) {
1677 /* There is another entry */
1678 user->nextuser->prevuser = NULL;
1680 /* We are the only entry */
1681 conf->lastuser = NULL;
1683 /* In either case */
1684 conf->firstuser = user->nextuser;
1685 } else if (user == conf->lastuser){
1687 user->prevuser->nextuser = NULL;
1689 ast_log(LOG_ERROR, "Bad bad bad! We're the last, not the first, but nobody before us??\n");
1690 conf->lastuser = user->prevuser;
1693 user->nextuser->prevuser = user->prevuser;
1695 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1697 user->prevuser->nextuser = user->nextuser;
1699 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1702 /* Return the number of seconds the user was in the conf */
1703 snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
1704 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1707 AST_LIST_UNLOCK(&confs);
1712 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin)
1714 struct ast_config *cfg;
1715 struct ast_variable *var;
1716 struct ast_conference *cnf;
1718 AST_DECLARE_APP_ARGS(args,
1719 AST_APP_ARG(confno);
1721 AST_APP_ARG(pinadmin);
1724 /* Check first in the conference list */
1725 AST_LIST_LOCK(&confs);
1726 AST_LIST_TRAVERSE(&confs, cnf, list) {
1727 if (!strcmp(confno, cnf->confno))
1730 AST_LIST_UNLOCK(&confs);
1734 /* No need to parse meetme.conf */
1735 ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1737 if (dynamic_pin[0] == 'q') {
1738 /* Query the user to enter a PIN */
1739 if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0) < 0)
1742 cnf = build_conf(confno, dynamic_pin, "", make, dynamic);
1744 cnf = build_conf(confno, "", "", make, dynamic);
1747 /* Check the config */
1748 cfg = ast_config_load(CONFIG_FILE_NAME);
1750 ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME);
1753 var = ast_variable_browse(cfg, "rooms");
1754 for (; var; var = var->next) {
1755 if (strcasecmp(var->name, "conf"))
1758 if (!(parse = ast_strdupa(var->value)))
1761 AST_STANDARD_APP_ARGS(args, parse);
1762 if (!strcasecmp(args.confno, confno)) {
1763 /* Bingo it's a valid conference */
1766 cnf = build_conf(args.confno, args.pin, args.pinadmin, make, dynamic);
1768 cnf = build_conf(args.confno, args.pin, "", make, dynamic);
1771 cnf = build_conf(args.confno, "", args.pinadmin, make, dynamic);
1773 cnf = build_conf(args.confno, "", "", make, dynamic);
1779 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1781 ast_config_destroy(cfg);
1783 } else if (dynamic_pin) {
1784 /* Correct for the user selecting 'D' instead of 'd' to have
1785 someone join into a conference that has already been created
1787 if (dynamic_pin[0] == 'q')
1788 dynamic_pin[0] = '\0';
1794 /*! \brief The MeetmeCount application */
1795 static int count_exec(struct ast_channel *chan, void *data)
1797 struct localuser *u;
1799 struct ast_conference *conf;
1803 AST_DECLARE_APP_ARGS(args,
1804 AST_APP_ARG(confno);
1805 AST_APP_ARG(varname);
1808 if (ast_strlen_zero(data)) {
1809 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1815 if (!(localdata = ast_strdupa(data))) {
1816 LOCAL_USER_REMOVE(u);
1820 AST_STANDARD_APP_ARGS(args, localdata);
1822 conf = find_conf(chan, args.confno, 0, 0, NULL);
1824 count = conf->users;
1828 if (!ast_strlen_zero(args.varname)){
1829 /* have var so load it and exit */
1830 snprintf(val, sizeof(val), "%d",count);
1831 pbx_builtin_setvar_helper(chan, args.varname, val);
1833 if (chan->_state != AST_STATE_UP)
1835 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1837 LOCAL_USER_REMOVE(u);
1842 /*! \brief The meetme() application */
1843 static int conf_exec(struct ast_channel *chan, void *data)
1846 struct localuser *u;
1847 char confno[AST_MAX_EXTENSION] = "";
1850 struct ast_conference *cnf;
1851 struct ast_flags confflags = {0};
1853 int empty = 0, empty_no_pin = 0;
1854 int always_prompt = 0;
1855 char *notdata, *info, the_pin[AST_MAX_EXTENSION] = "";
1856 AST_DECLARE_APP_ARGS(args,
1857 AST_APP_ARG(confno);
1858 AST_APP_ARG(options);
1864 if (ast_strlen_zero(data)) {
1871 if (chan->_state != AST_STATE_UP)
1874 info = ast_strdupa(notdata);
1876 AST_STANDARD_APP_ARGS(args, info);
1879 ast_copy_string(confno, args.confno, sizeof(confno));
1880 if (ast_strlen_zero(confno)) {
1886 ast_copy_string(the_pin, args.pin, sizeof(the_pin));
1889 ast_app_parse_options(meetme_opts, &confflags, NULL, args.options);
1890 dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN);
1891 if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && !args.pin)
1892 strcpy(the_pin, "q");
1894 empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN);
1895 empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN);
1896 always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT);
1903 int i, map[1024] = { 0, };
1904 struct ast_config *cfg;
1905 struct ast_variable *var;
1908 AST_LIST_LOCK(&confs);
1909 AST_LIST_TRAVERSE(&confs, cnf, list) {
1910 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1911 /* Disqualify in use conference */
1912 if (confno_int >= 0 && confno_int < 1024)
1916 AST_LIST_UNLOCK(&confs);
1918 /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1919 if ((empty_no_pin) || (!dynamic)) {
1920 cfg = ast_config_load(CONFIG_FILE_NAME);
1922 var = ast_variable_browse(cfg, "rooms");
1924 if (!strcasecmp(var->name, "conf")) {
1925 char *stringp = ast_strdupa(var->value);
1927 char *confno_tmp = strsep(&stringp, "|,");
1929 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
1930 if ((confno_int >= 0) && (confno_int < 1024)) {
1931 if (stringp && empty_no_pin) {
1937 /* For static: run through the list and see if this conference is empty */
1938 AST_LIST_LOCK(&confs);
1939 AST_LIST_TRAVERSE(&confs, cnf, list) {
1940 if (!strcmp(confno_tmp, cnf->confno)) {
1941 /* The conference exists, therefore it's not empty */
1946 AST_LIST_UNLOCK(&confs);
1948 /* At this point, we have a confno_tmp (static conference) that is empty */
1949 if ((empty_no_pin && ((!stringp) || (stringp && (stringp[0] == '\0')))) || (!empty_no_pin)) {
1950 /* Case 1: empty_no_pin and pin is nonexistent (NULL)
1951 * Case 2: empty_no_pin and pin is blank (but not NULL)
1952 * Case 3: not empty_no_pin
1954 ast_copy_string(confno, confno_tmp, sizeof(confno));
1956 /* XXX the map is not complete (but we do have a confno) */
1964 ast_config_destroy(cfg);
1968 /* Select first conference number not in use */
1969 if (ast_strlen_zero(confno) && dynamic) {
1970 for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
1972 snprintf(confno, sizeof(confno), "%d", i);
1979 if (ast_strlen_zero(confno)) {
1980 res = ast_streamfile(chan, "conf-noempty", chan->language);
1982 ast_waitstream(chan, "");
1984 if (sscanf(confno, "%d", &confno_int) == 1) {
1985 res = ast_streamfile(chan, "conf-enteringno", chan->language);
1987 ast_waitstream(chan, "");
1988 res = ast_say_digits(chan, confno_int, "", chan->language);
1991 ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
1996 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
1997 /* Prompt user for conference number */
1998 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
2000 /* Don't try to validate when we catch an error */
2006 if (!ast_strlen_zero(confno)) {
2007 /* Check the validity of the conference */
2008 cnf = find_conf(chan, confno, 1, dynamic, the_pin);
2010 res = ast_streamfile(chan, "conf-invalid", chan->language);
2012 ast_waitstream(chan, "");
2017 if ((!ast_strlen_zero(cnf->pin) &&
2018 !ast_test_flag(&confflags, CONFFLAG_ADMIN)) ||
2019 (!ast_strlen_zero(cnf->pinadmin) &&
2020 ast_test_flag(&confflags, CONFFLAG_ADMIN))) {
2021 char pin[AST_MAX_EXTENSION]="";
2024 /* Allow the pin to be retried up to 3 times */
2025 for (j = 0; j < 3; j++) {
2026 if (*the_pin && (always_prompt == 0)) {
2027 ast_copy_string(pin, the_pin, sizeof(pin));
2030 /* Prompt user for pin if pin is required */
2031 res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
2034 if (!strcasecmp(pin, cnf->pin) ||
2035 (!ast_strlen_zero(cnf->pinadmin) &&
2036 !strcasecmp(pin, cnf->pinadmin))) {
2039 if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin))
2040 ast_set_flag(&confflags, CONFFLAG_ADMIN);
2041 /* Run the conference */
2042 res = conf_run(chan, cnf, confflags.flags);
2046 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
2048 ast_waitstream(chan, AST_DIGIT_ANY);
2058 /* failed when getting the pin */
2061 /* see if we need to get rid of the conference */
2062 AST_LIST_LOCK(&confs);
2066 AST_LIST_UNLOCK(&confs);
2070 /* Don't retry pin with a static pin */
2071 if (*the_pin && (always_prompt==0)) {
2076 /* No pin required */
2079 /* Run the conference */
2080 res = conf_run(chan, cnf, confflags.flags);
2084 } while (allowretry);
2086 LOCAL_USER_REMOVE(u);
2091 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident)
2093 struct ast_conf_user *user = NULL;
2096 sscanf(callerident, "%i", &cid);
2097 if (conf && callerident) {
2098 user = conf->firstuser;
2100 if (cid == user->user_no)
2102 user = user->nextuser;
2108 /*! \brief The MeetMeadmin application */
2109 /* MeetMeAdmin(confno, command, caller) */
2110 static int admin_exec(struct ast_channel *chan, void *data) {
2112 struct ast_conference *cnf;
2113 struct ast_conf_user *user = NULL;
2114 struct localuser *u;
2115 AST_DECLARE_APP_ARGS(args,
2116 AST_APP_ARG(confno);
2117 AST_APP_ARG(command);
2123 AST_LIST_LOCK(&confs);
2124 /* The param has the conference number the user and the command to execute */
2125 if (!ast_strlen_zero(data)) {
2126 params = ast_strdupa((char *) data);
2128 AST_STANDARD_APP_ARGS(args, params);
2130 if (!args.command) {
2131 ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
2132 AST_LIST_UNLOCK(&confs);
2133 LOCAL_USER_REMOVE(u);
2136 AST_LIST_TRAVERSE(&confs, cnf, list) {
2137 if (!strcmp(cnf->confno, args.confno))
2142 user = find_user(cnf, args.user);
2145 switch((int) (*args.command)) {
2146 case 76: /* L: Lock */
2149 case 108: /* l: Unlock */
2152 case 75: /* K: kick all users*/
2153 user = cnf->firstuser;
2155 user->adminflags |= ADMINFLAG_KICKME;
2156 if (user->nextuser) {
2157 user = user->nextuser;
2163 case 101: /* e: Eject last user*/
2164 user = cnf->lastuser;
2165 if (!(user->userflags & CONFFLAG_ADMIN)) {
2166 user->adminflags |= ADMINFLAG_KICKME;
2169 ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
2171 case 77: /* M: Mute */
2173 user->adminflags |= ADMINFLAG_MUTED;
2175 ast_log(LOG_NOTICE, "Specified User not found!\n");
2178 case 78: /* N: Mute all users */
2179 user = cnf->firstuser;
2181 if (user && !(user->userflags & CONFFLAG_ADMIN))
2182 user->adminflags |= ADMINFLAG_MUTED;
2183 if (user->nextuser) {
2184 user = user->nextuser;
2190 case 109: /* m: Unmute */
2191 if (user && (user->adminflags & ADMINFLAG_MUTED)) {
2192 user->adminflags ^= ADMINFLAG_MUTED;
2194 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!\n");
2197 case 110: /* n: Unmute all users */
2198 user = cnf->firstuser;
2200 if (user && (user-> adminflags & ADMINFLAG_MUTED)) {
2201 user->adminflags ^= ADMINFLAG_MUTED;
2203 if (user->nextuser) {
2204 user = user->nextuser;
2210 case 107: /* k: Kick user */
2212 user->adminflags |= ADMINFLAG_KICKME;
2214 ast_log(LOG_NOTICE, "Specified User not found!");
2219 ast_log(LOG_NOTICE, "Conference Number not found\n");
2222 AST_LIST_UNLOCK(&confs);
2224 LOCAL_USER_REMOVE(u);
2229 static void *recordthread(void *args)
2231 struct ast_conference *cnf = args;
2232 struct ast_frame *f=NULL;
2234 struct ast_filestream *s=NULL;
2237 const char *oldrecordingfilename = NULL;
2239 if (!cnf || !cnf->lchan) {
2243 ast_stopstream(cnf->lchan);
2244 flags = O_CREAT|O_TRUNC|O_WRONLY;
2247 cnf->recording = MEETME_RECORD_ACTIVE;
2248 while (ast_waitfor(cnf->lchan, -1) > -1) {
2249 if (cnf->recording == MEETME_RECORD_TERMINATE) {
2250 AST_LIST_LOCK(&confs);
2251 AST_LIST_UNLOCK(&confs);
2254 if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) {
2255 s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
2256 oldrecordingfilename = cnf->recordingfilename;
2259 f = ast_read(cnf->lchan);
2264 if (f->frametype == AST_FRAME_VOICE) {
2265 ast_mutex_lock(&cnf->listenlock);
2266 for (x=0;x<AST_FRAME_BITS;x++) {
2267 /* Free any translations that have occured */
2268 if (cnf->transframe[x]) {
2269 ast_frfree(cnf->transframe[x]);
2270 cnf->transframe[x] = NULL;
2274 ast_frfree(cnf->origframe);
2276 ast_mutex_unlock(&cnf->listenlock);
2278 res = ast_writestream(s, f);
2286 cnf->recording = MEETME_RECORD_OFF;
2293 static void load_config(void)
2295 struct ast_config *cfg;
2298 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2300 if (!(cfg = ast_config_load(CONFIG_FILE_NAME)))
2303 if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
2304 if ((sscanf(val, "%d", &audio_buffers) != 1)) {
2305 ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val);
2306 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2307 } else if ((audio_buffers < ZT_DEFAULT_NUM_BUFS) || (audio_buffers > ZT_MAX_NUM_BUFS)) {
2308 ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n",
2309 ZT_DEFAULT_NUM_BUFS, ZT_MAX_NUM_BUFS);
2310 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2312 if (audio_buffers != DEFAULT_AUDIO_BUFFERS)
2313 ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers);
2316 ast_config_destroy(cfg);
2319 int unload_module(void)
2323 res = ast_cli_unregister(&cli_show_confs);
2324 res |= ast_cli_unregister(&cli_conf);
2325 res |= ast_unregister_application(app3);
2326 res |= ast_unregister_application(app2);
2327 res |= ast_unregister_application(app);
2329 STANDARD_HANGUP_LOCALUSERS;
2334 int load_module(void)
2340 res = ast_cli_register(&cli_show_confs);
2341 res |= ast_cli_register(&cli_conf);
2342 res |= ast_register_application(app3, admin_exec, synopsis3, descrip3);
2343 res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
2344 res |= ast_register_application(app, conf_exec, synopsis, descrip);
2356 char *description(void)
2358 return (char *) tdesc;
2365 STANDARD_USECOUNT(res);
2372 return ASTERISK_GPL_KEY;