Codename Pineapple

Home page | Mailing list | Docs

Last updated: Sat Feb 3 05:00:54 2007

Asterisk developer's documentation :: Codename Pineapple


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: 51537 $")
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 #include "asterisk/zapata.h"
00056 
00057 #include "asterisk/lock.h"
00058 #include "asterisk/file.h"
00059 #include "asterisk/logger.h"
00060 #include "asterisk/channel.h"
00061 #include "asterisk/pbx.h"
00062 #include "asterisk/options.h"
00063 #include "asterisk/module.h"
00064 #include "asterisk/translate.h"
00065 #include "asterisk/say.h"
00066 #include "asterisk/musiconhold.h"
00067 #include "asterisk/config.h"
00068 #include "asterisk/utils.h"
00069 #include "asterisk/cli.h"
00070 #include "asterisk/stringfields.h"
00071 #include "asterisk/linkedlists.h"
00072 
00073 #define INITIAL_NUM_FILES   8
00074 
00075 static char *app0 = "MusicOnHold";
00076 static char *app1 = "WaitMusicOnHold";
00077 static char *app2 = "SetMusicOnHold";
00078 static char *app3 = "StartMusicOnHold";
00079 static char *app4 = "StopMusicOnHold";
00080 
00081 static char *synopsis0 = "Play Music On Hold indefinitely";
00082 static char *synopsis1 = "Wait, playing Music On Hold";
00083 static char *synopsis2 = "Set default Music On Hold class";
00084 static char *synopsis3 = "Play Music On Hold";
00085 static char *synopsis4 = "Stop Playing Music On Hold";
00086 
00087 static char *descrip0 = "MusicOnHold(class): "
00088 "Plays hold music specified by class.  If omitted, the default\n"
00089 "music source for the channel will be used. Set the default \n"
00090 "class with the SetMusicOnHold() application.\n"
00091 "Returns -1 on hangup.\n"
00092 "Never returns otherwise.\n";
00093 
00094 static char *descrip1 = "WaitMusicOnHold(delay): "
00095 "Plays hold music specified number of seconds.  Returns 0 when\n"
00096 "done, or -1 on hangup.  If no hold music is available, the delay will\n"
00097 "still occur with no sound.\n";
00098 
00099 static char *descrip2 = "SetMusicOnHold(class): "
00100 "Sets the default class for music on hold for a given channel.  When\n"
00101 "music on hold is activated, this class will be used to select which\n"
00102 "music is played.\n";
00103 
00104 static char *descrip3 = "StartMusicOnHold(class): "
00105 "Starts playing music on hold, uses default music class for channel.\n"
00106 "Starts playing music specified by class.  If omitted, the default\n"
00107 "music source for the channel will be used.  Always returns 0.\n";
00108 
00109 static char *descrip4 = "StopMusicOnHold: "
00110 "Stops playing music on hold.\n";
00111 
00112 static int respawn_time = 20;
00113 
00114 struct moh_files_state {
00115    struct mohclass *class;
00116    int origwfmt;
00117    int samples;
00118    int sample_queue;
00119    unsigned char pos;
00120    unsigned char save_pos;
00121 };
00122 
00123 #define MOH_QUIET    (1 << 0)
00124 #define MOH_SINGLE      (1 << 1)
00125 #define MOH_CUSTOM      (1 << 2)
00126 #define MOH_RANDOMIZE      (1 << 3)
00127 
00128 struct mohclass {
00129    char name[MAX_MUSICCLASS];
00130    char dir[256];
00131    char args[256];
00132    char mode[80];
00133    /*! A dynamically sized array to hold the list of filenames in "files" mode */
00134    char **filearray;
00135    /*! The current size of the filearray */
00136    int allowed_files;
00137    /*! The current number of files loaded into the filearray */
00138    int total_files;
00139    unsigned int flags;
00140    /*! The format from the MOH source, not applicable to "files" mode */
00141    int format;
00142    /*! The pid of the external application delivering MOH */
00143    int pid;
00144    time_t start;
00145    pthread_t thread;
00146    /*! Source of audio */
00147    int srcfd;
00148    /*! FD for timing source */
00149    int pseudofd;
00150    AST_LIST_HEAD_NOLOCK(, mohdata) members;
00151    AST_LIST_ENTRY(mohclass) list;
00152 };
00153 
00154 struct mohdata {
00155    int pipe[2];
00156    int origwfmt;
00157    struct mohclass *parent;
00158    struct ast_frame f;
00159    AST_LIST_ENTRY(mohdata) list;
00160 };
00161 
00162 AST_LIST_HEAD_STATIC(mohclasses, mohclass);
00163 
00164 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
00165 #define MPG_123 "/usr/bin/mpg123"
00166 #define MAX_MP3S 256
00167 
00168 
00169 static void ast_moh_free_class(struct mohclass **mohclass) 
00170 {
00171    struct mohdata *member;
00172    struct mohclass *class = *mohclass;
00173    int i;
00174    
00175    while ((member = AST_LIST_REMOVE_HEAD(&class->members, list)))
00176       free(member);
00177    
00178    if (class->thread) {
00179       pthread_cancel(class->thread);
00180       class->thread = 0;
00181    }
00182 
00183    if (class->filearray) {
00184       for (i = 0; i < class->total_files; i++)
00185          free(class->filearray[i]);
00186       free(class->filearray);
00187    }
00188 
00189    free(class);
00190    *mohclass = NULL;
00191 }
00192 
00193 
00194 static void moh_files_release(struct ast_channel *chan, void *data)
00195 {
00196    struct moh_files_state *state = chan->music_state;
00197 
00198    if (chan && state) {
00199       if (chan->stream) {
00200                         ast_closestream(chan->stream);
00201                         chan->stream = NULL;
00202                 }
00203       if (option_verbose > 2)
00204          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00205 
00206       if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
00207          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
00208       }
00209       state->save_pos = state->pos;
00210    }
00211 }
00212 
00213 
00214 static int ast_moh_files_next(struct ast_channel *chan) 
00215 {
00216    struct moh_files_state *state = chan->music_state;
00217    int tries;
00218 
00219    if (state->save_pos) {
00220       state->pos = state->save_pos;
00221       state->save_pos = 0;
00222    }
00223 
00224    state->samples = 0;
00225    if (chan->stream) {
00226       ast_closestream(chan->stream);
00227       chan->stream = NULL;
00228       state->pos++;
00229       state->pos %= state->class->total_files;
00230    }
00231 
00232    if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
00233       /* Try 20 times to find something good */
00234       for (tries = 0; tries < 20; tries++) {
00235          state->pos = rand() % state->class->total_files;
00236 
00237          /* check to see if this file's format can be opened */
00238          if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
00239             break;
00240       }
00241    }
00242 
00243    if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
00244       ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
00245       state->pos++;
00246       state->pos %= state->class->total_files;
00247       return -1;
00248    }
00249 
00250    if (option_debug)
00251       ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
00252 
00253    if (state->samples)
00254       ast_seekstream(chan->stream, state->samples, SEEK_SET);
00255 
00256    return 0;
00257 }
00258 
00259 
00260 static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
00261 {
00262    struct ast_frame *f = NULL;
00263    
00264    if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
00265       if (!ast_moh_files_next(chan))
00266          f = ast_readframe(chan->stream);
00267    }
00268 
00269    return f;
00270 }
00271 
00272 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
00273 {
00274    struct moh_files_state *state = chan->music_state;
00275    struct ast_frame *f = NULL;
00276    int res = 0;
00277 
00278    state->sample_queue += samples;
00279 
00280    while (state->sample_queue > 0) {
00281       if ((f = moh_files_readframe(chan))) {
00282          state->samples += f->samples;
00283          res = ast_write(chan, f);
00284          state->sample_queue -= f->samples;
00285          ast_frfree(f);
00286          if (res < 0) {
00287             ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00288             return -1;
00289          }
00290       } else
00291          return -1;  
00292    }
00293    return res;
00294 }
00295 
00296 
00297 static void *moh_files_alloc(struct ast_channel *chan, void *params)
00298 {
00299    struct moh_files_state *state;
00300    struct mohclass *class = params;
00301 
00302    if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
00303       chan->music_state = state;
00304       state->class = class;
00305    } else 
00306       state = chan->music_state;
00307 
00308    if (state) {
00309       if (state->class != class) {
00310          /* initialize */
00311          memset(state, 0, sizeof(*state));
00312          state->class = class;
00313          if (ast_test_flag(state->class, MOH_RANDOMIZE))
00314             state->pos = ast_random() % class->total_files;
00315       }
00316 
00317       state->origwfmt = chan->writeformat;
00318 
00319       if (option_verbose > 2)
00320          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name);
00321    }
00322    
00323    return chan->music_state;
00324 }
00325 
00326 static struct ast_generator moh_file_stream = 
00327 {
00328    alloc: moh_files_alloc,
00329    release: moh_files_release,
00330    generate: moh_files_generator,
00331 };
00332 
00333 static int spawn_mp3(struct mohclass *class)
00334 {
00335    int fds[2];
00336    int files = 0;
00337    char fns[MAX_MP3S][80];
00338    char *argv[MAX_MP3S + 50];
00339    char xargs[256];
00340    char *argptr;
00341    int argc = 0;
00342    DIR *dir = NULL;
00343    struct dirent *de;
00344    sigset_t signal_set, old_set;
00345 
00346    
00347    if (!strcasecmp(class->dir, "nodir")) {
00348       files = 1;
00349    } else {
00350       dir = opendir(class->dir);
00351       if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) {
00352          ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
00353          return -1;
00354       }
00355    }
00356 
00357    if (!ast_test_flag(class, MOH_CUSTOM)) {
00358       argv[argc++] = "mpg123";
00359       argv[argc++] = "-q";
00360       argv[argc++] = "-s";
00361       argv[argc++] = "--mono";
00362       argv[argc++] = "-r";
00363       argv[argc++] = "8000";
00364       
00365       if (!ast_test_flag(class, MOH_SINGLE)) {
00366          argv[argc++] = "-b";
00367          argv[argc++] = "2048";
00368       }
00369       
00370       argv[argc++] = "-f";
00371       
00372       if (ast_test_flag(class, MOH_QUIET))
00373          argv[argc++] = "4096";
00374       else
00375          argv[argc++] = "8192";
00376       
00377       /* Look for extra arguments and add them to the list */
00378       ast_copy_string(xargs, class->args, sizeof(xargs));
00379       argptr = xargs;
00380       while (!ast_strlen_zero(argptr)) {
00381          argv[argc++] = argptr;
00382          strsep(&argptr, ",");
00383       }
00384    } else  {
00385       /* Format arguments for argv vector */
00386       ast_copy_string(xargs, class->args, sizeof(xargs));
00387       argptr = xargs;
00388       while (!ast_strlen_zero(argptr)) {
00389          argv[argc++] = argptr;
00390          strsep(&argptr, " ");
00391       }
00392    }
00393 
00394 
00395    if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) {
00396       ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
00397       argv[argc++] = fns[files];
00398       files++;
00399    } else if (dir) {
00400       while ((de = readdir(dir)) && (files < MAX_MP3S)) {
00401          if ((strlen(de->d_name) > 3) && 
00402              ((ast_test_flag(class, MOH_CUSTOM) && 
00403                (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || 
00404                 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
00405               !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
00406             ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
00407             argv[argc++] = fns[files];
00408             files++;
00409          }
00410       }
00411    }
00412    argv[argc] = NULL;
00413    if (dir) {
00414       closedir(dir);
00415    }
00416    if (pipe(fds)) {  
00417       ast_log(LOG_WARNING, "Pipe failed\n");
00418       return -1;
00419    }
00420    if (!files) {
00421       ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
00422       close(fds[0]);
00423       close(fds[1]);
00424       return -1;
00425    }
00426    if (time(NULL) - class->start < respawn_time) {
00427       sleep(respawn_time - (time(NULL) - class->start));
00428    }
00429 
00430    /* Block signals during the fork() */
00431    sigfillset(&signal_set);
00432    pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
00433 
00434    time(&class->start);
00435    class->pid = fork();
00436    if (class->pid < 0) {
00437       close(fds[0]);
00438       close(fds[1]);
00439       ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
00440       return -1;
00441    }
00442    if (!class->pid) {
00443       int x;
00444 
00445       if (ast_opt_high_priority)
00446          ast_set_priority(0);
00447 
00448       /* Reset ignored signals back to default */
00449       signal(SIGPIPE, SIG_DFL);
00450       pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
00451 
00452       close(fds[0]);
00453       /* Stdout goes to pipe */
00454       dup2(fds[1], STDOUT_FILENO);
00455       /* Close unused file descriptors */
00456       for (x=3;x<8192;x++) {
00457          if (-1 != fcntl(x, F_GETFL)) {
00458             close(x);
00459          }
00460       }
00461       /* Child */
00462       chdir(class->dir);
00463       if (ast_test_flag(class, MOH_CUSTOM)) {
00464          execv(argv[0], argv);
00465       } else {
00466          /* Default install is /usr/local/bin */
00467          execv(LOCAL_MPG_123, argv);
00468          /* Many places have it in /usr/bin */
00469          execv(MPG_123, argv);
00470          /* Check PATH as a last-ditch effort */
00471          execvp("mpg123", argv);
00472       }
00473       ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno));
00474       close(fds[1]);
00475       _exit(1);
00476    } else {
00477       /* Parent */
00478       pthread_sigmask(SIG_SETMASK, &old_set, NULL);
00479       close(fds[1]);
00480    }
00481    return fds[0];
00482 }
00483 
00484 static void *monmp3thread(void *data)
00485 {
00486 #define  MOH_MS_INTERVAL      100
00487 
00488    struct mohclass *class = data;
00489    struct mohdata *moh;
00490    char buf[8192];
00491    short sbuf[8192];
00492    int res, res2;
00493    int len;
00494    struct timeval tv, tv_tmp;
00495 
00496    tv.tv_sec = 0;
00497    tv.tv_usec = 0;
00498    for(;/* ever */;) {
00499       pthread_testcancel();
00500       /* Spawn mp3 player if it's not there */
00501       if (class->srcfd < 0) {
00502          if ((class->srcfd = spawn_mp3(class)) < 0) {
00503             ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
00504             /* Try again later */
00505             sleep(500);
00506             pthread_testcancel();
00507          }
00508       }
00509       if (class->pseudofd > -1) {
00510 #ifdef SOLARIS
00511          thr_yield();
00512 #endif
00513          /* Pause some amount of time */
00514          res = read(class->pseudofd, buf, sizeof(buf));
00515          pthread_testcancel();
00516       } else {
00517          long delta;
00518          /* Reliable sleep */
00519          tv_tmp = ast_tvnow();
00520          if (ast_tvzero(tv))
00521             tv = tv_tmp;
00522          delta = ast_tvdiff_ms(tv_tmp, tv);
00523          if (delta < MOH_MS_INTERVAL) {   /* too early */
00524             tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000));  /* next deadline */
00525             usleep(1000 * (MOH_MS_INTERVAL - delta));
00526             pthread_testcancel();
00527          } else {
00528             ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
00529             tv = tv_tmp;
00530          }
00531          res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
00532       }
00533       if (AST_LIST_EMPTY(&class->members))
00534          continue;
00535       /* Read mp3 audio */
00536       len = ast_codec_get_len(class->format, res);
00537       
00538       if ((res2 = read(class->srcfd, sbuf, len)) != len) {
00539          if (!res2) {
00540             close(class->srcfd);
00541             class->srcfd = -1;
00542             pthread_testcancel();
00543             if (class->pid > 1) {
00544                kill(class->pid, SIGHUP);
00545                usleep(100000);
00546                kill(class->pid, SIGTERM);
00547                usleep(100000);
00548                kill(class->pid, SIGKILL);
00549                class->pid = 0;
00550             }
00551          } else {
00552             if (option_debug)
00553                ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
00554          }
00555          continue;
00556       }
00557       pthread_testcancel();
00558       AST_LIST_LOCK(&mohclasses);
00559       AST_LIST_TRAVERSE(&class->members, moh, list) {
00560          /* Write data */
00561          if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
00562             if (option_debug)
00563                ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2);
00564          }
00565       }
00566       AST_LIST_UNLOCK(&mohclasses);
00567    }
00568    return NULL;
00569 }
00570 
00571 static int moh0_exec(struct ast_channel *chan, void *data)
00572 {
00573    if (ast_moh_start(chan, data, NULL)) {
00574       ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
00575       return -1;
00576    }
00577    while (!ast_safe_sleep(chan, 10000));
00578    ast_moh_stop(chan);
00579    return -1;
00580 }
00581 
00582 static int moh1_exec(struct ast_channel *chan, void *data)
00583 {
00584    int res;
00585    if (!data || !atoi(data)) {
00586       ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
00587       return -1;
00588    }
00589    if (ast_moh_start(chan, NULL, NULL)) {
00590       ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
00591       return -1;
00592    }
00593    res = ast_safe_sleep(chan, atoi(data) * 1000);
00594    ast_moh_stop(chan);
00595    return res;
00596 }
00597 
00598 static int moh2_exec(struct ast_channel *chan, void *data)
00599 {
00600    if (ast_strlen_zero(data)) {
00601       ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
00602       return -1;
00603    }
00604    ast_string_field_set(chan, musicclass, data);
00605    return 0;
00606 }
00607 
00608 static int moh3_exec(struct ast_channel *chan, void *data)
00609 {
00610    char *class = NULL;
00611    if (data && strlen(data))
00612       class = data;
00613    if (ast_moh_start(chan, class, NULL)) 
00614       ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name);
00615 
00616    return 0;
00617 }
00618 
00619 static int moh4_exec(struct ast_channel *chan, void *data)
00620 {
00621    ast_moh_stop(chan);
00622 
00623    return 0;
00624 }
00625 
00626 /*! \note This function should be called with the mohclasses list locked */
00627 static struct mohclass *get_mohbyname(const char *name)
00628 {
00629    struct mohclass *moh = NULL;
00630 
00631    AST_LIST_TRAVERSE(&mohclasses, moh, list) {
00632       if (!strcasecmp(name, moh->name))
00633          break;
00634    }
00635 
00636    return moh;
00637 }
00638 
00639 static struct mohdata *mohalloc(struct mohclass *cl)
00640 {
00641    struct mohdata *moh;
00642    long flags; 
00643    
00644    if (!(moh = ast_calloc(1, sizeof(*moh))))
00645       return NULL;
00646    
00647    if (pipe(moh->pipe)) {
00648       ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
00649       free(moh);
00650       return NULL;
00651    }
00652 
00653    /* Make entirely non-blocking */
00654    flags = fcntl(moh->pipe[0], F_GETFL);
00655    fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
00656    flags = fcntl(moh->pipe[1], F_GETFL);
00657    fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
00658 
00659    moh->f.frametype = AST_FRAME_VOICE;
00660    moh->f.subclass = cl->format;
00661    moh->f.offset = AST_FRIENDLY_OFFSET;
00662 
00663    moh->parent = cl;
00664    AST_LIST_INSERT_HEAD(&cl->members, moh, list);
00665    
00666    return moh;
00667 }
00668 
00669 static void moh_release(struct ast_channel *chan, void *data)
00670 {
00671    struct mohdata *moh = data;
00672    int oldwfmt;
00673 
00674    AST_LIST_LOCK(&mohclasses);
00675    AST_LIST_REMOVE(&moh->parent->members, moh, list); 
00676    AST_LIST_UNLOCK(&mohclasses);
00677    
00678    close(moh->pipe[0]);
00679    close(moh->pipe[1]);
00680    oldwfmt = moh->origwfmt;
00681    free(moh);
00682    if (chan) {
00683       if (oldwfmt && ast_set_write_format(chan, oldwfmt)) 
00684          ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt));
00685       if (option_verbose > 2)
00686          ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
00687    }
00688 }
00689 
00690 static void *moh_alloc(struct ast_channel *chan, void *params)
00691 {
00692    struct mohdata *res;
00693    struct mohclass *class = params;
00694 
00695    if ((res = mohalloc(class))) {
00696       res->origwfmt = chan->writeformat;
00697       if (ast_set_write_format(chan, class->format)) {
00698          ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
00699          moh_release(NULL, res);
00700          res = NULL;
00701       }
00702       if (option_verbose > 2)
00703          ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
00704    }
00705    return res;
00706 }
00707 
00708 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
00709 {
00710    struct mohdata *moh = data;
00711    short buf[1280 + AST_FRIENDLY_OFFSET / 2];
00712    int res;
00713 
00714    if (!moh->parent->pid)
00715       return -1;
00716 
00717    len = ast_codec_get_len(moh->parent->format, samples);
00718 
00719    if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00720       ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
00721       len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00722    }
00723    res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
00724    if (res <= 0)
00725       return 0;
00726 
00727    moh->f.datalen = res;
00728    moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
00729    moh->f.samples = ast_codec_get_samples(&moh->f);
00730 
00731    if (ast_write(chan, &moh->f) < 0) {
00732       ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
00733       return -1;
00734    }
00735 
00736    return 0;
00737 }
00738 
00739 static struct ast_generator mohgen = 
00740 {
00741    alloc: moh_alloc,
00742    release: moh_release,
00743    generate: moh_generate,
00744 };
00745 
00746 static int moh_add_file(struct mohclass *class, const char *filepath)
00747 {
00748    if (!class->allowed_files) {
00749       if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
00750          return -1;
00751       class->allowed_files = INITIAL_NUM_FILES;
00752    } else if (class->total_files == class->allowed_files) {
00753       if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
00754          class->allowed_files = 0;
00755          class->total_files = 0;
00756          return -1;
00757       }
00758       class->allowed_files *= 2;
00759    }
00760 
00761    if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
00762       return -1;
00763 
00764    class->total_files++;
00765 
00766    return 0;
00767 }
00768 
00769 static int moh_scan_files(struct mohclass *class) {
00770 
00771    DIR *files_DIR;
00772    struct dirent *files_dirent;
00773    char path[PATH_MAX];
00774    char filepath[PATH_MAX];
00775    char *ext;
00776    struct stat statbuf;
00777    int dirnamelen;
00778    int i;
00779    
00780    files_DIR = opendir(class->dir);
00781    if (!files_DIR) {
00782       ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir);
00783       return -1;
00784    }
00785 
00786    for (i = 0; i < class->total_files; i++)
00787       free(class->filearray[i]);
00788 
00789    class->total_files = 0;
00790    dirnamelen = strlen(class->dir) + 2;
00791    getcwd(path, sizeof(path));
00792    chdir(class->dir);
00793    while ((files_dirent = readdir(files_DIR))) {
00794       /* The file name must be at least long enough to have the file type extension */
00795       if ((strlen(files_dirent->d_name) < 4))
00796          continue;
00797 
00798       /* Skip files that starts with a dot */
00799       if (files_dirent->d_name[0] == '.')
00800          continue;
00801 
00802       /* Skip files without extensions... they are not audio */
00803       if (!strchr(files_dirent->d_name, '.'))
00804          continue;
00805 
00806       snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name);
00807 
00808       if (stat(filepath, &statbuf))
00809          continue;
00810 
00811       if (!S_ISREG(statbuf.st_mode))
00812          continue;
00813 
00814       if ((ext = strrchr(filepath, '.'))) {
00815          *ext = '\0';
00816          ext++;
00817       }
00818 
00819       /* if the file is present in multiple formats, ensure we only put it into the list once */
00820       for (i = 0; i < class->total_files; i++)
00821          if (!strcmp(filepath, class->filearray[i]))
00822             break;
00823 
00824       if (i == class->total_files) {
00825          if (moh_add_file(class, filepath))
00826             break;
00827       }
00828    }
00829 
00830    closedir(files_DIR);
00831    chdir(path);
00832    return class->total_files;
00833 }
00834 
00835 static int moh_register(struct mohclass *moh, int reload)
00836 {
00837 #ifdef HAVE_ZAPTEL
00838    int x;
00839 #endif
00840    AST_LIST_LOCK(&mohclasses);
00841    if (get_mohbyname(moh->name)) {
00842       if (reload) {
00843          if (option_debug)
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 *cat;
00987    int numclasses = 0;
00988 
00989    cfg = ast_config_load("musiconhold.conf");
00990 
00991    if (!cfg)
00992       return 0;
00993 
00994    cat = ast_category_browse(cfg, NULL);
00995    for (; cat; cat = ast_category_browse(cfg, cat)) {
00996       /* These names were deprecated in 1.4 and should not be used until after the next major release. */
00997       if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {       
00998          if (!(class = moh_class_malloc())) {
00999             break;
01000          }           
01001          ast_copy_string(class->name, cat, sizeof(class->name));  
01002          var = ast_variable_browse(cfg, cat);
01003          while (var) {
01004             if (!strcasecmp(var->name, "mode"))
01005                ast_copy_string(class->mode, var->value, sizeof(class->mode)); 
01006             else if (!strcasecmp(var->name, "directory"))
01007                ast_copy_string(class->dir, var->value, sizeof(class->dir));
01008             else if (!strcasecmp(var->name, "application"))
01009                ast_copy_string(class->args, var->value, sizeof(class->args));
01010             else if (!strcasecmp(var->name, "random"))
01011                ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
01012             else if (!strcasecmp(var->name, "format")) {
01013                class->format = ast_getformatbyname(var->value);
01014                if (!class->format) {
01015                   ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
01016                   class->format = AST_FORMAT_SLINEAR;
01017                }
01018             }
01019             var = var->next;
01020          }
01021 
01022          if (ast_strlen_zero(class->dir)) {
01023             if (!strcasecmp(class->mode, "custom")) {
01024                strcpy(class->dir, "nodir");
01025             } else {
01026                ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
01027                free(class);
01028                continue;
01029             }
01030          }
01031          if (ast_strlen_zero(class->mode)) {
01032             ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
01033             free(class);
01034             continue;
01035          }
01036          if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
01037             ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
01038             free(class);
01039             continue;
01040          }
01041 
01042          /* Don't leak a class when it's already registered */
01043          moh_register(class, reload);
01044 
01045          numclasses++;
01046       }
01047    }
01048    
01049    ast_config_destroy(cfg);
01050 
01051    return numclasses;
01052 }
01053 
01054 static void ast_moh_destroy(void)
01055 {
01056    struct mohclass *moh;
01057    char buff[8192];
01058    int bytes, tbytes = 0, stime = 0, pid = 0;
01059 
01060    if (option_verbose > 1)
01061       ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
01062 
01063    AST_LIST_LOCK(&mohclasses);
01064    while ((moh = AST_LIST_REMOVE_HEAD(&mohclasses, list))) {
01065       if (moh->pid > 1) {
01066          if (option_debug)
01067             ast_log(LOG_DEBUG, "killing %d!\n", moh->pid);
01068          stime = time(NULL) + 2;
01069          pid = moh->pid;
01070          moh->pid = 0;
01071          /* Back when this was just mpg123, SIGKILL was fine.  Now we need
01072           * to give the process a reason and time enough to kill off its
01073           * children. */
01074          kill(pid, SIGHUP);
01075          usleep(100000);
01076          kill(pid, SIGTERM);
01077          usleep(100000);
01078          kill(pid, SIGKILL);
01079          while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime)
01080             tbytes = tbytes + bytes;
01081          if (option_debug)
01082             ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
01083          close(moh->srcfd);
01084       }
01085       ast_moh_free_class(&moh);
01086    }
01087    AST_LIST_UNLOCK(&mohclasses);
01088 }
01089 
01090 static void moh_on_off(int on)
01091 {
01092    struct ast_channel *chan = NULL;
01093 
01094    while ( (chan = ast_channel_walk_locked(chan)) != NULL) {
01095       if (ast_test_flag(chan, AST_FLAG_MOH)) {
01096          if (on)
01097             local_ast_moh_start(chan, NULL, NULL);
01098          else
01099             ast_deactivate_generator(chan);
01100       }
01101       ast_channel_unlock(chan);
01102    }
01103 }
01104 
01105 static int moh_cli(int fd, int argc, char *argv[]) 
01106 {
01107    int x;
01108 
01109    moh_on_off(0);
01110    ast_moh_destroy();
01111    x = load_moh_classes(1);
01112    moh_on_off(1);
01113    ast_cli(fd, "\n%d class%s reloaded.\n", x, x == 1 ? "" : "es");
01114    return 0;
01115 }
01116 
01117 static int cli_files_show(int fd, int argc, char *argv[])
01118 {
01119    int i;
01120    struct mohclass *class;
01121 
01122    AST_LIST_LOCK(&mohclasses);
01123    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01124       if (!class->total_files)
01125          continue;
01126 
01127       ast_cli(fd, "Class: %s\n", class->name);
01128       for (i = 0; i < class->total_files; i++)
01129          ast_cli(fd, "\tFile: %s\n", class->filearray[i]);
01130    }
01131    AST_LIST_UNLOCK(&mohclasses);
01132 
01133    return 0;
01134 }
01135 
01136 static int moh_classes_show(int fd, int argc, char *argv[])
01137 {
01138    struct mohclass *class;
01139 
01140    AST_LIST_LOCK(&mohclasses);
01141    AST_LIST_TRAVERSE(&mohclasses, class, list) {
01142       ast_cli(fd, "Class: %s\n", class->name);
01143       ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
01144       ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
01145       if (ast_test_flag(class, MOH_CUSTOM))
01146          ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
01147       if (strcasecmp(class->mode, "files"))
01148          ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
01149    }
01150    AST_LIST_UNLOCK(&mohclasses);
01151 
01152    return 0;
01153 }
01154 
01155 static struct ast_cli_entry cli_moh[] = {
01156    { { "moh", "reload"},
01157    moh_cli, "Music On Hold",
01158    "Music On Hold" },
01159 
01160    { { "moh", "show", "classes"},
01161    moh_classes_show, "List MOH classes",
01162    "Lists all MOH classes" },
01163 
01164    { { "moh", "show", "files"},
01165    cli_files_show, "List MOH file-based classes",
01166    "Lists all loaded file-based MOH classes and their files" },
01167 };
01168 
01169 static int init_classes(int reload) 
01170 {
01171    struct mohclass *moh;
01172     
01173    if (!load_moh_classes(reload))      /* Load classes from config */
01174       return 0;         /* Return if nothing is found */
01175 
01176    AST_LIST_LOCK(&mohclasses);
01177    AST_LIST_TRAVERSE(&mohclasses, moh, list) {
01178       if (moh->total_files)
01179          moh_scan_files(moh);
01180    }
01181    AST_LIST_UNLOCK(&mohclasses);
01182 
01183    return 1;
01184 }
01185 
01186 static int load_module(void)
01187 {
01188    int res;
01189 
01190    res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
01191    ast_register_atexit(ast_moh_destroy);
01192    ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry));
01193    if (!res)
01194       res = ast_register_application(app1, moh1_exec, synopsis1, descrip1);
01195    if (!res)
01196       res = ast_register_application(app2, moh2_exec, synopsis2, descrip2);
01197    if (!res)
01198       res = ast_register_application(app3, moh3_exec, synopsis3, descrip3);
01199    if (!res)
01200       res = ast_register_application(app4, moh4_exec, synopsis4, descrip4);
01201 
01202    if (!init_classes(0)) {    /* No music classes configured, so skip it */
01203       ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n");
01204    } else {
01205       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01206    }
01207 
01208    return 0;
01209 }
01210 
01211 static int reload(void)
01212 {
01213    if (init_classes(1))
01214       ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup);
01215 
01216    return 0;
01217 }
01218 
01219 static int unload_module(void)
01220 {
01221    return -1;
01222 }
01223 
01224 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
01225       .load = load_module,
01226       .unload = unload_module,
01227       .reload = reload,
01228           );

Asterisk is a trademark for Digium, inc.. | Edvina.net | Asterisk.org | This documentation was generated with Doxygen