Fri Sep 25 19:28:15 2009

Asterisk developer's documentation


res_musiconhold.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Routines implementing music on hold
00022  *
00023  * \arg See also \ref Config_moh
00024  * 
00025  * \author Mark Spencer <markster@digium.com>
00026  */
00027 
00028 /*** MODULEINFO
00029    <conflict>win32</conflict>
00030    <use>zaptel</use>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 89053 $")
00036 
00037 #include <stdlib.h>
00038 #include <errno.h>
00039 #include <unistd.h>
00040 #include <string.h>
00041 #include <signal.h>
00042 #include <stdlib.h>
00043 #include <stdio.h>
00044 #include <sys/time.h>
00045 #include <sys/signal.h>
00046 #include <netinet/in.h>
00047 #include <sys/stat.h>
00048 #include <dirent.h>
00049 #include <unistd.h>
00050 #include <sys/ioctl.h>
00051 #ifdef SOLARIS
00052 #include <thread.h>
00053 #endif
00054 
00055 #ifdef HAVE_ZAPTEL
00056 #include <zaptel/zaptel.h>
00057 #endif
00058 
00059 #include "asterisk/lock.h"
00060 #include "asterisk/file.h"
00061 #include "asterisk/logger.h"
00062 #include "asterisk/channel.h"
00063 #include "asterisk/pbx.h"
00064 #include "asterisk/options.h"
00065 #include "asterisk/module.h"
00066 #include "asterisk/translate.h"
00067 #include "asterisk/say.h"
00068 #include "asterisk/musiconhold.h"
00069 #include "asterisk/config.h"
00070 #include "asterisk/utils.h"
00071 #include "asterisk/cli.h"
00072 #include "asterisk/stringfields.h"
00073 #include "asterisk/linkedlists.h"
00074 
00075 #define INITIAL_NUM_FILES   8
00076 
00077 static char *app0 = "MusicOnHold";
00078 static char *app1 = "WaitMusicOnHold";
00079 static char *app2 = "SetMusicOnHold";
00080 static char *app3 = "StartMusicOnHold";
00081 static char *app4 = "StopMusicOnHold";
00082 
00083 static char *synopsis0 = "Play Music On Hold indefinitely";
00084 static char *synopsis1 = "Wait, playing Music On Hold";
00085 static char *synopsis2 = "Set default Music On Hold class";
00086 static char *synopsis3 = "Play Music On Hold";
00087 static char *synopsis4 = "Stop Playing Music On Hold";
00088 
00089 static char *descrip0 = "MusicOnHold(class): "
00090 "Plays hold music specified by class.  If omitted, the default\n"
00091 "music source for the channel will be used. Set the default \n"
00092 "class with the SetMusicOnHold() application.\n"
00093 "Returns -1 on hangup.\n"
00094 "Never returns otherwise.\n";
00095 
00096 static char *descrip1 = "WaitMusicOnHold(delay): "
00097 "Plays hold music specified number of seconds.  Returns 0 when\n"
00098 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
00099 "still occur with no sound.\n";
00100 
00101 static char *descrip2 = "SetMusicOnHold(class): "
00102 "Sets the default class for music on hold for a given channel.  When\n"
00103 "music on hold is activated, this class will be used to select which\n"
00104 "music is played.\n";
00105 
00106 static char *descrip3 = "StartMusicOnHold(class): "
00107 "Starts playing music on hold, uses default music class for channel.\n"
00108 "Starts playing music specified by class.  If omitted, the default\n"
00109 "music source for the channel will be used.  Always returns 0.\n";
00110 
00111 static char *descrip4 = "StopMusicOnHold: "
00112 "Stops playing music on hold.\n";
00113 
00114 static int respawn_time = 20;
00115 
00116 struct moh_files_state {
00117    struct mohclass *class;
00118    int origwfmt;
00119    int samples;
00120    int sample_queue;
00121    int pos;
00122    int save_pos;
00123 };
00124 
00125 #define MOH_QUIET    (1 << 0)
00126 #define MOH_SINGLE      (1 << 1)
00127 #define MOH_CUSTOM      (1 << 2)
00128 #define MOH_RANDOMIZE      (1 << 3)
00129 
00130 struct mohclass {
00131    char name[MAX_MUSICCLASS];
00132    char dir[256];
00133    char args[256];
00134    char mode[80];
00135    /*! A dynamically sized array to hold the list of filenames in "files" mode */
00136    char **filearray;
00137    /*! The current size of the filearray */
00138    int allowed_files;
00139    /*! The current number of files loaded into the filearray */
00140    int total_files;
00141    unsigned int flags;
00142    /*! The format from the MOH source, not applicable to "files" mode */
00143    int format;
00144    /*! The pid of the external application delivering MOH */
00145    int pid;
00146    time_t start;
00147    pthread_t thread;
00148    /*! Source of audio */
00149    int srcfd;
00150    /*! FD for timing source */
00151    int pseudofd;
00152    /*! Number of users */
00153    int inuse;
00154    unsigned int delete:1;
00155    AST_LIST_HEAD_NOLOCK(, mohdata) members;
00156    AST_LIST_ENTRY(mohclass) list;
00157 };
00158 
00159 struct mohdata {
00160    int pipe[2];
00161    int origwfmt;
00162    struct mohclass *parent;
00163    struct ast_frame f;
00164    AST_LIST_ENTRY(mohdata) list;
00165 };
00166 
00167 AST_LIST_HEAD_STATIC(mohclasses, mohclass);
00168 
00169 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00170 #define MPG_123 "/usr/bin/mpg123"
00171 #define MAX_MP3S 256
00172 
00173 static int ast_moh_destroy_one(struct mohclass *moh);
00174 static int reload(void);
00175 
00176 static void ast_moh_free_class(struct mohclass **mohclass) 
00177 {
00178    struct mohdata *member;
00179    struct mohclass *class = *mohclass;
00180    int i;
00181    
00182    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list)))
00183       free(member);
00184    
00185    if (class->thread) {
00186       pthread_cancel(class->thread);
00187       class->thread = 0;
00188    }
00189 
00190    if (class->filearray) {
00191       for (i = 0; i < class->total_files; i++)
00192          free(class->filearray[i]);
00193       free(class->filearray);
00194    }
00195 
00196    free(class);
00197    *mohclass = NULL;
00198 }
00199 
00200 
00201 static void moh_files_release(struct ast_channel *chan, void *data)
00202 {
00203    struct moh_files_state *state = chan->music_state;
00204 
00205    if (chan && state) {
00206       if (chan->stream) {
00207                         ast_closestream(chan->stream);
00208                         chan->stream = NULL;
00209                 }
00210       if (option_verbose > 2)
00211          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00212 
00213       if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00214          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00215       }
00216       state->save_pos = state->pos;
00217    }
00218    if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
00219       ast_moh_destroy_one(state->class);
00220 }
00221 
00222 
00223 static int ast_moh_files_next(struct ast_channel *chan) 
00224 {
00225    struct moh_files_state *state = chan->music_state;
00226    int tries;
00227 
00228    /* Discontinue a stream if it is running already */
00229    if (chan->stream) {
00230       ast_closestream(chan->stream);
00231       chan->stream = NULL;
00232    }
00233 
00234    if (!state->class->total_files) {
00235       ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
00236       return -1;
00237    }
00238 
00239    /* If a specific file has been saved, use it */
00240    if (state->save_pos >= 0) {
00241       state->pos = state->save_pos;
00242       state->save_pos = -1;
00243    } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00244       /* Get a random file and ensure we can open it */
00245       for (tries = 0; tries < 20; tries++) {
00246          state->pos = rand() % state->class->total_files;
00247          if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00248             break;
00249       }
00250       state->samples = 0;
00251    } else {
00252       /* This is easy, just increment our position and make sure we don't exceed the total file count */
00253       state->pos++;
00254       state->pos %= state->class->total_files;
00255       state->samples = 0;
00256    }
00257 
00258    if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00259       ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00260       state->pos++;
00261       state->pos %= state->class->total_files;
00262       return -1;
00263    }
00264 
00265    if (option_debug)
00266       ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
00267 
00268    if (state->samples)
00269       ast_seekstream(chan->stream, state->samples, SEEK_SET);
00270 
00271    return 0;
00272 }
00273 
00274 
00275 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
00276 {
00277    struct ast_frame *f = NULL;
00278    
00279    if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00280       if (!ast_moh_files_next(chan))
00281          f = ast_readframe(chan->stream);
00282    }
00283 
00284    return f;
00285 }
00286 
00287 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00288 {
00289    struct moh_files_state *state = chan->music_state;
00290    struct ast_frame *f = NULL;
00291    int res = 0;
00292 
00293    state->sample_queue += samples;
00294 
00295    while (state->sample_queue > 0) {
00296       if ((f = moh_files_readframe(chan))) {
00297          state->samples += f->samples;
00298          res = ast_write(chan, f);
00299          state->sample_queue -= f->samples;
00300          ast_frfree(f);
00301          if (res < 0) {
00302             ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00303             return -1;
00304          }
00305       } else
00306          return -1;  
00307    }
00308    return res;
00309 }
00310 
00311 
00312 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00313 {
00314    struct moh_files_state *state;
00315    struct mohclass *class = params;
00316 
00317    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00318       chan->music_state = state;
00319       state->class = class;
00320       state->save_pos = -1;
00321    } else 
00322       state = chan->music_state;
00323 
00324    if (state) {
00325       if (state->class != class) {
00326          /* initialize */
00327          memset(state, 0, sizeof(*state));
00328          state->class = class;
00329          if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files)
00330             state->pos = ast_random() % class->total_files;
00331       }
00332 
00333       state->origwfmt = chan->writeformat;
00334 
00335       if (option_verbose > 2)
00336          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name);
00337    }
00338    
00339    return chan->music_state;
00340 }
00341 
00342 static struct ast_generator moh_file_stream = 
00343 {
00344    alloc: moh_files_alloc,
00345    release: moh_files_release,
00346    generate: moh_files_generator,
00347 };
00348 
00349 static int spawn_mp3(struct mohclass *class)
00350 {
00351    int fds[2];
00352    int files = 0;
00353    char fns[MAX_MP3S][80];
00354    char *argv[MAX_MP3S + 50];
00355    char xargs[256];
00356    char *argptr;
00357    int argc = 0;
00358    DIR *dir = NULL;
00359    struct dirent *de;
00360    sigset_t signal_set, old_set;
00361 
00362    
00363    if (!strcasecmp(class->dir, "nodir")) {
00364       files = 1;
00365    } else {
00366       dir = opendir(class->dir);
00367       if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
00368          ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00369          return -1;
00370       }
00371    }
00372 
00373    if (!ast_test_flag(class, MOH_CUSTOM)) {
00374       argv[argc++] = "mpg123";
00375       argv[argc++] = "-q";
00376       argv[argc++] = "-s";
00377       argv[argc++] = "--mono";
00378       argv[argc++] = "-r";
00379       argv[argc++] = "8000";
00380       
00381       if (!ast_test_flag(class, MOH_SINGLE)) {
00382          argv[argc++] = "-b";
00383          argv[argc++] = "2048";
00384       }
00385       
00386       argv[argc++] = "-f";
00387       
00388       if (ast_test_flag(class, MOH_QUIET))
00389          argv[argc++] = "4096";
00390       else
00391          argv[argc++] = "8192";
00392       
00393       /* Look for extra arguments and add them to the list */
00394       ast_copy_string(xargs, class->args, sizeof(xargs));
00395       argptr = xargs;
00396       while (!ast_strlen_zero(argptr)) {
00397          argv[argc++] = argptr;
00398          strsep(&argptr, ",");
00399       }
00400    } else  {
00401       /* Format arguments for argv vector */
00402       ast_copy_string(xargs, class->args, sizeof(xargs));
00403       argptr = xargs;
00404       while (!ast_strlen_zero(argptr)) {
00405          argv[argc++] = argptr;
00406          strsep(&argptr, " ");
00407       }
00408    }
00409 
00410 
00411    if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) {
00412       ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00413       argv[argc++] = fns[files];
00414       files++;
00415    } else if (dir) {
00416       while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00417          if ((strlen(de->d_name) > 3) && 
00418              ((ast_test_flag(class, MOH_CUSTOM) && 
00419                (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
00420                 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00421               !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00422             ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00423             argv[argc++] = fns[files];
00424             files++;
00425          }
00426       }
00427    }
00428    argv[argc] = NULL;
00429    if (dir) {
00430       closedir(dir);
00431    }
00432    if (pipe(fds)) {  
00433       ast_log(LOG_WARNING, "Pipe failed\n");
00434       return -1;
00435    }
00436    if (!files) {
00437       ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00438       close(fds[0]);
00439       close(fds[1]);
00440       return -1;
00441    }
00442    if (time(NULL) - class->start < respawn_time) {
00443       sleep(respawn_time - (time(NULL) - class->start));
00444    }
00445 
00446    /* Block signals during the fork() */
00447    sigfillset(&signal_set);
00448    pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
00449 
00450    time(&class->start);
00451    class->pid = fork();
00452    if (class->pid < 0) {
00453       close(fds[0]);
00454       close(fds[1]);
00455       ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00456       return -1;
00457    }
00458    if (!class->pid) {
00459       int x;
00460 
00461       if (ast_opt_high_priority)
00462          ast_set_priority(0);
00463 
00464       /* Reset ignored signals back to default */
00465       signal(SIGPIPE, SIG_DFL);
00466       pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
00467 
00468       close(fds[0]);
00469       /* Stdout goes to pipe */
00470       dup2(fds[1], STDOUT_FILENO);
00471       /* Close unused file descriptors */
00472       for (x=3;x<8192;x++) {
00473          if (-1 != fcntl(x, F_GETFL)) {
00474             close(x);
00475          }
00476       }
00477       /* Child */
00478       chdir(class->dir);
00479       if (ast_test_flag(class, MOH_CUSTOM)) {
00480          execv(argv[0], argv);
00481       } else {
00482          /* Default install is /usr/local/bin */
00483          execv(LOCAL_MPG_123, argv);
00484          /* Many places have it in /usr/bin */
00485          execv(MPG_123, argv);
00486          /* Check PATH as a last-ditch effort */
00487          execvp("mpg123", argv);
00488       }
00489       ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
00490       close(fds[1]);
00491       _exit(1);
00492    } else {
00493       /* Parent */
00494       pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00495       close(fds[1]);
00496    }
00497    return fds[0];
00498 }
00499 
00500 static void *monmp3thread(void *data)
00501 {
00502 #define  MOH_MS_INTERVAL      100
00503 
00504    struct mohclass *class = data;
00505    struct mohdata *moh;
00506    char buf[8192];
00507    short sbuf[8192];
00508    int res, res2;
00509    int len;
00510    struct timeval tv, tv_tmp;
00511 
00512    tv.tv_sec = 0;
00513    tv.tv_usec = 0;
00514    for(;/* ever */;) {
00515       pthread_testcancel();
00516       /* Spawn mp3 player if it's not there */
00517       if (class->srcfd < 0) {
00518          if ((class->srcfd = spawn_mp3(class)) < 0) {
00519             ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00520             /* Try again later */
00521             sleep(500);
00522             pthread_testcancel();
00523          }
00524       }
00525       if (class->pseudofd > -1) {
00526 #ifdef SOLARIS
00527          thr_yield();
00528 #endif
00529          /* Pause some amount of time */
00530          res = read(class->pseudofd, buf, sizeof(buf));
00531          pthread_testcancel();
00532       } else {
00533          long delta;
00534          /* Reliable sleep */
00535          tv_tmp = ast_tvnow();
00536          if (ast_tvzero(tv))
00537             tv = tv_tmp;
00538          delta = ast_tvdiff_ms(tv_tmp, tv);
00539          if (delta < MOH_MS_INTERVAL) {   /* too early */
00540             tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000));  /* next deadline */
00541             usleep(1000 * (MOH_MS_INTERVAL - delta));
00542             pthread_testcancel();
00543          } else {
00544             ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00545             tv = tv_tmp;
00546          }
00547          res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
00548       }
00549       if (AST_LIST_EMPTY(&class->members))
00550          continue;
00551       /* Read mp3 audio */
00552       len = ast_codec_get_len(class->format, res);
00553       
00554       if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00555          if (!res2) {
00556             close(class->srcfd);
00557             class->srcfd = -1;
00558             pthread_testcancel();
00559             if (class->pid > 1) {
00560                kill(class->pid, SIGHUP);
00561                usleep(100000);
00562                kill(class->pid, SIGTERM);
00563                usleep(100000);
00564                kill(class->pid, SIGKILL);
00565                class->pid = 0;
00566             }
00567          } else
00568             ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
00569          continue;
00570       }
00571       pthread_testcancel();
00572       AST_LIST_LOCK(&mohclasses);
00573       AST_LIST_TRAVERSE(&class->members, moh, list) {
00574          /* Write data */
00575          if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00576             if (option_debug)
00577                ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
00578          }
00579       }
00580       AST_LIST_UNLOCK(&mohclasses);
00581    }
00582    return NULL;
00583 }
00584 
00585 static int moh0_exec(struct ast_channel *chan, void *data)
00586 {
00587    if (ast_moh_start(chan, data, NULL)) {
00588       ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
00589       return 0;
00590    }
00591    while (!ast_safe_sleep(chan, 10000));
00592    ast_moh_stop(chan);
00593    return -1;
00594 }
00595 
00596 static int moh1_exec(struct ast_channel *chan, void *data)
00597 {
00598    int res;
00599    if (!data || !atoi(data)) {
00600       ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00601       return -1;
00602    }
00603    if (ast_moh_start(chan, NULL, NULL)) {
00604       ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00605       return 0;
00606    }
00607    res = ast_safe_sleep(chan, atoi(data) * 1000);
00608    ast_moh_stop(chan);
00609    return res;
00610 }
00611 
00612 static int moh2_exec(struct ast_channel *chan, void *data)
00613 {
00614    if (ast_strlen_zero(data)) {
00615       ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00616       return -1;
00617    }
00618    ast_string_field_set(chan, musicclass, data);
00619    return 0;
00620 }
00621 
00622 static int moh3_exec(struct ast_channel *chan, void *data)
00623 {
00624    char *class = NULL;
00625    if (data && strlen(data))
00626       class = data;
00627    if (ast_moh_start(chan, class, NULL)) 
00628       ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name);
00629 
00630    return 0;
00631 }
00632 
00633 static int moh4_exec(struct ast_channel *chan, void *data)
00634 {
00635    ast_moh_stop(chan);
00636 
00637    return 0;
00638 }
00639 
00640 /*! \note This function should be called with the mohclasses list locked */
00641 static struct mohclass *get_mohbyname(const char *name, int warn)
00642 {
00643    struct mohclass *moh = NULL;
00644 
00645    AST_LIST_TRAVERSE(&mohclasses, moh, list) {
00646       if (!strcasecmp(name, moh->name))
00647          break;
00648    }
00649 
00650    if (!moh && warn)
00651       ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
00652 
00653    return moh;
00654 }
00655 
00656 static struct mohdata *mohalloc(struct mohclass *cl)
00657 {
00658    struct mohdata *moh;
00659    long flags; 
00660    
00661    if (!(moh = ast_calloc(1, sizeof(*moh))))
00662       return NULL;
00663    
00664    if (pipe(moh->pipe)) {
00665       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00666       free(moh);
00667       return NULL;
00668    }
00669 
00670    /* Make entirely non-blocking */
00671    flags = fcntl(moh->pipe[0], F_GETFL);
00672    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00673    flags = fcntl(moh->pipe[1], F_GETFL);
00674    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00675 
00676    moh->f.frametype = AST_FRAME_VOICE;
00677    moh->f.subclass = cl->format;
00678    moh->f.offset = AST_FRIENDLY_OFFSET;
00679 
00680    moh->parent = cl;
00681 
00682    AST_LIST_LOCK(&mohclasses);
00683    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00684    AST_LIST_UNLOCK(&mohclasses);
00685    
00686    return moh;
00687 }
00688 
00689 static void moh_release(struct ast_channel *chan, void *data)
00690 {
00691    struct mohdata *moh = data;
00692    int oldwfmt;
00693 
00694    AST_LIST_LOCK(&mohclasses);
00695    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00696    AST_LIST_UNLOCK(&mohclasses);
00697    
00698    close(moh->pipe[0]);
00699    close(moh->pipe[1]);
00700    oldwfmt = moh->origwfmt;
00701    if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse))
00702       ast_moh_destroy_one(moh->parent);
00703    free(moh);
00704    if (chan) {
00705       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
00706          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
00707       if (option_verbose > 2)
00708          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00709    }
00710 }
00711 
00712 static void *moh_alloc(struct ast_channel *chan, void *params)
00713 {
00714    struct mohdata *res;
00715    struct mohclass *class = params;
00716 
00717    if ((res = mohalloc(class))) {
00718       res->origwfmt = chan->writeformat;
00719       if (ast_set_write_format(chan, class->format)) {
00720          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00721          moh_release(NULL, res);
00722          res = NULL;
00723       }
00724       if (option_verbose > 2)
00725          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00726    }
00727    return res;
00728 }
00729 
00730 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00731 {
00732    struct mohdata *moh = data;
00733    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00734    int res;
00735 
00736    if (!moh->parent->pid)
00737       return -1;
00738 
00739    len = ast_codec_get_len(moh->parent->format, samples);
00740 
00741    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00742       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00743       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00744    }
00745    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00746    if (res <= 0)
00747       return 0;
00748 
00749    moh->f.datalen = res;
00750    moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
00751    moh->f.samples = ast_codec_get_samples(&moh->f);
00752 
00753    if (ast_write(chan, &moh->f) < 0) {
00754       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00755       return -1;
00756    }
00757 
00758    return 0;
00759 }
00760 
00761 static struct ast_generator mohgen = 
00762 {
00763    alloc: moh_alloc,
00764    release: moh_release,
00765    generate: moh_generate,
00766 };
00767 
00768 static int moh_add_file(struct mohclass *class, const char *filepath)
00769 {
00770    if (!class->allowed_files) {
00771       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00772          return -1;
00773       class->allowed_files = INITIAL_NUM_FILES;
00774    } else if (class->total_files == class->allowed_files) {
00775       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00776          class->allowed_files = 0;
00777          class->total_files = 0;
00778          return -1;
00779       }
00780       class->allowed_files *= 2;
00781    }
00782 
00783    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00784       return -1;
00785 
00786    class->total_files++;
00787 
00788    return 0;
00789 }
00790 
00791 static int moh_scan_files(struct mohclass *class) {
00792 
00793    DIR *files_DIR;
00794    struct dirent *files_dirent;
00795    char path[PATH_MAX];
00796    char filepath[PATH_MAX];
00797    char *ext;
00798    struct stat statbuf;
00799    int dirnamelen;
00800    int i;
00801    
00802    files_DIR = opendir(class->dir);
00803    if (!files_DIR) {
00804       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00805       return -1;
00806    }
00807 
00808    for (i = 0; i < class->total_files; i++)
00809       free(class->filearray[i]);
00810 
00811    class->total_files = 0;
00812    dirnamelen = strlen(class->dir) + 2;
00813    getcwd(path, sizeof(path));
00814    chdir(class->dir);
00815    while ((files_dirent = readdir(files_DIR))) {
00816       /* The file name must be at least long enough to have the file type extension */
00817       if ((strlen(files_dirent->d_name) < 4))
00818          continue;
00819 
00820       /* Skip files that starts with a dot */
00821       if (files_dirent->d_name[0] == '.')
00822          continue;
00823 
00824       /* Skip files without extensions... they are not audio */
00825       if (!strchr(files_dirent->d_name, '.'))
00826          continue;
00827 
00828       snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00829 
00830       if (stat(filepath, &statbuf))
00831          continue;
00832 
00833       if (!S_ISREG(statbuf.st_mode))
00834          continue;
00835 
00836       if ((ext = strrchr(filepath, '.'))) {
00837          *ext = '\0';
00838          ext++;
00839       }
00840 
00841       /* if the file is present in multiple formats, ensure we only put it into the list once */
00842       for (i = 0; i < class->total_files; i++)
00843          if (!strcmp(filepath, class->filearray[i]))
00844             break;
00845 
00846       if (i == class->total_files) {
00847          if (moh_add_file(class, filepath))
00848             break;
00849       }
00850    }
00851 
00852    closedir(files_DIR);
00853    chdir(path);
00854    return class->total_files;
00855 }
00856 
00857 static int moh_register(struct mohclass *moh, int reload)
00858 {
00859 #ifdef HAVE_ZAPTEL
00860    int x;
00861 #endif
00862    struct mohclass *mohclass = NULL;
00863 
00864    AST_LIST_LOCK(&mohclasses);
00865    if ((mohclass = get_mohbyname(moh->name, 0))) {
00866       mohclass->delete = 0;
00867       if (reload) {
00868          ast_log(LOG_DEBUG, "Music on Hold class '%s' left alone from initial load.\n", moh->name);
00869       } else {
00870          ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
00871       }
00872       free(moh);  
00873       AST_LIST_UNLOCK(&mohclasses);
00874       return -1;
00875    }
00876    AST_LIST_UNLOCK(&mohclasses);
00877 
00878    time(&moh->start);
00879    moh->start -= respawn_time;
00880    
00881    if (!strcasecmp(moh->mode, "files")) {
00882       if (!moh_scan_files(moh)) {
00883          ast_moh_free_class(&moh);
00884          return -1;
00885       }
00886       if (strchr(moh->args, 'r'))
00887          ast_set_flag(moh, MOH_RANDOMIZE);
00888    } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
00889 
00890       if (!strcasecmp(moh->mode, "custom"))
00891          ast_set_flag(moh, MOH_CUSTOM);
00892       else if (!strcasecmp(moh->mode, "mp3nb"))
00893          ast_set_flag(moh, MOH_SINGLE);
00894       else if (!strcasecmp(moh->mode, "quietmp3nb"))
00895          ast_set_flag(moh, MOH_SINGLE | MOH_QUIET);
00896       else if (!strcasecmp(moh->mode, "quietmp3"))
00897          ast_set_flag(moh, MOH_QUIET);
00898       
00899       moh->srcfd = -1;
00900 #ifdef HAVE_ZAPTEL
00901       /* Open /dev/zap/pseudo for timing...  Is
00902          there a better, yet reliable way to do this? */
00903       moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
00904       if (moh->pseudofd < 0) {
00905          ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
00906       } else {
00907          x = 320;
00908          ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
00909       }
00910 #else
00911       moh->pseudofd = -1;
00912 #endif
00913       if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) {
00914          ast_log(LOG_WARNING, "Unable to create moh...\n");
00915          if (moh->pseudofd > -1)
00916             close(moh->pseudofd);
00917          ast_moh_free_class(&moh);
00918          return -1;
00919       }
00920    } else {
00921       ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
00922       ast_moh_free_class(&moh);
00923       return -1;
00924    }
00925 
00926    AST_LIST_LOCK(&mohclasses);
00927    AST_LIST_INSERT_HEAD(&mohclasses, moh, list);
00928    AST_LIST_UNLOCK(&mohclasses);
00929    
00930    return 0;
00931 }
00932 
00933 static void local_ast_moh_cleanup(struct ast_channel *chan)
00934 {
00935    if (chan->music_state) {
00936       free(chan->music_state);
00937       chan->music_state = NULL;
00938    }
00939 }
00940 
00941 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
00942 {
00943    struct mohclass *mohclass = NULL;
00944 
00945    /* The following is the order of preference for which class to use:
00946     * 1) The channels explicitly set musicclass, which should *only* be
00947     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
00948     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
00949     *    result of receiving a HOLD control frame, this should be the
00950     *    payload that came with the frame.
00951     * 3) The interpclass argument. This would be from the mohinterpret
00952     *    option from channel drivers. This is the same as the old musicclass
00953     *    option.
00954     * 4) The default class.
00955     */
00956    AST_LIST_LOCK(&mohclasses);
00957    if (!ast_strlen_zero(chan->musicclass))
00958       mohclass = get_mohbyname(chan->musicclass, 1);
00959    if (!mohclass && !ast_strlen_zero(mclass))
00960       mohclass = get_mohbyname(mclass, 1);
00961    if (!mohclass && !ast_strlen_zero(interpclass))
00962       mohclass = get_mohbyname(interpclass, 1);
00963    if (!mohclass) 
00964       mohclass = get_mohbyname("default", 1);
00965    if (mohclass)
00966       ast_atomic_fetchadd_int(&mohclass->inuse, +1);
00967    AST_LIST_UNLOCK(&mohclasses);
00968 
00969    if (!mohclass)
00970       return -1;
00971 
00972    ast_set_flag(chan, AST_FLAG_MOH);
00973    if (mohclass->total_files) {
00974       return ast_activate_generator(chan, &moh_file_stream, mohclass);
00975    } else
00976       return ast_activate_generator(chan, &mohgen, mohclass);
00977 }
00978 
00979 static void local_ast_moh_stop(struct ast_channel *chan)
00980 {
00981    ast_clear_flag(chan, AST_FLAG_MOH);
00982    ast_deactivate_generator(chan);
00983 
00984    if (chan->music_state) {
00985       if (chan->stream) {
00986          ast_closestream(chan->stream);
00987          chan->stream = NULL;
00988       }
00989    }
00990 }
00991 
00992 static struct mohclass *moh_class_malloc(void)
00993 {
00994    struct mohclass *class;
00995 
00996    if ((class = ast_calloc(1, sizeof(*class))))
00997       class->format = AST_FORMAT_SLINEAR;
00998 
00999    return class;
01000 }
01001 
01002 static int load_moh_classes(int reload)
01003 {
01004    struct ast_config *cfg;
01005    struct ast_variable *var;
01006    struct mohclass *class; 
01007    char *data;
01008    char *args;
01009    char *cat;
01010    int numclasses = 0;
01011    static int dep_warning = 0;
01012 
01013    cfg = ast_config_load("musiconhold.conf");
01014 
01015    if (!cfg)
01016       return 0;
01017 
01018    if (reload) {
01019       AST_LIST_LOCK(&mohclasses);
01020       AST_LIST_TRAVERSE(&mohclasses, class, list)
01021          class->delete = 1;
01022       AST_LIST_UNLOCK(&mohclasses);
01023    }
01024 
01025    cat = ast_category_browse(cfg, NULL);
01026    for (; cat; cat = ast_category_browse(cfg, cat)) {
01027       if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {       
01028          if (!(class = moh_class_malloc())) {
01029             break;
01030          }           
01031          ast_copy_string(class->name, cat, sizeof(class->name));  
01032          var = ast_variable_browse(cfg, cat);
01033          while (var) {
01034             if (!strcasecmp(var->name, "mode"))
01035                ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01036             else if (!strcasecmp(var->name, "directory"))
01037                ast_copy_string(class->dir, var->value, sizeof(class->dir));
01038             else if (!strcasecmp(var->name, "application"))
01039                ast_copy_string(class->args, var->value, sizeof(class->args));
01040             else if (!strcasecmp(var->name, "random"))
01041                ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01042             else if (!strcasecmp(var->name, "format")) {
01043                class->format = ast_getformatbyname(var->value);
01044                if (!class->format) {
01045                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01046                   class->format = AST_FORMAT_SLINEAR;
01047                }
01048             }
01049             var = var->next;
01050          }
01051 
01052          if (ast_strlen_zero(class->dir)) {
01053             if (!strcasecmp(class->mode, "custom")) {
01054                strcpy(class->dir, "nodir");
01055             } else {
01056                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01057                free(class);
01058                continue;
01059             }
01060          }
01061          if (ast_strlen_zero(class->mode)) {
01062             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01063             free(class);
01064             continue;
01065          }
01066          if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01067             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01068             free(class);
01069             continue;
01070          }
01071 
01072          /* Don't leak a class when it's already registered */
01073          moh_register(class, reload);
01074 
01075          numclasses++;
01076       }
01077    }
01078    
01079 
01080    /* Deprecated Old-School Configuration */
01081    var = ast_variable_browse(cfg, "classes");
01082    while (var) {
01083       if (!dep_warning) {
01084          ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
01085          dep_warning = 1;
01086       }
01087       data = strchr(var->value, ':');
01088       if (data) {
01089          *data++ = '\0';
01090          args = strchr(data, ',');
01091          if (args)
01092             *args++ = '\0';
01093          if (!(get_mohbyname(var->name, 0))) {        
01094             if (!(class = moh_class_malloc())) {
01095                break;
01096             }
01097             
01098             ast_copy_string(class->name, var->name, sizeof(class->name));
01099             ast_copy_string(class->dir, data, sizeof(class->dir));
01100             ast_copy_string(class->mode, var->value, sizeof(class->mode));
01101             if (args)
01102                ast_copy_string(class->args, args, sizeof(class->args));
01103             
01104             moh_register(class, reload);
01105             numclasses++;
01106          }
01107       }
01108       var = var->next;
01109    }
01110    var = ast_variable_browse(cfg, "moh_files");
01111    while (var) {
01112       if (!dep_warning) {
01113          ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated!  Please refer to the sample configuration for information on the new syntax.\n");
01114          dep_warning = 1;
01115       }
01116       if (!(get_mohbyname(var->name, 0))) {
01117          args = strchr(var->value, ',');
01118          if (args)
01119             *args++ = '\0';         
01120          if (!(class = moh_class_malloc())) {
01121             break;
01122          }
01123          
01124          ast_copy_string(class->name, var->name, sizeof(class->name));
01125          ast_copy_string(class->dir, var->value, sizeof(class->dir));
01126          strcpy(class->mode, "files");
01127          if (args)   
01128             ast_copy_string(class->args, args, sizeof(class->args));
01129          
01130          moh_register(class, reload);
01131          numclasses++;
01132       }
01133       var = var->next;
01134    }
01135 
01136    ast_config_destroy(cfg);
01137 
01138    return numclasses;
01139 }
01140 
01141 static int ast_moh_destroy_one(struct mohclass *moh)
01142 {
01143    char buff[8192];
01144    int bytes, tbytes = 0, stime = 0, pid = 0;
01145 
01146    if (moh) {
01147       if (moh->pid > 1) {
01148          ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
01149          stime = time(NULL) + 2;
01150          pid = moh->pid;
01151          moh->pid = 0;
01152          /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01153           * to give the process a reason and time enough to kill off its
01154           * children. */
01155          kill(pid, SIGHUP);
01156          usleep(100000);
01157          kill(pid, SIGTERM);
01158          usleep(100000);
01159          kill(pid, SIGKILL);
01160          while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime)
01161             tbytes = tbytes + bytes;
01162          ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01163          close(moh->srcfd);
01164       }
01165       ast_moh_free_class(&moh);
01166    }
01167 
01168    return 0;
01169 }
01170 
01171 static void ast_moh_destroy(void)
01172 {
01173    struct mohclass *moh;
01174 
01175    if (option_verbose > 1)
01176       ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
01177 
01178    AST_LIST_LOCK(&mohclasses);
01179    while ((moh = AST_LIST_REMOVE_HEAD(&mohclasses, list))) {
01180       ast_moh_destroy_one(moh);
01181    }
01182    AST_LIST_UNLOCK(&mohclasses);
01183 }
01184 
01185 static int moh_cli(int fd, int argc, char *argv[]) 
01186 {
01187    reload();
01188    return 0;
01189 }
01190 
01191 static int cli_files_show(int fd, int argc, char *argv[])
01192 {
01193    int i;
01194    struct mohclass *class;
01195 
01196    AST_LIST_LOCK(&mohclasses);
01197    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01198       if (!class->total_files)
01199          continue;
01200 
01201       ast_cli(fd, "Class: %s\n", class->name);
01202       for (i = 0; i < class->total_files; i++)
01203          ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
01204    }
01205    AST_LIST_UNLOCK(&mohclasses);
01206 
01207    return 0;
01208 }
01209 
01210 static int moh_classes_show(int fd, int argc, char *argv[])
01211 {
01212    struct mohclass *class;
01213 
01214    AST_LIST_LOCK(&mohclasses);
01215    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01216       ast_cli(fd, "Class: %s\n", class->name);
01217       ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01218       ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01219       ast_cli(fd, "\tUse Count: %d\n", class->inuse);
01220       if (ast_test_flag(class, MOH_CUSTOM))
01221          ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01222       if (strcasecmp(class->mode, "files"))
01223          ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
01224    }
01225    AST_LIST_UNLOCK(&mohclasses);
01226 
01227    return 0;
01228 }
01229 
01230 static struct ast_cli_entry cli_moh_classes_show_deprecated = {
01231    { "moh", "classes", "show"},
01232    moh_classes_show, NULL,
01233    NULL };
01234 
01235 static struct ast_cli_entry cli_moh_files_show_deprecated = {
01236    { "moh", "files", "show"},
01237    cli_files_show, NULL,
01238    NULL };
01239 
01240 static struct ast_cli_entry cli_moh[] = {
01241    { { "moh", "reload"},
01242    moh_cli, "Music On Hold",
01243    "Music On Hold" },
01244 
01245    { { "moh", "show", "classes"},
01246    moh_classes_show, "List MOH classes",
01247    "Lists all MOH classes", NULL, &cli_moh_classes_show_deprecated },
01248 
01249    { { "moh", "show", "files"},
01250    cli_files_show, "List MOH file-based classes",
01251    "Lists all loaded file-based MOH classes and their files", NULL, &cli_moh_files_show_deprecated },
01252 };
01253 
01254 static int init_classes(int reload) 
01255 {
01256    struct mohclass *moh;
01257     
01258    if (!load_moh_classes(reload))      /* Load classes from config */
01259       return 0;         /* Return if nothing is found */
01260 
01261    AST_LIST_LOCK(&mohclasses);
01262    AST_LIST_TRAVERSE_SAFE_BEGIN(&mohclasses, moh, list) {
01263       if (reload && moh->delete) {
01264          AST_LIST_REMOVE_CURRENT(&mohclasses, list);
01265          if (!moh->inuse)
01266             ast_moh_destroy_one(moh);
01267       } else if (moh->total_files) {
01268          if (moh_scan_files(moh) <= 0) {
01269             ast_log(LOG_WARNING, "No files found for class '%s'\n", moh->name);
01270             moh->delete = 1;
01271             AST_LIST_REMOVE_CURRENT(&mohclasses, list);
01272             if (!moh->inuse)
01273                ast_moh_destroy_one(moh);
01274          }
01275       }
01276    }
01277    AST_LIST_TRAVERSE_SAFE_END
01278    AST_LIST_UNLOCK(&mohclasses);
01279 
01280    return 1;
01281 }
01282 
01283 static int load_module(void)
01284 {
01285    int res;
01286 
01287    res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
01288    ast_register_atexit(ast_moh_destroy);
01289    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01290    if (!res)
01291       res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
01292    if (!res)
01293       res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
01294    if (!res)
01295       res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
01296    if (!res)
01297       res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
01298 
01299    if (!init_classes(0)) {    /* No music classes configured, so skip it */
01300       ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n");
01301    } else {
01302       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01303    }
01304 
01305    return 0;
01306 }
01307 
01308 static int reload(void)
01309 {
01310    if (init_classes(1))
01311       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01312 
01313    return 0;
01314 }
01315 
01316 static int unload_module(void)
01317 {
01318    return -1;
01319 }
01320 
01321 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
01322       .load = load_module,
01323       .unload = unload_module,
01324       .reload = reload,
01325           );

Generated on Fri Sep 25 19:28:15 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.5