Wed Mar 3 22:37:10 2010

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>dahdi</use>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 211569 $")
00036 
00037 #include <ctype.h>
00038 #include <signal.h>
00039 #include <sys/time.h>
00040 #include <sys/signal.h>
00041 #include <netinet/in.h>
00042 #include <sys/stat.h>
00043 #include <dirent.h>
00044 #include <sys/ioctl.h>
00045 #ifdef SOLARIS
00046 #include <thread.h>
00047 #endif
00048 
00049 #ifdef HAVE_DAHDI
00050 #include <dahdi/user.h>
00051 #endif
00052 
00053 #include "asterisk/lock.h"
00054 #include "asterisk/file.h"
00055 #include "asterisk/channel.h"
00056 #include "asterisk/pbx.h"
00057 #include "asterisk/app.h"
00058 #include "asterisk/module.h"
00059 #include "asterisk/translate.h"
00060 #include "asterisk/say.h"
00061 #include "asterisk/musiconhold.h"
00062 #include "asterisk/config.h"
00063 #include "asterisk/utils.h"
00064 #include "asterisk/cli.h"
00065 #include "asterisk/stringfields.h"
00066 #include "asterisk/linkedlists.h"
00067 #include "asterisk/manager.h"
00068 #include "asterisk/astobj2.h"
00069 
00070 #define INITIAL_NUM_FILES   8
00071 
00072 static char *play_moh = "MusicOnHold";
00073 static char *wait_moh = "WaitMusicOnHold";
00074 static char *set_moh = "SetMusicOnHold";
00075 static char *start_moh = "StartMusicOnHold";
00076 static char *stop_moh = "StopMusicOnHold";
00077 
00078 static char *play_moh_syn = "Play Music On Hold indefinitely";
00079 static char *wait_moh_syn = "Wait, playing Music On Hold";
00080 static char *set_moh_syn = "Set default Music On Hold class";
00081 static char *start_moh_syn = "Play Music On Hold";
00082 static char *stop_moh_syn = "Stop Playing Music On Hold";
00083 
00084 static char *play_moh_desc = "  MusicOnHold(class[,duration]):\n"
00085 "Plays hold music specified by class.  If omitted, the default\n"
00086 "music source for the channel will be used. Change the default \n"
00087 "class with Set(CHANNEL(musicclass)=...).\n"
00088 "If duration is given, hold music will be played specified number\n"
00089 "of seconds. If duration is ommited, music plays indefinitely.\n"
00090 "Returns 0 when done, -1 on hangup.\n";
00091 
00092 static char *wait_moh_desc = "  WaitMusicOnHold(delay):\n"
00093 "\n"
00094 "  !!! DEPRECATED. Use MusicOnHold instead !!!\n"
00095 "\n"
00096 "Plays hold music specified number of seconds.  Returns 0 when\n"
00097 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
00098 "still occur with no sound.\n"
00099 "\n"
00100 "  !!! DEPRECATED. Use MusicOnHold instead !!!\n";
00101 
00102 static char *set_moh_desc = "  SetMusicOnHold(class):\n"
00103 "\n"
00104 "  !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n"
00105 "\n"
00106 "Sets the default class for music on hold for a given channel.  When\n"
00107 "music on hold is activated, this class will be used to select which\n"
00108 "music is played.\n"
00109 "\n"
00110 "  !!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!\n";
00111 
00112 static char *start_moh_desc = "  StartMusicOnHold(class):\n"
00113 "Starts playing music on hold, uses default music class for channel.\n"
00114 "Starts playing music specified by class.  If omitted, the default\n"
00115 "music source for the channel will be used.  Always returns 0.\n";
00116 
00117 static char *stop_moh_desc = "  StopMusicOnHold(): "
00118 "Stops playing music on hold.\n";
00119 
00120 static int respawn_time = 20;
00121 
00122 struct moh_files_state {
00123    struct mohclass *class;
00124    int origwfmt;
00125    int samples;
00126    int sample_queue;
00127    int pos;
00128    int save_pos;
00129    char *save_pos_filename;
00130 };
00131 
00132 #define MOH_QUIET    (1 << 0)
00133 #define MOH_SINGLE      (1 << 1)
00134 #define MOH_CUSTOM      (1 << 2)
00135 #define MOH_RANDOMIZE      (1 << 3)
00136 #define MOH_SORTALPHA      (1 << 4)
00137 
00138 #define MOH_CACHERTCLASSES      (1 << 5)        /*!< Should we use a separate instance of MOH for each user or not */
00139 
00140 static struct ast_flags global_flags[1] = {{0}};        /*!< global MOH_ flags */
00141 
00142 struct mohclass {
00143    char name[MAX_MUSICCLASS];
00144    char dir[256];
00145    char args[256];
00146    char mode[80];
00147    char digit;
00148    /*! A dynamically sized array to hold the list of filenames in "files" mode */
00149    char **filearray;
00150    /*! The current size of the filearray */
00151    int allowed_files;
00152    /*! The current number of files loaded into the filearray */
00153    int total_files;
00154    unsigned int flags;
00155    /*! The format from the MOH source, not applicable to "files" mode */
00156    int format;
00157    /*! The pid of the external application delivering MOH */
00158    int pid;
00159    time_t start;
00160    pthread_t thread;
00161    /*! Source of audio */
00162    int srcfd;
00163    /*! FD for timing source */
00164    int pseudofd;
00165    /*! Created on the fly, from RT engine */
00166    int realtime;
00167    unsigned int delete:1;
00168    AST_LIST_HEAD_NOLOCK(, mohdata) members;
00169    AST_LIST_ENTRY(mohclass) list;
00170 };
00171 
00172 struct mohdata {
00173    int pipe[2];
00174    int origwfmt;
00175    struct mohclass *parent;
00176    struct ast_frame f;
00177    AST_LIST_ENTRY(mohdata) list;
00178 };
00179 
00180 static struct ao2_container *mohclasses;
00181 
00182 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00183 #define MPG_123 "/usr/bin/mpg123"
00184 #define MAX_MP3S 256
00185 
00186 static int reload(void);
00187 
00188 #define mohclass_ref(class,string)   (ao2_t_ref((class), +1, (string)), class)
00189 #define mohclass_unref(class,string) (ao2_t_ref((class), -1, (string)), (struct mohclass *) NULL)
00190 
00191 static void moh_files_release(struct ast_channel *chan, void *data)
00192 {
00193    struct moh_files_state *state;
00194 
00195    if (!chan || !chan->music_state) {
00196       return;
00197    }
00198 
00199    state = chan->music_state;
00200 
00201    if (chan->stream) {
00202       ast_closestream(chan->stream);
00203       chan->stream = NULL;
00204    }
00205    
00206    if (option_verbose > 2) {
00207       ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00208    }
00209 
00210    if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00211       ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00212    }
00213 
00214    state->save_pos = state->pos;
00215 
00216    mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
00217 }
00218 
00219 static int ast_moh_files_next(struct ast_channel *chan) 
00220 {
00221    struct moh_files_state *state = chan->music_state;
00222    int tries;
00223 
00224    /* Discontinue a stream if it is running already */
00225    if (chan->stream) {
00226       ast_closestream(chan->stream);
00227       chan->stream = NULL;
00228    }
00229 
00230    if (!state->class->total_files) {
00231       ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
00232       return -1;
00233    }
00234 
00235    /* If a specific file has been saved confirm it still exists and that it is still valid */
00236    if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
00237       state->pos = state->save_pos;
00238       state->save_pos = -1;
00239    } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00240       /* Get a random file and ensure we can open it */
00241       for (tries = 0; tries < 20; tries++) {
00242          state->pos = ast_random() % state->class->total_files;
00243          if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00244             break;
00245       }
00246       state->save_pos = -1;
00247       state->samples = 0;
00248    } else {
00249       /* This is easy, just increment our position and make sure we don't exceed the total file count */
00250       state->pos++;
00251       state->pos %= state->class->total_files;
00252       state->save_pos = -1;
00253       state->samples = 0;
00254    }
00255 
00256    if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00257       ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00258       state->pos++;
00259       state->pos %= state->class->total_files;
00260       return -1;
00261    }
00262 
00263    /* Record the pointer to the filename for position resuming later */
00264    state->save_pos_filename = state->class->filearray[state->pos];
00265 
00266    ast_debug(1, "%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 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
00275 {
00276    struct ast_frame *f = NULL;
00277    
00278    if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00279       if (!ast_moh_files_next(chan))
00280          f = ast_readframe(chan->stream);
00281    }
00282 
00283    return f;
00284 }
00285 
00286 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00287 {
00288    struct moh_files_state *state = chan->music_state;
00289    struct ast_frame *f = NULL;
00290    int res = 0;
00291 
00292    state->sample_queue += samples;
00293 
00294    while (state->sample_queue > 0) {
00295       if ((f = moh_files_readframe(chan))) {
00296          state->samples += f->samples;
00297          state->sample_queue -= f->samples;
00298          res = ast_write(chan, f);
00299          ast_frfree(f);
00300          if (res < 0) {
00301             ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00302             return -1;
00303          }
00304       } else
00305          return -1;  
00306    }
00307    return res;
00308 }
00309 
00310 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00311 {
00312    struct moh_files_state *state;
00313    struct mohclass *class = params;
00314 
00315    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00316       chan->music_state = state;
00317    } else {
00318       state = chan->music_state;
00319    }
00320 
00321    if (!state) {
00322       return NULL;
00323    }
00324 
00325    if (state->class != class) {
00326       memset(state, 0, sizeof(*state));
00327       if (ast_test_flag(class, MOH_RANDOMIZE) && class->total_files) {
00328          state->pos = ast_random() % class->total_files;
00329       }
00330    }
00331 
00332    state->class = mohclass_ref(class, "Reffing music class for channel");
00333    state->origwfmt = chan->writeformat;
00334 
00335    ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
00336    
00337    return chan->music_state;
00338 }
00339 
00340 static int moh_digit_match(void *obj, void *arg, int flags)
00341 {
00342    char *digit = arg;
00343    struct mohclass *class = obj;
00344 
00345    return (*digit == class->digit) ? CMP_MATCH | CMP_STOP : 0;
00346 }
00347 
00348 /*! \note This function should be called with the mohclasses list locked */
00349 static struct mohclass *get_mohbydigit(char digit)
00350 {
00351    return ao2_t_callback(mohclasses, 0, moh_digit_match, &digit, "digit callback");
00352 }
00353 
00354 static void moh_handle_digit(struct ast_channel *chan, char digit)
00355 {
00356    struct mohclass *class;
00357    const char *classname = NULL;
00358 
00359    if ((class = get_mohbydigit(digit))) {
00360       classname = ast_strdupa(class->name);
00361       class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
00362    }
00363 
00364    if (!class) {
00365       return;
00366    }
00367 
00368    ast_moh_stop(chan);
00369    ast_moh_start(chan, classname, NULL);
00370 }
00371 
00372 static struct ast_generator moh_file_stream = 
00373 {
00374    .alloc    = moh_files_alloc,
00375    .release  = moh_files_release,
00376    .generate = moh_files_generator,
00377    .digit    = moh_handle_digit,
00378 };
00379 
00380 static int spawn_mp3(struct mohclass *class)
00381 {
00382    int fds[2];
00383    int files = 0;
00384    char fns[MAX_MP3S][80];
00385    char *argv[MAX_MP3S + 50];
00386    char xargs[256];
00387    char *argptr;
00388    int argc = 0;
00389    DIR *dir = NULL;
00390    struct dirent *de;
00391 
00392    
00393    if (!strcasecmp(class->dir, "nodir")) {
00394       files = 1;
00395    } else {
00396       dir = opendir(class->dir);
00397       if (!dir && strncasecmp(class->dir, "http://", 7)) {
00398          ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00399          return -1;
00400       }
00401    }
00402 
00403    if (!ast_test_flag(class, MOH_CUSTOM)) {
00404       argv[argc++] = "mpg123";
00405       argv[argc++] = "-q";
00406       argv[argc++] = "-s";
00407       argv[argc++] = "--mono";
00408       argv[argc++] = "-r";
00409       argv[argc++] = "8000";
00410       
00411       if (!ast_test_flag(class, MOH_SINGLE)) {
00412          argv[argc++] = "-b";
00413          argv[argc++] = "2048";
00414       }
00415       
00416       argv[argc++] = "-f";
00417       
00418       if (ast_test_flag(class, MOH_QUIET))
00419          argv[argc++] = "4096";
00420       else
00421          argv[argc++] = "8192";
00422       
00423       /* Look for extra arguments and add them to the list */
00424       ast_copy_string(xargs, class->args, sizeof(xargs));
00425       argptr = xargs;
00426       while (!ast_strlen_zero(argptr)) {
00427          argv[argc++] = argptr;
00428          strsep(&argptr, ",");
00429       }
00430    } else  {
00431       /* Format arguments for argv vector */
00432       ast_copy_string(xargs, class->args, sizeof(xargs));
00433       argptr = xargs;
00434       while (!ast_strlen_zero(argptr)) {
00435          argv[argc++] = argptr;
00436          strsep(&argptr, " ");
00437       }
00438    }
00439 
00440    if (!strncasecmp(class->dir, "http://", 7)) {
00441       ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00442       argv[argc++] = fns[files];
00443       files++;
00444    } else if (dir) {
00445       while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00446          if ((strlen(de->d_name) > 3) && 
00447              ((ast_test_flag(class, MOH_CUSTOM) && 
00448                (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
00449                 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00450               !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00451             ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00452             argv[argc++] = fns[files];
00453             files++;
00454          }
00455       }
00456    }
00457    argv[argc] = NULL;
00458    if (dir) {
00459       closedir(dir);
00460    }
00461    if (pipe(fds)) {  
00462       ast_log(LOG_WARNING, "Pipe failed\n");
00463       return -1;
00464    }
00465    if (!files) {
00466       ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00467       close(fds[0]);
00468       close(fds[1]);
00469       return -1;
00470    }
00471    if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
00472       sleep(respawn_time - (time(NULL) - class->start));
00473    }
00474 
00475    time(&class->start);
00476    class->pid = ast_safe_fork(0);
00477    if (class->pid < 0) {
00478       close(fds[0]);
00479       close(fds[1]);
00480       ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00481       return -1;
00482    }
00483    if (!class->pid) {
00484       if (ast_opt_high_priority)
00485          ast_set_priority(0);
00486 
00487       close(fds[0]);
00488       /* Stdout goes to pipe */
00489       dup2(fds[1], STDOUT_FILENO);
00490 
00491       /* Close everything else */
00492       ast_close_fds_above_n(STDERR_FILENO);
00493 
00494       /* Child */
00495       if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
00496          ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00497          _exit(1);
00498       }
00499       setpgid(0, getpid());
00500       if (ast_test_flag(class, MOH_CUSTOM)) {
00501          execv(argv[0], argv);
00502       } else {
00503          /* Default install is /usr/local/bin */
00504          execv(LOCAL_MPG_123, argv);
00505          /* Many places have it in /usr/bin */
00506          execv(MPG_123, argv);
00507          /* Check PATH as a last-ditch effort */
00508          execvp("mpg123", argv);
00509       }
00510       /* Can't use logger, since log FDs are closed */
00511       fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
00512       close(fds[1]);
00513       _exit(1);
00514    } else {
00515       /* Parent */
00516       close(fds[1]);
00517    }
00518    return fds[0];
00519 }
00520 
00521 static void *monmp3thread(void *data)
00522 {
00523 #define  MOH_MS_INTERVAL      100
00524 
00525    struct mohclass *class = data;
00526    struct mohdata *moh;
00527    char buf[8192];
00528    short sbuf[8192];
00529    int res, res2;
00530    int len;
00531    struct timeval deadline, tv_tmp;
00532 
00533    deadline.tv_sec = 0;
00534    deadline.tv_usec = 0;
00535    for(;/* ever */;) {
00536       pthread_testcancel();
00537       /* Spawn mp3 player if it's not there */
00538       if (class->srcfd < 0) {
00539          if ((class->srcfd = spawn_mp3(class)) < 0) {
00540             ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00541             /* Try again later */
00542             sleep(500);
00543             pthread_testcancel();
00544          }
00545       }
00546       if (class->pseudofd > -1) {
00547 #ifdef SOLARIS
00548          thr_yield();
00549 #endif
00550          /* Pause some amount of time */
00551          res = read(class->pseudofd, buf, sizeof(buf));
00552          pthread_testcancel();
00553       } else {
00554          long delta;
00555          /* Reliable sleep */
00556          tv_tmp = ast_tvnow();
00557          if (ast_tvzero(deadline))
00558             deadline = tv_tmp;
00559          delta = ast_tvdiff_ms(tv_tmp, deadline);
00560          if (delta < MOH_MS_INTERVAL) {   /* too early */
00561             deadline = ast_tvadd(deadline, ast_samp2tv(MOH_MS_INTERVAL, 1000));  /* next deadline */
00562             usleep(1000 * (MOH_MS_INTERVAL - delta));
00563             pthread_testcancel();
00564          } else {
00565             ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00566             deadline = tv_tmp;
00567          }
00568          res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
00569       }
00570       if ((strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir")) && AST_LIST_EMPTY(&class->members))
00571          continue;
00572       /* Read mp3 audio */
00573       len = ast_codec_get_len(class->format, res);
00574 
00575       if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00576          if (!res2) {
00577             close(class->srcfd);
00578             class->srcfd = -1;
00579             pthread_testcancel();
00580             if (class->pid > 1) {
00581                killpg(class->pid, SIGHUP);
00582                usleep(100000);
00583                killpg(class->pid, SIGTERM);
00584                usleep(100000);
00585                killpg(class->pid, SIGKILL);
00586                class->pid = 0;
00587             }
00588          } else {
00589             ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
00590          }
00591          continue;
00592       }
00593 
00594       pthread_testcancel();
00595 
00596       ao2_lock(class);
00597       AST_LIST_TRAVERSE(&class->members, moh, list) {
00598          /* Write data */
00599          if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00600             ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
00601          }
00602       }
00603       ao2_unlock(class);
00604    }
00605    return NULL;
00606 }
00607 
00608 static int play_moh_exec(struct ast_channel *chan, void *data)
00609 {
00610    char *parse;
00611    char *class;
00612    int timeout = -1;
00613    int res;
00614    AST_DECLARE_APP_ARGS(args,
00615       AST_APP_ARG(class);
00616       AST_APP_ARG(duration);
00617    );
00618 
00619    parse = ast_strdupa(data);
00620 
00621    AST_STANDARD_APP_ARGS(args, parse);
00622 
00623    if (!ast_strlen_zero(args.duration)) {
00624       if (sscanf(args.duration, "%30d", &timeout) == 1) {
00625          timeout *= 1000;
00626       } else {
00627          ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
00628       }
00629    }
00630 
00631    class = S_OR(args.class, NULL);
00632    if (ast_moh_start(chan, class, NULL)) {
00633       ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
00634       return 0;
00635    }
00636 
00637    if (timeout > 0)
00638       res = ast_safe_sleep(chan, timeout);
00639    else {
00640       while (!(res = ast_safe_sleep(chan, 10000)));
00641    }
00642 
00643    ast_moh_stop(chan);
00644 
00645    return res;
00646 }
00647 
00648 static int wait_moh_exec(struct ast_channel *chan, void *data)
00649 {
00650    static int deprecation_warning = 0;
00651    int res;
00652 
00653    if (!deprecation_warning) {
00654       deprecation_warning = 1;
00655       ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
00656    }
00657 
00658    if (!data || !atoi(data)) {
00659       ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00660       return -1;
00661    }
00662    if (ast_moh_start(chan, NULL, NULL)) {
00663       ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00664       return 0;
00665    }
00666    res = ast_safe_sleep(chan, atoi(data) * 1000);
00667    ast_moh_stop(chan);
00668    return res;
00669 }
00670 
00671 static int set_moh_exec(struct ast_channel *chan, void *data)
00672 {
00673    static int deprecation_warning = 0;
00674 
00675    if (!deprecation_warning) {
00676       deprecation_warning = 1;
00677       ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
00678    }
00679 
00680    if (ast_strlen_zero(data)) {
00681       ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00682       return -1;
00683    }
00684    ast_string_field_set(chan, musicclass, data);
00685    return 0;
00686 }
00687 
00688 static int start_moh_exec(struct ast_channel *chan, void *data)
00689 {
00690    char *parse;
00691    char *class;
00692    AST_DECLARE_APP_ARGS(args,
00693       AST_APP_ARG(class);
00694    );
00695 
00696    parse = ast_strdupa(data);
00697 
00698    AST_STANDARD_APP_ARGS(args, parse);
00699 
00700    class = S_OR(args.class, NULL);
00701    if (ast_moh_start(chan, class, NULL)) 
00702       ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
00703 
00704    return 0;
00705 }
00706 
00707 static int stop_moh_exec(struct ast_channel *chan, void *data)
00708 {
00709    ast_moh_stop(chan);
00710 
00711    return 0;
00712 }
00713 
00714 static struct mohclass *get_mohbyname(const char *name, int warn)
00715 {
00716    struct mohclass *moh = NULL;
00717    struct mohclass tmp_class = {
00718       .flags = 0,
00719    };
00720 
00721    ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
00722 
00723    moh = ao2_t_find(mohclasses, &tmp_class, 0, "Finding by name");
00724 
00725    if (!moh && warn) {
00726       ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
00727    }
00728 
00729    return moh;
00730 }
00731 
00732 static struct mohdata *mohalloc(struct mohclass *cl)
00733 {
00734    struct mohdata *moh;
00735    long flags; 
00736    
00737    if (!(moh = ast_calloc(1, sizeof(*moh))))
00738       return NULL;
00739    
00740    if (pipe(moh->pipe)) {
00741       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00742       ast_free(moh);
00743       return NULL;
00744    }
00745 
00746    /* Make entirely non-blocking */
00747    flags = fcntl(moh->pipe[0], F_GETFL);
00748    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00749    flags = fcntl(moh->pipe[1], F_GETFL);
00750    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00751 
00752    moh->f.frametype = AST_FRAME_VOICE;
00753    moh->f.subclass = cl->format;
00754    moh->f.offset = AST_FRIENDLY_OFFSET;
00755 
00756    moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
00757 
00758    ao2_lock(cl);
00759    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00760    ao2_unlock(cl);
00761    
00762    return moh;
00763 }
00764 
00765 static void moh_release(struct ast_channel *chan, void *data)
00766 {
00767    struct mohdata *moh = data;
00768    struct mohclass *class = moh->parent;
00769    int oldwfmt;
00770 
00771    ao2_lock(class);
00772    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00773    ao2_unlock(class);
00774    
00775    close(moh->pipe[0]);
00776    close(moh->pipe[1]);
00777 
00778    oldwfmt = moh->origwfmt;
00779 
00780    moh->parent = class = mohclass_unref(class, "unreffing moh->parent upon deactivation of generator");
00781 
00782    ast_free(moh);
00783 
00784    if (chan) {
00785       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
00786          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
00787                chan->name, ast_getformatname(oldwfmt));
00788       }
00789 
00790       ast_verb(3, "Stopped music on hold on %s\n", chan->name);
00791    }
00792 }
00793 
00794 static void *moh_alloc(struct ast_channel *chan, void *params)
00795 {
00796    struct mohdata *res;
00797    struct mohclass *class = params;
00798    struct moh_files_state *state;
00799 
00800    /* Initiating music_state for current channel. Channel should know name of moh class */
00801    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00802       chan->music_state = state;
00803       state->class = class;
00804    } else
00805       state = chan->music_state;
00806    if (state && state->class != class) {
00807       memset(state, 0, sizeof(*state));
00808       state->class = class;
00809    }
00810 
00811    if ((res = mohalloc(class))) {
00812       res->origwfmt = chan->writeformat;
00813       if (ast_set_write_format(chan, class->format)) {
00814          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00815          moh_release(NULL, res);
00816          res = NULL;
00817       }
00818       ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00819    }
00820    return res;
00821 }
00822 
00823 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00824 {
00825    struct mohdata *moh = data;
00826    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00827    int res;
00828 
00829    len = ast_codec_get_len(moh->parent->format, samples);
00830 
00831    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00832       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00833       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00834    }
00835    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00836    if (res <= 0)
00837       return 0;
00838 
00839    moh->f.datalen = res;
00840    moh->f.data.ptr = buf + AST_FRIENDLY_OFFSET / 2;
00841    moh->f.samples = ast_codec_get_samples(&moh->f);
00842 
00843    if (ast_write(chan, &moh->f) < 0) {
00844       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00845       return -1;
00846    }
00847 
00848    return 0;
00849 }
00850 
00851 static struct ast_generator mohgen = {
00852    .alloc    = moh_alloc,
00853    .release  = moh_release,
00854    .generate = moh_generate,
00855    .digit    = moh_handle_digit,
00856 };
00857 
00858 static int moh_add_file(struct mohclass *class, const char *filepath)
00859 {
00860    if (!class->allowed_files) {
00861       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00862          return -1;
00863       class->allowed_files = INITIAL_NUM_FILES;
00864    } else if (class->total_files == class->allowed_files) {
00865       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00866          class->allowed_files = 0;
00867          class->total_files = 0;
00868          return -1;
00869       }
00870       class->allowed_files *= 2;
00871    }
00872 
00873    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00874       return -1;
00875 
00876    class->total_files++;
00877 
00878    return 0;
00879 }
00880 
00881 static int moh_sort_compare(const void *i1, const void *i2)
00882 {
00883    char *s1, *s2;
00884 
00885    s1 = ((char **)i1)[0];
00886    s2 = ((char **)i2)[0];
00887 
00888    return strcasecmp(s1, s2);
00889 }
00890 
00891 static int moh_scan_files(struct mohclass *class) {
00892 
00893    DIR *files_DIR;
00894    struct dirent *files_dirent;
00895    char path[PATH_MAX];
00896    char filepath[PATH_MAX];
00897    char *ext;
00898    struct stat statbuf;
00899    int dirnamelen;
00900    int i;
00901 
00902    files_DIR = opendir(class->dir);
00903    if (!files_DIR) {
00904       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00905       return -1;
00906    }
00907 
00908    for (i = 0; i < class->total_files; i++)
00909       ast_free(class->filearray[i]);
00910 
00911    class->total_files = 0;
00912    dirnamelen = strlen(class->dir) + 2;
00913    if (!getcwd(path, sizeof(path))) {
00914       ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
00915       return -1;
00916    }
00917    if (chdir(class->dir) < 0) {
00918       ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00919       return -1;
00920    }
00921    while ((files_dirent = readdir(files_DIR))) {
00922       /* The file name must be at least long enough to have the file type extension */
00923       if ((strlen(files_dirent->d_name) < 4))
00924          continue;
00925 
00926       /* Skip files that starts with a dot */
00927       if (files_dirent->d_name[0] == '.')
00928          continue;
00929 
00930       /* Skip files without extensions... they are not audio */
00931       if (!strchr(files_dirent->d_name, '.'))
00932          continue;
00933 
00934       snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00935 
00936       if (stat(filepath, &statbuf))
00937          continue;
00938 
00939       if (!S_ISREG(statbuf.st_mode))
00940          continue;
00941 
00942       if ((ext = strrchr(filepath, '.')))
00943          *ext = '\0';
00944 
00945       /* if the file is present in multiple formats, ensure we only put it into the list once */
00946       for (i = 0; i < class->total_files; i++)
00947          if (!strcmp(filepath, class->filearray[i]))
00948             break;
00949 
00950       if (i == class->total_files) {
00951          if (moh_add_file(class, filepath))
00952             break;
00953       }
00954    }
00955 
00956    closedir(files_DIR);
00957    if (chdir(path) < 0) {
00958       ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
00959       return -1;
00960    }
00961    if (ast_test_flag(class, MOH_SORTALPHA))
00962       qsort(&class->filearray[0], class->total_files, sizeof(char *), moh_sort_compare);
00963    return class->total_files;
00964 }
00965 
00966 static int init_files_class(struct mohclass *class)
00967 {
00968    int res;
00969 
00970    res = moh_scan_files(class);
00971 
00972    if (res < 0) {
00973       return -1;
00974    }
00975 
00976    if (!res) {
00977       if (option_verbose > 2) {
00978          ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
00979                class->dir, class->name);
00980       }
00981       return -1;
00982    }
00983 
00984    if (strchr(class->args, 'r')) {
00985       ast_set_flag(class, MOH_RANDOMIZE);
00986    }
00987 
00988    return 0;
00989 }
00990 
00991 
00992 static int moh_diff(struct mohclass *old, struct mohclass *new)
00993 {
00994    if (!old || !new) {
00995       return -1;
00996    }
00997 
00998    if (strcmp(old->dir, new->dir)) {
00999       return -1;
01000    } else if (strcmp(old->mode, new->mode)) {
01001       return -1;
01002    } else if (strcmp(old->args, new->args)) {
01003       return -1;
01004    } else if (old->flags != new->flags) {
01005       return -1;
01006    }
01007 
01008    return 0;
01009 }
01010 
01011 static int init_app_class(struct mohclass *class)
01012 {
01013 #ifdef HAVE_DAHDI
01014    int x;
01015 #endif
01016 
01017    if (!strcasecmp(class->mode, "custom")) {
01018       ast_set_flag(class, MOH_CUSTOM);
01019    } else if (!strcasecmp(class->mode, "mp3nb")) {
01020       ast_set_flag(class, MOH_SINGLE);
01021    } else if (!strcasecmp(class->mode, "quietmp3nb")) {
01022       ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
01023    } else if (!strcasecmp(class->mode, "quietmp3")) {
01024       ast_set_flag(class, MOH_QUIET);
01025    }
01026       
01027    class->srcfd = -1;
01028    class->pseudofd = -1;
01029 
01030 #ifdef HAVE_DAHDI
01031    /* Open /dev/zap/pseudo for timing...  Is
01032       there a better, yet reliable way to do this? */
01033    class->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
01034    if (class->pseudofd < 0) {
01035       ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
01036    } else {
01037       x = 320;
01038       ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
01039    }
01040 #endif
01041 
01042    if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
01043       ast_log(LOG_WARNING, "Unable to create moh thread...\n");
01044       if (class->pseudofd > -1) {
01045          close(class->pseudofd);
01046          class->pseudofd = -1;
01047       }
01048       return -1;
01049    }
01050 
01051    return 0;
01052 }
01053 
01054 /*!
01055  * \note This function owns the reference it gets to moh
01056  */
01057 static int moh_register(struct mohclass *moh, int reload, int unref)
01058 {
01059    struct mohclass *mohclass = NULL;
01060 
01061    if ((mohclass = get_mohbyname(moh->name, 0)) && !moh_diff(mohclass, moh)) {
01062       if (!mohclass->delete) {
01063          ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
01064          mohclass = mohclass_unref(mohclass, "unreffing mohclass we just found by name");
01065          if (unref) {
01066             moh = mohclass_unref(moh, "unreffing potential new moh class (it is a duplicate)");
01067          }
01068          return -1;
01069       }
01070       mohclass = mohclass_unref(mohclass, "Unreffing mohclass we just found by name");
01071    }
01072 
01073    time(&moh->start);
01074    moh->start -= respawn_time;
01075    
01076    if (!strcasecmp(moh->mode, "files")) {
01077       if (init_files_class(moh)) {
01078          moh = mohclass_unref(moh, "unreffing potential new moh class (init_files_class failed)");
01079          return -1;
01080       }
01081    } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || 
01082          !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || 
01083          !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
01084       if (init_app_class(moh)) {
01085          moh = mohclass_unref(moh, "unreffing potential new moh class (init_app_class_failed)");
01086          return -1;
01087       }
01088    } else {
01089       ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
01090       moh = mohclass_unref(moh, "unreffing potential new moh class (unknown mode)");
01091       return -1;
01092    }
01093 
01094    ao2_t_link(mohclasses, moh, "Adding class to container");
01095 
01096    if (unref) {
01097       moh = mohclass_unref(moh, "Unreffing new moh class because we just added it to the container");
01098    }
01099    
01100    return 0;
01101 }
01102 
01103 static void local_ast_moh_cleanup(struct ast_channel *chan)
01104 {
01105    struct moh_files_state *state = chan->music_state;
01106 
01107    if (state) {
01108       ast_free(chan->music_state);
01109       chan->music_state = NULL;
01110    }
01111 }
01112 
01113 static void moh_class_destructor(void *obj);
01114 
01115 static struct mohclass *moh_class_malloc(void)
01116 {
01117    struct mohclass *class;
01118 
01119    if ((class = ao2_t_alloc(sizeof(*class), moh_class_destructor, "Allocating new moh class"))) {
01120       class->format = AST_FORMAT_SLINEAR;
01121    }
01122 
01123    return class;
01124 }
01125 
01126 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
01127 {
01128    struct mohclass *mohclass = NULL;
01129    struct moh_files_state *state = chan->music_state;
01130    struct ast_variable *var = NULL;
01131    int res;
01132    int realtime_possible = ast_check_realtime("musiconhold");
01133 
01134    /* The following is the order of preference for which class to use:
01135     * 1) The channels explicitly set musicclass, which should *only* be
01136     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
01137     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
01138     *    result of receiving a HOLD control frame, this should be the
01139     *    payload that came with the frame.
01140     * 3) The interpclass argument. This would be from the mohinterpret
01141     *    option from channel drivers. This is the same as the old musicclass
01142     *    option.
01143     * 4) The default class.
01144     */
01145    if (!ast_strlen_zero(chan->musicclass)) {
01146       mohclass = get_mohbyname(chan->musicclass, 1);
01147       if (!mohclass && realtime_possible) {
01148          var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
01149       }
01150    }
01151    if (!mohclass && !var && !ast_strlen_zero(mclass)) {
01152       mohclass = get_mohbyname(mclass, 1);
01153       if (!mohclass && realtime_possible) {
01154          var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL);
01155       }
01156    }
01157    if (!mohclass && !var && !ast_strlen_zero(interpclass)) {
01158       mohclass = get_mohbyname(interpclass, 1);
01159       if (!mohclass && realtime_possible) {
01160          var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL);
01161       }
01162    }
01163 
01164    if (!mohclass && !var) {
01165       mohclass = get_mohbyname("default", 1);
01166       if (!mohclass && realtime_possible) {
01167          var = ast_load_realtime("musiconhold", "name", "default", SENTINEL);
01168       }
01169    }
01170 
01171    /* If no moh class found in memory, then check RT. Note that the logic used
01172     * above guarantees that if var is non-NULL, then mohclass must be NULL.
01173     */
01174    if (var) {
01175       struct ast_variable *tmp = NULL;
01176 
01177       if ((mohclass = moh_class_malloc())) {
01178          mohclass->realtime = 1;
01179          for (tmp = var; tmp; tmp = tmp->next) {
01180             if (!strcasecmp(tmp->name, "name"))
01181                ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
01182             else if (!strcasecmp(tmp->name, "mode"))
01183                ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode)); 
01184             else if (!strcasecmp(tmp->name, "directory"))
01185                ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
01186             else if (!strcasecmp(tmp->name, "application"))
01187                ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
01188             else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
01189                mohclass->digit = *tmp->value;
01190             else if (!strcasecmp(tmp->name, "random"))
01191                ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
01192             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
01193                ast_set_flag(mohclass, MOH_RANDOMIZE);
01194             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha")) 
01195                ast_set_flag(mohclass, MOH_SORTALPHA);
01196             else if (!strcasecmp(tmp->name, "format")) {
01197                mohclass->format = ast_getformatbyname(tmp->value);
01198                if (!mohclass->format) {
01199                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
01200                   mohclass->format = AST_FORMAT_SLINEAR;
01201                }
01202             }
01203          }
01204          ast_variables_destroy(var);
01205          if (ast_strlen_zero(mohclass->dir)) {
01206             if (!strcasecmp(mohclass->mode, "custom")) {
01207                strcpy(mohclass->dir, "nodir");
01208             } else {
01209                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
01210                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no directory specified)");
01211                return -1;
01212             }
01213          }
01214          if (ast_strlen_zero(mohclass->mode)) {
01215             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
01216             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no mode specified)");
01217             return -1;
01218          }
01219          if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
01220             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
01221             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no app specified for custom mode");
01222             return -1;
01223          }
01224 
01225          if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
01226             /* CACHERTCLASSES enabled, let's add this class to default tree */
01227             if (state && state->class) {
01228                /* Class already exist for this channel */
01229                ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01230                if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01231                   /* we found RT class with the same name, seems like we should continue playing existing one */
01232                   /* XXX This code is impossible to reach */
01233                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has a class)");
01234                   mohclass = state->class;
01235                }
01236             }
01237             /* We don't want moh_register to unref the mohclass because we do it at the end of this function as well.
01238              * If we allowed moh_register to unref the mohclass,too, then the count would be off by one. The result would
01239              * be that the destructor would be called when the generator on the channel is deactivated. The container then
01240              * has a pointer to a freed mohclass, so any operations involving the mohclass container would result in reading
01241              * invalid memory.
01242              */
01243             moh_register(mohclass, 0, 0);
01244          } else {
01245             /* We don't register RT moh class, so let's init it manualy */
01246 
01247             time(&mohclass->start);
01248             mohclass->start -= respawn_time;
01249    
01250             if (!strcasecmp(mohclass->mode, "files")) {
01251                if (!moh_scan_files(mohclass)) {
01252                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
01253                   return -1;
01254                }
01255                if (strchr(mohclass->args, 'r'))
01256                   ast_set_flag(mohclass, MOH_RANDOMIZE);
01257             } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) {
01258 
01259                if (!strcasecmp(mohclass->mode, "custom"))
01260                   ast_set_flag(mohclass, MOH_CUSTOM);
01261                else if (!strcasecmp(mohclass->mode, "mp3nb"))
01262                   ast_set_flag(mohclass, MOH_SINGLE);
01263                else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
01264                   ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
01265                else if (!strcasecmp(mohclass->mode, "quietmp3"))
01266                   ast_set_flag(mohclass, MOH_QUIET);
01267          
01268                mohclass->srcfd = -1;
01269 #ifdef HAVE_DAHDI
01270                /* Open /dev/dahdi/pseudo for timing...  Is
01271                   there a better, yet reliable way to do this? */
01272                mohclass->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
01273                if (mohclass->pseudofd < 0) {
01274                   ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
01275                } else {
01276                   int x = 320;
01277                   ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
01278                }
01279 #else
01280                mohclass->pseudofd = -1;
01281 #endif
01282                /* Let's check if this channel already had a moh class before */
01283                if (state && state->class) {
01284                   /* Class already exist for this channel */
01285                   ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01286                   if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01287                      /* we found RT class with the same name, seems like we should continue playing existing one */
01288                      mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has one)");
01289                      mohclass = state->class;
01290                   }
01291                } else {
01292                   if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
01293                      ast_log(LOG_WARNING, "Unable to create moh...\n");
01294                      if (mohclass->pseudofd > -1) {
01295                         close(mohclass->pseudofd);
01296                         mohclass->pseudofd = -1;
01297                      }
01298                      mohclass = mohclass_unref(mohclass, "Unreffing potential mohclass (failed to create background thread)");
01299                      return -1;
01300                   }
01301                }
01302             } else {
01303                ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
01304                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (unknown mode)");
01305                return -1;
01306             }
01307          }
01308       } else {
01309          ast_variables_destroy(var);
01310       }
01311    }
01312 
01313    if (!mohclass) {
01314       return -1;
01315    }
01316 
01317    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01318       "State: Start\r\n"
01319       "Channel: %s\r\n"
01320       "UniqueID: %s\r\n",
01321       chan->name, chan->uniqueid);
01322 
01323    ast_set_flag(chan, AST_FLAG_MOH);
01324 
01325    if (mohclass->total_files) {
01326       res = ast_activate_generator(chan, &moh_file_stream, mohclass);
01327    } else {
01328       res = ast_activate_generator(chan, &mohgen, mohclass);
01329    }
01330 
01331    mohclass = mohclass_unref(mohclass, "unreffing local reference to mohclass in local_ast_moh_start");
01332 
01333    return res;
01334 }
01335 
01336 static void local_ast_moh_stop(struct ast_channel *chan)
01337 {
01338    struct moh_files_state *state = chan->music_state;
01339    ast_clear_flag(chan, AST_FLAG_MOH);
01340    ast_deactivate_generator(chan);
01341 
01342    if (state) {
01343       if (chan->stream) {
01344          ast_closestream(chan->stream);
01345          chan->stream = NULL;
01346       }
01347    }
01348 
01349    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01350       "State: Stop\r\n"
01351       "Channel: %s\r\n"
01352       "UniqueID: %s\r\n",
01353       chan->name, chan->uniqueid);
01354 }
01355 
01356 static void moh_class_destructor(void *obj)
01357 {
01358    struct mohclass *class = obj;
01359    struct mohdata *member;
01360 
01361    ast_debug(1, "Destroying MOH class '%s'\n", class->name);
01362 
01363    if (class->pid > 1) {
01364       char buff[8192];
01365       int bytes, tbytes = 0, stime = 0, pid = 0;
01366 
01367       ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
01368 
01369       stime = time(NULL) + 2;
01370       pid = class->pid;
01371       class->pid = 0;
01372 
01373       /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01374        * to give the process a reason and time enough to kill off its
01375        * children. */
01376       killpg(pid, SIGHUP);
01377       usleep(100000);
01378       killpg(pid, SIGTERM);
01379       usleep(100000);
01380       killpg(pid, SIGKILL);
01381 
01382       while ((ast_wait_for_input(class->srcfd, 100) > 0) && 
01383             (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
01384          tbytes = tbytes + bytes;
01385       }
01386 
01387       ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01388 
01389       close(class->srcfd);
01390    }
01391 
01392    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
01393       free(member);
01394    }
01395 
01396    if (class->thread) {
01397       pthread_cancel(class->thread);
01398       pthread_join(class->thread, NULL);
01399       class->thread = AST_PTHREADT_NULL;
01400    }
01401 
01402    if (class->filearray) {
01403       int i;
01404       for (i = 0; i < class->total_files; i++) {
01405          free(class->filearray[i]);
01406       }
01407       free(class->filearray);
01408       class->filearray = NULL;
01409    }
01410 }
01411 
01412 static int moh_class_mark(void *obj, void *arg, int flags)
01413 {
01414    struct mohclass *class = obj;
01415 
01416    class->delete = 1;
01417 
01418    return 0;
01419 }
01420 
01421 static int moh_classes_delete_marked(void *obj, void *arg, int flags)
01422 {
01423    struct mohclass *class = obj;
01424 
01425    return class->delete ? CMP_MATCH : 0;
01426 }
01427 
01428 static int load_moh_classes(int reload)
01429 {
01430    struct ast_config *cfg;
01431    struct ast_variable *var;
01432    struct mohclass *class; 
01433    char *cat;
01434    int numclasses = 0;
01435    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01436 
01437    cfg = ast_config_load("musiconhold.conf", config_flags);
01438 
01439    if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
01440       return 0;
01441 
01442    if (reload) {
01443       ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
01444    }
01445    
01446    ast_clear_flag(global_flags, AST_FLAGS_ALL);
01447 
01448    cat = ast_category_browse(cfg, NULL);
01449    for (; cat; cat = ast_category_browse(cfg, cat)) {
01450       /* Setup common options from [general] section */
01451       if (!strcasecmp(cat, "general")) {
01452          for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01453             if (!strcasecmp(var->name, "cachertclasses")) {
01454                ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
01455             } else {
01456                ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
01457             }
01458          }
01459       }
01460       /* These names were deprecated in 1.4 and should not be used until after the next major release. */
01461       if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") || 
01462             !strcasecmp(cat, "general")) {
01463          continue;
01464       }
01465 
01466       if (!(class = moh_class_malloc())) {
01467          break;
01468       }
01469 
01470       ast_copy_string(class->name, cat, sizeof(class->name));  
01471       for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01472          if (!strcasecmp(var->name, "mode"))
01473             ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01474          else if (!strcasecmp(var->name, "directory"))
01475             ast_copy_string(class->dir, var->value, sizeof(class->dir));
01476          else if (!strcasecmp(var->name, "application"))
01477             ast_copy_string(class->args, var->value, sizeof(class->args));
01478          else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
01479             class->digit = *var->value;
01480          else if (!strcasecmp(var->name, "random"))
01481             ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01482          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
01483             ast_set_flag(class, MOH_RANDOMIZE);
01484          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
01485             ast_set_flag(class, MOH_SORTALPHA);
01486          else if (!strcasecmp(var->name, "format")) {
01487             class->format = ast_getformatbyname(var->value);
01488             if (!class->format) {
01489                ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01490                class->format = AST_FORMAT_SLINEAR;
01491             }
01492          }
01493       }
01494 
01495       if (ast_strlen_zero(class->dir)) {
01496          if (!strcasecmp(class->mode, "custom")) {
01497             strcpy(class->dir, "nodir");
01498          } else {
01499             ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01500             class = mohclass_unref(class, "unreffing potential mohclass (no directory)");
01501             continue;
01502          }
01503       }
01504       if (ast_strlen_zero(class->mode)) {
01505          ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01506          class = mohclass_unref(class, "unreffing potential mohclass (no mode)");
01507          continue;
01508       }
01509       if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01510          ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01511          class = mohclass_unref(class, "unreffing potential mohclass (no app for custom mode)");
01512          continue;
01513       }
01514 
01515       /* Don't leak a class when it's already registered */
01516       if (!moh_register(class, reload, 1)) {
01517          numclasses++;
01518       }
01519    }
01520 
01521    ast_config_destroy(cfg);
01522 
01523    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, 
01524          moh_classes_delete_marked, NULL, "Purge marked classes");
01525 
01526    return numclasses;
01527 }
01528 
01529 static void ast_moh_destroy(void)
01530 {
01531    ast_verb(2, "Destroying musiconhold processes\n");
01532    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
01533 }
01534 
01535 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01536 {
01537    switch (cmd) {
01538    case CLI_INIT:
01539       e->command = "moh reload";
01540       e->usage =
01541          "Usage: moh reload\n"
01542          "       Reloads the MusicOnHold module.\n"
01543          "       Alias for 'module reload res_musiconhold.so'\n";
01544       return NULL;
01545    case CLI_GENERATE:
01546       return NULL;
01547    }
01548 
01549    if (a->argc != e->args)
01550       return CLI_SHOWUSAGE;
01551 
01552    reload();
01553 
01554    return CLI_SUCCESS;
01555 }
01556 
01557 static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01558 {
01559    struct mohclass *class;
01560    struct ao2_iterator i;
01561 
01562    switch (cmd) {
01563    case CLI_INIT:
01564       e->command = "moh show files";
01565       e->usage =
01566          "Usage: moh show files\n"
01567          "       Lists all loaded file-based MusicOnHold classes and their\n"
01568          "       files.\n";
01569       return NULL;
01570    case CLI_GENERATE:
01571       return NULL;
01572    }
01573 
01574    if (a->argc != e->args)
01575       return CLI_SHOWUSAGE;
01576 
01577    i = ao2_iterator_init(mohclasses, 0);
01578 
01579    for (; (class = ao2_t_iterator_next(&i, "Show files iterator")); mohclass_unref(class, "Unref iterator in moh show files")) {
01580       int x;
01581 
01582       if (!class->total_files) {
01583          continue;
01584       }
01585 
01586       ast_cli(a->fd, "Class: %s\n", class->name);
01587       for (x = 0; x < class->total_files; x++) {
01588          ast_cli(a->fd, "\tFile: %s\n", class->filearray[x]);
01589       }
01590    }
01591 
01592    return CLI_SUCCESS;
01593 }
01594 
01595 static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01596 {
01597    struct mohclass *class;
01598    struct ao2_iterator i;
01599 
01600    switch (cmd) {
01601    case CLI_INIT:
01602       e->command = "moh show classes";
01603       e->usage =
01604          "Usage: moh show classes\n"
01605          "       Lists all MusicOnHold classes.\n";
01606       return NULL;
01607    case CLI_GENERATE:
01608       return NULL;
01609    }
01610 
01611    if (a->argc != e->args)
01612       return CLI_SHOWUSAGE;
01613 
01614    i = ao2_iterator_init(mohclasses, 0);
01615 
01616    for (; (class = ao2_t_iterator_next(&i, "Show classes iterator")); mohclass_unref(class, "Unref iterator in moh show classes")) {
01617       ast_cli(a->fd, "Class: %s\n", class->name);
01618       ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01619       ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01620       if (ast_test_flag(class, MOH_CUSTOM)) {
01621          ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01622       }
01623       if (strcasecmp(class->mode, "files")) {
01624          ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
01625       }
01626    }
01627 
01628    return CLI_SUCCESS;
01629 }
01630 
01631 static struct ast_cli_entry cli_moh[] = {
01632    AST_CLI_DEFINE(handle_cli_moh_reload,       "Reload MusicOnHold"),
01633    AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"),
01634    AST_CLI_DEFINE(handle_cli_moh_show_files,   "List MusicOnHold file-based classes")
01635 };
01636 
01637 static int moh_class_hash(const void *obj, const int flags)
01638 {
01639    const struct mohclass *class = obj;
01640 
01641    return ast_str_case_hash(class->name);
01642 }
01643 
01644 static int moh_class_cmp(void *obj, void *arg, int flags)
01645 {
01646    struct mohclass *class = obj, *class2 = arg;
01647 
01648    return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
01649 }
01650 
01651 static int load_module(void)
01652 {
01653    int res;
01654 
01655    if (!(mohclasses = ao2_t_container_alloc(53, moh_class_hash, moh_class_cmp, "Moh class container"))) {
01656       return AST_MODULE_LOAD_DECLINE;
01657    }
01658 
01659    if (!load_moh_classes(0)) {   /* No music classes configured, so skip it */
01660       ast_log(LOG_WARNING, "No music on hold classes configured, "
01661             "disabling music on hold.\n");
01662    } else {
01663       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01664             local_ast_moh_cleanup);
01665    }
01666 
01667    res = ast_register_application(play_moh, play_moh_exec, play_moh_syn, play_moh_desc);
01668    ast_register_atexit(ast_moh_destroy);
01669    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01670    if (!res)
01671       res = ast_register_application(wait_moh, wait_moh_exec, wait_moh_syn, wait_moh_desc);
01672    if (!res)
01673       res = ast_register_application(set_moh, set_moh_exec, set_moh_syn, set_moh_desc);
01674    if (!res)
01675       res = ast_register_application(start_moh, start_moh_exec, start_moh_syn, start_moh_desc);
01676    if (!res)
01677       res = ast_register_application(stop_moh, stop_moh_exec, stop_moh_syn, stop_moh_desc);
01678 
01679    return AST_MODULE_LOAD_SUCCESS;
01680 }
01681 
01682 static int reload(void)
01683 {
01684    if (load_moh_classes(1)) {
01685       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01686             local_ast_moh_cleanup);
01687    }
01688 
01689    return AST_MODULE_LOAD_SUCCESS;
01690 }
01691 
01692 static int moh_class_inuse(void *obj, void *arg, int flags)
01693 {
01694    struct mohclass *class = obj;
01695 
01696    return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
01697 }
01698 
01699 static int unload_module(void)
01700 {
01701    int res = 0;
01702    struct mohclass *class = NULL;
01703 
01704    /* XXX This check shouldn't be required if module ref counting was being used
01705     * properly ... */
01706    if ((class = ao2_t_callback(mohclasses, 0, moh_class_inuse, NULL, "Module unload callback"))) {
01707       class = mohclass_unref(class, "unref of class from module unload callback");
01708       res = -1;
01709    }
01710 
01711    if (res < 0) {
01712       ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
01713       return res;
01714    }
01715 
01716    ast_uninstall_music_functions();
01717 
01718    ast_moh_destroy();
01719    res = ast_unregister_application(play_moh);
01720    res |= ast_unregister_application(wait_moh);
01721    res |= ast_unregister_application(set_moh);
01722    res |= ast_unregister_application(start_moh);
01723    res |= ast_unregister_application(stop_moh);
01724    ast_cli_unregister_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01725    ast_unregister_atexit(ast_moh_destroy);
01726 
01727    return res;
01728 }
01729 
01730 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
01731    .load = load_module,
01732    .unload = unload_module,
01733    .reload = reload,
01734 );

Generated on 3 Mar 2010 for Asterisk - the Open Source PBX by  doxygen 1.6.1