diff --git a/apps/app_meetme.c b/apps/app_meetme.c index 2c7970bb79beadc5aab95054633092f64dfdbefb..d966cfd2786ec140f78ffc643feb2ad8c00ae904 100644 --- a/apps/app_meetme.c +++ b/apps/app_meetme.c @@ -54,6 +54,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/say.h" #include "asterisk/utils.h" +#include "asterisk/translate.h" +#include "asterisk/ulaw.h" static const char *tdesc = "MeetMe conference bridge"; @@ -95,6 +97,10 @@ static const char *descrip = " 's' -- Present menu (user or admin) when '*' is received ('send' to menu)\n" " 't' -- set talk only mode. (Talk only, no listening)\n" " 'T' -- set talker detection (sent to manager interface and meetme list)\n" +" 'o' -- set talker optimization - treats talkers who aren't speaking as\n" +" being muted, meaning (a) No encode is done on transmission and\n" +" (b) Received audio that is not registered as talking is omitted\n" +" causing no buildup in background noise\n" " 'v' -- video mode\n" " 'w' -- wait until the marked user enters the conference\n" " 'x' -- close the conference when last marked user exits\n" @@ -129,6 +135,8 @@ STANDARD_LOCAL_USER; LOCAL_USER_DECL; static struct ast_conference { + ast_mutex_t playlock; /* Conference specific lock (players) */ + ast_mutex_t listenlock; /* Conference specific lock (listeners) */ char confno[AST_MAX_EXTENSION]; /* Conference */ struct ast_channel *chan; /* Announcements channel */ int fd; /* Announcements fd */ @@ -147,6 +155,9 @@ static struct ast_conference { const char *recordingformat; /* Format to record the Conference in */ char pin[AST_MAX_EXTENSION]; /* If protected by a PIN */ char pinadmin[AST_MAX_EXTENSION]; /* If protected by a admin PIN */ + struct ast_frame *transframe[32]; + struct ast_frame *origframe; + struct ast_trans_pvt *transpath[32]; struct ast_conference *next; } *confs; @@ -182,6 +193,8 @@ static int audio_buffers; /* The number of audio buffers to be allocated on ps #define MEETME_DELAYDETECTTALK 300 #define MEETME_DELAYDETECTENDTALK 1000 +#define AST_FRAME_BITS 32 + enum volume_action { VOL_UP, VOL_DOWN, @@ -190,6 +203,7 @@ enum volume_action { AST_MUTEX_DEFINE_STATIC(conflock); static int admin_exec(struct ast_channel *chan, void *data); +static struct ast_frame null_frame = { AST_FRAME_NULL, }; static void *recordthread(void *args); @@ -200,8 +214,9 @@ static void *recordthread(void *args); #define LEAVE 1 #define MEETME_RECORD_OFF 0 -#define MEETME_RECORD_ACTIVE 1 -#define MEETME_RECORD_TERMINATE 2 +#define MEETME_RECORD_STARTED 1 +#define MEETME_RECORD_ACTIVE 2 +#define MEETME_RECORD_TERMINATE 3 #define CONF_SIZE 320 @@ -227,12 +242,14 @@ static void *recordthread(void *args); #define CONFFLAG_EMPTYNOPIN (1 << 20) #define CONFFLAG_ALWAYSPROMPT (1 << 21) #define CONFFLAG_ANNOUNCEUSERCOUNT (1 << 22) /* If set, when user joins the conference, they will be told the number of users that are already in */ +#define CONFFLAG_OPTIMIZETALKER (1 << 23) /* If set, treats talking users as muted users */ AST_APP_OPTIONS(meetme_opts, { AST_APP_OPTION('a', CONFFLAG_ADMIN ), AST_APP_OPTION('c', CONFFLAG_ANNOUNCEUSERCOUNT ), AST_APP_OPTION('T', CONFFLAG_MONITORTALKER ), + AST_APP_OPTION('o', CONFFLAG_OPTIMIZETALKER ), AST_APP_OPTION('i', CONFFLAG_INTROUSER ), AST_APP_OPTION('m', CONFFLAG_MONITOR ), AST_APP_OPTION('p', CONFFLAG_POUNDEXIT ), @@ -406,6 +423,8 @@ static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int unsigned char *data; int len; int res = -1; + short *data2; + int x; if (!chan->_softhangup) res = ast_autoservice_start(chan); @@ -425,8 +444,12 @@ static void conf_play(struct ast_channel *chan, struct ast_conference *conf, int data = NULL; len = 0; } - if (data) - careful_write(conf->fd, data, len, 1); + if (data) { + data2 = alloca(len * 2); + for (x=0;x<len;x++) + data2[x] = AST_MULAW(data[x]); + careful_write(conf->fd, (unsigned char *)data2, len << 1, 1); + } ast_mutex_unlock(&conflock); @@ -450,12 +473,16 @@ static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin /* Make a new one */ cnf = calloc(1, sizeof(*cnf)); if (cnf) { + ast_mutex_init(&cnf->playlock); + ast_mutex_init(&cnf->listenlock); ast_copy_string(cnf->confno, confno, sizeof(cnf->confno)); ast_copy_string(cnf->pin, pin, sizeof(cnf->pin)); ast_copy_string(cnf->pinadmin, pinadmin, sizeof(cnf->pinadmin)); cnf->markedusers = 0; - cnf->chan = ast_request("zap", AST_FORMAT_ULAW, "pseudo", NULL); + cnf->chan = ast_request("zap", AST_FORMAT_SLINEAR, "pseudo", NULL); if (cnf->chan) { + ast_set_read_format(cnf->chan, AST_FORMAT_SLINEAR); + ast_set_write_format(cnf->chan, AST_FORMAT_SLINEAR); cnf->fd = cnf->chan->fds[0]; /* for use by conf_play() */ } else { ast_log(LOG_WARNING, "Unable to open pseudo channel - trying device\n"); @@ -482,6 +509,7 @@ static struct ast_conference *build_conf(char *confno, char *pin, char *pinadmin cnf = NULL; goto cnfout; } + /* Fill the conference struct */ cnf->start = time(NULL); cnf->zapconf = ztc.confno; @@ -752,7 +780,8 @@ static void conf_flush(int fd, struct ast_channel *chan) static int conf_free(struct ast_conference *conf) { struct ast_conference *prev = NULL, *cur = confs; - + int x; + while (cur) { if (cur == conf) { if (prev) @@ -779,6 +808,14 @@ static int conf_free(struct ast_conference *conf) } } + for (x=0;x<AST_FRAME_BITS;x++) { + if (conf->transframe[x]) + ast_frfree(conf->transframe[x]); + if (conf->transpath[x]) + ast_translator_free_path(conf->transpath[x]); + if (conf->origframe) + ast_frfree(conf->origframe); + } if (conf->chan) ast_hangup(conf->chan); else @@ -831,21 +868,26 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c return ret; } - if (confflags & CONFFLAG_RECORDCONF && conf->recording !=MEETME_RECORD_ACTIVE) { - conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"); + if (confflags & CONFFLAG_RECORDCONF) { if (!conf->recordingfilename) { - snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid); - conf->recordingfilename = ast_strdupa(recordingtmp); - } - conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"); - if (!conf->recordingformat) { - snprintf(recordingtmp, sizeof(recordingtmp), "wav"); - conf->recordingformat = ast_strdupa(recordingtmp); + conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"); + if (!conf->recordingfilename) { + snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid); + conf->recordingfilename = ast_strdupa(recordingtmp); + } + conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"); + if (!conf->recordingformat) { + snprintf(recordingtmp, sizeof(recordingtmp), "wav"); + conf->recordingformat = ast_strdupa(recordingtmp); + } + ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n", + conf->confno, conf->recordingfilename, conf->recordingformat); } + } + + if ((conf->recording == MEETME_RECORD_OFF) && ((confflags & CONFFLAG_RECORDCONF) || (conf->chan))) { pthread_attr_init(&conf->attr); pthread_attr_setdetachstate(&conf->attr, PTHREAD_CREATE_DETACHED); - ast_verbose(VERBOSE_PREFIX_4 "Starting recording of MeetMe Conference %s into file %s.%s.\n", - conf->confno, conf->recordingfilename, conf->recordingformat); ast_pthread_create(&conf->recordthread, &conf->attr, recordthread, conf); } @@ -861,7 +903,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c if (confflags & CONFFLAG_MARKEDUSER) conf->markedusers++; - ast_mutex_lock(&conflock); + ast_mutex_lock(&conf->playlock); if (!conf->firstuser) { /* Fill the first new User struct */ user->user_no = 1; @@ -873,7 +915,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c user->prevuser = conf->lastuser; if (conf->lastuser->nextuser) { ast_log(LOG_WARNING, "Error in User Management!\n"); - ast_mutex_unlock(&conflock); + ast_mutex_unlock(&conf->playlock); goto outrun; } else { conf->lastuser->nextuser = user; @@ -886,7 +928,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c user->adminflags = 0; user->talking = -1; conf->users++; - ast_mutex_unlock(&conflock); + ast_mutex_unlock(&conf->playlock); if (confflags & CONFFLAG_EXIT_CONTEXT) { if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) @@ -1029,7 +1071,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c ztc.chan = 0; ztc.confno = conf->zapconf; - ast_mutex_lock(&conflock); + ast_mutex_lock(&conf->playlock); if (!(confflags & CONFFLAG_QUIET) && (confflags & CONFFLAG_INTROUSER) && conf->users > 1) { if (conf->chan && ast_fileexists(user->namerecloc, NULL, NULL)) { @@ -1050,7 +1092,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c if (ioctl(fd, ZT_SETCONF, &ztc)) { ast_log(LOG_WARNING, "Error setting conference\n"); close(fd); - ast_mutex_unlock(&conflock); + ast_mutex_unlock(&conf->playlock); goto outrun; } ast_log(LOG_DEBUG, "Placed channel %s in ZAP conf %d\n", chan->name, conf->zapconf); @@ -1069,7 +1111,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c conf_play(chan, conf, ENTER); } - ast_mutex_unlock(&conflock); + ast_mutex_unlock(&conf->playlock); conf_flush(fd, chan); @@ -1106,7 +1148,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c x = 1; ast_channel_setoption(chan, AST_OPTION_TONE_VERIFY, &x, sizeof(char), 0); } - if (confflags & CONFFLAG_MONITORTALKER && !(dsp = ast_dsp_new())) { + if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER) && !(dsp = ast_dsp_new())) { ast_log(LOG_WARNING, "Unable to allocate DSP!\n"); res = -1; } @@ -1146,6 +1188,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c c = ast_waitfor_nandfds(&chan, 1, &fd, nfds, NULL, &outfd, &ms); + /* Update the struct with the actual confflags */ user->userflags = confflags; @@ -1269,14 +1312,17 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c user->zapchannel = !retryzap; goto zapretry; } - f = ast_read(c); + if (!(confflags & CONFFLAG_MONITOR)) + f = ast_read(c); + else + f = ast_read_noaudio(c); if (!f) break; if ((f->frametype == AST_FRAME_VOICE) && (f->subclass == AST_FORMAT_SLINEAR)) { if (user->talk.actual) ast_frame_adjust_volume(f, user->talk.actual); - if (confflags & CONFFLAG_MONITORTALKER) { + if (confflags & (CONFFLAG_MONITORTALKER | CONFFLAG_OPTIMIZETALKER)) { int totalsilence; if (user->talking == -1) @@ -1285,7 +1331,8 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c res = ast_dsp_silence(dsp, f, &totalsilence); if (!user->talking && totalsilence < MEETME_DELAYDETECTTALK) { user->talking = 1; - manager_event(EVENT_FLAG_CALL, "MeetmeTalking", + if (confflags & CONFFLAG_MONITORTALKER) + manager_event(EVENT_FLAG_CALL, "MeetmeTalking", "Channel: %s\r\n" "Uniqueid: %s\r\n" "Meetme: %s\r\n" @@ -1294,7 +1341,8 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c } if (user->talking && totalsilence > MEETME_DELAYDETECTENDTALK) { user->talking = 0; - manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking", + if (confflags & CONFFLAG_MONITORTALKER) + manager_event(EVENT_FLAG_CALL, "MeetmeStopTalking", "Channel: %s\r\n" "Uniqueid: %s\r\n" "Meetme: %s\r\n" @@ -1315,7 +1363,8 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c don't want to block, but we do want to at least *try* to write out all the samples. */ - careful_write(fd, f->data, f->datalen, 0); + if (user->talking || !(confflags & CONFFLAG_OPTIMIZETALKER)) + careful_write(fd, f->data, f->datalen, 0); } } else if ((f->frametype == AST_FRAME_DTMF) && (confflags & CONFFLAG_EXIT_CONTEXT)) { char tmp[2]; @@ -1511,10 +1560,46 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c fr.samples = res/2; fr.data = buf; fr.offset = AST_FRIENDLY_OFFSET; - if (user->listen.actual) - ast_frame_adjust_volume(&fr, user->listen.actual); - if (ast_write(chan, &fr) < 0) { - ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno)); + if (!user->listen.actual && + ((confflags & CONFFLAG_MONITOR) || + (user->adminflags & ADMINFLAG_MUTED) || + (user->talking && (confflags & CONFFLAG_OPTIMIZETALKER)) + )) { + int index; + for (index=0;index<AST_FRAME_BITS;index++) + if (chan->rawwriteformat & (1 << index)) + break; + if (index >= AST_FRAME_BITS) + goto bailoutandtrynormal; + ast_mutex_lock(&conf->listenlock); + if (!conf->transframe[index]) { + if (conf->origframe) { + if (!conf->transpath[index]) + conf->transpath[index] = ast_translator_build_path((1 << index), AST_FORMAT_SLINEAR); + if (conf->transpath[index]) { + conf->transframe[index] = ast_translate(conf->transpath[index], conf->origframe, 0); + if (!conf->transframe[index]) + conf->transframe[index] = &null_frame; + } + } + } + if (conf->transframe[index]) { + if (conf->transframe[index]->frametype != AST_FRAME_NULL) { + if (ast_write(chan, conf->transframe[index])) + ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno)); + } + } else { + ast_mutex_unlock(&conf->listenlock); + goto bailoutandtrynormal; + } + ast_mutex_unlock(&conf->listenlock); + } else { +bailoutandtrynormal: + if (user->listen.actual) + ast_frame_adjust_volume(&fr, user->listen.actual); + if (ast_write(chan, &fr) < 0) { + ast_log(LOG_WARNING, "Unable to write frame to channel: %s\n", strerror(errno)); + } } } else ast_log(LOG_WARNING, "Failed to read frame: %s\n", strerror(errno)); @@ -1556,7 +1641,7 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c outrun: ast_mutex_lock(&conflock); - if (confflags & CONFFLAG_MONITORTALKER && dsp) + if (dsp) ast_dsp_free(dsp); if (user->user_no) { /* Only cleanup users who really joined! */ @@ -1998,15 +2083,16 @@ static int conf_exec(struct ast_channel *chan, void *data) return res; } -static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident) { +static struct ast_conf_user* find_user(struct ast_conference *conf, char *callerident) +{ struct ast_conf_user *user = NULL; - char usrno[1024] = ""; - + int cid; + + sscanf(callerident, "%i", &cid); if (conf && callerident) { user = conf->firstuser; while (user) { - snprintf(usrno, sizeof(usrno), "%d", user->user_no); - if (strcmp(usrno, callerident) == 0) + if (cid == user->user_no) return user; user = user->nextuser; } @@ -2100,7 +2186,7 @@ static int admin_exec(struct ast_channel *chan, void *data) { if (user && (user->adminflags & ADMINFLAG_MUTED)) { user->adminflags ^= ADMINFLAG_MUTED; } else { - ast_log(LOG_NOTICE, "Specified User not found or he muted himself!"); + ast_log(LOG_NOTICE, "Specified User not found or he muted himself!\n"); } break; case 110: /* n: Unmute all users */ @@ -2140,39 +2226,62 @@ static void *recordthread(void *args) struct ast_conference *cnf = args; struct ast_frame *f=NULL; int flags; - struct ast_filestream *s; + struct ast_filestream *s=NULL; int res=0; + int x; + const char *oldrecordingfilename = NULL; if (!cnf || !cnf->chan) { pthread_exit(0); } + ast_stopstream(cnf->chan); flags = O_CREAT|O_TRUNC|O_WRONLY; - s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644); - if (s) { - cnf->recording = MEETME_RECORD_ACTIVE; - while (ast_waitfor(cnf->chan, -1) > -1) { - f = ast_read(cnf->chan); - if (!f) { - res = -1; - break; + + cnf->recording = MEETME_RECORD_ACTIVE; + while (ast_waitfor(cnf->chan, -1) > -1) { + if (cnf->recording == MEETME_RECORD_TERMINATE) { + ast_mutex_lock(&conflock); + ast_mutex_unlock(&conflock); + break; + } + if (!s && cnf->recordingfilename && (cnf->recordingfilename != oldrecordingfilename)) { + s = ast_writefile(cnf->recordingfilename, cnf->recordingformat, NULL, flags, 0, 0644); + oldrecordingfilename = cnf->recordingfilename; + } + + f = ast_read(cnf->chan); + if (!f) { + res = -1; + break; + } + if (f->frametype == AST_FRAME_VOICE) { + ast_mutex_lock(&cnf->listenlock); + for (x=0;x<AST_FRAME_BITS;x++) { + /* Free any translations that have occured */ + if (cnf->transframe[x]) { + ast_frfree(cnf->transframe[x]); + cnf->transframe[x] = NULL; + } + if (cnf->origframe) + ast_frfree(cnf->origframe); + cnf->origframe = f; } - if (f->frametype == AST_FRAME_VOICE) { + ast_mutex_unlock(&cnf->listenlock); + if (s) res = ast_writestream(s, f); - if (res) - break; - } - ast_frfree(f); - if (cnf->recording == MEETME_RECORD_TERMINATE) { - ast_mutex_lock(&conflock); - ast_mutex_unlock(&conflock); + if (res) { + ast_frfree(f); break; } } - cnf->recording = MEETME_RECORD_OFF; - ast_closestream(s); + ast_frfree(f); } + cnf->recording = MEETME_RECORD_OFF; + if (s) + ast_closestream(s); + pthread_exit(0); } diff --git a/channel.c b/channel.c index e573e855eb537cb541d5b99b6b767c405078fcff..078b9e541662eed5f65e2d408123d97383b80ae7 100644 --- a/channel.c +++ b/channel.c @@ -1773,7 +1773,7 @@ int ast_waitfordigit_full(struct ast_channel *c, int ms, int audiofd, int cmdfd) return 0; /* Time is up */ } -struct ast_frame *ast_read(struct ast_channel *chan) +static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio) { struct ast_frame *f = NULL; int blah; @@ -1897,7 +1897,10 @@ struct ast_frame *ast_read(struct ast_channel *chan) if (f && (f->frametype == AST_FRAME_VOICE)) { - if (!(f->subclass & chan->nativeformats)) { + if (dropaudio) { + ast_frfree(f); + f = &null_frame; + } else if (!(f->subclass & chan->nativeformats)) { /* This frame can't be from the current native formats -- drop it on the floor */ ast_log(LOG_NOTICE, "Dropping incompatible voice frame on %s of format %s since our native format has changed to %s\n", chan->name, ast_getformatname(f->subclass), ast_getformatname(chan->nativeformats)); @@ -1998,6 +2001,16 @@ struct ast_frame *ast_read(struct ast_channel *chan) return f; } +struct ast_frame *ast_read(struct ast_channel *chan) +{ + return __ast_read(chan, 0); +} + +struct ast_frame *ast_read_noaudio(struct ast_channel *chan) +{ + return __ast_read(chan, 1); +} + int ast_indicate(struct ast_channel *chan, int condition) { int res = -1; @@ -2247,7 +2260,12 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr) break; default: if (chan->tech->write) { - f = (chan->writetrans) ? ast_translate(chan->writetrans, fr, 0) : fr; + /* Bypass translator if we're writing format in the raw write format. This + allows mixing of native / non-native formats */ + if (fr->subclass == chan->rawwriteformat) + f = fr; + else + f = (chan->writetrans) ? ast_translate(chan->writetrans, fr, 0) : fr; if (f) { if (f->frametype == AST_FRAME_VOICE && chan->spies) queue_frame_to_spies(chan, f, SPY_WRITE); diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 6829f83f6a013a917590a9d82aa6492014237873..373ab6ac23d6a2bd210a8becc7c17d809e9a6297 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -741,6 +741,15 @@ int ast_waitfor_n_fd(int *fds, int n, int *ms, int *exception); disconnected. */ struct ast_frame *ast_read(struct ast_channel *chan); +/*! Reads a frame, returning AST_FRAME_NULL frame if audio. */ +/*! + * \param chan channel to read a frame from + * Read a frame. Returns a frame, or NULL on error. If it returns NULL, you + best just stop reading frames and assume the channel has been + disconnected. Audio is replaced with AST_FRAME_NULL to avoid + transcode when the resulting audio is not necessary. */ +struct ast_frame *ast_read_noaudio(struct ast_channel *chan); + /*! Write a frame to a channel */ /*! * \param chan destination channel of the frame