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));
520 static int moh1_exec(struct ast_channel *chan, void *data)
523 if (!data || !atoi(data)) {
524 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
527 if (ast_moh_start(chan, NULL)) {
528 ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
531 res = ast_safe_sleep(chan, atoi(data) * 1000);
536 static int moh2_exec(struct ast_channel *chan, void *data)
538 if (!data || ast_strlen_zero(data)) {
539 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
542 strncpy(chan->musicclass, data, sizeof(chan->musicclass) - 1);
546 static struct mohclass *get_mohbyname(char *name)
548 struct mohclass *moh;
551 if (!strcasecmp(name, moh->class))
558 static struct mohdata *mohalloc(struct mohclass *cl)
562 moh = malloc(sizeof(struct mohdata));
565 memset(moh, 0, sizeof(struct mohdata));
566 if (pipe(moh->pipe)) {
567 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
571 /* Make entirely non-blocking */
572 flags = fcntl(moh->pipe[0], F_GETFL);
573 fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
574 flags = fcntl(moh->pipe[1], F_GETFL);
575 fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
577 moh->next = cl->members;
582 static void moh_release(struct ast_channel *chan, void *data)
584 struct mohdata *moh = data, *prev, *cur;
586 ast_mutex_lock(&moh_lock);
589 cur = moh->parent->members;
593 prev->next = cur->next;
595 moh->parent->members = cur->next;
601 ast_mutex_unlock(&moh_lock);
604 oldwfmt = moh->origwfmt;
607 if (oldwfmt && ast_set_write_format(chan, oldwfmt))
608 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
609 if (option_verbose > 2)
610 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
614 static void *moh_alloc(struct ast_channel *chan, void *params)
617 struct mohclass *class;
620 res = mohalloc(class);
622 if (strcasecmp(params, "default"))
623 ast_log(LOG_WARNING, "No class: %s\n", (char *)params);
627 res->origwfmt = chan->writeformat;
628 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
629 ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name);
630 moh_release(NULL, res);
633 if (option_verbose > 2)
634 ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name);
639 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
642 struct mohdata *moh = data;
643 short buf[1280 + AST_FRIENDLY_OFFSET / 2];
645 if(!moh->parent->pid)
648 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
649 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), (int)len, chan->name);
650 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
652 res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
655 ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno));
659 memset(&f, 0, sizeof(f));
660 f.frametype = AST_FRAME_VOICE;
661 f.subclass = AST_FORMAT_SLINEAR;
665 f.data = buf + AST_FRIENDLY_OFFSET / 2;
666 f.offset = AST_FRIENDLY_OFFSET;
667 if (ast_write(chan, &f)< 0) {
668 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
675 static struct ast_generator mohgen =
678 release: moh_release,
679 generate: moh_generate,
682 static int moh_scan_files(struct mohclass *class) {
685 struct dirent *files_dirent;
687 char filepath[MAX_MOHFILE_LEN];
693 files_DIR = opendir(class->dir);
695 ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist", class->dir);
699 class->total_files = 0;
700 dirnamelen = strlen(class->dir) + 2;
703 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, '.'))) {
721 /* check to see if this file's format can be opened */
722 if (ast_fileexists(filepath, ext, NULL) == -1)
725 /* if the file is present in multiple formats, ensure we only put it into the list once */
726 for (i = 0; i < class->total_files; i++)
727 if (!strcmp(filepath, class->filearray[i]))
730 if (i == class->total_files)
731 strcpy(class->filearray[class->total_files++], filepath);
736 return class->total_files;
739 static int moh_register(char *classname, char *mode, char *param, char *miscargs)
741 struct mohclass *moh;
745 ast_mutex_lock(&moh_lock);
746 moh = get_mohbyname(classname);
747 ast_mutex_unlock(&moh_lock);
749 ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname);
752 moh = malloc(sizeof(struct mohclass));
755 memset(moh, 0, sizeof(struct mohclass));
757 moh->start -= respawn_time;
758 strncpy(moh->class, classname, sizeof(moh->class) - 1);
760 strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1);
761 if (strchr(miscargs,'r'))
764 if (!strcasecmp(mode, "files")) {
766 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
767 if (!moh_scan_files(moh)) {
771 } else if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) {
774 strncpy(moh->dir, param, sizeof(moh->dir) - 1);
776 if (!strcasecmp(mode, "custom"))
778 else if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb"))
780 else if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb"))
785 /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing... Is
786 there a better, yet reliable way to do this? */
787 moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
788 if (moh->pseudofd < 0) {
789 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
792 ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
797 if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) {
798 ast_log(LOG_WARNING, "Unable to create moh...\n");
799 if (moh->pseudofd > -1)
800 close(moh->pseudofd);
805 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode);
809 ast_mutex_lock(&moh_lock);
810 moh->next = mohclasses;
812 ast_mutex_unlock(&moh_lock);
816 static void local_ast_moh_cleanup(struct ast_channel *chan)
818 if(chan->music_state) {
819 free(chan->music_state);
820 chan->music_state = NULL;
824 static int local_ast_moh_start(struct ast_channel *chan, char *class)
826 struct mohclass *mohclass;
828 if (!class || ast_strlen_zero(class))
829 class = chan->musicclass;
830 if (!class || ast_strlen_zero(class))
832 ast_mutex_lock(&moh_lock);
833 mohclass = get_mohbyname(class);
834 ast_mutex_unlock(&moh_lock);
837 ast_log(LOG_WARNING, "No class: %s\n", (char *)class);
841 ast_set_flag(chan, AST_FLAG_MOH);
842 if (mohclass->total_files) {
843 return ast_activate_generator(chan, &moh_file_stream, mohclass);
845 return ast_activate_generator(chan, &mohgen, mohclass);
848 static void local_ast_moh_stop(struct ast_channel *chan)
850 ast_clear_flag(chan, AST_FLAG_MOH);
851 ast_deactivate_generator(chan);
853 if(chan->music_state) {
855 ast_closestream(chan->stream);
861 static int load_moh_classes(void)
863 struct ast_config *cfg;
864 struct ast_variable *var;
868 cfg = ast_load("musiconhold.conf");
870 var = ast_variable_browse(cfg, "classes");
872 data = strchr(var->value, ':');
876 args = strchr(data, ',');
881 if(!(get_mohbyname(var->name))) {
882 moh_register(var->name, var->value, data, args);
888 var = ast_variable_browse(cfg, "moh_files");
890 if(!(get_mohbyname(var->name))) {
891 args = strchr(var->value, ',');
896 moh_register(var->name, "files", var->value, args);
907 static void ast_moh_destroy(void)
909 struct mohclass *moh,*tmp;
911 int bytes, tbytes=0, stime = 0, pid = 0;
912 if (option_verbose > 1)
913 ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
914 ast_mutex_lock(&moh_lock);
919 ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
924 while ((bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime + 5) {
925 tbytes = tbytes + bytes;
927 ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
935 ast_mutex_unlock(&moh_lock);
939 static void moh_on_off(int on) {
940 struct ast_channel *chan = ast_channel_walk_locked(NULL);
942 if(ast_test_flag(chan, AST_FLAG_MOH)) {
944 local_ast_moh_start(chan,NULL);
946 ast_deactivate_generator(chan);
948 ast_mutex_unlock(&chan->lock);
949 chan = ast_channel_walk_locked(chan);
953 static int moh_cli(int fd, int argc, char *argv[])
958 x = load_moh_classes();
960 ast_cli(fd,"\n%d class%s reloaded.\n",x,x == 1 ? "" : "es");
964 static int cli_files_show(int fd, int argc, char *argv[])
967 struct mohclass *class;
969 ast_mutex_lock(&moh_lock);
970 for (class = mohclasses; class; class = class->next) {
971 if (!class->total_files)
974 ast_cli(fd, "Class: %s\n", class->class);
975 for (i = 0; i < class->total_files; i++)
976 ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
978 ast_mutex_unlock(&moh_lock);
983 static struct ast_cli_entry cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL};
985 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};
987 static int init_classes(void) {
988 struct mohclass *moh;
992 if (moh->total_files)
999 int load_module(void)
1002 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
1003 res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
1004 ast_register_atexit(ast_moh_destroy);
1005 ast_cli_register(&cli_moh);
1006 ast_cli_register(&cli_moh_files_show);
1008 res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
1010 res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
1012 return init_classes();
1017 return init_classes();
1021 int unload_module(void)
1026 char *description(void)
1028 return "Music On Hold Resource";
1033 /* Never allow Music On Hold to be unloaded
1034 unresolve needed symbols in the dialer */
1037 STANDARD_USECOUNT(res);
1046 return ASTERISK_GPL_KEY;