cvs diff -r 1.41 res_musiconhold.c Index: res_musiconhold.c =================================================================== RCS file: /usr/cvsroot/asterisk/res/res_musiconhold.c,v retrieving revision 1.41 retrieving revision 1.42 diff -u -d -b -w -r1.41 -r1.42 --- res_musiconhold.c 9 Dec 2004 19:55:01 -0000 1.41 +++ res_musiconhold.c 24 Dec 2004 01:40:07 -0000 1.42 @@ -16,6 +16,7 @@ #include #include #include +#include <../astconf.h> #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +47,8 @@ #endif #include #include +#define MAX_MOHFILES 512 +#define MAX_MOHFILE_LEN 128 static char *app0 = "MusicOnHold"; static char *app1 = "WaitMusicOnHold"; @@ -73,15 +77,28 @@ static int respawn_time = 20; +struct moh_files_state { + struct mohclass *class; + struct ast_filestream *stream; + int origwfmt; + int samples; + int sample_queue; + unsigned char pos; + unsigned char save_pos; +}; + struct mohclass { char class[80]; char dir[256]; char miscargs[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 +118,154 @@ 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->music_state; + + if (chan && state) { + 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); + } + state->save_pos = state->pos + 1; + } +} + + +static int ast_moh_files_next(struct ast_channel *chan) { + struct moh_files_state *state = chan->music_state; + + if(state->save_pos) { + state->pos = state->save_pos - 1; + state->save_pos = 0; + } else { + state->samples = 0; + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + state->pos++; + } + + 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_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name); + return -1; + } + if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) { + ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno)); + 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]); + + + if (state->samples) + ast_seekstream(chan->stream, state->samples, SEEK_SET); + + return state->pos; +} + + +static struct ast_frame *moh_files_readframe(struct ast_channel *chan) { + struct ast_frame *f = NULL; + + if (!chan->stream || !(f = ast_readframe(chan->stream))) { + if (ast_moh_files_next(chan) > -1) + f = ast_readframe(chan->stream); + } + + return f; +} + +static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct moh_files_state *state = chan->music_state; + struct ast_frame *f = NULL; + int res = 0; + state->sample_queue += samples; + + while(state->sample_queue > 0) { + if ((f = moh_files_readframe(chan))) { + state->samples += f->samples; + res = ast_write(chan, f); + ast_frfree(f); + if(res < 0) { + ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); + return -1; + } + state->sample_queue -= f->samples; + } else + return -1; + } + return res; +} + + +static void *moh_files_alloc(struct ast_channel *chan, void *params) +{ + struct moh_files_state *state; + struct mohclass *class = params; + int allocated = 0; + + if ((!chan->music_state) && ((state = malloc(sizeof(struct moh_files_state))))) { + chan->music_state = state; + allocated = 1; + } else + state = chan->music_state; + + + if(state) { + if (allocated || state->class != class) { + /* initialize */ + memset(state, 0, sizeof(struct moh_files_state)); + 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->music_state); + chan->music_state = NULL; + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name); + } + + + } + + return chan->music_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,6 +273,8 @@ 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); @@ -171,16 +327,17 @@ } files = 0; - if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) - { + if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) { strncpy(fns[files], class->dir, sizeof(fns[files]) - 1); argv[argc++] = fns[files]; files++; - } - else - { + } else { while((de = readdir(dir)) && (files < MAX_MP3S)) { - if ((strlen(de->d_name) > 3) && !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3")) { + if ((strlen(de->d_name) > 3) && + ((class->custom && + (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || + !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) + || !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++; @@ -189,6 +346,7 @@ } argv[argc] = NULL; closedir(dir); + if (pipe(fds)) { ast_log(LOG_WARNING, "Pipe failed\n"); return -1; @@ -230,8 +388,9 @@ } } /* Child */ - chdir(class->dir); + if(class->custom) { + chdir(class->dir); execv(argv[0], argv); } else { /* Default install is /usr/local/bin */ @@ -456,8 +615,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 +623,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)) { @@ -473,10 +630,6 @@ moh_release(NULL, res); res = NULL; } -#if 0 - /* Allow writes to interrupt */ - chan->writeinterrupt = 1; -#endif if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name); } @@ -489,7 +642,8 @@ struct mohdata *moh = data; short buf[1280 + AST_FRIENDLY_OFFSET / 2]; int res; - + if(!moh->parent->pid) + return - 1; len = samples * 2; if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name); @@ -525,6 +679,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 +751,30 @@ 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")) { + + 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 @@ -588,26 +808,58 @@ return 0; } +static void local_ast_moh_cleanup(struct ast_channel *chan) +{ + if(chan->music_state) { + free(chan->music_state); + chan->music_state = NULL; + } +} + static int local_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; + } + + ast_set_flag(chan, AST_FLAG_MOH); + if (mohclass->total_files) { + return ast_activate_generator(chan, &moh_file_stream, mohclass); + } else + return ast_activate_generator(chan, &mohgen, mohclass); } static void local_ast_moh_stop(struct ast_channel *chan) { + ast_clear_flag(chan, AST_FLAG_MOH); ast_deactivate_generator(chan); + + if(chan->music_state) { + if(chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + } } -static void load_moh_classes(void) +static int load_moh_classes(void) { struct ast_config *cfg; struct ast_variable *var; char *data; char *args; + int x = 0; cfg = ast_load("musiconhold.conf"); if (cfg) { var = ast_variable_browse(cfg, "classes"); @@ -621,53 +873,120 @@ *args = '\0'; args++; } + if(!(get_mohbyname(var->name))) { moh_register(var->name, var->value, data,args); + x++; + } + } + 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); + x++; } var = var->next; } + ast_destroy(cfg); } + return x; } static void ast_moh_destroy(void) { - struct mohclass *moh; + struct mohclass *moh,*tmp; char buff[8192]; - int bytes, tbytes=0, stime = 0; + int bytes, tbytes=0, stime = 0, pid = 0; if (option_verbose > 1) - ast_verbose(VERBOSE_PREFIX_2 "Destroying any remaining musiconhold processes\n"); + ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n"); ast_mutex_lock(&moh_lock); moh = mohclasses; + while(moh) { if (moh->pid) { ast_log(LOG_DEBUG, "killing %d!\n", moh->pid); stime = time(NULL); - kill(moh->pid, SIGKILL); + pid = moh->pid; + moh->pid = 0; + kill(pid, SIGKILL); while ((bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime + 5) { tbytes = tbytes + bytes; } - ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", moh->pid, tbytes); + ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes); close(moh->srcfd); - moh->pid = 0; } + tmp = moh; moh = moh->next; + free(tmp); } + mohclasses = NULL; ast_mutex_unlock(&moh_lock); } + +static void moh_on_off(int on) { + struct ast_channel *chan = ast_channel_walk_locked(NULL); + while(chan) { + if(ast_test_flag(chan, AST_FLAG_MOH)) { + if(on) + local_ast_moh_start(chan,NULL); + else + ast_deactivate_generator(chan); + } + ast_mutex_unlock(&chan->lock); + chan = ast_channel_walk_locked(chan); + } +} + +static int moh_cli(int fd, int argc, char *argv[]) +{ + int x = 0; + moh_on_off(0); + ast_moh_destroy(); + x = load_moh_classes(); + moh_on_off(1); + ast_cli(fd,"\n%d class%s reloaded.\n",x,x == 1 ? "" : "es"); + return 0; +} + +static struct ast_cli_entry cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL}; + + int load_module(void) { int res; load_moh_classes(); - ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop); + ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); res = ast_register_application(app0, moh0_exec, synopsis0, descrip0); ast_register_atexit(ast_moh_destroy); + ast_cli_register(&cli_moh); if (!res) res = ast_register_application(app1, moh1_exec, synopsis1, descrip1); if (!res) res = ast_register_application(app2, moh2_exec, synopsis2, descrip2); + 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) {