2 * Asterisk -- A telephony toolkit for Linux.
4 * Routines implementing music on hold
6 * Copyright (C) 1999-2004, Digium, Inc.
8 * Mark Spencer <markster@digium.com>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <asterisk/lock.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <../astconf.h>
20 #include <asterisk/options.h>
21 #include <asterisk/module.h>
22 #include <asterisk/translate.h>
23 #include <asterisk/say.h>
24 #include <asterisk/channel_pvt.h>
25 #include <asterisk/musiconhold.h>
26 #include <asterisk/config.h>
27 #include <asterisk/utils.h>
29 #include <asterisk/cli.h>
37 #include <sys/signal.h>
38 #include <netinet/in.h>
43 #include <linux/zaptel.h>
46 #endif /* __linux__ */
49 #include <sys/ioctl.h>
50 #define MAX_MOHFILES 512
51 #define MAX_MOHFILE_LEN 128
53 static char *app0 = "MusicOnHold";
54 static char *app1 = "WaitMusicOnHold";
55 static char *app2 = "SetMusicOnHold";
57 static char *synopsis0 = "Play Music On Hold indefinitely";
58 static char *synopsis1 = "Wait, playing Music On Hold";
59 static char *synopsis2 = "Set default Music On Hold class";
61 static char *descrip0 = "MusicOnHold(class): "
62 "Plays hold music specified by class. If omitted, the default\n"
63 "music source for the channel will be used. Set the default \n"
64 "class with the SetMusicOnHold() application.\n"
65 "Returns -1 on hangup.\n"
66 "Never returns otherwise.\n";
68 static char *descrip1 = "WaitMusicOnHold(delay): "
69 "Plays hold music specified number of seconds. Returns 0 when\n"
70 "done, or -1 on hangup. If no hold music is available, the delay will\n"
71 "still occur with no sound.\n";
73 static char *descrip2 = "SetMusicOnHold(class): "
74 "Sets the default class for music on hold for a given channel. When\n"
75 "music on hold is activated, this class will be used to select which\n"
78 static int respawn_time = 20;
80 struct moh_files_state {
81 struct mohclass *class;
82 struct ast_filestream *stream;
87 unsigned char save_pos;
94 char filearray[MAX_MOHFILES][MAX_MOHFILE_LEN];
97 int pid; /* PID of mpg123 */
104 struct mohdata *members;
105 /* Source of audio */
107 /* FD for timing source */
109 struct mohclass *next;
115 struct mohclass *parent;
116 struct mohdata *next;
119 static struct mohclass *mohclasses;
121 AST_MUTEX_DEFINE_STATIC(moh_lock);
123 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
124 #define MPG_123 "/usr/bin/mpg123"
127 static void moh_files_release(struct ast_channel *chan, void *data)
129 struct moh_files_state *state = chan->music_state;
132 if (option_verbose > 2)
133 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
135 if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
136 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
138 state->save_pos = state->pos + 1;
143 static int ast_moh_files_next(struct ast_channel *chan) {
144 struct moh_files_state *state = chan->music_state;
146 if(state->save_pos) {
147 state->pos = state->save_pos - 1;
152 ast_closestream(chan->stream);
157 if (state->class->randomize) {
158 srand(time(NULL)+getpid()+strlen(chan->name)-state->class->total_files);
163 state->pos = state->pos % state->class->total_files;
165 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
166 ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name);
169 if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
170 ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
175 if (option_verbose > 2)
176 ast_log(LOG_NOTICE, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
180 ast_seekstream(chan->stream, state->samples, SEEK_SET);
186 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) {
187 struct ast_frame *f = NULL;
189 if (!chan->stream || !(f = ast_readframe(chan->stream))) {
190 if (ast_moh_files_next(chan) > -1)
191 f = ast_readframe(chan->stream);
197 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
199 struct moh_files_state *state = chan->music_state;
200 struct ast_frame *f = NULL;
202 state->sample_queue += samples;
204 while(state->sample_queue > 0) {
205 if ((f = moh_files_readframe(chan))) {
206 state->samples += f->samples;
207 res = ast_write(chan, f);
210 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
213 state->sample_queue -= f->samples;
221 static void *moh_files_alloc(struct ast_channel *chan, void *params)
223 struct moh_files_state *state;
224 struct mohclass *class = params;
227 if ((!chan->music_state) && ((state = malloc(sizeof(struct moh_files_state))))) {
228 chan->music_state = state;
231 state = chan->music_state;
235 if (allocated || state->class != class) {
237 memset(state, 0, sizeof(struct moh_files_state));
238 state->class = class;
241 state->origwfmt = chan->writeformat;
243 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
244 ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name);
245 free(chan->music_state);
246 chan->music_state = NULL;
248 if (option_verbose > 2)
249 ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name);
255 return chan->music_state;
258 static struct ast_generator moh_file_stream =
260 alloc: moh_files_alloc,
261 release: moh_files_release,
262 generate: moh_files_generator,
265 static int spawn_mp3(struct mohclass *class)
269 char fns[MAX_MP3S][80];
270 char *argv[MAX_MP3S + 50];
278 dir = opendir(class->dir);
279 if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
280 ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
284 if (!class->custom) {
285 argv[argc++] = "mpg123";
288 argv[argc++] = "--mono";
290 argv[argc++] = "8000";
292 if (!class->single) {
294 argv[argc++] = "2048";
300 argv[argc++] = "4096";
302 argv[argc++] = "8192";
304 /* Look for extra arguments and add them to the list */
305 strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
307 while(argptr && !ast_strlen_zero(argptr)) {
308 argv[argc++] = argptr;
309 argptr = strchr(argptr, ',');
316 /* Format arguments for argv vector */
317 strncpy(xargs, class->miscargs, sizeof(xargs) - 1);
319 while(argptr && !ast_strlen_zero(argptr)) {
320 argv[argc++] = argptr;
321 argptr = strchr(argptr, ' ');
330 if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) {
331 strncpy(fns[files], class->dir, sizeof(fns[files]) - 1);
332 argv[argc++] = fns[files];
335 while((de = readdir(dir)) && (files < MAX_MP3S)) {
336 if ((strlen(de->d_name) > 3) &&
338 (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") ||
339 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln")))
340 || !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
341 strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1);
342 argv[argc++] = fns[files];
351 ast_log(LOG_WARNING, "Pipe failed\n");
355 printf("%d files total, %d args total\n", files, argc);
358 for (x=0;argv[x];x++)
359 printf("arg%d: %s\n", x, argv[x]);
363 ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
368 if (time(NULL) - class->start < respawn_time) {
369 sleep(respawn_time - (time(NULL) - class->start));
373 if (class->pid < 0) {
376 ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
382 /* Stdout goes to pipe */
383 dup2(fds[1], STDOUT_FILENO);
384 /* Close unused file descriptors */
385 for (x=3;x<8192;x++) {
386 if (-1 != fcntl(x, F_GETFL)) {
393 execv(argv[0], argv);
395 /* Default install is /usr/local/bin */
396 execv(LOCAL_MPG_123, argv);
397 /* Many places have it in /usr/bin */
398 execv(MPG_123, argv);
399 /* Check PATH as a last-ditch effort */
400 execvp("mpg123", argv);
402 ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
412 static void *monmp3thread(void *data)
414 #define MOH_MS_INTERVAL 100
416 struct mohclass *class = data;
422 struct timeval tv_tmp;
423 long error_sec, error_usec;
433 /* Spawn mp3 player if it's not there */
434 if (class->srcfd < 0) {
435 if ((class->srcfd = spawn_mp3(class)) < 0) {
436 ast_log(LOG_WARNING, "unable to spawn mp3player\n");
437 /* Try again later */
441 if (class->pseudofd > -1) {
442 /* Pause some amount of time */
443 res = read(class->pseudofd, buf, sizeof(buf));
446 if (gettimeofday(&tv_tmp, NULL) < 0) {
447 ast_log(LOG_NOTICE, "gettimeofday() failed!\n");
450 if (((unsigned long)(tv.tv_sec) > 0)&&((unsigned long)(tv.tv_usec) > 0)) {
451 if ((unsigned long)(tv_tmp.tv_usec) < (unsigned long)(tv.tv_usec)) {
452 tv_tmp.tv_usec += 1000000;
455 error_sec = (unsigned long)(tv_tmp.tv_sec) - (unsigned long)(tv.tv_sec);
456 error_usec = (unsigned long)(tv_tmp.tv_usec) - (unsigned long)(tv.tv_usec);
461 if (error_sec * 1000 + error_usec / 1000 < MOH_MS_INTERVAL) {
462 tv.tv_sec = tv_tmp.tv_sec + (MOH_MS_INTERVAL/1000 - error_sec);
463 tv.tv_usec = tv_tmp.tv_usec + ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec);
464 delay = (MOH_MS_INTERVAL/1000 - error_sec) * 1000 +
465 ((MOH_MS_INTERVAL % 1000) * 1000 - error_usec) / 1000;
467 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
468 tv.tv_sec = tv_tmp.tv_sec;
469 tv.tv_usec = tv_tmp.tv_usec;
472 if (tv.tv_usec > 1000000) {
474 tv.tv_usec-= 1000000;
477 usleep(delay * 1000);
478 res = 800; /* 800 samples */
483 if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) {
488 kill(class->pid, SIGKILL);
492 ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2);
495 ast_mutex_lock(&moh_lock);
496 moh = class->members;
499 if ((res = write(moh->pipe[1], sbuf, res2)) != res2)
501 ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
504 ast_mutex_unlock(&moh_lock);
509 static int moh0_exec(struct ast_channel *chan, void *data)
511 if (ast_moh_start(chan, data)) {
512 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
515 while(!ast_safe_sleep(chan, 10000));
519 static int moh1_exec(struct ast_channel *chan, void *data)
522 if (!data || !atoi(data)) {
523 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
526 if (ast_moh_start(chan, NULL)) {
527 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
530 res = ast_safe_sleep(chan, atoi(data) * 1000);
535 static int moh2_exec(struct ast_channel *chan, void *data)
537 if (!data || ast_strlen_zero(data)) {
538 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
541 strncpy(chan->musicclass, data, sizeof(chan->musicclass) - 1);
545 static struct mohclass *get_mohbyname(char *name)
547 struct mohclass *moh;
550 if (!strcasecmp(name, moh->class))
557 static struct mohdata *mohalloc(struct mohclass *cl)
561 moh = malloc(sizeof(struct mohdata));
564 memset(moh, 0, sizeof(struct mohdata));
565 if (pipe(moh->pipe)) {
566 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
570 /* Make entirely non-blocking */
571 flags = fcntl(moh->pipe[0], F_GETFL);
572 fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
573 flags = fcntl(moh->pipe[1], F_GETFL);
574 fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
576 moh->next = cl->members;
581 static void moh_release(struct ast_channel *chan, void *data)
583 struct mohdata *moh = data, *prev, *cur;
585 ast_mutex_lock(&moh_lock);
588 cur = moh->parent->members;
592 prev->next = cur->next;
594 moh->parent->members = cur->next;
600 ast_mutex_unlock(&moh_lock);
603 oldwfmt = moh->origwfmt;
606 if (oldwfmt && ast_set_write_format(chan, oldwfmt))
607 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
608 if (option_verbose > 2)
609 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
613 static void *moh_alloc(struct ast_channel *chan, void *params)
616 struct mohclass *class;
619 res = mohalloc(class);
621 if (strcasecmp(params, "default"))
622 ast_log(LOG_WARNING, "No class: %s\n", (char *)params);
626 res->origwfmt = chan->writeformat;
627 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
628 ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name);
629 moh_release(NULL, res);
632 if (option_verbose > 2)
633 ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name);
638 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
641 struct mohdata *moh = data;
642 short buf[1280 + AST_FRIENDLY_OFFSET / 2];
644 if(!moh->parent->pid)
647 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
648 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name);
649 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
651 res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
654 ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno));
658 memset(&f, 0, sizeof(f));
659 f.frametype = AST_FRAME_VOICE;
660 f.subclass = AST_FORMAT_SLINEAR;
664 f.data = buf + AST_FRIENDLY_OFFSET / 2;
665 f.offset = AST_FRIENDLY_OFFSET;
666 if (ast_write(chan, &f)< 0) {
667 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
674 static struct ast_generator mohgen =
677 release: moh_release,
678 generate: moh_generate,
681 static int moh_scan_files(struct mohclass *class) {
684 struct dirent *files_dirent;
686 char filepath[MAX_MOHFILE_LEN];
692 files_DIR = opendir(class->dir);
694 ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist", class->dir);
698 class->total_files = 0;
699 dirnamelen = strlen(class->dir) + 2;
702 memset(class->filearray, 0, MAX_MOHFILES*MAX_MOHFILE_LEN);
704 while ((files_dirent = readdir(files_DIR))) {
705 if ((strlen(files_dirent->d_name) < 4) || ((strlen(files_dirent->d_name) + dirnamelen) >= MAX_MOHFILE_LEN))
708 snprintf(filepath, MAX_MOHFILE_LEN, "%s/%s", class->dir, files_dirent->d_name);
710 if (stat(filepath, &statbuf))
713 if (!S_ISREG(statbuf.st_mode))
716 if ((ext = strrchr(filepath, '.')))
719 /* check to see if this file's format can be opened */
720 if (ast_fileexists(filepath, ext, NULL) == -1)
723 /* if the file is present in multiple formats, ensure we only put it into the list once */
724 for (i = 0; i < class->total_files; i++)
725 if (!strcmp(filepath, class->filearray[i]))
728 if (i == class->total_files)
729 strcpy(class->filearray[class->total_files++], filepath);
734 return class->total_files;
737 static int moh_register(char *classname, char *mode, char *param, char *miscargs)
739 struct mohclass *moh;
743 ast_mutex_lock(&moh_lock);
744 moh = get_mohbyname(classname);
745 ast_mutex_unlock(&moh_lock);
747 ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname);
750 moh = malloc(sizeof(struct mohclass));
753 memset(moh, 0, sizeof(struct mohclass));
755 moh->start -= respawn_time;
756 strncpy(moh->class, classname, sizeof(moh->class) - 1);
758 strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1);
759 if (strchr(miscargs,'r'))
762 if (!strcasecmp(mode, "files")) {
764 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
765 if (!moh_scan_files(moh)) {
769 } else if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) {
772 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
774 if (!strcasecmp(mode, "custom"))
776 else if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb"))
778 else if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb"))
783 /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing... Is
784 there a better, yet reliable way to do this? */
785 moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
786 if (moh->pseudofd < 0) {
787 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
790 ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
795 if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) {
796 ast_log(LOG_WARNING, "Unable to create moh...\n");
797 if (moh->pseudofd > -1)
798 close(moh->pseudofd);
803 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode);
807 ast_mutex_lock(&moh_lock);
808 moh->next = mohclasses;
810 ast_mutex_unlock(&moh_lock);
814 static void local_ast_moh_cleanup(struct ast_channel *chan)
816 if(chan->music_state) {
817 free(chan->music_state);
818 chan->music_state = NULL;
822 static int local_ast_moh_start(struct ast_channel *chan, char *class)
824 struct mohclass *mohclass;
826 if (!class || ast_strlen_zero(class))
827 class = chan->musicclass;
828 if (!class || ast_strlen_zero(class))
830 ast_mutex_lock(&moh_lock);
831 mohclass = get_mohbyname(class);
832 ast_mutex_unlock(&moh_lock);
835 ast_log(LOG_WARNING, "No class: %s\n", (char *)class);
839 ast_set_flag(chan, AST_FLAG_MOH);
840 if (mohclass->total_files) {
841 return ast_activate_generator(chan, &moh_file_stream, mohclass);
843 return ast_activate_generator(chan, &mohgen, mohclass);
846 static void local_ast_moh_stop(struct ast_channel *chan)
848 ast_clear_flag(chan, AST_FLAG_MOH);
849 ast_deactivate_generator(chan);
851 if(chan->music_state) {
853 ast_closestream(chan->stream);
859 static int load_moh_classes(void)
861 struct ast_config *cfg;
862 struct ast_variable *var;
866 cfg = ast_load("musiconhold.conf");
868 var = ast_variable_browse(cfg, "classes");
870 data = strchr(var->value, ':');
874 args = strchr(data, ',');
879 if(!(get_mohbyname(var->name))) {
880 moh_register(var->name, var->value, data, args);
886 var = ast_variable_browse(cfg, "moh_files");
888 if(!(get_mohbyname(var->name))) {
889 args = strchr(var->value, ',');
894 moh_register(var->name, "files", var->value, args);
905 static void ast_moh_destroy(void)
907 struct mohclass *moh,*tmp;
909 int bytes, tbytes=0, stime = 0, pid = 0;
910 if (option_verbose > 1)
911 ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
912 ast_mutex_lock(&moh_lock);
917 ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
922 while ((bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime + 5) {
923 tbytes = tbytes + bytes;
925 ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
933 ast_mutex_unlock(&moh_lock);
937 static void moh_on_off(int on) {
938 struct ast_channel *chan = ast_channel_walk_locked(NULL);
940 if(ast_test_flag(chan, AST_FLAG_MOH)) {
942 local_ast_moh_start(chan,NULL);
944 ast_deactivate_generator(chan);
946 ast_mutex_unlock(&chan->lock);
947 chan = ast_channel_walk_locked(chan);
951 static int moh_cli(int fd, int argc, char *argv[])
956 x = load_moh_classes();
958 ast_cli(fd,"\n%d class%s reloaded.\n",x,x == 1 ? "" : "es");
962 static int cli_files_show(int fd, int argc, char *argv[])
965 struct mohclass *class;
967 ast_mutex_lock(&moh_lock);
968 for (class = mohclasses; class; class = class->next) {
969 if (!class->total_files)
972 ast_cli(fd, "Class: %s\n", class->class);
973 for (i = 0; i < class->total_files; i++)
974 ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
976 ast_mutex_unlock(&moh_lock);
981 static struct ast_cli_entry cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL};
983 static struct ast_cli_entry cli_moh_files_show = { { "moh", "files", "show"}, cli_files_show, "List MOH file-based classes", "Lists all loaded file-based MOH classes and their files", NULL};
986 int load_module(void)
990 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
991 res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
992 ast_register_atexit(ast_moh_destroy);
993 ast_cli_register(&cli_moh);
994 ast_cli_register(&cli_moh_files_show);
996 res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
998 res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
1005 struct mohclass *moh = mohclasses;
1008 if (moh->total_files)
1009 moh_scan_files(moh);
1016 int unload_module(void)
1021 char *description(void)
1023 return "Music On Hold Resource";
1028 /* Never allow Music On Hold to be unloaded
1029 unresolve needed symbols in the dialer */
1032 STANDARD_USECOUNT(res);
1041 return ASTERISK_GPL_KEY;