Mon May 14 04:43:00 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$")
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 -1;
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 -1;
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)
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    return moh;
00638 }
00639 
00640 static struct mohdata *mohalloc(struct mohclass *cl)
00641 {
00642    struct mohdata *moh;
00643    long flags; 
00644    
00645    if (!(moh = ast_calloc(1, sizeof(*moh))))
00646       return NULL;
00647    
00648    if (pipe(moh->pipe)) {
00649       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00650       free(moh);
00651       return NULL;
00652    }
00653 
00654    /* Make entirely non-blocking */
00655    flags = fcntl(moh->pipe[0], F_GETFL);
00656    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00657    flags = fcntl(moh->pipe[1], F_GETFL);
00658    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00659 
00660    moh->f.frametype = AST_FRAME_VOICE;
00661    moh->f.subclass = cl->format;
00662    moh->f.offset = AST_FRIENDLY_OFFSET;
00663 
00664    moh->parent = cl;
00665    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00666    
00667    return moh;
00668 }
00669 
00670 static void moh_release(struct ast_channel *chan, void *data)
00671 {
00672    struct mohdata *moh = data;
00673    int oldwfmt;
00674 
00675    AST_LIST_LOCK(&mohclasses);
00676    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00677    AST_LIST_UNLOCK(&mohclasses);
00678    
00679    close(moh->pipe[0]);
00680    close(moh->pipe[1]);
00681    oldwfmt = moh->origwfmt;
00682    free(moh);
00683    if (chan) {
00684       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
00685          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
00686       if (option_verbose > 2)
00687          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00688    }
00689 }
00690 
00691 static void *moh_alloc(struct ast_channel *chan, void *params)
00692 {
00693    struct mohdata *res;
00694    struct mohclass *class = params;
00695 
00696    if ((res = mohalloc(class))) {
00697       res->origwfmt = chan->writeformat;
00698       if (ast_set_write_format(chan, class->format)) {
00699          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00700          moh_release(NULL, res);
00701          res = NULL;
00702       }
00703       if (option_verbose > 2)
00704          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00705    }
00706    return res;
00707 }
00708 
00709 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00710 {
00711    struct mohdata *moh = data;
00712    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00713    int res;
00714 
00715    if (!moh->parent->pid)
00716       return -1;
00717 
00718    len = ast_codec_get_len(moh->parent->format, samples);
00719 
00720    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00721       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00722       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00723    }
00724    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00725    if (res <= 0)
00726       return 0;
00727 
00728    moh->f.datalen = res;
00729    moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
00730    moh->f.samples = ast_codec_get_samples(&moh->f);
00731 
00732    if (ast_write(chan, &moh->f) < 0) {
00733       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00734       return -1;
00735    }
00736 
00737    return 0;
00738 }
00739 
00740 static struct ast_generator mohgen = 
00741 {
00742    alloc: moh_alloc,
00743    release: moh_release,
00744    generate: moh_generate,
00745 };
00746 
00747 static int moh_add_file(struct mohclass *class, const char *filepath)
00748 {
00749    if (!class->allowed_files) {
00750       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00751          return -1;
00752       class->allowed_files = INITIAL_NUM_FILES;
00753    } else if (class->total_files == class->allowed_files) {
00754       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00755          class->allowed_files = 0;
00756          class->total_files = 0;
00757          return -1;
00758       }
00759       class->allowed_files *= 2;
00760    }
00761 
00762    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00763       return -1;
00764 
00765    class->total_files++;
00766 
00767    return 0;
00768 }
00769 
00770 static int moh_scan_files(struct mohclass *class) {
00771 
00772    DIR *files_DIR;
00773    struct dirent *files_dirent;
00774    char path[PATH_MAX];
00775    char filepath[PATH_MAX];
00776    char *ext;
00777    struct stat statbuf;
00778    int dirnamelen;
00779    int i;
00780    
00781    files_DIR = opendir(class->dir);
00782    if (!files_DIR) {
00783       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00784       return -1;
00785    }
00786 
00787    for (i = 0; i < class->total_files; i++)
00788       free(class->filearray[i]);
00789 
00790    class->total_files = 0;
00791    dirnamelen = strlen(class->dir) + 2;
00792    getcwd(path, sizeof(path));
00793    chdir(class->dir);
00794    while ((files_dirent = readdir(files_DIR))) {
00795       /* The file name must be at least long enough to have the file type extension */
00796       if ((strlen(files_dirent->d_name) < 4))
00797          continue;
00798 
00799       /* Skip files that starts with a dot */
00800       if (files_dirent->d_name[0] == '.')
00801          continue;
00802 
00803       /* Skip files without extensions... they are not audio */
00804       if (!strchr(files_dirent->d_name, '.'))
00805          continue;
00806 
00807       snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00808 
00809       if (stat(filepath, &statbuf))
00810          continue;
00811 
00812       if (!S_ISREG(statbuf.st_mode))
00813          continue;
00814 
00815       if ((ext = strrchr(filepath, '.'))) {
00816          *ext = '\0';
00817          ext++;
00818       }
00819 
00820       /* if the file is present in multiple formats, ensure we only put it into the list once */
00821       for (i = 0; i < class->total_files; i++)
00822          if (!strcmp(filepath, class->filearray[i]))
00823             break;
00824 
00825       if (i == class->total_files) {
00826          if (moh_add_file(class, filepath))
00827             break;
00828       }
00829    }
00830 
00831    closedir(files_DIR);
00832    chdir(path);
00833    return class->total_files;
00834 }
00835 
00836 static int moh_register(struct mohclass *moh, int reload)
00837 {
00838 #ifdef HAVE_ZAPTEL
00839    int x;
00840 #endif
00841    AST_LIST_LOCK(&mohclasses);
00842    if (get_mohbyname(moh->name)) {
00843       if (reload) {
00844          ast_log(LOG_DEBUG, "Music on Hold class '%s' left alone from initial load.\n", moh->name);
00845       } else {
00846          ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
00847       }
00848       free(moh);  
00849       AST_LIST_UNLOCK(&mohclasses);
00850       return -1;
00851    }
00852    AST_LIST_UNLOCK(&mohclasses);
00853 
00854    time(&moh->start);
00855    moh->start -= respawn_time;
00856    
00857    if (!strcasecmp(moh->mode, "files")) {
00858       if (!moh_scan_files(moh)) {
00859          ast_moh_free_class(&moh);
00860          return -1;
00861       }
00862       if (strchr(moh->args, 'r'))
00863          ast_set_flag(moh, MOH_RANDOMIZE);
00864    } 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")) {
00865 
00866       if (!strcasecmp(moh->mode, "custom"))
00867          ast_set_flag(moh, MOH_CUSTOM);
00868       else if (!strcasecmp(moh->mode, "mp3nb"))
00869          ast_set_flag(moh, MOH_SINGLE);
00870       else if (!strcasecmp(moh->mode, "quietmp3nb"))
00871          ast_set_flag(moh, MOH_SINGLE | MOH_QUIET);
00872       else if (!strcasecmp(moh->mode, "quietmp3"))
00873          ast_set_flag(moh, MOH_QUIET);
00874       
00875       moh->srcfd = -1;
00876 #ifdef HAVE_ZAPTEL
00877       /* Open /dev/zap/pseudo for timing...  Is
00878          there a better, yet reliable way to do this? */
00879       moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
00880       if (moh->pseudofd < 0) {
00881          ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
00882       } else {
00883          x = 320;
00884          ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x);
00885       }
00886 #else
00887       moh->pseudofd = -1;
00888 #endif
00889       if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) {
00890          ast_log(LOG_WARNING, "Unable to create moh...\n");
00891          if (moh->pseudofd > -1)
00892             close(moh->pseudofd);
00893          ast_moh_free_class(&moh);
00894          return -1;
00895       }
00896    } else {
00897       ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
00898       ast_moh_free_class(&moh);
00899       return -1;
00900    }
00901 
00902    AST_LIST_LOCK(&mohclasses);
00903    AST_LIST_INSERT_HEAD(&mohclasses, moh, list);
00904    AST_LIST_UNLOCK(&mohclasses);
00905    
00906    return 0;
00907 }
00908 
00909 static void local_ast_moh_cleanup(struct ast_channel *chan)
00910 {
00911    if (chan->music_state) {
00912       free(chan->music_state);
00913       chan->music_state = NULL;
00914    }
00915 }
00916 
00917 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
00918 {
00919    struct mohclass *mohclass;
00920    const char *class;
00921 
00922    /* The following is the order of preference for which class to use:
00923     * 1) The channels explicitly set musicclass, which should *only* be
00924     *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
00925     * 2) The mclass argument. If a channel is calling ast_moh_start() as the
00926     *    result of receiving a HOLD control frame, this should be the
00927     *    payload that came with the frame.
00928     * 3) The interpclass argument. This would be from the mohinterpret
00929     *    option from channel drivers. This is the same as the old musicclass
00930     *    option.
00931     * 4) The default class.
00932     */
00933    if (!ast_strlen_zero(chan->musicclass))
00934       class = chan->musicclass;
00935    else if (!ast_strlen_zero(mclass))
00936       class = mclass;
00937    else if (!ast_strlen_zero(interpclass))
00938       class = interpclass;
00939    else
00940       class = "default";
00941 
00942    AST_LIST_LOCK(&mohclasses);
00943    mohclass = get_mohbyname(class);
00944    AST_LIST_UNLOCK(&mohclasses);
00945 
00946    if (!mohclass) {
00947       ast_log(LOG_WARNING, "No class: %s\n", class);
00948       return -1;
00949    }
00950 
00951    ast_set_flag(chan, AST_FLAG_MOH);
00952    if (mohclass->total_files) {
00953       return ast_activate_generator(chan, &moh_file_stream, mohclass);
00954    } else
00955       return ast_activate_generator(chan, &mohgen, mohclass);
00956 }
00957 
00958 static void local_ast_moh_stop(struct ast_channel *chan)
00959 {
00960    ast_clear_flag(chan, AST_FLAG_MOH);
00961    ast_deactivate_generator(chan);
00962 
00963    if (chan->music_state) {
00964       if (chan->stream) {
00965          ast_closestream(chan->stream);
00966          chan->stream = NULL;
00967       }
00968    }
00969 }
00970 
00971 static struct mohclass *moh_class_malloc(void)
00972 {
00973    struct mohclass *class;
00974 
00975    if ((class = ast_calloc(1, sizeof(*class))))    
00976       class->format = AST_FORMAT_SLINEAR;
00977 
00978    return class;
00979 }
00980 
00981 static int load_moh_classes(int reload)
00982 {
00983    struct ast_config *cfg;
00984    struct ast_variable *var;
00985    struct mohclass *class; 
00986    char *data;
00987    char *args;
00988    char *cat;
00989    int numclasses = 0;
00990    static int dep_warning = 0;
00991 
00992    cfg = ast_config_load("musiconhold.conf");
00993 
00994    if (!cfg)
00995       return 0;
00996 
00997    cat = ast_category_browse(cfg, NULL);
00998    for (; cat; cat = ast_category_browse(cfg, cat)) {
00999       if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {       
01000          if (!(class = moh_class_malloc())) {
01001             break;
01002          }           
01003          ast_copy_string(class->name, cat, sizeof(class->name));  
01004          var = ast_variable_browse(cfg, cat);
01005          while (var) {
01006             if (!strcasecmp(var->name, "mode"))
01007                ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01008             else if (!strcasecmp(var->name, "directory"))
01009                ast_copy_string(class->dir, var->value, sizeof(class->dir));
01010             else if (!strcasecmp(var->name, "application"))
01011                ast_copy_string(class->args, var->value, sizeof(class->args));
01012             else if (!strcasecmp(var->name, "random"))
01013                ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01014             else if (!strcasecmp(var->name, "format")) {
01015                class->format = ast_getformatbyname(var->value);
01016                if (!class->format) {
01017                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01018                   class->format = AST_FORMAT_SLINEAR;
01019                }
01020             }
01021             var = var->next;
01022          }
01023 
01024          if (ast_strlen_zero(class->dir)) {
01025             if (!strcasecmp(class->mode, "custom")) {
01026                strcpy(class->dir, "nodir");
01027             } else {
01028                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01029                free(class);
01030                continue;
01031             }
01032          }
01033          if (ast_strlen_zero(class->mode)) {
01034             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01035             free(class);
01036             continue;
01037          }
01038          if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01039             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01040             free(class);
01041             continue;
01042          }
01043 
01044          /* Don't leak a class when it's already registered */
01045          moh_register(class, reload);
01046 
01047          numclasses++;
01048       }
01049    }
01050    
01051 
01052    /* Deprecated Old-School Configuration */
01053    var = ast_variable_browse(cfg, "classes");
01054    while (var) {
01055       if (!dep_warning) {
01056          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");
01057          dep_warning = 1;
01058       }
01059       data = strchr(var->value, ':');
01060       if (data) {
01061          *data++ = '\0';
01062          args = strchr(data, ',');
01063          if (args)
01064             *args++ = '\0';
01065          if (!(get_mohbyname(var->name))) {        
01066             if (!(class = moh_class_malloc())) {
01067                return numclasses;
01068             }
01069             
01070             ast_copy_string(class->name, var->name, sizeof(class->name));
01071             ast_copy_string(class->dir, data, sizeof(class->dir));
01072             ast_copy_string(class->mode, var->value, sizeof(class->mode));
01073             if (args)
01074                ast_copy_string(class->args, args, sizeof(class->args));
01075             
01076             moh_register(class, reload);
01077             numclasses++;
01078          }
01079       }
01080       var = var->next;
01081    }
01082    var = ast_variable_browse(cfg, "moh_files");
01083    while (var) {
01084       if (!dep_warning) {
01085          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");
01086          dep_warning = 1;
01087       }
01088       if (!(get_mohbyname(var->name))) {
01089          args = strchr(var->value, ',');
01090          if (args)
01091             *args++ = '\0';         
01092          if (!(class = moh_class_malloc())) {
01093             return numclasses;
01094          }
01095          
01096          ast_copy_string(class->name, var->name, sizeof(class->name));
01097          ast_copy_string(class->dir, var->value, sizeof(class->dir));
01098          strcpy(class->mode, "files");
01099          if (args)   
01100             ast_copy_string(class->args, args, sizeof(class->args));
01101          
01102          moh_register(class, reload);
01103          numclasses++;
01104       }
01105       var = var->next;
01106    }
01107 
01108    ast_config_destroy(cfg);
01109 
01110    return numclasses;
01111 }
01112 
01113 static void ast_moh_destroy(void)
01114 {
01115    struct mohclass *moh;
01116    char buff[8192];
01117    int bytes, tbytes = 0, stime = 0, pid = 0;
01118 
01119    if (option_verbose > 1)
01120       ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
01121 
01122    AST_LIST_LOCK(&mohclasses);
01123    while ((moh = AST_LIST_REMOVE_HEAD(&mohclasses, list))) {
01124       if (moh->pid > 1) {
01125          ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
01126          stime = time(NULL) + 2;
01127          pid = moh->pid;
01128          moh->pid = 0;
01129          /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01130           * to give the process a reason and time enough to kill off its
01131           * children. */
01132          kill(pid, SIGHUP);
01133          usleep(100000);
01134          kill(pid, SIGTERM);
01135          usleep(100000);
01136          kill(pid, SIGKILL);
01137          while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime)
01138             tbytes = tbytes + bytes;
01139          ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01140          close(moh->srcfd);
01141       }
01142       ast_moh_free_class(&moh);
01143    }
01144    AST_LIST_UNLOCK(&mohclasses);
01145 }
01146 
01147 static void moh_on_off(int on)
01148 {
01149    struct ast_channel *chan = NULL;
01150 
01151    while ( (chan = ast_channel_walk_locked(chan)) != NULL) {
01152       if (ast_test_flag(chan, AST_FLAG_MOH)) {
01153          if (on)
01154             local_ast_moh_start(chan, NULL, NULL);
01155          else
01156             ast_deactivate_generator(chan);
01157       }
01158       ast_channel_unlock(chan);
01159    }
01160 }
01161 
01162 static int moh_cli(int fd, int argc, char *argv[]) 
01163 {
01164    int x;
01165 
01166    moh_on_off(0);
01167    ast_moh_destroy();
01168    x = load_moh_classes(1);
01169    moh_on_off(1);
01170    ast_cli(fd, "\n%d class%s reloaded.\n", x, x == 1 ? "" : "es");
01171    return 0;
01172 }
01173 
01174 static int cli_files_show(int fd, int argc, char *argv[])
01175 {
01176    int i;
01177    struct mohclass *class;
01178 
01179    AST_LIST_LOCK(&mohclasses);
01180    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01181       if (!class->total_files)
01182          continue;
01183 
01184       ast_cli(fd, "Class: %s\n", class->name);
01185       for (i = 0; i < class->total_files; i++)
01186          ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
01187    }
01188    AST_LIST_UNLOCK(&mohclasses);
01189 
01190    return 0;
01191 }
01192 
01193 static int moh_classes_show(int fd, int argc, char *argv[])
01194 {
01195    struct mohclass *class;
01196 
01197    AST_LIST_LOCK(&mohclasses);
01198    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01199       ast_cli(fd, "Class: %s\n", class->name);
01200       ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01201       ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01202       if (ast_test_flag(class, MOH_CUSTOM))
01203          ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01204       if (strcasecmp(class->mode, "files"))
01205          ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
01206    }
01207    AST_LIST_UNLOCK(&mohclasses);
01208 
01209    return 0;
01210 }
01211 
01212 static struct ast_cli_entry cli_moh_classes_show_deprecated = {
01213    { "moh", "classes", "show"},
01214    moh_classes_show, NULL,
01215    NULL };
01216 
01217 static struct ast_cli_entry cli_moh_files_show_deprecated = {
01218    { "moh", "files", "show"},
01219    cli_files_show, NULL,
01220    NULL };
01221 
01222 static struct ast_cli_entry cli_moh[] = {
01223    { { "moh", "reload"},
01224    moh_cli, "Music On Hold",
01225    "Music On Hold" },
01226 
01227    { { "moh", "show", "classes"},
01228    moh_classes_show, "List MOH classes",
01229    "Lists all MOH classes", NULL, &cli_moh_classes_show_deprecated },
01230 
01231    { { "moh", "show", "files"},
01232    cli_files_show, "List MOH file-based classes",
01233    "Lists all loaded file-based MOH classes and their files", NULL, &cli_moh_files_show_deprecated },
01234 };
01235 
01236 static int init_classes(int reload) 
01237 {
01238    struct mohclass *moh;
01239     
01240    if (!load_moh_classes(reload))      /* Load classes from config */
01241       return 0;         /* Return if nothing is found */
01242 
01243    AST_LIST_LOCK(&mohclasses);
01244    AST_LIST_TRAVERSE(&mohclasses, moh, list) {
01245       if (moh->total_files)
01246          moh_scan_files(moh);
01247    }
01248    AST_LIST_UNLOCK(&mohclasses);
01249 
01250    return 1;
01251 }
01252 
01253 static int load_module(void)
01254 {
01255    int res;
01256 
01257    res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
01258    ast_register_atexit(ast_moh_destroy);
01259    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01260    if (!res)
01261       res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
01262    if (!res)
01263       res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
01264    if (!res)
01265       res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
01266    if (!res)
01267       res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
01268 
01269    if (!init_classes(0)) {    /* No music classes configured, so skip it */
01270       ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n");
01271    } else {
01272       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01273    }
01274 
01275    return 0;
01276 }
01277 
01278 static int reload(void)
01279 {
01280    if (init_classes(1))
01281       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01282 
01283    return 0;
01284 }
01285 
01286 static int unload_module(void)
01287 {
01288    return -1;
01289 }
01290 
01291 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
01292       .load = load_module,
01293       .unload = unload_module,
01294       .reload = reload,
01295           );

Generated on Mon May 14 04:43:00 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1