3 * Asterisk -- An open source telephony toolkit.
5 * Copyright (C) 1999 - 2005, Digium, Inc.
7 * Mark Spencer <markster@digium.com>
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
22 * \brief Meet me conference bridge
24 * \author Mark Spencer <markster@digium.com>
26 * \ingroup applications
34 #include <sys/ioctl.h>
36 #include <linux/zaptel.h>
39 #endif /* __linux__ */
43 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
45 #include "asterisk/lock.h"
46 #include "asterisk/file.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/channel.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/module.h"
51 #include "asterisk/config.h"
52 #include "asterisk/app.h"
53 #include "asterisk/dsp.h"
54 #include "asterisk/musiconhold.h"
55 #include "asterisk/manager.h"
56 #include "asterisk/options.h"
57 #include "asterisk/cli.h"
58 #include "asterisk/say.h"
59 #include "asterisk/utils.h"
60 #include "asterisk/translate.h"
61 #include "asterisk/ulaw.h"
63 static const char *tdesc = "MeetMe conference bridge";
65 static const char *app = "MeetMe";
66 static const char *app2 = "MeetMeCount";
67 static const char *app3 = "MeetMeAdmin";
69 static const char *synopsis = "MeetMe conference bridge";
70 static const char *synopsis2 = "MeetMe participant count";
71 static const char *synopsis3 = "MeetMe conference Administration";
73 static const char *descrip =
74 " MeetMe([confno][,[options][,pin]]): Enters the user into a specified MeetMe\n"
75 "conference. If the conference number is omitted, the user will be prompted\n"
76 "to enter one. User can exit the conference by hangup, or if the 'p' option\n"
77 "is specified, by pressing '#'.\n"
78 "Please note: A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING TO WORK!\n\n"
80 "The option string may contain zero or more of the following characters:\n"
81 " 'a' -- set admin mode\n"
82 " 'A' -- set marked mode\n"
83 " 'b' -- run AGI script specified in ${MEETME_AGI_BACKGROUND}\n"
84 " Default: conf-background.agi (Note: This does not work with\n"
85 " non-Zap channels in the same conference)\n"
86 " 'c' -- announce user(s) count on joining a conference\n"
87 " 'd' -- dynamically add conference\n"
88 " 'D' -- dynamically add conference, prompting for a PIN\n"
89 " 'e' -- select an empty conference\n"
90 " 'E' -- select an empty pinless conference\n"
91 " 'i' -- announce user join/leave\n"
92 " 'm' -- set monitor only mode (Listen only, no talking)\n"
93 " 'M' -- enable music on hold when the conference has a single caller\n"
94 " 'o' -- set talker optimization - treats talkers who aren't speaking as\n"
95 " being muted, meaning (a) No encode is done on transmission and\n"
96 " (b) Received audio that is not registered as talking is omitted\n"
97 " causing no buildup in background noise\n"
98 " 'p' -- allow user to exit the conference by pressing '#'\n"
99 " 'P' -- always prompt for the pin even if it is specified\n"
100 " 'q' -- quiet mode (don't play enter/leave sounds)\n"
101 " 'r' -- Record conference (records as ${MEETME_RECORDINGFILE}\n"
102 " using format ${MEETME_RECORDINGFORMAT}). Default filename is\n"
103 " meetme-conf-rec-${CONFNO}-${UNIQUEID} and the default format is\n"
105 " 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n"
106 " 't' -- set talk only mode. (Talk only, no listening)\n"
107 " 'T' -- set talker detection (sent to manager interface and meetme list)\n"
108 " 'v' -- video mode\n"
109 " 'w' -- wait until the marked user enters the conference\n"
110 " 'x' -- close the conference when last marked user exits\n"
111 " 'X' -- allow user to exit the conference by entering a valid single\n"
112 " digit extension ${MEETME_EXIT_CONTEXT} or the current context\n"
113 " if that variable is not defined.\n"
114 " '1' -- do not play message when first person enters\n";
116 static const char *descrip2 =
117 " MeetMeCount(confno[|var]): Plays back the number of users in the specified\n"
118 "MeetMe conference. If var is specified, playback will be skipped and the value\n"
119 "will be returned in the variable. Upon app completion, MeetMeCount will hangup\n"
120 "the channel, unless priority n+1 exists, in which case priority progress will\n"
122 "A ZAPTEL INTERFACE MUST BE INSTALLED FOR CONFERENCING FUNCTIONALITY.\n";
124 static const char *descrip3 =
125 " MeetMeAdmin(confno,command[,user]): Run admin command for conference\n"
126 " 'e' -- Eject last user that joined\n"
127 " 'k' -- Kick one user out of conference\n"
128 " 'K' -- Kick all users out of conference\n"
129 " 'l' -- Unlock conference\n"
130 " 'L' -- Lock conference\n"
131 " 'm' -- Unmute conference\n"
132 " 'M' -- Mute conference\n"
133 " 'n' -- Unmute entire conference (except admin)\n"
134 " 'N' -- Mute entire conference (except admin)\n"
137 #define CONFIG_FILE_NAME "meetme.conf"
141 struct ast_conference {
142 ast_mutex_t playlock; /* Conference specific lock (players) */
143 ast_mutex_t listenlock; /* Conference specific lock (listeners) */
144 char confno[AST_MAX_EXTENSION]; /* Conference */
145 struct ast_channel *chan; /* Announcements channel */
146 struct ast_channel *lchan; /* Listen/Record channel */
147 int fd; /* Announcements fd */
148 int zapconf; /* Zaptel Conf # */
149 int users; /* Number of active users */
150 int markedusers; /* Number of marked users */
151 struct ast_conf_user *firstuser; /* Pointer to the first user struct */
152 struct ast_conf_user *lastuser; /* Pointer to the last user struct */
153 time_t start; /* Start time (s) */
154 int refcount; /* reference count of usage */
155 unsigned int recording:2; /* recording status */
156 unsigned int isdynamic:1; /* Created on the fly? */
157 unsigned int locked:1; /* Is the conference locked? */
158 pthread_t recordthread; /* thread for recording */
159 pthread_attr_t attr; /* thread attribute */
160 const char *recordingfilename; /* Filename to record the Conference into */
161 const char *recordingformat; /* Format to record the Conference in */
162 char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */
163 char pinadmin[AST_MAX_EXTENSION]; /* If protected by a admin PIN */
164 struct ast_frame *transframe[32];
165 struct ast_frame *origframe;
166 struct ast_trans_pvt *transpath[32];
167 AST_LIST_ENTRY(ast_conference) list;
170 static AST_LIST_HEAD_STATIC(confs, ast_conference);
173 int desired; /* Desired volume adjustment */
174 int actual; /* Actual volume adjustment (for channels that can't adjust) */
177 struct ast_conf_user {
178 int user_no; /* User Number */
179 struct ast_conf_user *prevuser; /* Pointer to the previous user */
180 struct ast_conf_user *nextuser; /* Pointer to the next user */
181 int userflags; /* Flags as set in the conference */
182 int adminflags; /* Flags set by the Admin */
183 struct ast_channel *chan; /* Connected channel */
184 int talking; /* Is user talking */
185 int zapchannel; /* Is a Zaptel channel */
186 char usrvalue[50]; /* Custom User Value */
187 char namerecloc[AST_MAX_EXTENSION]; /* Name Recorded file Location */
188 time_t jointime; /* Time the user joined the conference */
190 struct volume listen;
193 static int audio_buffers; /* The number of audio buffers to be allocated on pseudo channels
197 #define DEFAULT_AUDIO_BUFFERS 32 /* each buffer is 20ms, so this is 640ms total */
199 #define ADMINFLAG_MUTED (1 << 1) /* User is muted */
200 #define ADMINFLAG_KICKME (1 << 2) /* User is kicked */
201 #define MEETME_DELAYDETECTTALK 300
202 #define MEETME_DELAYDETECTENDTALK 1000
204 #define AST_FRAME_BITS 32
211 static int admin_exec(struct ast_channel *chan, void *data);
213 static void *recordthread(void *args);
221 #define MEETME_RECORD_OFF 0
222 #define MEETME_RECORD_STARTED 1
223 #define MEETME_RECORD_ACTIVE 2
224 #define MEETME_RECORD_TERMINATE 3
226 #define CONF_SIZE 320
228 #define CONFFLAG_ADMIN (1 << 1) /* If set the user has admin access on the conference */
229 #define CONFFLAG_MONITOR (1 << 2) /* If set the user can only receive audio from the conference */
230 #define CONFFLAG_POUNDEXIT (1 << 3) /* If set asterisk will exit conference when '#' is pressed */
231 #define CONFFLAG_STARMENU (1 << 4) /* If set asterisk will provide a menu to the user when '*' is pressed */
232 #define CONFFLAG_TALKER (1 << 5) /* If set the use can only send audio to the conference */
233 #define CONFFLAG_QUIET (1 << 6) /* If set there will be no enter or leave sounds */
234 #define CONFFLAG_VIDEO (1 << 7) /* Set to enable video mode */
235 #define CONFFLAG_AGI (1 << 8) /* Set to run AGI Script in Background */
236 #define CONFFLAG_MOH (1 << 9) /* Set to have music on hold when user is alone in conference */
237 #define CONFFLAG_MARKEDEXIT (1 << 10) /* If set the MeetMe will return if all marked with this flag left */
238 #define CONFFLAG_WAITMARKED (1 << 11) /* If set, the MeetMe will wait until a marked user enters */
239 #define CONFFLAG_EXIT_CONTEXT (1 << 12) /* If set, the MeetMe will exit to the specified context */
240 #define CONFFLAG_MARKEDUSER (1 << 13) /* If set, the user will be marked */
241 #define CONFFLAG_INTROUSER (1 << 14) /* If set, user will be ask record name on entry of conference */
242 #define CONFFLAG_RECORDCONF (1<< 15) /* If set, the MeetMe will be recorded */
243 #define CONFFLAG_MONITORTALKER (1 << 16) /* If set, the user will be monitored if the user is talking or not */
244 #define CONFFLAG_DYNAMIC (1 << 17)
245 #define CONFFLAG_DYNAMICPIN (1 << 18)
246 #define CONFFLAG_EMPTY (1 << 19)
247 #define CONFFLAG_EMPTYNOPIN (1 << 20)
248 #define CONFFLAG_ALWAYSPROMPT (1 << 21)
249 #define CONFFLAG_ANNOUNCEUSERCOUNT (1 << 22) /* If set, when user joins the conference, they will be told the number of users that are already in */
250 #define CONFFLAG_OPTIMIZETALKER (1 << 23) /* If set, treats talking users as muted users */
251 #define CONFFLAG_NOONLYPERSON (1 << 24) /* If set, won't speak the extra prompt when the first person enters the conference */
254 AST_APP_OPTIONS(meetme_opts, {
255 AST_APP_OPTION('A', CONFFLAG_MARKEDUSER ),
256 AST_APP_OPTION('a', CONFFLAG_ADMIN ),
257 AST_APP_OPTION('b', CONFFLAG_AGI ),
258 AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ),
259 AST_APP_OPTION('D', CONFFLAG_DYNAMICPIN ),
260 AST_APP_OPTION('d', CONFFLAG_DYNAMIC ),
261 AST_APP_OPTION('E', CONFFLAG_EMPTYNOPIN ),
262 AST_APP_OPTION('e', CONFFLAG_EMPTY ),
263 AST_APP_OPTION('i', CONFFLAG_INTROUSER ),
264 AST_APP_OPTION('M', CONFFLAG_MOH ),
265 AST_APP_OPTION('m', CONFFLAG_MONITOR ),
266 AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ),
267 AST_APP_OPTION('P', CONFFLAG_ALWAYSPROMPT ),
268 AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ),
269 AST_APP_OPTION('q', CONFFLAG_QUIET ),
270 AST_APP_OPTION('r', CONFFLAG_RECORDCONF ),
271 AST_APP_OPTION('s', CONFFLAG_STARMENU ),
272 AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ),
273 AST_APP_OPTION('t', CONFFLAG_TALKER ),
274 AST_APP_OPTION('w', CONFFLAG_WAITMARKED ),
275 AST_APP_OPTION('X', CONFFLAG_EXIT_CONTEXT ),
276 AST_APP_OPTION('x', CONFFLAG_MARKEDEXIT ),
277 AST_APP_OPTION('1', CONFFLAG_NOONLYPERSON ),
280 static char *istalking(int x)
285 return "(unmonitored)";
287 return "(not talking)";
290 static int careful_write(int fd, unsigned char *data, int len, int block)
297 x = ZT_IOMUX_WRITE | ZT_IOMUX_SIGEVENT;
298 res = ioctl(fd, ZT_IOMUX, &x);
302 res = write(fd, data, len);
304 if (errno != EAGAIN) {
305 ast_log(LOG_WARNING, "Failed to write audio data to conference: %s\n", strerror(errno));
317 /* Map 'volume' levels from -5 through +5 into
318 decibel (dB) settings for channel drivers
319 Note: these are not a straight linear-to-dB
320 conversion... the numbers have been modified
321 to give the user a better level of adjustability
323 static signed char gain_map[] = {
337 static int set_talk_volume(struct ast_conf_user *user, int volume)
339 signed char gain_adjust;
341 /* attempt to make the adjustment in the channel driver;
342 if successful, don't adjust in the frame reading routine
344 gain_adjust = gain_map[volume + 5];
346 return ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
349 static int set_listen_volume(struct ast_conf_user *user, int volume)
351 signed char gain_adjust;
353 /* attempt to make the adjustment in the channel driver;
354 if successful, don't adjust in the frame reading routine
356 gain_adjust = gain_map[volume + 5];
358 return ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &gain_adjust, sizeof(gain_adjust), 0);
361 static void tweak_volume(struct volume *vol, enum volume_action action)
365 switch (vol->desired) {
380 switch (vol->desired) {
396 static void tweak_talk_volume(struct ast_conf_user *user, enum volume_action action)
398 tweak_volume(&user->talk, action);
399 /* attempt to make the adjustment in the channel driver;
400 if successful, don't adjust in the frame reading routine
402 if (!set_talk_volume(user, user->talk.desired))
403 user->talk.actual = 0;
405 user->talk.actual = user->talk.desired;
408 static void tweak_listen_volume(struct ast_conf_user *user, enum volume_action action)
410 tweak_volume(&user->listen, action);
411 /* attempt to make the adjustment in the channel driver;
412 if successful, don't adjust in the frame reading routine
414 if (!set_listen_volume(user, user->listen.desired))
415 user->listen.actual = 0;
417 user->listen.actual = user->listen.desired;
420 static void reset_volumes(struct ast_conf_user *user)
422 signed char zero_volume = 0;
424 ast_channel_setoption(user->chan, AST_OPTION_TXGAIN, &zero_volume, sizeof(zero_volume), 0);
425 ast_channel_setoption(user->chan, AST_OPTION_RXGAIN, &zero_volume, sizeof(zero_volume), 0);
428 static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int sound)
434 if (!chan->_softhangup)
435 res = ast_autoservice_start(chan);
437 AST_LIST_LOCK(&confs);
453 careful_write(conf->fd, data, len, 1);
456 AST_LIST_UNLOCK(&confs);
459 ast_autoservice_stop(chan);
462 static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin, int make, int dynamic, int refcount)
464 struct ast_conference *cnf;
465 struct zt_confinfo ztc;
467 AST_LIST_LOCK(&confs);
469 AST_LIST_TRAVERSE(&confs, cnf, list) {
470 if (!strcmp(confno, cnf->confno))
474 if (!cnf && (make || dynamic)) {
476 if ((cnf = ast_calloc(1, sizeof(*cnf)))) {
477 ast_mutex_init(&cnf->playlock);
478 ast_mutex_init(&cnf->listenlock);
479 ast_copy_string(cnf->confno, confno, sizeof(cnf->confno));
480 ast_copy_string(cnf->pin, pin, sizeof(cnf->pin));
481 ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin));
483 cnf->markedusers = 0;
484 cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
486 ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR);
487 ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR);
488 cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */
490 ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n");
491 cnf->fd = open("/dev/zap/pseudo", O_RDWR);
493 ast_log(LOG_WARNING, "Unable to open pseudo device\n");
499 memset(&ztc, 0, sizeof(ztc));
500 /* Setup a new zap conference */
503 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
504 if (ioctl(cnf->fd, ZT_SETCONF, &ztc)) {
505 ast_log(LOG_WARNING, "Error setting conference\n");
507 ast_hangup(cnf->chan);
514 cnf->lchan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL);
516 ast_set_read_format(cnf->lchan, AST_FORMAT_SLINEAR);
517 ast_set_write_format(cnf->lchan, AST_FORMAT_SLINEAR);
519 ztc.confmode = ZT_CONF_CONFANN | ZT_CONF_CONFANNMON;
520 if (ioctl(cnf->lchan->fds[0], ZT_SETCONF, &ztc)) {
521 ast_log(LOG_WARNING, "Error setting conference\n");
522 ast_hangup(cnf->lchan);
526 /* Fill the conference struct */
527 cnf->start = time(NULL);
528 cnf->zapconf = ztc.confno;
529 cnf->isdynamic = dynamic ? 1 : 0;
530 cnf->firstuser = NULL;
531 cnf->lastuser = NULL;
533 if (option_verbose > 2)
534 ast_verbose(VERBOSE_PREFIX_3 "Created MeetMe conference %d for conference '%s'\n", cnf->zapconf, cnf->confno);
535 AST_LIST_INSERT_HEAD(&confs, cnf, list);
540 cnf->refcount += refcount;
542 AST_LIST_UNLOCK(&confs);
546 static int confs_show(int fd, int argc, char **argv)
548 ast_cli(fd, "Deprecated! Please use 'meetme' instead.\n");
550 return RESULT_SUCCESS;
553 static char show_confs_usage[] =
554 "Deprecated! Please use 'meetme' instead.\n";
556 static struct ast_cli_entry cli_show_confs = {
557 { "show", "conferences", NULL }, confs_show,
558 "Show status of conferences", show_confs_usage, NULL };
560 static int conf_cmd(int fd, int argc, char **argv) {
561 /* Process the command */
562 struct ast_conference *cnf;
563 struct ast_conf_user *user;
565 int i = 0, total = 0;
567 char *header_format = "%-14s %-14s %-10s %-8s %-8s\n";
568 char *data_format = "%-12.12s %4.4d %4.4s %02d:%02d:%02d %-8s\n";
569 char cmdline[1024] = "";
572 ast_cli(fd, "Invalid Arguments.\n");
573 /* Check for length so no buffer will overflow... */
574 for (i = 0; i < argc; i++) {
575 if (strlen(argv[i]) > 100)
576 ast_cli(fd, "Invalid Arguments.\n");
579 /* 'MeetMe': List all the conferences */
581 if (AST_LIST_EMPTY(&confs)) {
582 ast_cli(fd, "No active MeetMe conferences.\n");
583 return RESULT_SUCCESS;
585 ast_cli(fd, header_format, "Conf Num", "Parties", "Marked", "Activity", "Creation");
586 AST_LIST_TRAVERSE(&confs, cnf, list) {
587 if (cnf->markedusers == 0)
588 strcpy(cmdline, "N/A ");
590 snprintf(cmdline, sizeof(cmdline), "%4.4d", cnf->markedusers);
591 hr = (now - cnf->start) / 3600;
592 min = ((now - cnf->start) % 3600) / 60;
593 sec = (now - cnf->start) % 60;
595 ast_cli(fd, data_format, cnf->confno, cnf->users, cmdline, hr, min, sec, cnf->isdynamic ? "Dynamic" : "Static");
599 ast_cli(fd, "* Total number of MeetMe users: %d\n", total);
600 return RESULT_SUCCESS;
603 return RESULT_SHOWUSAGE;
604 ast_copy_string(cmdline, argv[2], sizeof(cmdline)); /* Argv 2: conference number */
605 if (strstr(argv[1], "lock")) {
606 if (strcmp(argv[1], "lock") == 0) {
608 strncat(cmdline, "|L", sizeof(cmdline) - strlen(cmdline) - 1);
611 strncat(cmdline, "|l", sizeof(cmdline) - strlen(cmdline) - 1);
613 } else if (strstr(argv[1], "mute")) {
615 return RESULT_SHOWUSAGE;
616 if (strcmp(argv[1], "mute") == 0) {
618 if (strcmp(argv[3], "all") == 0) {
619 strncat(cmdline, "|N", sizeof(cmdline) - strlen(cmdline) - 1);
621 strncat(cmdline, "|M|", sizeof(cmdline) - strlen(cmdline) - 1);
622 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
626 if (strcmp(argv[3], "all") == 0) {
627 strncat(cmdline, "|n", sizeof(cmdline) - strlen(cmdline) - 1);
629 strncat(cmdline, "|m|", sizeof(cmdline) - strlen(cmdline) - 1);
630 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
633 } else if (strcmp(argv[1], "kick") == 0) {
635 return RESULT_SHOWUSAGE;
636 if (strcmp(argv[3], "all") == 0) {
638 strncat(cmdline, "|K", sizeof(cmdline) - strlen(cmdline) - 1);
640 /* Kick a single user */
641 strncat(cmdline, "|k|", sizeof(cmdline) - strlen(cmdline) - 1);
642 strncat(cmdline, argv[3], sizeof(cmdline) - strlen(cmdline) - 1);
644 } else if(strcmp(argv[1], "list") == 0) {
645 /* List all the users in a conference */
646 if (AST_LIST_EMPTY(&confs)) {
647 ast_cli(fd, "No active conferences.\n");
648 return RESULT_SUCCESS;
650 /* Find the right conference */
651 AST_LIST_TRAVERSE(&confs, cnf, list) {
652 if (strcmp(cnf->confno, argv[2]) == 0)
656 ast_cli(fd, "No such conference: %s.\n",argv[2]);
657 return RESULT_SUCCESS;
659 /* Show all the users */
660 for (user = cnf->firstuser; user; user = user->nextuser){
662 hr = (now - user->jointime) / 3600;
663 min = ((now - user->jointime) % 3600) / 60;
664 sec = (now - user->jointime) % 60;
667 ast_cli(fd, "User #: %-2.2d %12.12s %-20.20s Channel: %s %s %s %s %s %02d:%02d:%02d\n",
669 user->chan->cid.cid_num ? user->chan->cid.cid_num : "<unknown>",
670 user->chan->cid.cid_name ? user->chan->cid.cid_name : "<no name>",
672 user->userflags & CONFFLAG_ADMIN ? "(Admin)" : "",
673 user->userflags & CONFFLAG_MONITOR ? "(Listen only)" : "",
674 user->adminflags & ADMINFLAG_MUTED ? "(Admn Muted)" : "",
675 istalking(user->talking), hr, min, sec);
677 ast_cli(fd,"%d users in that conference.\n",cnf->users);
679 return RESULT_SUCCESS;
681 return RESULT_SHOWUSAGE;
682 ast_log(LOG_DEBUG, "Cmdline: %s\n", cmdline);
683 admin_exec(NULL, cmdline);
688 static char *complete_confcmd(const char *line, const char *word, int pos, int state) {
689 #define CONF_COMMANDS 6
690 int which = 0, x = 0;
691 struct ast_conference *cnf = NULL;
692 struct ast_conf_user *usr = NULL;
695 char cmds[CONF_COMMANDS][20] = {"lock", "unlock", "mute", "unmute", "kick", "list"};
700 for (x = 0;x < CONF_COMMANDS; x++) {
701 if (!strncasecmp(cmds[x], word, strlen(word))) {
702 if (++which > state) {
703 return strdup(cmds[x]);
707 } else if (pos == 2) {
708 /* Conference Number */
709 AST_LIST_LOCK(&confs);
710 AST_LIST_TRAVERSE(&confs, cnf, list) {
711 if (!strncasecmp(word, cnf->confno, strlen(word))) {
716 AST_LIST_UNLOCK(&confs);
717 return cnf ? strdup(cnf->confno) : NULL;
718 } else if (pos == 3) {
719 /* User Number || Conf Command option*/
720 if (strstr(line, "mute") || strstr(line, "kick")) {
721 if ((state == 0) && (strstr(line, "kick") || strstr(line,"mute")) && !(strncasecmp(word, "all", strlen(word)))) {
722 return strdup("all");
725 AST_LIST_LOCK(&confs);
727 /* TODO: Find the conf number from the cmdline (ignore spaces) <- test this and make it fail-safe! */
728 myline = ast_strdupa(line);
729 if (strsep(&myline, " ") && strsep(&myline, " ") && !confno) {
730 while((confno = strsep(&myline, " ")) && (strcmp(confno, " ") == 0))
734 AST_LIST_TRAVERSE(&confs, cnf, list) {
735 if (!strcmp(confno, cnf->confno))
740 /* Search for the user */
741 for (usr = cnf->firstuser; usr; usr = usr->nextuser) {
742 snprintf(usrno, sizeof(usrno), "%d", usr->user_no);
743 if (!strncasecmp(word, usrno, strlen(word))) {
749 AST_LIST_UNLOCK(&confs);
750 return usr ? strdup(usrno) : NULL;
757 static char conf_usage[] =
758 "Usage: meetme (un)lock|(un)mute|kick|list <confno> <usernumber>\n"
759 " Executes a command for the conference or on a conferee\n";
761 static struct ast_cli_entry cli_conf = {
762 {"meetme", NULL, NULL }, conf_cmd,
763 "Execute a command on a conference or conferee", conf_usage, complete_confcmd};
765 static void conf_flush(int fd, struct ast_channel *chan)
769 /* read any frames that may be waiting on the channel
775 /* when no frames are available, this will wait
776 for 1 millisecond maximum
778 while (ast_waitfor(chan, 1)) {
785 /* flush any data sitting in the pseudo channel */
787 if (ioctl(fd, ZT_FLUSH, &x))
788 ast_log(LOG_WARNING, "Error flushing channel\n");
792 /* Remove the conference from the list and free it.
793 We assume that this was called while holding conflock. */
794 static int conf_free(struct ast_conference *conf)
798 AST_LIST_REMOVE(&confs, conf, list);
800 if (conf->recording == MEETME_RECORD_ACTIVE) {
801 conf->recording = MEETME_RECORD_TERMINATE;
802 AST_LIST_UNLOCK(&confs);
804 AST_LIST_LOCK(&confs);
805 if (conf->recording == MEETME_RECORD_OFF)
807 AST_LIST_UNLOCK(&confs);
811 for (x=0;x<AST_FRAME_BITS;x++) {
812 if (conf->transframe[x])
813 ast_frfree(conf->transframe[x]);
814 if (conf->transpath[x])
815 ast_translator_free_path(conf->transpath[x]);
818 ast_frfree(conf->origframe);
820 ast_hangup(conf->lchan);
822 ast_hangup(conf->chan);
831 static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int confflags)
833 struct ast_conf_user *user = NULL;
834 struct ast_conf_user *usr = NULL;
836 struct zt_confinfo ztc, ztc_empty;
838 struct ast_channel *c;
850 int currentmarked = 0;
854 int using_pseudo = 0;
858 struct ast_dsp *dsp=NULL;
861 const char *agifiledefault = "conf-background.agi";
862 char meetmesecs[30] = "";
863 char exitcontext[AST_MAX_CONTEXT] = "";
864 char recordingtmp[AST_MAX_EXTENSION] = "";
865 char members[10] = "";
868 char __buf[CONF_SIZE + AST_FRIENDLY_OFFSET];
869 char *buf = __buf + AST_FRIENDLY_OFFSET;
871 if (!(user = ast_calloc(1, sizeof(*user)))) {
872 AST_LIST_LOCK(&confs);
874 if (!conf->refcount){
877 AST_LIST_UNLOCK(&confs);
881 if (confflags & CONFFLAG_RECORDCONF) {
882 if (!conf->recordingfilename) {
883 conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
884 if (!conf->recordingfilename) {
885 snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
886 conf->recordingfilename = ast_strdupa(recordingtmp);
888 conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
889 if (!conf->recordingformat) {
890 snprintf(recordingtmp, sizeof(recordingtmp), "wav");
891 conf->recordingformat = ast_strdupa(recordingtmp);
893 ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n",
894 conf->confno, conf->recordingfilename, conf->recordingformat);
898 if ((conf->recording == MEETME_RECORD_OFF) && ((confflags & CONFFLAG_RECORDCONF) || (conf->lchan))) {
899 pthread_attr_init(&conf->attr);
900 pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED);
901 ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf);
904 time(&user->jointime);
906 if (conf->locked && (!(confflags & CONFFLAG_ADMIN))) {
907 /* Sorry, but this confernce is locked! */
908 if (!ast_streamfile(chan, "conf-locked", chan->language))
909 ast_waitstream(chan, "");
913 if (confflags & CONFFLAG_MARKEDUSER)
916 ast_mutex_lock(&conf->playlock);
917 if (!conf->firstuser) {
918 /* Fill the first new User struct */
920 conf->firstuser = user;
921 conf->lastuser = user;
923 /* Fill the new user struct */
924 user->user_no = conf->lastuser->user_no + 1;
925 user->prevuser = conf->lastuser;
926 if (conf->lastuser->nextuser) {
927 ast_log(LOG_WARNING, "Error in User Management!\n");
928 ast_mutex_unlock(&conf->playlock);
931 conf->lastuser->nextuser = user;
932 conf->lastuser = user;
937 user->userflags = confflags;
938 user->adminflags = 0;
942 snprintf(members, sizeof(members), "%d", conf->users);
943 ast_update_realtime("meetme", "confno", conf->confno, "members", members , NULL);
945 ast_mutex_unlock(&conf->playlock);
947 if (confflags & CONFFLAG_EXIT_CONTEXT) {
948 if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT")))
949 ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
950 else if (!ast_strlen_zero(chan->macrocontext))
951 ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
953 ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
956 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
957 snprintf(user->namerecloc, sizeof(user->namerecloc),
958 "%s/meetme/meetme-username-%s-%d", ast_config_AST_SPOOL_DIR,
959 conf->confno, user->user_no);
960 res = ast_record_review(chan, "vm-rec-name", user->namerecloc, 10, "sln", &duration, NULL);
965 if ( !(confflags & (CONFFLAG_QUIET | CONFFLAG_NOONLYPERSON)) ) {
966 if (conf->users == 1 && !(confflags & CONFFLAG_WAITMARKED))
967 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
968 ast_waitstream(chan, "");
969 if ((confflags & CONFFLAG_WAITMARKED) && conf->markedusers == 0)
970 if (!ast_streamfile(chan, "conf-waitforleader", chan->language))
971 ast_waitstream(chan, "");
974 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_ANNOUNCEUSERCOUNT) && conf->users > 1) {
977 if (conf->users == 2) {
978 if (!ast_streamfile(chan,"conf-onlyone",chan->language)) {
979 res = ast_waitstream(chan, AST_DIGIT_ANY);
986 if (!ast_streamfile(chan, "conf-thereare", chan->language)) {
987 res = ast_waitstream(chan, AST_DIGIT_ANY);
994 res = ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
1000 if (keepplaying && !ast_streamfile(chan, "conf-otherinparty", chan->language)) {
1001 res = ast_waitstream(chan, AST_DIGIT_ANY);
1010 ast_indicate(chan, -1);
1012 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR) < 0) {
1013 ast_log(LOG_WARNING, "Unable to set '%s' to write linear mode\n", chan->name);
1017 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0) {
1018 ast_log(LOG_WARNING, "Unable to set '%s' to read linear mode\n", chan->name);
1022 retryzap = strcasecmp(chan->tech->type, "Zap");
1023 user->zapchannel = !retryzap;
1026 origfd = chan->fds[0];
1028 fd = open("/dev/zap/pseudo", O_RDWR);
1030 ast_log(LOG_WARNING, "Unable to open pseudo channel: %s\n", strerror(errno));
1034 /* Make non-blocking */
1035 flags = fcntl(fd, F_GETFL);
1037 ast_log(LOG_WARNING, "Unable to get flags: %s\n", strerror(errno));
1041 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
1042 ast_log(LOG_WARNING, "Unable to set flags: %s\n", strerror(errno));
1046 /* Setup buffering information */
1047 memset(&bi, 0, sizeof(bi));
1048 bi.bufsize = CONF_SIZE/2;
1049 bi.txbufpolicy = ZT_POLICY_IMMEDIATE;
1050 bi.rxbufpolicy = ZT_POLICY_IMMEDIATE;
1051 bi.numbufs = audio_buffers;
1052 if (ioctl(fd, ZT_SET_BUFINFO, &bi)) {
1053 ast_log(LOG_WARNING, "Unable to set buffering information: %s\n", strerror(errno));
1058 if (ioctl(fd, ZT_SETLINEAR, &x)) {
1059 ast_log(LOG_WARNING, "Unable to set linear mode: %s\n", strerror(errno));
1065 /* XXX Make sure we're not running on a pseudo channel XXX */
1069 memset(&ztc, 0, sizeof(ztc));
1070 memset(&ztc_empty, 0, sizeof(ztc_empty));
1071 /* Check to see if we're in a conference... */
1073 if (ioctl(fd, ZT_GETCONF, &ztc)) {
1074 ast_log(LOG_WARNING, "Error getting conference\n");
1079 /* Whoa, already in a conference... Retry... */
1081 ast_log(LOG_DEBUG, "Zap channel is in a conference already, retrying with pseudo\n");
1086 memset(&ztc, 0, sizeof(ztc));
1087 /* Add us to the conference */
1089 ztc.confno = conf->zapconf;
1091 ast_mutex_lock(&conf->playlock);
1093 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) {
1094 if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) {
1095 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1096 ast_waitstream(conf->chan, "");
1097 if (!ast_streamfile(conf->chan, "conf-hasjoin", chan->language))
1098 ast_waitstream(conf->chan, "");
1102 if (confflags & CONFFLAG_MONITOR)
1103 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1104 else if (confflags & CONFFLAG_TALKER)
1105 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1107 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1109 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1110 ast_log(LOG_WARNING, "Error setting conference\n");
1112 ast_mutex_unlock(&conf->playlock);
1115 ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf);
1117 manager_event(EVENT_FLAG_CALL, "MeetmeJoin",
1122 chan->name, chan->uniqueid, conf->confno, user->user_no);
1124 if (!firstpass && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN)) {
1126 if (!(confflags & CONFFLAG_QUIET))
1127 if (!(confflags & CONFFLAG_WAITMARKED) || (conf->markedusers >= 1))
1128 conf_play(chan, conf, ENTER);
1131 ast_mutex_unlock(&conf->playlock);
1133 conf_flush(fd, chan);
1135 if (confflags & CONFFLAG_AGI) {
1136 /* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
1137 or use default filename of conf-background.agi */
1139 agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
1141 agifile = agifiledefault;
1143 if (user->zapchannel) {
1144 /* Set CONFMUTE mode on Zap channel to mute DTMF tones */
1146 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1148 /* Find a pointer to the agi app and execute the script */
1149 app = pbx_findapp("agi");
1151 char *s = ast_strdupa(agifile);
1152 ret = pbx_exec(chan, app, s, 1);
1154 ast_log(LOG_WARNING, "Could not find application (agi)\n");
1157 if (user->zapchannel) {
1158 /* Remove CONFMUTE mode on Zap channel */
1160 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1163 if (user->zapchannel && (confflags & CONFFLAG_STARMENU)) {
1164 /* Set CONFMUTE mode on Zap channel to mute DTMF tones when the menu is enabled */
1166 ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0);
1168 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) {
1169 ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
1173 int menu_was_active = 0;
1178 /* if we have just exited from the menu, and the user had a channel-driver
1179 volume adjustment, restore it
1181 if (!menu_active && menu_was_active && user->listen.desired && !user->listen.actual)
1182 set_talk_volume(user, user->listen.desired);
1184 menu_was_active = menu_active;
1186 currentmarked = conf->markedusers;
1187 if (!(confflags & CONFFLAG_QUIET) &&
1188 (confflags & CONFFLAG_MARKEDUSER) &&
1189 (confflags & CONFFLAG_WAITMARKED) &&
1191 if (currentmarked == 1 && conf->users > 1) {
1192 ast_say_number(chan, conf->users - 1, AST_DIGIT_ANY, chan->language, (char *) NULL);
1193 if (conf->users - 1 == 1) {
1194 if (!ast_streamfile(chan, "conf-userwilljoin", chan->language))
1195 ast_waitstream(chan, "");
1197 if (!ast_streamfile(chan, "conf-userswilljoin", chan->language))
1198 ast_waitstream(chan, "");
1201 if (conf->users == 1 && ! (confflags & CONFFLAG_MARKEDUSER))
1202 if (!ast_streamfile(chan, "conf-onlyperson", chan->language))
1203 ast_waitstream(chan, "");
1206 c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms);
1209 /* Update the struct with the actual confflags */
1210 user->userflags = confflags;
1212 if (confflags & CONFFLAG_WAITMARKED) {
1213 if(currentmarked == 0) {
1214 if (lastmarked != 0) {
1215 if (!(confflags & CONFFLAG_QUIET))
1216 if (!ast_streamfile(chan, "conf-leaderhasleft", chan->language))
1217 ast_waitstream(chan, "");
1218 if(confflags & CONFFLAG_MARKEDEXIT)
1221 ztc.confmode = ZT_CONF_CONF;
1222 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1223 ast_log(LOG_WARNING, "Error setting conference\n");
1229 if (musiconhold == 0 && (confflags & CONFFLAG_MOH)) {
1230 ast_moh_start(chan, NULL);
1233 ztc.confmode = ZT_CONF_CONF;
1234 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1235 ast_log(LOG_WARNING, "Error setting conference\n");
1240 } else if(currentmarked >= 1 && lastmarked == 0) {
1241 if (confflags & CONFFLAG_MONITOR)
1242 ztc.confmode = ZT_CONF_CONFMON | ZT_CONF_LISTENER;
1243 else if (confflags & CONFFLAG_TALKER)
1244 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER;
1246 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1247 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1248 ast_log(LOG_WARNING, "Error setting conference\n");
1252 if (musiconhold && (confflags & CONFFLAG_MOH)) {
1256 if ( !(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MARKEDUSER)) {
1257 if (!ast_streamfile(chan, "conf-placeintoconf", chan->language))
1258 ast_waitstream(chan, "");
1259 conf_play(chan, conf, ENTER);
1264 /* trying to add moh for single person conf */
1265 if ((confflags & CONFFLAG_MOH) && !(confflags & CONFFLAG_WAITMARKED)) {
1266 if (conf->users == 1) {
1267 if (musiconhold == 0) {
1268 ast_moh_start(chan, NULL);
1279 /* Leave if the last marked user left */
1280 if (currentmarked == 0 && lastmarked != 0 && (confflags & CONFFLAG_MARKEDEXIT)) {
1285 /* Check if the admin changed my modes */
1286 if (user->adminflags) {
1287 /* Set the new modes */
1288 if ((user->adminflags & ADMINFLAG_MUTED) && (ztc.confmode & ZT_CONF_TALKER)) {
1289 ztc.confmode ^= ZT_CONF_TALKER;
1290 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1291 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1296 if (!(user->adminflags & ADMINFLAG_MUTED) && !(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1297 ztc.confmode |= ZT_CONF_TALKER;
1298 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1299 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1304 if (user->adminflags & ADMINFLAG_KICKME) {
1305 /* You have been kicked. */
1306 if (!ast_streamfile(chan, "conf-kicked", chan->language))
1307 ast_waitstream(chan, "");
1311 } else if (!(confflags & CONFFLAG_MONITOR) && !(ztc.confmode & ZT_CONF_TALKER)) {
1312 ztc.confmode |= ZT_CONF_TALKER;
1313 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1314 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1321 if (c->fds[0] != origfd) {
1323 /* Kill old pseudo */
1327 ast_log(LOG_DEBUG, "Ooh, something swapped out under us, starting over\n");
1328 retryzap = strcasecmp(c->tech->type, "Zap");
1329 user->zapchannel = !retryzap;
1332 if ((confflags & CONFFLAG_MONITOR) || (user->adminflags & ADMINFLAG_MUTED))
1333 f = ast_read_noaudio(c);
1338 if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) {
1339 if (user->talk.actual)
1340 ast_frame_adjust_volume(f, user->talk.actual);
1342 if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) {
1345 if (user->talking == -1)
1348 res = ast_dsp_silence(dsp, f, &totalsilence);
1349 if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) {
1351 if (confflags & CONFFLAG_MONITORTALKER)
1352 manager_event(EVENT_FLAG_CALL, "MeetmeTalking",
1357 chan->name, chan->uniqueid, conf->confno, user->user_no);
1359 if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) {
1361 if (confflags & CONFFLAG_MONITORTALKER)
1362 manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking",
1367 chan->name, chan->uniqueid, conf->confno, user->user_no);
1371 /* Absolutely do _not_ use careful_write here...
1372 it is important that we read data from the channel
1373 as fast as it arrives, and feed it into the conference.
1374 The buffering in the pseudo channel will take care of any
1375 timing differences, unless they are so drastic as to lose
1376 audio frames (in which case carefully writing would only
1377 have delayed the audio even further).
1379 /* As it turns out, we do want to use careful write. We just
1380 don't want to block, but we do want to at least *try*
1381 to write out all the samples.
1383 if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER))
1384 careful_write(fd, f->data, f->datalen, 0);
1386 } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) {
1389 tmp[0] = f->subclass;
1391 if (!ast_goto_if_exists(chan, exitcontext, tmp, 1)) {
1392 ast_log(LOG_DEBUG, "Got DTMF %c, goto context %s\n", tmp[0], exitcontext);
1395 } else if (option_debug > 1)
1396 ast_log(LOG_DEBUG, "Exit by single digit did not work in meetme. Extension %s does not exist in context %s\n", tmp, exitcontext);
1397 } else if ((f->frametype == AST_FRAME_DTMF) && (f->subclass == '#') && (confflags & CONFFLAG_POUNDEXIT)) {
1400 } else if (((f->frametype == AST_FRAME_DTMF) && (f->subclass == '*') && (confflags & CONFFLAG_STARMENU)) || ((f->frametype == AST_FRAME_DTMF) && menu_active)) {
1401 if (ioctl(fd, ZT_SETCONF, &ztc_empty)) {
1402 ast_log(LOG_WARNING, "Error setting conference\n");
1407 /* if we are entering the menu, and the user has a channel-driver
1408 volume adjustment, clear it
1410 if (!menu_active && user->talk.desired && !user->talk.actual)
1411 set_talk_volume(user, 0);
1416 if ((confflags & CONFFLAG_ADMIN)) {
1420 /* Record this sound! */
1421 if (!ast_streamfile(chan, "conf-adminmenu", chan->language)) {
1422 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1423 ast_stopstream(chan);
1430 case '1': /* Un/Mute */
1432 if (ztc.confmode & ZT_CONF_TALKER) {
1433 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
1434 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
1436 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1437 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
1439 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1440 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1444 if (ztc.confmode & ZT_CONF_TALKER) {
1445 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1446 ast_waitstream(chan, "");
1448 if (!ast_streamfile(chan, "conf-muted", chan->language))
1449 ast_waitstream(chan, "");
1452 case '2': /* Un/Lock the Conference */
1456 if (!ast_streamfile(chan, "conf-unlockednow", chan->language))
1457 ast_waitstream(chan, "");
1460 if (!ast_streamfile(chan, "conf-lockednow", chan->language))
1461 ast_waitstream(chan, "");
1464 case '3': /* Eject last user */
1466 usr = conf->lastuser;
1467 if ((usr->chan->name == chan->name)||(usr->userflags & CONFFLAG_ADMIN)) {
1468 if(!ast_streamfile(chan, "conf-errormenu", chan->language))
1469 ast_waitstream(chan, "");
1471 usr->adminflags |= ADMINFLAG_KICKME;
1472 ast_stopstream(chan);
1475 tweak_listen_volume(user, VOL_DOWN);
1478 tweak_listen_volume(user, VOL_UP);
1481 tweak_talk_volume(user, VOL_DOWN);
1487 tweak_talk_volume(user, VOL_UP);
1491 /* Play an error message! */
1492 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1493 ast_waitstream(chan, "");
1501 if (!ast_streamfile(chan, "conf-usermenu", chan->language)) {
1502 dtmf = ast_waitstream(chan, AST_DIGIT_ANY);
1503 ast_stopstream(chan);
1510 case '1': /* Un/Mute */
1512 if (ztc.confmode & ZT_CONF_TALKER) {
1513 ztc.confmode = ZT_CONF_CONF | ZT_CONF_LISTENER;
1514 confflags |= CONFFLAG_MONITOR ^ CONFFLAG_TALKER;
1515 } else if (!(user->adminflags & ADMINFLAG_MUTED)) {
1516 ztc.confmode = ZT_CONF_CONF | ZT_CONF_TALKER | ZT_CONF_LISTENER;
1517 confflags ^= CONFFLAG_MONITOR | CONFFLAG_TALKER;
1519 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1520 ast_log(LOG_WARNING, "Error setting conference - Un/Mute \n");
1524 if (ztc.confmode & ZT_CONF_TALKER) {
1525 if (!ast_streamfile(chan, "conf-unmuted", chan->language))
1526 ast_waitstream(chan, "");
1528 if (!ast_streamfile(chan, "conf-muted", chan->language))
1529 ast_waitstream(chan, "");
1533 tweak_listen_volume(user, VOL_DOWN);
1536 tweak_listen_volume(user, VOL_UP);
1539 tweak_talk_volume(user, VOL_DOWN);
1545 tweak_talk_volume(user, VOL_UP);
1549 if (!ast_streamfile(chan, "conf-errormenu", chan->language))
1550 ast_waitstream(chan, "");
1556 ast_moh_start(chan, NULL);
1558 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1559 ast_log(LOG_WARNING, "Error setting conference\n");
1561 AST_LIST_UNLOCK(&confs);
1565 conf_flush(fd, chan);
1566 } else if (option_debug) {
1568 "Got unrecognized frame on channel %s, f->frametype=%d,f->subclass=%d\n",
1569 chan->name, f->frametype, f->subclass);
1572 } else if (outfd > -1) {
1573 res = read(outfd, buf, CONF_SIZE);
1575 memset(&fr, 0, sizeof(fr));
1576 fr.frametype = AST_FRAME_VOICE;
1577 fr.subclass = AST_FORMAT_SLINEAR;
1581 fr.offset = AST_FRIENDLY_OFFSET;
1582 if (!user->listen.actual &&
1583 ((confflags & CONFFLAG_MONITOR) ||
1584 (user->adminflags & ADMINFLAG_MUTED) ||
1585 (!user->talking && (confflags & CONFFLAG_OPTIMIZETALKER))
1588 for (index=0;index<AST_FRAME_BITS;index++)
1589 if (chan->rawwriteformat & (1 << index))
1591 if (index >= AST_FRAME_BITS)
1592 goto bailoutandtrynormal;
1593 ast_mutex_lock(&conf->listenlock);
1594 if (!conf->transframe[index]) {
1595 if (conf->origframe) {
1596 if (!conf->transpath[index])
1597 conf->transpath[index] = ast_translator_build_path((1 << index), AST_FORMAT_SLINEAR);
1598 if (conf->transpath[index]) {
1599 conf->transframe[index] = ast_translate(conf->transpath[index], conf->origframe, 0);
1600 if (!conf->transframe[index])
1601 conf->transframe[index] = &ast_null_frame;
1605 if (conf->transframe[index]) {
1606 if (conf->transframe[index]->frametype != AST_FRAME_NULL) {
1607 if (ast_write(chan, conf->transframe[index]))
1608 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
1611 ast_mutex_unlock(&conf->listenlock);
1612 goto bailoutandtrynormal;
1614 ast_mutex_unlock(&conf->listenlock);
1616 bailoutandtrynormal:
1617 if (user->listen.actual)
1618 ast_frame_adjust_volume(&fr, user->listen.actual);
1619 if (ast_write(chan, &fr) < 0) {
1620 ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno));
1624 ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno));
1626 lastmarked = currentmarked;
1636 /* Take out of conference */
1640 if (ioctl(fd, ZT_SETCONF, &ztc)) {
1641 ast_log(LOG_WARNING, "Error setting conference\n");
1645 reset_volumes(user);
1647 AST_LIST_LOCK(&confs);
1648 if (!(confflags & CONFFLAG_QUIET) && !(confflags & CONFFLAG_MONITOR) && !(confflags & CONFFLAG_ADMIN))
1649 conf_play(chan, conf, LEAVE);
1651 if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER)) {
1652 if (ast_fileexists(user->namerecloc, NULL, NULL)) {
1653 if ((conf->chan) && (conf->users > 1)) {
1654 if (!ast_streamfile(conf->chan, user->namerecloc, chan->language))
1655 ast_waitstream(conf->chan, "");
1656 if (!ast_streamfile(conf->chan, "conf-hasleft", chan->language))
1657 ast_waitstream(conf->chan, "");
1659 ast_filedelete(user->namerecloc, NULL);
1662 AST_LIST_UNLOCK(&confs);
1665 AST_LIST_LOCK(&confs);
1670 if (user->user_no) { /* Only cleanup users who really joined! */
1672 hr = (now - user->jointime) / 3600;
1673 min = ((now - user->jointime) % 3600) / 60;
1674 sec = (now - user->jointime) % 60;
1676 manager_event(EVENT_FLAG_CALL, "MeetmeLeave",
1683 "Duration: %02d:%02d:%02d\r\n",
1684 chan->name, chan->uniqueid, conf->confno,
1686 user->chan->cid.cid_num ? user->chan->cid.cid_num :
1688 user->chan->cid.cid_name ? user->chan->cid.cid_name :
1689 "<no name>", hr, min, sec);
1694 snprintf(members, sizeof(members), "%d", conf->users);
1695 ast_update_realtime("meetme", "confno", conf->confno, "members", members, NULL);
1696 if (confflags & CONFFLAG_MARKEDUSER)
1697 conf->markedusers--;
1699 /* close this one when no more users and no references*/
1700 if (!conf->refcount){
1704 /* Remove the user struct */
1705 if (user == conf->firstuser) {
1706 if (user->nextuser) {
1707 /* There is another entry */
1708 user->nextuser->prevuser = NULL;
1710 /* We are the only entry */
1711 conf->lastuser = NULL;
1713 /* In either case */
1714 conf->firstuser = user->nextuser;
1715 } else if (user == conf->lastuser){
1717 user->prevuser->nextuser = NULL;
1719 ast_log(LOG_ERROR, "Bad bad bad! We're the last, not the first, but nobody before us??\n");
1720 conf->lastuser = user->prevuser;
1723 user->nextuser->prevuser = user->prevuser;
1725 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->nextuser is NULL but we're not the end!\n");
1727 user->prevuser->nextuser = user->nextuser;
1729 ast_log(LOG_ERROR, "Bad! Bad! Bad! user->prevuser is NULL but we're not the beginning!\n");
1732 /* Return the number of seconds the user was in the conf */
1733 snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
1734 pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
1737 AST_LIST_UNLOCK(&confs);
1743 This function looks for a conference via the RealTime module
1745 static struct ast_conference *find_conf_realtime(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin, int refcount)
1748 struct ast_variable *var;
1749 struct ast_conference *cnf;
1751 /* Check first in the conference list */
1752 AST_LIST_LOCK(&confs);
1753 AST_LIST_TRAVERSE(&confs, cnf, list) {
1754 if (!strcmp(confno, cnf->confno))
1758 cnf->refcount += refcount;
1760 AST_LIST_UNLOCK(&confs);
1763 char *pin = NULL, *pinadmin = NULL; /* For temp use */
1765 cnf = ast_calloc(1, sizeof(struct ast_conference));
1767 ast_log(LOG_ERROR, "Out of memory\n");
1771 var = ast_load_realtime("meetme", "confno", confno, NULL);
1773 if (!strcasecmp(var->name, "confno")) {
1774 ast_copy_string(cnf->confno, var->value, sizeof(cnf->confno));
1775 } else if (!strcasecmp(var->name, "pin")) {
1776 pin = ast_strdupa(var->value);
1777 } else if (!strcasecmp(var->name, "adminpin")) {
1778 pinadmin = ast_strdupa(var->value);
1782 ast_variables_destroy(var);
1784 cnf = build_conf(confno, pin ? pin : "", pinadmin ? pinadmin : "", make, dynamic, refcount);
1791 static struct ast_conference *find_conf(struct ast_channel *chan, char *confno, int make, int dynamic, char *dynamic_pin, int refcount)
1793 struct ast_config *cfg;
1794 struct ast_variable *var;
1795 struct ast_conference *cnf;
1797 AST_DECLARE_APP_ARGS(args,
1798 AST_APP_ARG(confno);
1800 AST_APP_ARG(pinadmin);
1803 /* Check first in the conference list */
1804 AST_LIST_LOCK(&confs);
1805 AST_LIST_TRAVERSE(&confs, cnf, list) {
1806 if (!strcmp(confno, cnf->confno))
1810 cnf->refcount += refcount;
1812 AST_LIST_UNLOCK(&confs);
1816 /* No need to parse meetme.conf */
1817 ast_log(LOG_DEBUG, "Building dynamic conference '%s'\n", confno);
1819 if (dynamic_pin[0] == 'q') {
1820 /* Query the user to enter a PIN */
1821 if (ast_app_getdata(chan, "conf-getpin", dynamic_pin, AST_MAX_EXTENSION - 1, 0) < 0)
1824 cnf = build_conf(confno, dynamic_pin, "", make, dynamic, refcount);
1826 cnf = build_conf(confno, "", "", make, dynamic, refcount);
1829 /* Check the config */
1830 cfg = ast_config_load(CONFIG_FILE_NAME);
1832 ast_log(LOG_WARNING, "No %s file :(\n", CONFIG_FILE_NAME);
1835 for (var = ast_variable_browse(cfg, "rooms"); var; var = var->next) {
1836 if (strcasecmp(var->name, "conf"))
1839 if (!(parse = ast_strdupa(var->value)))
1842 AST_NONSTANDARD_APP_ARGS(args, parse, ',');
1843 if (!strcasecmp(args.confno, confno)) {
1844 /* Bingo it's a valid conference */
1845 cnf = build_conf(args.confno,
1846 ast_strlen_zero(args.pin) ? "" : args.pin,
1847 ast_strlen_zero(args.pinadmin) ? "" : args.pinadmin,
1848 make, dynamic, refcount);
1853 ast_log(LOG_DEBUG, "%s isn't a valid conference\n", confno);
1855 ast_config_destroy(cfg);
1857 } else if (dynamic_pin) {
1858 /* Correct for the user selecting 'D' instead of 'd' to have
1859 someone join into a conference that has already been created
1861 if (dynamic_pin[0] == 'q')
1862 dynamic_pin[0] = '\0';
1868 /*! \brief The MeetmeCount application */
1869 static int count_exec(struct ast_channel *chan, void *data)
1871 struct localuser *u;
1873 struct ast_conference *conf;
1877 AST_DECLARE_APP_ARGS(args,
1878 AST_APP_ARG(confno);
1879 AST_APP_ARG(varname);
1882 if (ast_strlen_zero(data)) {
1883 ast_log(LOG_WARNING, "MeetMeCount requires an argument (conference number)\n");
1889 if (!(localdata = ast_strdupa(data))) {
1890 LOCAL_USER_REMOVE(u);
1894 AST_STANDARD_APP_ARGS(args, localdata);
1896 conf = find_conf(chan, args.confno, 0, 0, NULL, 0);
1898 count = conf->users;
1902 if (!ast_strlen_zero(args.varname)){
1903 /* have var so load it and exit */
1904 snprintf(val, sizeof(val), "%d",count);
1905 pbx_builtin_setvar_helper(chan, args.varname, val);
1907 if (chan->_state != AST_STATE_UP)
1909 res = ast_say_number(chan, count, "", chan->language, (char *) NULL); /* Needs gender */
1911 LOCAL_USER_REMOVE(u);
1916 /*! \brief The meetme() application */
1917 static int conf_exec(struct ast_channel *chan, void *data)
1920 struct localuser *u;
1921 char confno[AST_MAX_EXTENSION] = "";
1924 struct ast_conference *cnf;
1925 struct ast_flags confflags = {0};
1927 int empty = 0, empty_no_pin = 0;
1928 int always_prompt = 0;
1929 char *notdata, *info, the_pin[AST_MAX_EXTENSION] = "";
1930 AST_DECLARE_APP_ARGS(args,
1931 AST_APP_ARG(confno);
1932 AST_APP_ARG(options);
1938 if (ast_strlen_zero(data)) {
1945 if (chan->_state != AST_STATE_UP)
1948 info = ast_strdupa(notdata);
1950 AST_STANDARD_APP_ARGS(args, info);
1953 ast_copy_string(confno, args.confno, sizeof(confno));
1954 if (ast_strlen_zero(confno)) {
1960 ast_copy_string(the_pin, args.pin, sizeof(the_pin));
1963 ast_app_parse_options(meetme_opts, &confflags, NULL, args.options);
1964 dynamic = ast_test_flag(&confflags, CONFFLAG_DYNAMIC | CONFFLAG_DYNAMICPIN);
1965 if (ast_test_flag(&confflags, CONFFLAG_DYNAMICPIN) && !args.pin)
1966 strcpy(the_pin, "q");
1968 empty = ast_test_flag(&confflags, CONFFLAG_EMPTY | CONFFLAG_EMPTYNOPIN);
1969 empty_no_pin = ast_test_flag(&confflags, CONFFLAG_EMPTYNOPIN);
1970 always_prompt = ast_test_flag(&confflags, CONFFLAG_ALWAYSPROMPT);
1977 int i, map[1024] = { 0, };
1978 struct ast_config *cfg;
1979 struct ast_variable *var;
1982 AST_LIST_LOCK(&confs);
1983 AST_LIST_TRAVERSE(&confs, cnf, list) {
1984 if (sscanf(cnf->confno, "%d", &confno_int) == 1) {
1985 /* Disqualify in use conference */
1986 if (confno_int >= 0 && confno_int < 1024)
1990 AST_LIST_UNLOCK(&confs);
1992 /* We only need to load the config file for static and empty_no_pin (otherwise we don't care) */
1993 if ((empty_no_pin) || (!dynamic)) {
1994 cfg = ast_config_load(CONFIG_FILE_NAME);
1996 var = ast_variable_browse(cfg, "rooms");
1998 if (!strcasecmp(var->name, "conf")) {
1999 char *stringp = ast_strdupa(var->value);
2001 char *confno_tmp = strsep(&stringp, "|,");
2003 if (sscanf(confno_tmp, "%d", &confno_int) == 1) {
2004 if ((confno_int >= 0) && (confno_int < 1024)) {
2005 if (stringp && empty_no_pin) {
2011 /* For static: run through the list and see if this conference is empty */
2012 AST_LIST_LOCK(&confs);
2013 AST_LIST_TRAVERSE(&confs, cnf, list) {
2014 if (!strcmp(confno_tmp, cnf->confno)) {
2015 /* The conference exists, therefore it's not empty */
2020 AST_LIST_UNLOCK(&confs);
2022 /* At this point, we have a confno_tmp (static conference) that is empty */
2023 if ((empty_no_pin && ast_strlen_zero(stringp)) || (!empty_no_pin)) {
2024 /* Case 1: empty_no_pin and pin is nonexistent (NULL)
2025 * Case 2: empty_no_pin and pin is blank (but not NULL)
2026 * Case 3: not empty_no_pin
2028 ast_copy_string(confno, confno_tmp, sizeof(confno));
2030 /* XXX the map is not complete (but we do have a confno) */
2038 ast_config_destroy(cfg);
2042 /* Select first conference number not in use */
2043 if (ast_strlen_zero(confno) && dynamic) {
2044 for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
2046 snprintf(confno, sizeof(confno), "%d", i);
2053 if (ast_strlen_zero(confno)) {
2054 res = ast_streamfile(chan, "conf-noempty", chan->language);
2056 ast_waitstream(chan, "");
2058 if (sscanf(confno, "%d", &confno_int) == 1) {
2059 res = ast_streamfile(chan, "conf-enteringno", chan->language);
2061 ast_waitstream(chan, "");
2062 res = ast_say_digits(chan, confno_int, "", chan->language);
2065 ast_log(LOG_ERROR, "Could not scan confno '%s'\n", confno);
2070 while (allowretry && (ast_strlen_zero(confno)) && (++retrycnt < 4)) {
2071 /* Prompt user for conference number */
2072 res = ast_app_getdata(chan, "conf-getconfno", confno, sizeof(confno) - 1, 0);
2074 /* Don't try to validate when we catch an error */
2080 if (!ast_strlen_zero(confno)) {
2081 /* Check the validity of the conference */
2082 cnf = find_conf(chan, confno, 1, dynamic, the_pin, 1);
2084 cnf = find_conf_realtime(chan, confno, 1, dynamic, the_pin, 1);
2087 res = ast_streamfile(chan, "conf-invalid", chan->language);
2089 ast_waitstream(chan, "");
2094 if ((!ast_strlen_zero(cnf->pin) &&
2095 !ast_test_flag(&confflags, CONFFLAG_ADMIN)) ||
2096 (!ast_strlen_zero(cnf->pinadmin) &&
2097 ast_test_flag(&confflags, CONFFLAG_ADMIN))) {
2098 char pin[AST_MAX_EXTENSION]="";
2101 /* Allow the pin to be retried up to 3 times */
2102 for (j = 0; j < 3; j++) {
2103 if (*the_pin && (always_prompt == 0)) {
2104 ast_copy_string(pin, the_pin, sizeof(pin));
2107 /* Prompt user for pin if pin is required */
2108 res = ast_app_getdata(chan, "conf-getpin", pin + strlen(pin), sizeof(pin) - 1 - strlen(pin), 0);
2111 if (!strcasecmp(pin, cnf->pin) ||
2112 (!ast_strlen_zero(cnf->pinadmin) &&
2113 !strcasecmp(pin, cnf->pinadmin))) {
2116 if (!ast_strlen_zero(cnf->pinadmin) && !strcasecmp(pin, cnf->pinadmin))
2117 ast_set_flag(&confflags, CONFFLAG_ADMIN);
2118 /* Run the conference */
2119 res = conf_run(chan, cnf, confflags.flags);
2123 res = ast_streamfile(chan, "conf-invalidpin", chan->language);
2125 ast_waitstream(chan, AST_DIGIT_ANY);
2127 AST_LIST_LOCK(&confs);
2129 if (!cnf->refcount){
2132 AST_LIST_UNLOCK(&confs);
2142 /* failed when getting the pin */
2145 /* see if we need to get rid of the conference */
2146 AST_LIST_LOCK(&confs);
2148 if (!cnf->refcount) {
2151 AST_LIST_UNLOCK(&confs);
2155 /* Don't retry pin with a static pin */
2156 if (*the_pin && (always_prompt==0)) {
2161 /* No pin required */
2164 /* Run the conference */
2165 res = conf_run(chan, cnf, confflags.flags);
2169 } while (allowretry);
2171 LOCAL_USER_REMOVE(u);
2176 static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident)
2178 struct ast_conf_user *user = NULL;
2181 sscanf(callerident, "%i", &cid);
2182 if (conf && callerident) {
2183 user = conf->firstuser;
2185 if (cid == user->user_no)
2187 user = user->nextuser;
2193 /*! \brief The MeetMeadmin application */
2194 /* MeetMeAdmin(confno, command, caller) */
2195 static int admin_exec(struct ast_channel *chan, void *data) {
2197 struct ast_conference *cnf;
2198 struct ast_conf_user *user = NULL;
2199 struct localuser *u;
2200 AST_DECLARE_APP_ARGS(args,
2201 AST_APP_ARG(confno);
2202 AST_APP_ARG(command);
2208 AST_LIST_LOCK(&confs);
2209 /* The param has the conference number the user and the command to execute */
2210 if (!ast_strlen_zero(data)) {
2211 params = ast_strdupa((char *) data);
2213 AST_STANDARD_APP_ARGS(args, params);
2215 if (!args.command) {
2216 ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
2217 AST_LIST_UNLOCK(&confs);
2218 LOCAL_USER_REMOVE(u);
2221 AST_LIST_TRAVERSE(&confs, cnf, list) {
2222 if (!strcmp(cnf->confno, args.confno))
2227 user = find_user(cnf, args.user);
2230 switch((int) (*args.command)) {
2231 case 76: /* L: Lock */
2234 case 108: /* l: Unlock */
2237 case 75: /* K: kick all users*/
2238 user = cnf->firstuser;
2240 user->adminflags |= ADMINFLAG_KICKME;
2241 if (user->nextuser) {
2242 user = user->nextuser;
2248 case 101: /* e: Eject last user*/
2249 user = cnf->lastuser;
2250 if (!(user->userflags & CONFFLAG_ADMIN)) {
2251 user->adminflags |= ADMINFLAG_KICKME;
2254 ast_log(LOG_NOTICE, "Not kicking last user, is an Admin!\n");
2256 case 77: /* M: Mute */
2258 user->adminflags |= ADMINFLAG_MUTED;
2260 ast_log(LOG_NOTICE, "Specified User not found!\n");
2263 case 78: /* N: Mute all users */
2264 user = cnf->firstuser;
2266 if (user && !(user->userflags & CONFFLAG_ADMIN))
2267 user->adminflags |= ADMINFLAG_MUTED;
2268 if (user->nextuser) {
2269 user = user->nextuser;
2275 case 109: /* m: Unmute */
2276 if (user && (user->adminflags & ADMINFLAG_MUTED)) {
2277 user->adminflags ^= ADMINFLAG_MUTED;
2279 ast_log(LOG_NOTICE, "Specified User not found or he muted himself!\n");
2282 case 110: /* n: Unmute all users */
2283 user = cnf->firstuser;
2285 if (user && (user-> adminflags & ADMINFLAG_MUTED)) {
2286 user->adminflags ^= ADMINFLAG_MUTED;
2288 if (user->nextuser) {
2289 user = user->nextuser;
2295 case 107: /* k: Kick user */
2297 user->adminflags |= ADMINFLAG_KICKME;
2299 ast_log(LOG_NOTICE, "Specified User not found!");
2304 ast_log(LOG_NOTICE, "Conference Number not found\n");
2307 AST_LIST_UNLOCK(&confs);
2309 LOCAL_USER_REMOVE(u);
2314 static void *recordthread(void *args)
2316 struct ast_conference *cnf = args;
2317 struct ast_frame *f=NULL;
2319 struct ast_filestream *s=NULL;
2322 const char *oldrecordingfilename = NULL;
2324 if (!cnf || !cnf->lchan) {
2328 ast_stopstream(cnf->lchan);
2329 flags = O_CREAT|O_TRUNC|O_WRONLY;
2332 cnf->recording = MEETME_RECORD_ACTIVE;
2333 while (ast_waitfor(cnf->lchan, -1) > -1) {
2334 if (cnf->recording == MEETME_RECORD_TERMINATE) {
2335 AST_LIST_LOCK(&confs);
2336 AST_LIST_UNLOCK(&confs);
2339 if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) {
2340 s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644);
2341 oldrecordingfilename = cnf->recordingfilename;
2344 f = ast_read(cnf->lchan);
2349 if (f->frametype == AST_FRAME_VOICE) {
2350 ast_mutex_lock(&cnf->listenlock);
2351 for (x=0;x<AST_FRAME_BITS;x++) {
2352 /* Free any translations that have occured */
2353 if (cnf->transframe[x]) {
2354 ast_frfree(cnf->transframe[x]);
2355 cnf->transframe[x] = NULL;
2359 ast_frfree(cnf->origframe);
2361 ast_mutex_unlock(&cnf->listenlock);
2363 res = ast_writestream(s, f);
2371 cnf->recording = MEETME_RECORD_OFF;
2378 static void load_config(void)
2380 struct ast_config *cfg;
2383 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2385 if (!(cfg = ast_config_load(CONFIG_FILE_NAME)))
2388 if ((val = ast_variable_retrieve(cfg, "general", "audiobuffers"))) {
2389 if ((sscanf(val, "%d", &audio_buffers) != 1)) {
2390 ast_log(LOG_WARNING, "audiobuffers setting must be a number, not '%s'\n", val);
2391 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2392 } else if ((audio_buffers < ZT_DEFAULT_NUM_BUFS) || (audio_buffers > ZT_MAX_NUM_BUFS)) {
2393 ast_log(LOG_WARNING, "audiobuffers setting must be between %d and %d\n",
2394 ZT_DEFAULT_NUM_BUFS, ZT_MAX_NUM_BUFS);
2395 audio_buffers = DEFAULT_AUDIO_BUFFERS;
2397 if (audio_buffers != DEFAULT_AUDIO_BUFFERS)
2398 ast_log(LOG_NOTICE, "Audio buffers per channel set to %d\n", audio_buffers);
2401 ast_config_destroy(cfg);
2404 int unload_module(void)
2408 res = ast_cli_unregister(&cli_show_confs);
2409 res |= ast_cli_unregister(&cli_conf);
2410 res |= ast_unregister_application(app3);
2411 res |= ast_unregister_application(app2);
2412 res |= ast_unregister_application(app);
2414 STANDARD_HANGUP_LOCALUSERS;
2419 int load_module(void)
2425 res = ast_cli_register(&cli_show_confs);
2426 res |= ast_cli_register(&cli_conf);
2427 res |= ast_register_application(app3, admin_exec, synopsis3, descrip3);
2428 res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
2429 res |= ast_register_application(app, conf_exec, synopsis, descrip);
2441 char *description(void)
2443 return (char *) tdesc;
2450 STANDARD_USECOUNT(res);
2457 return ASTERISK_GPL_KEY;