Index: channel.c =================================================================== RCS file: /usr/cvsroot/asterisk/channel.c,v retrieving revision 1.148 diff -u -r1.148 channel.c --- channel.c 7 Nov 2004 16:21:00 -0000 1.148 +++ channel.c 24 Nov 2004 22:03:09 -0000 @@ -613,6 +613,9 @@ chan->monitor->stop( chan, 0 ); } + /* If there is native format music-on-hold state, free it */ + free(chan->moh_files_state); + /* Free translatosr */ if (chan->pvt->readtrans) ast_translator_free_path(chan->pvt->readtrans); Index: include/asterisk/channel.h =================================================================== RCS file: /usr/cvsroot/asterisk/include/asterisk/channel.h,v retrieving revision 1.61 diff -u -r1.61 channel.h --- include/asterisk/channel.h 7 Nov 2004 16:21:01 -0000 1.61 +++ include/asterisk/channel.h 24 Nov 2004 22:03:14 -0000 @@ -73,6 +73,15 @@ int cid_tns; }; +//! structure for storing native format based music-on-hold info in a channel +struct moh_files_state { + int origwfmt; + struct mohclass *class; + int pos; + struct timeval started; + int elapsed; +}; + //! Main Channel structure associated with a channel. /*! * This is the side of it mostly used by the pbx and call management. @@ -89,7 +98,7 @@ /*! Default music class */ char musicclass[MAX_LANGUAGE]; - + struct moh_files_state *moh_files_state; /*! Current generator data if there is any */ void *generatordata; /*! Current active data generator */ Index: res/res_musiconhold.c =================================================================== RCS file: /usr/cvsroot/asterisk/res/res_musiconhold.c,v retrieving revision 1.40 diff -u -r1.40 res_musiconhold.c --- res/res_musiconhold.c 17 Nov 2004 18:16:08 -0000 1.40 +++ res/res_musiconhold.c 24 Nov 2004 22:03:19 -0000 @@ -16,6 +16,7 @@ #include #include #include +#include <../astconf.h> #include #include #include @@ -45,6 +46,8 @@ #endif #include #include +#define MAX_MOHFILES 512 +#define MAX_MOHFILE_LEN 128 static char *app0 = "MusicOnHold"; static char *app1 = "WaitMusicOnHold"; @@ -77,11 +80,15 @@ char class[80]; char dir[256]; char miscargs[256]; + char customexec[256]; + char filearray[MAX_MOHFILES][MAX_MOHFILE_LEN]; + int total_files; int destroyme; int pid; /* PID of mpg123 */ int quiet; int single; int custom; + int randomize; time_t start; pthread_t thread; struct mohdata *members; @@ -101,17 +108,141 @@ static struct mohclass *mohclasses; - AST_MUTEX_DEFINE_STATIC(moh_lock); #define LOCAL_MPG_123 "/usr/local/bin/mpg123" #define MPG_123 "/usr/bin/mpg123" #define MAX_MP3S 256 +static void moh_files_release(struct ast_channel *chan, void *data) +{ + struct moh_files_state *state = chan->moh_files_state; + + if (!chan) + return; + + ast_mutex_lock(&chan->lock); + if (state) { + if (chan->stream) { + struct timeval stopped; + + ast_closestream(chan->stream); + chan->stream = NULL; + if (!state->class->randomize) { + gettimeofday(&stopped, NULL); + state->elapsed += ((stopped.tv_sec * 1000) + (stopped.tv_usec / 1000)) - ((state->started.tv_sec * 1000) + (state->started.tv_usec / 1000)); + } + + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name); + } + + if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt); + } + } + ast_mutex_unlock(&chan->lock); +} + + +static int ast_moh_files_next(struct ast_channel *chan) { + struct moh_files_state *state = chan->moh_files_state; + + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + state->pos++; + state->elapsed = 0; + } + + if (state->class->randomize) { + srand(time(NULL)+getpid()+strlen(chan->name)-state->class->total_files); + state->pos = rand(); + } + + state->pos = state->pos % state->class->total_files; + + if (!ast_openstream(chan, state->class->filearray[state->pos], chan->language)) { + ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno)); + state->elapsed = 0; + state->pos++; + return -1; + } + + if (option_verbose > 2) + ast_log(LOG_NOTICE, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]); + + gettimeofday(&state->started, NULL); + + if (state->elapsed) + ast_stream_fastforward(chan->stream, state->elapsed); + + return state->pos; +} + + +static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct ast_frame *f = NULL; + short buf[2048 + AST_FRIENDLY_OFFSET / 2]; + + len = samples * 2; + + if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { + ast_log(LOG_WARNING, "Can't generate %d bytes of data!\n" ,len); + len = sizeof(buf) - AST_FRIENDLY_OFFSET; + } + + if (!chan->stream || !(f = ast_readframe(chan->stream))) + if (ast_moh_files_next(chan) > -1) + f = ast_readframe(chan->stream); + + if (f) { + ast_write(chan, f); + ast_frfree(f); + return 0; + } + + return -1; +} + +static void *moh_files_alloc(struct ast_channel *chan, void *params) +{ + struct moh_files_state *state = chan->moh_files_state; + struct mohclass *class = params; + + if (state->class != class) { + state->pos = 0; + state->elapsed = 0; + state->class = class; + } + + state->origwfmt = chan->writeformat; + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name); + free(chan->moh_files_state); + chan->moh_files_state = NULL; + } else { + chan->writeinterrupt = 1; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name); + } + + return chan->moh_files_state; +} + +static struct ast_generator moh_file_stream = +{ + alloc: moh_files_alloc, + release: moh_files_release, + generate: moh_files_generator, +}; + static int spawn_mp3(struct mohclass *class) { int fds[2]; - int files; + int files=0; char fns[MAX_MP3S][80]; char *argv[MAX_MP3S + 50]; char xargs[256]; @@ -119,76 +250,91 @@ int argc = 0; DIR *dir; struct dirent *de; - dir = opendir(class->dir); - if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) { - ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir); - return -1; - } - - if (!class->custom) { - argv[argc++] = "mpg123"; - argv[argc++] = "-q"; - argv[argc++] = "-s"; - argv[argc++] = "--mono"; - argv[argc++] = "-r"; - argv[argc++] = "8000"; - - if (!class->single) { - argv[argc++] = "-b"; - argv[argc++] = "2048"; - } - - argv[argc++] = "-f"; - - if (class->quiet) - argv[argc++] = "4096"; - else - argv[argc++] = "8192"; - - /* Look for extra arguments and add them to the list */ - strncpy(xargs, class->miscargs, sizeof(xargs) - 1); - argptr = xargs; - while(argptr && !ast_strlen_zero(argptr)) { - argv[argc++] = argptr; - argptr = strchr(argptr, ','); - if (argptr) { - *argptr = '\0'; - argptr++; - } - } + + if(class->customexec && strlen(class->customexec)) { + argc = 0; + files=1; + strncpy(xargs, class->customexec, sizeof(xargs) - 1); + argptr = xargs; + + while(argptr && strlen(argptr)) { + argv[argc] = argptr; + argptr = strchr(argptr, ','); + if (argptr) { + *argptr = '\0'; + argptr++; + } + argc++; + } } else { - /* Format arguments for argv vector */ - strncpy(xargs, class->miscargs, sizeof(xargs) - 1); - argptr = xargs; - while(argptr && !ast_strlen_zero(argptr)) { - argv[argc++] = argptr; - argptr = strchr(argptr, ' '); - if (argptr) { - *argptr = '\0'; - argptr++; + dir = opendir(class->dir); + if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) { + ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir); + return -1; + } + + if (!class->custom) { + argv[argc++] = "mpg123"; + argv[argc++] = "-q"; + argv[argc++] = "-s"; + argv[argc++] = "--mono"; + argv[argc++] = "-r"; + argv[argc++] = "8000"; + + if (!class->single) { + argv[argc++] = "-b"; + argv[argc++] = "2048"; + } + + argv[argc++] = "-f"; + + if (class->quiet) + argv[argc++] = "4096"; + else + argv[argc++] = "8192"; + + /* Look for extra arguments and add them to the list */ + strncpy(xargs, class->miscargs, sizeof(xargs) - 1); + argptr = xargs; + while(argptr && !ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + argptr = strchr(argptr, ','); + if (argptr) { + *argptr = '\0'; + argptr++; + } + } + } else { + /* Format arguments for argv vector */ + strncpy(xargs, class->miscargs, sizeof(xargs) - 1); + argptr = xargs; + while(argptr && !ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + argptr = strchr(argptr, ' '); + if (argptr) { + *argptr = '\0'; + argptr++; + } } } - } - files = 0; - if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) - { - strncpy(fns[files], class->dir, sizeof(fns[files]) - 1); - argv[argc++] = fns[files]; - files++; - } - else - { - while((de = readdir(dir)) && (files < MAX_MP3S)) { - if ((strlen(de->d_name) > 3) && !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3")) { - strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1); - argv[argc++] = fns[files]; - files++; + files = 0; + if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) { + strncpy(fns[files], class->dir, sizeof(fns[files]) - 1); + argv[argc++] = fns[files]; + files++; + } else { + while((de = readdir(dir)) && (files < MAX_MP3S)) { + if ((strlen(de->d_name) > 3) && !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3")) { + strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1); + argv[argc++] = fns[files]; + files++; + } } } + argv[argc] = NULL; + closedir(dir); } - argv[argc] = NULL; - closedir(dir); if (pipe(fds)) { ast_log(LOG_WARNING, "Pipe failed\n"); return -1; @@ -230,6 +376,9 @@ } } /* Child */ + /* try custom */ + if(!ast_strlen_zero(class->customexec)) + execv(argv[0], argv); chdir(class->dir); if(class->custom) { execv(argv[0], argv); @@ -456,8 +605,7 @@ { struct mohdata *res; struct mohclass *class; - ast_mutex_lock(&moh_lock); - class = get_mohbyname(params); + class = params; if (class) res = mohalloc(class); else { @@ -465,7 +613,6 @@ ast_log(LOG_WARNING, "No class: %s\n", (char *)params); res = NULL; } - ast_mutex_unlock(&moh_lock); if (res) { res->origwfmt = chan->writeformat; if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { @@ -525,6 +672,58 @@ generate: moh_generate, }; +static int moh_scan_files(struct mohclass *class) { + + DIR *files_DIR; + struct dirent *files_dirent; + char path[512]; + char filepath[MAX_MOHFILE_LEN]; + char *scan; + struct stat statbuf; + int dirnamelen; + int i; + + files_DIR = opendir(class->dir); + if (!files_DIR) { + ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist", class->dir); + return -1; + } + + class->total_files = 0; + dirnamelen = strlen(class->dir) + 2; + getcwd(path, 512); + chdir(class->dir); + memset(class->filearray, 0, MAX_MOHFILES*MAX_MOHFILE_LEN); + + while ((files_dirent = readdir(files_DIR))) { + if ((strlen(files_dirent->d_name) < 4) || ((strlen(files_dirent->d_name) + dirnamelen) >= MAX_MOHFILE_LEN)) + continue; + + snprintf(filepath, MAX_MOHFILE_LEN, "%s/%s", class->dir, files_dirent->d_name); + + if (stat(filepath, &statbuf)) + continue; + + if (!S_ISREG(statbuf.st_mode)) + continue; + + if ((scan = strrchr(filepath, '.'))) + *scan = '\0'; + + /* if the file is present in multiple formats, ensure we only put it into the list once */ + for (i = 0; i < class->total_files; i++) + if (!strcmp(filepath, class->filearray[i])) + break; + + if (i == class->total_files) + strcpy(class->filearray[class->total_files++], filepath); + } + + closedir(files_DIR); + chdir(path); + return class->total_files; +} + static int moh_register(char *classname, char *mode, char *param, char *miscargs) { struct mohclass *moh; @@ -545,16 +744,31 @@ time(&moh->start); moh->start -= respawn_time; strncpy(moh->class, classname, sizeof(moh->class) - 1); - if (miscargs) + if (miscargs) { strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1); - if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) { + if (strchr(miscargs,'r')) + moh->randomize=1; + } + if (!strcasecmp(mode, "files")) { + if (param) + strncpy(moh->dir, param, sizeof(moh->dir) - 1); + if (!moh_scan_files(moh)) { + free(moh); + return -1; + } + } else if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom") || !strcasecmp(mode, "customexec")) { + if(!strcasecmp(mode, "customexec") && !ast_strlen_zero(param)) + strncpy(moh->customexec, param, sizeof(moh->customexec) - 1); + else if (param) + strncpy(moh->dir, param, sizeof(moh->dir) - 1); + if (!strcasecmp(mode, "custom")) moh->custom = 1; - if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb")) + else if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb")) moh->single = 1; - if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb")) + else if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb")) moh->quiet = 1; - strncpy(moh->dir, param, sizeof(moh->dir) - 1); + moh->srcfd = -1; #ifdef ZAPATA_MOH /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing... Is @@ -590,11 +804,31 @@ int ast_moh_start(struct ast_channel *chan, char *class) { + struct mohclass *mohclass; + if (!class || ast_strlen_zero(class)) class = chan->musicclass; if (!class || ast_strlen_zero(class)) class = "default"; - return ast_activate_generator(chan, &mohgen, class); + ast_mutex_lock(&moh_lock); + mohclass = get_mohbyname(class); + ast_mutex_unlock(&moh_lock); + + if (!mohclass) { + ast_log(LOG_WARNING, "No class: %s\n", (char *)class); + return -1; + } + + if (mohclass->total_files) { + if ((!chan->moh_files_state) && ((chan->moh_files_state = malloc(sizeof(struct moh_files_state))))) + memset(chan->moh_files_state, 0, sizeof(struct moh_files_state)); + if (chan->moh_files_state) + return ast_activate_generator(chan, &moh_file_stream, mohclass); + else + return -1; + + } else + return ast_activate_generator(chan, &mohgen, mohclass); } void ast_moh_stop(struct ast_channel *chan) @@ -621,10 +855,30 @@ *args = '\0'; args++; } - moh_register(var->name, var->value, data,args); + if(!(get_mohbyname(var->name))) + moh_register(var->name, var->value, data, args); } var = var->next; } + var = ast_variable_browse(cfg, "custom_exec"); + while(var) { + if(!(get_mohbyname(var->name))) + moh_register(var->name, "customexec", var->value, NULL); + var = var->next; + } + var = ast_variable_browse(cfg, "moh_files"); + while(var) { + if(!(get_mohbyname(var->name))) { + args = strchr(var->value, ','); + if (args) { + *args = '\0'; + args++; + } + moh_register(var->name, "files", var->value, args); + } + var = var->next; + } + ast_destroy(cfg); } } @@ -668,6 +922,18 @@ return res; } +int reload(void) +{ + struct mohclass *moh = mohclasses; + load_moh_classes(); + while(moh) { + if (moh->total_files) + moh_scan_files(moh); + moh = moh->next; + } + return 0; +} + int unload_module(void) { return -1;