Wed Aug 15 01:24:24 2007

Asterisk developer's documentation


res_musiconhold.c

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

Generated on Wed Aug 15 01:24:24 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.3