Index: res/res_musiconhold.c
===================================================================
RCS file: /usr/cvsroot/asterisk/res/res_musiconhold.c,v
retrieving revision 1.41
diff -u -r1.41 res_musiconhold.c
--- res/res_musiconhold.c	9 Dec 2004 19:55:01 -0000	1.41
+++ res/res_musiconhold.c	14 Dec 2004 16:19:49 -0000
@@ -16,6 +16,7 @@
 #include <asterisk/logger.h>
 #include <asterisk/channel.h>
 #include <asterisk/pbx.h>
+#include <../astconf.h>
 #include <asterisk/options.h>
 #include <asterisk/module.h>
 #include <asterisk/translate.h>
@@ -45,6 +46,8 @@
 #endif
 #include <unistd.h>
 #include <sys/ioctl.h>
+#define MAX_MOHFILES 512
+#define MAX_MOHFILE_LEN 128
 
 static char *app0 = "MusicOnHold";
 static char *app1 = "WaitMusicOnHold";
@@ -73,15 +76,27 @@
 
 static int respawn_time = 20;
 
+struct moh_files_state {
+	struct mohclass *class;
+	int origwfmt;
+	int samples;
+	unsigned char pos;
+	unsigned char counter;
+};
+
 struct mohclass {
 	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 +116,181 @@
 
 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);
+		}
+	}
+
+}
+
+
+static int ast_moh_files_next(struct ast_channel *chan) {
+	struct moh_files_state *state = chan->music_state;
+
+	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(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->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;
+	struct moh_files_state *state = chan->music_state;
+	
+	if (!chan->stream || !(f = ast_readframe(chan->stream))) {
+		state->samples = 0;
+		if (ast_moh_files_next(chan) > -1)
+			f = ast_readframe(chan->stream);
+	}
+
+	return f;
+}
+
+static int moh_files_calc_ratio(int need, int have, int *need_r, int *have_r) 
+{
+	/* calculate have/need into a/b ratio based on the least common mutiple */
+    int a = need, b = have, x = 0, mod = 0;
+	*need_r = *have_r = 0;
+
+    for (x=1; (mod= a % b); x++) {
+        if (x > 100) /*give up*/
+            return -1;
+        b += have;
+        if (mod == need)
+            a += need;
+	}
+    *need_r = b/need;
+    *have_r = a/have;
+    return 0;
+}
+
+static int moh_files_deliver_frame(struct ast_channel *chan) 
+{
+	struct moh_files_state *state = chan->music_state;
+	struct ast_frame *f = NULL;
+	if ((f = moh_files_readframe(chan))) {
+		state->samples += f->samples;
+		state->counter++;
+		ast_write(chan, f);
+		ast_frfree(f);
+		return 0;
+	} else
+		return -1;	
+}
+
+static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
+{
+	struct moh_files_state *state = chan->music_state;
+	int x = 0,requests=0,deliver=0;
+	len = samples * 2;
+
+	/* look for the least common mutiple and calculate the ratio. 
+	   For every "requests" frames requested we must deliver a total of "deliver" */
+	if (moh_files_calc_ratio(len,320,&requests,&deliver)) {
+		ast_log(LOG_ERROR,"Cannot find the least common mutiple between %d and 320.\n",len);
+		return -1;
+	}
+
+	/* deliver frames on a 1 to 1 ratio as long as we can */
+	if (moh_files_deliver_frame(chan))
+		return -1;
+
+
+	if (state->counter == requests) {
+		/* ok we have sent as many as we can on a 1 to 1 ratio, now we gotta send 
+		   the extra frames to break even */
+		for(x=requests;x<deliver;x++) {
+			if(moh_files_deliver_frame(chan))
+				return -1;
+		}
+		state->counter = 0;
+	}
+
+	return 0;
+}
+
+
+
+
+static void *moh_files_alloc(struct ast_channel *chan, void *params)
+{
+	struct moh_files_state *state = chan->music_state;
+	struct mohclass *class = params;
+
+	if(state) {
+		if (state->class != class) {
+			state->pos = 0;
+			state->samples = 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->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,76 +298,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 +424,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 +653,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 +661,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 +668,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);
 	}
@@ -525,6 +716,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 +788,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 +848,31 @@
 
 static int local_ast_moh_start(struct ast_channel *chan, char *class)
 {
+	struct mohclass *mohclass;
+	struct moh_files_state *state=NULL;
+
 	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->music_state) && ((state = malloc(sizeof(struct moh_files_state))))) {
+			memset(state, 0, sizeof(struct moh_files_state));
+			chan->music_state = state;
+		} else 
+			state = chan->music_state;
+		return state ? ast_activate_generator(chan, &moh_file_stream, mohclass) : -1;
+	} else
+		return ast_activate_generator(chan, &mohgen, mohclass);
 }
 
 static void local_ast_moh_stop(struct ast_channel *chan)
@@ -621,10 +899,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);
 	}
 }
@@ -667,6 +965,18 @@
 	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)
Index: channel.c
===================================================================
RCS file: /usr/cvsroot/asterisk/channel.c,v
retrieving revision 1.150
diff -u -r1.150 channel.c
--- channel.c	9 Dec 2004 19:55:01 -0000	1.150
+++ channel.c	14 Dec 2004 16:19:51 -0000
@@ -613,6 +613,10 @@
 		chan->monitor->stop( chan, 0 );
 	}
 
+	/* If there is native format music-on-hold state, free it */
+	if(chan->music_state)
+		free(chan->music_state);
+
 	/* Free translatosr */
 	if (chan->pvt->readtrans)
 		ast_translator_free_path(chan->pvt->readtrans);
@@ -2961,7 +2965,6 @@
 	return group;
 }
 
-
 static int (*ast_moh_start_ptr)(struct ast_channel *, char *) = NULL;
 static void (*ast_moh_stop_ptr)(struct ast_channel *) = NULL;
 
@@ -2997,3 +3000,4 @@
 	if(ast_moh_stop_ptr)
 		ast_moh_stop_ptr(chan);
 }
+
Index: include/asterisk/channel.h
===================================================================
RCS file: /usr/cvsroot/asterisk/include/asterisk/channel.h,v
retrieving revision 1.62
diff -u -r1.62 channel.h
--- include/asterisk/channel.h	7 Dec 2004 20:38:43 -0000	1.62
+++ include/asterisk/channel.h	14 Dec 2004 16:19:52 -0000
@@ -88,7 +88,8 @@
 
 	/*! Default music class */
 	char musicclass[MAX_LANGUAGE];
-
+	/*! Music State*/
+	void *music_state;
 	/*! Current generator data if there is any */
 	void *generatordata;
 	/*! Current active data generator */
