Fri Jul 24 00:41:02 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>dahdi</use>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 188104 $")
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, "%d", &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    int res;
01131 
01132    /* The following is the order of preference for which class to use:
01133     * 1) The channels explicitly set musicclass, which should *only* be
01134     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
01135     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
01136     *    result of receiving a HOLD control frame, this should be the
01137     *    payload that came with the frame.
01138     * 3) The interpclass argument. This would be from the mohinterpret
01139     *    option from channel drivers. This is the same as the old musicclass
01140     *    option.
01141     * 4) The default class.
01142     */
01143    if (!ast_strlen_zero(chan->musicclass)) {
01144       mohclass = get_mohbyname(chan->musicclass, 1);
01145    }
01146    if (!mohclass && !ast_strlen_zero(mclass)) {
01147       mohclass = get_mohbyname(mclass, 1);
01148    }
01149    if (!mohclass && !ast_strlen_zero(interpclass)) {
01150       mohclass = get_mohbyname(interpclass, 1);
01151    }
01152 
01153    /* If no moh class found in memory, then check RT */
01154    if (!mohclass && ast_check_realtime("musiconhold")) {
01155       struct ast_variable *var = NULL, *tmp = NULL;
01156 
01157       if (!ast_strlen_zero(chan->musicclass)) {
01158          var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
01159       }
01160       if (!var && !ast_strlen_zero(mclass))
01161          var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL);
01162       if (!var && !ast_strlen_zero(interpclass))
01163          var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL);
01164       if (!var)
01165          var = ast_load_realtime("musiconhold", "name", "default", SENTINEL);
01166       if (var && (mohclass = moh_class_malloc())) {
01167          mohclass->realtime = 1;
01168          for (tmp = var; tmp; tmp = tmp->next) {
01169             if (!strcasecmp(tmp->name, "name"))
01170                ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
01171             else if (!strcasecmp(tmp->name, "mode"))
01172                ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode)); 
01173             else if (!strcasecmp(tmp->name, "directory"))
01174                ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
01175             else if (!strcasecmp(tmp->name, "application"))
01176                ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
01177             else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
01178                mohclass->digit = *tmp->value;
01179             else if (!strcasecmp(tmp->name, "random"))
01180                ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
01181             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
01182                ast_set_flag(mohclass, MOH_RANDOMIZE);
01183             else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha")) 
01184                ast_set_flag(mohclass, MOH_SORTALPHA);
01185             else if (!strcasecmp(tmp->name, "format")) {
01186                mohclass->format = ast_getformatbyname(tmp->value);
01187                if (!mohclass->format) {
01188                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
01189                   mohclass->format = AST_FORMAT_SLINEAR;
01190                }
01191             }
01192          }
01193          ast_variables_destroy(var);
01194          if (ast_strlen_zero(mohclass->dir)) {
01195             if (!strcasecmp(mohclass->mode, "custom")) {
01196                strcpy(mohclass->dir, "nodir");
01197             } else {
01198                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
01199                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no directory specified)");
01200                return -1;
01201             }
01202          }
01203          if (ast_strlen_zero(mohclass->mode)) {
01204             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
01205             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no mode specified)");
01206             return -1;
01207          }
01208          if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
01209             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
01210             mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no app specified for custom mode");
01211             return -1;
01212          }
01213 
01214          if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
01215             /* CACHERTCLASSES enabled, let's add this class to default tree */
01216             if (state && state->class) {
01217                /* Class already exist for this channel */
01218                ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01219                if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01220                   /* we found RT class with the same name, seems like we should continue playing existing one */
01221                   /* XXX This code is impossible to reach */
01222                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has a class)");
01223                   mohclass = state->class;
01224                }
01225             }
01226             /* We don't want moh_register to unref the mohclass because we do it at the end of this function as well.
01227              * If we allowed moh_register to unref the mohclass,too, then the count would be off by one. The result would
01228              * be that the destructor would be called when the generator on the channel is deactivated. The container then
01229              * has a pointer to a freed mohclass, so any operations involving the mohclass container would result in reading
01230              * invalid memory.
01231              */
01232             moh_register(mohclass, 0, 0);
01233          } else {
01234             /* We don't register RT moh class, so let's init it manualy */
01235 
01236             time(&mohclass->start);
01237             mohclass->start -= respawn_time;
01238    
01239             if (!strcasecmp(mohclass->mode, "files")) {
01240                if (!moh_scan_files(mohclass)) {
01241                   mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
01242                   return -1;
01243                }
01244                if (strchr(mohclass->args, 'r'))
01245                   ast_set_flag(mohclass, MOH_RANDOMIZE);
01246             } 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")) {
01247 
01248                if (!strcasecmp(mohclass->mode, "custom"))
01249                   ast_set_flag(mohclass, MOH_CUSTOM);
01250                else if (!strcasecmp(mohclass->mode, "mp3nb"))
01251                   ast_set_flag(mohclass, MOH_SINGLE);
01252                else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
01253                   ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
01254                else if (!strcasecmp(mohclass->mode, "quietmp3"))
01255                   ast_set_flag(mohclass, MOH_QUIET);
01256          
01257                mohclass->srcfd = -1;
01258 #ifdef HAVE_DAHDI
01259                /* Open /dev/dahdi/pseudo for timing...  Is
01260                   there a better, yet reliable way to do this? */
01261                mohclass->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
01262                if (mohclass->pseudofd < 0) {
01263                   ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
01264                } else {
01265                   int x = 320;
01266                   ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
01267                }
01268 #else
01269                mohclass->pseudofd = -1;
01270 #endif
01271                /* Let's check if this channel already had a moh class before */
01272                if (state && state->class) {
01273                   /* Class already exist for this channel */
01274                   ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
01275                   if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
01276                      /* we found RT class with the same name, seems like we should continue playing existing one */
01277                      mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has one)");
01278                      mohclass = state->class;
01279                   }
01280                } else {
01281                   if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
01282                      ast_log(LOG_WARNING, "Unable to create moh...\n");
01283                      if (mohclass->pseudofd > -1) {
01284                         close(mohclass->pseudofd);
01285                         mohclass->pseudofd = -1;
01286                      }
01287                      mohclass = mohclass_unref(mohclass, "Unreffing potential mohclass (failed to create background thread)");
01288                      return -1;
01289                   }
01290                }
01291             } else {
01292                ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
01293                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (unknown mode)");
01294                return -1;
01295             }
01296          }
01297       } else if (var) {
01298          ast_variables_destroy(var);
01299       }
01300    }
01301 
01302    if (!mohclass) {
01303       mohclass = get_mohbyname("default", 1);
01304    }
01305 
01306    if (!mohclass) {
01307       return -1;
01308    }
01309 
01310    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01311       "State: Start\r\n"
01312       "Channel: %s\r\n"
01313       "UniqueID: %s\r\n",
01314       chan->name, chan->uniqueid);
01315 
01316    ast_set_flag(chan, AST_FLAG_MOH);
01317 
01318    if (mohclass->total_files) {
01319       res = ast_activate_generator(chan, &moh_file_stream, mohclass);
01320    } else {
01321       res = ast_activate_generator(chan, &mohgen, mohclass);
01322    }
01323 
01324    mohclass = mohclass_unref(mohclass, "unreffing local reference to mohclass in local_ast_moh_start");
01325 
01326    return res;
01327 }
01328 
01329 static void local_ast_moh_stop(struct ast_channel *chan)
01330 {
01331    struct moh_files_state *state = chan->music_state;
01332    ast_clear_flag(chan, AST_FLAG_MOH);
01333    ast_deactivate_generator(chan);
01334 
01335    if (state) {
01336       if (chan->stream) {
01337          ast_closestream(chan->stream);
01338          chan->stream = NULL;
01339       }
01340    }
01341 
01342    manager_event(EVENT_FLAG_CALL, "MusicOnHold",
01343       "State: Stop\r\n"
01344       "Channel: %s\r\n"
01345       "UniqueID: %s\r\n",
01346       chan->name, chan->uniqueid);
01347 }
01348 
01349 static void moh_class_destructor(void *obj)
01350 {
01351    struct mohclass *class = obj;
01352    struct mohdata *member;
01353 
01354    ast_debug(1, "Destroying MOH class '%s'\n", class->name);
01355 
01356    if (class->pid > 1) {
01357       char buff[8192];
01358       int bytes, tbytes = 0, stime = 0, pid = 0;
01359 
01360       ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
01361 
01362       stime = time(NULL) + 2;
01363       pid = class->pid;
01364       class->pid = 0;
01365 
01366       /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01367        * to give the process a reason and time enough to kill off its
01368        * children. */
01369       killpg(pid, SIGHUP);
01370       usleep(100000);
01371       killpg(pid, SIGTERM);
01372       usleep(100000);
01373       killpg(pid, SIGKILL);
01374 
01375       while ((ast_wait_for_input(class->srcfd, 100) > 0) && 
01376             (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
01377          tbytes = tbytes + bytes;
01378       }
01379 
01380       ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01381 
01382       close(class->srcfd);
01383    }
01384 
01385    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
01386       free(member);
01387    }
01388    
01389    if (class->thread) {
01390       pthread_cancel(class->thread);
01391       class->thread = AST_PTHREADT_NULL;
01392    }
01393 
01394    if (class->filearray) {
01395       int i;
01396       for (i = 0; i < class->total_files; i++) {
01397          free(class->filearray[i]);
01398       }
01399       free(class->filearray);
01400       class->filearray = NULL;
01401    }
01402 }
01403 
01404 static int moh_class_mark(void *obj, void *arg, int flags)
01405 {
01406    struct mohclass *class = obj;
01407 
01408    class->delete = 1;
01409 
01410    return 0;
01411 }
01412 
01413 static int moh_classes_delete_marked(void *obj, void *arg, int flags)
01414 {
01415    struct mohclass *class = obj;
01416 
01417    return class->delete ? CMP_MATCH : 0;
01418 }
01419 
01420 static int load_moh_classes(int reload)
01421 {
01422    struct ast_config *cfg;
01423    struct ast_variable *var;
01424    struct mohclass *class; 
01425    char *cat;
01426    int numclasses = 0;
01427    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
01428 
01429    cfg = ast_config_load("musiconhold.conf", config_flags);
01430 
01431    if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED)
01432       return 0;
01433 
01434    if (reload) {
01435       ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
01436    }
01437    
01438    ast_clear_flag(global_flags, AST_FLAGS_ALL);
01439 
01440    cat = ast_category_browse(cfg, NULL);
01441    for (; cat; cat = ast_category_browse(cfg, cat)) {
01442       /* Setup common options from [general] section */
01443       if (!strcasecmp(cat, "general")) {
01444          for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01445             if (!strcasecmp(var->name, "cachertclasses")) {
01446                ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
01447             } else {
01448                ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
01449             }
01450          }
01451       }
01452       /* These names were deprecated in 1.4 and should not be used until after the next major release. */
01453       if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") || 
01454             !strcasecmp(cat, "general")) {
01455          continue;
01456       }
01457 
01458       if (!(class = moh_class_malloc())) {
01459          break;
01460       }
01461 
01462       ast_copy_string(class->name, cat, sizeof(class->name));  
01463       for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
01464          if (!strcasecmp(var->name, "mode"))
01465             ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01466          else if (!strcasecmp(var->name, "directory"))
01467             ast_copy_string(class->dir, var->value, sizeof(class->dir));
01468          else if (!strcasecmp(var->name, "application"))
01469             ast_copy_string(class->args, var->value, sizeof(class->args));
01470          else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
01471             class->digit = *var->value;
01472          else if (!strcasecmp(var->name, "random"))
01473             ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01474          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
01475             ast_set_flag(class, MOH_RANDOMIZE);
01476          else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
01477             ast_set_flag(class, MOH_SORTALPHA);
01478          else if (!strcasecmp(var->name, "format")) {
01479             class->format = ast_getformatbyname(var->value);
01480             if (!class->format) {
01481                ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01482                class->format = AST_FORMAT_SLINEAR;
01483             }
01484          }
01485       }
01486 
01487       if (ast_strlen_zero(class->dir)) {
01488          if (!strcasecmp(class->mode, "custom")) {
01489             strcpy(class->dir, "nodir");
01490          } else {
01491             ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01492             class = mohclass_unref(class, "unreffing potential mohclass (no directory)");
01493             continue;
01494          }
01495       }
01496       if (ast_strlen_zero(class->mode)) {
01497          ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01498          class = mohclass_unref(class, "unreffing potential mohclass (no mode)");
01499          continue;
01500       }
01501       if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01502          ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01503          class = mohclass_unref(class, "unreffing potential mohclass (no app for custom mode)");
01504          continue;
01505       }
01506 
01507       /* Don't leak a class when it's already registered */
01508       if (!moh_register(class, reload, 1)) {
01509          numclasses++;
01510       }
01511    }
01512 
01513    ast_config_destroy(cfg);
01514 
01515    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, 
01516          moh_classes_delete_marked, NULL, "Purge marked classes");
01517 
01518    return numclasses;
01519 }
01520 
01521 static void ast_moh_destroy(void)
01522 {
01523    ast_verb(2, "Destroying musiconhold processes\n");
01524    ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
01525 }
01526 
01527 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01528 {
01529    switch (cmd) {
01530    case CLI_INIT:
01531       e->command = "moh reload";
01532       e->usage =
01533          "Usage: moh reload\n"
01534          "       Reloads the MusicOnHold module.\n"
01535          "       Alias for 'module reload res_musiconhold.so'\n";
01536       return NULL;
01537    case CLI_GENERATE:
01538       return NULL;
01539    }
01540 
01541    if (a->argc != e->args)
01542       return CLI_SHOWUSAGE;
01543 
01544    reload();
01545 
01546    return CLI_SUCCESS;
01547 }
01548 
01549 static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01550 {
01551    struct mohclass *class;
01552    struct ao2_iterator i;
01553 
01554    switch (cmd) {
01555    case CLI_INIT:
01556       e->command = "moh show files";
01557       e->usage =
01558          "Usage: moh show files\n"
01559          "       Lists all loaded file-based MusicOnHold classes and their\n"
01560          "       files.\n";
01561       return NULL;
01562    case CLI_GENERATE:
01563       return NULL;
01564    }
01565 
01566    if (a->argc != e->args)
01567       return CLI_SHOWUSAGE;
01568 
01569    i = ao2_iterator_init(mohclasses, 0);
01570 
01571    for (; (class = ao2_t_iterator_next(&i, "Show files iterator")); mohclass_unref(class, "Unref iterator in moh show files")) {
01572       int x;
01573 
01574       if (!class->total_files) {
01575          continue;
01576       }
01577 
01578       ast_cli(a->fd, "Class: %s\n", class->name);
01579       for (x = 0; x < class->total_files; x++) {
01580          ast_cli(a->fd, "\tFile: %s\n", class->filearray[x]);
01581       }
01582    }
01583 
01584    return CLI_SUCCESS;
01585 }
01586 
01587 static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01588 {
01589    struct mohclass *class;
01590    struct ao2_iterator i;
01591 
01592    switch (cmd) {
01593    case CLI_INIT:
01594       e->command = "moh show classes";
01595       e->usage =
01596          "Usage: moh show classes\n"
01597          "       Lists all MusicOnHold classes.\n";
01598       return NULL;
01599    case CLI_GENERATE:
01600       return NULL;
01601    }
01602 
01603    if (a->argc != e->args)
01604       return CLI_SHOWUSAGE;
01605 
01606    i = ao2_iterator_init(mohclasses, 0);
01607 
01608    for (; (class = ao2_t_iterator_next(&i, "Show classes iterator")); mohclass_unref(class, "Unref iterator in moh show classes")) {
01609       ast_cli(a->fd, "Class: %s\n", class->name);
01610       ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01611       ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01612       if (ast_test_flag(class, MOH_CUSTOM)) {
01613          ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01614       }
01615       if (strcasecmp(class->mode, "files")) {
01616          ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
01617       }
01618    }
01619 
01620    return CLI_SUCCESS;
01621 }
01622 
01623 static struct ast_cli_entry cli_moh[] = {
01624    AST_CLI_DEFINE(handle_cli_moh_reload,       "Reload MusicOnHold"),
01625    AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"),
01626    AST_CLI_DEFINE(handle_cli_moh_show_files,   "List MusicOnHold file-based classes")
01627 };
01628 
01629 static int moh_class_hash(const void *obj, const int flags)
01630 {
01631    const struct mohclass *class = obj;
01632 
01633    return ast_str_case_hash(class->name);
01634 }
01635 
01636 static int moh_class_cmp(void *obj, void *arg, int flags)
01637 {
01638    struct mohclass *class = obj, *class2 = arg;
01639 
01640    return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
01641 }
01642 
01643 static int load_module(void)
01644 {
01645    int res;
01646 
01647    if (!(mohclasses = ao2_t_container_alloc(53, moh_class_hash, moh_class_cmp, "Moh class container"))) {
01648       return AST_MODULE_LOAD_DECLINE;
01649    }
01650 
01651    if (!load_moh_classes(0)) {   /* No music classes configured, so skip it */
01652       ast_log(LOG_WARNING, "No music on hold classes configured, "
01653             "disabling music on hold.\n");
01654    } else {
01655       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01656             local_ast_moh_cleanup);
01657    }
01658 
01659    res = ast_register_application(play_moh, play_moh_exec, play_moh_syn, play_moh_desc);
01660    ast_register_atexit(ast_moh_destroy);
01661    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01662    if (!res)
01663       res = ast_register_application(wait_moh, wait_moh_exec, wait_moh_syn, wait_moh_desc);
01664    if (!res)
01665       res = ast_register_application(set_moh, set_moh_exec, set_moh_syn, set_moh_desc);
01666    if (!res)
01667       res = ast_register_application(start_moh, start_moh_exec, start_moh_syn, start_moh_desc);
01668    if (!res)
01669       res = ast_register_application(stop_moh, stop_moh_exec, stop_moh_syn, stop_moh_desc);
01670 
01671    return AST_MODULE_LOAD_SUCCESS;
01672 }
01673 
01674 static int reload(void)
01675 {
01676    if (load_moh_classes(1)) {
01677       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
01678             local_ast_moh_cleanup);
01679    }
01680 
01681    return AST_MODULE_LOAD_SUCCESS;
01682 }
01683 
01684 static int moh_class_inuse(void *obj, void *arg, int flags)
01685 {
01686    struct mohclass *class = obj;
01687 
01688    return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
01689 }
01690 
01691 static int unload_module(void)
01692 {
01693    int res = 0;
01694    struct mohclass *class = NULL;
01695 
01696    /* XXX This check shouldn't be required if module ref counting was being used
01697     * properly ... */
01698    if ((class = ao2_t_callback(mohclasses, 0, moh_class_inuse, NULL, "Module unload callback"))) {
01699       class = mohclass_unref(class, "unref of class from module unload callback");
01700       res = -1;
01701    }
01702 
01703    if (res < 0) {
01704       ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
01705       return res;
01706    }
01707 
01708    ast_uninstall_music_functions();
01709 
01710    ast_moh_destroy();
01711    res = ast_unregister_application(play_moh);
01712    res |= ast_unregister_application(wait_moh);
01713    res |= ast_unregister_application(set_moh);
01714    res |= ast_unregister_application(start_moh);
01715    res |= ast_unregister_application(stop_moh);
01716    ast_cli_unregister_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01717    ast_unregister_atexit(ast_moh_destroy);
01718 
01719    return res;
01720 }
01721 
01722 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
01723    .load = load_module,
01724    .unload = unload_module,
01725    .reload = reload,
01726 );

Generated on Fri Jul 24 00:41:02 2009 for Asterisk - the Open Source PBX by  doxygen 1.4.7