diff options
Diffstat (limited to '1.2-netsec/res/res_musiconhold.c')
-rw-r--r-- | 1.2-netsec/res/res_musiconhold.c | 1237 |
1 files changed, 1237 insertions, 0 deletions
diff --git a/1.2-netsec/res/res_musiconhold.c b/1.2-netsec/res/res_musiconhold.c new file mode 100644 index 000000000..2a27dcec8 --- /dev/null +++ b/1.2-netsec/res/res_musiconhold.c @@ -0,0 +1,1237 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Routines implementing music on hold + * + * \arg See also \ref Config_moh + * + */ + +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <netinet/in.h> +#include <sys/stat.h> +#include <dirent.h> +#ifdef ZAPATA_MOH +#ifdef __linux__ +#include <linux/zaptel.h> +#else +#include <zaptel.h> +#endif /* __linux__ */ +#endif +#include <unistd.h> +#include <sys/ioctl.h> + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/options.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/say.h" +#include "asterisk/musiconhold.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" + +#define MAX_MOHFILES 512 +#define MAX_MOHFILE_LEN 128 + +static char *app0 = "MusicOnHold"; +static char *app1 = "WaitMusicOnHold"; +static char *app2 = "SetMusicOnHold"; +static char *app3 = "StartMusicOnHold"; +static char *app4 = "StopMusicOnHold"; + +static char *synopsis0 = "Play Music On Hold indefinitely"; +static char *synopsis1 = "Wait, playing Music On Hold"; +static char *synopsis2 = "Set default Music On Hold class"; +static char *synopsis3 = "Play Music On Hold"; +static char *synopsis4 = "Stop Playing Music On Hold"; + +static char *descrip0 = "MusicOnHold(class): " +"Plays hold music specified by class. If omitted, the default\n" +"music source for the channel will be used. Set the default \n" +"class with the SetMusicOnHold() application.\n" +"Returns -1 on hangup.\n" +"Never returns otherwise.\n"; + +static char *descrip1 = "WaitMusicOnHold(delay): " +"Plays hold music specified number of seconds. Returns 0 when\n" +"done, or -1 on hangup. If no hold music is available, the delay will\n" +"still occur with no sound.\n"; + +static char *descrip2 = "SetMusicOnHold(class): " +"Sets the default class for music on hold for a given channel. When\n" +"music on hold is activated, this class will be used to select which\n" +"music is played.\n"; + +static char *descrip3 = "StartMusicOnHold(class): " +"Starts playing music on hold, uses default music class for channel.\n" +"Starts playing music specified by class. If omitted, the default\n" +"music source for the channel will be used. Always returns 0.\n"; + +static char *descrip4 = "StopMusicOnHold: " +"Stops playing music on hold.\n"; + +static int respawn_time = 20; + +struct moh_files_state { + struct mohclass *class; + int origwfmt; + int samples; + int sample_queue; + unsigned char pos; + unsigned char save_pos; +}; + +#define MOH_QUIET (1 << 0) +#define MOH_SINGLE (1 << 1) +#define MOH_CUSTOM (1 << 2) +#define MOH_RANDOMIZE (1 << 3) + +struct mohclass { + char name[MAX_MUSICCLASS]; + char dir[256]; + char args[256]; + char mode[80]; + char filearray[MAX_MOHFILES][MAX_MOHFILE_LEN]; + unsigned int flags; + int total_files; + int format; + int pid; /* PID of mpg123 */ + time_t start; + pthread_t thread; + struct mohdata *members; + /* Source of audio */ + int srcfd; + /* FD for timing source */ + int pseudofd; + struct mohclass *next; +}; + +struct mohdata { + int pipe[2]; + int origwfmt; + struct mohclass *parent; + struct mohdata *next; +}; + +static struct mohclass *mohclasses; + +AST_MUTEX_DEFINE_STATIC(moh_lock); + +#define LOCAL_MPG_123 "/usr/local/bin/mpg123" +#define MPG_123 "/usr/bin/mpg123" +#define MAX_MP3S 256 + + +static void ast_moh_free_class(struct mohclass **class) +{ + struct mohdata *members, *mtmp; + + members = (*class)->members; + while(members) { + mtmp = members; + members = members->next; + free(mtmp); + } + free(*class); + *class = NULL; +} + + +static void moh_files_release(struct ast_channel *chan, void *data) +{ + struct moh_files_state *state = chan->music_state; + + if (chan && state) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name); + + if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt); + } + state->save_pos = state->pos + 1; + } +} + + +static int ast_moh_files_next(struct ast_channel *chan) +{ + struct moh_files_state *state = chan->music_state; + int tries; + + if (state->save_pos) { + state->pos = state->save_pos - 1; + state->save_pos = 0; + } else { + /* Try 20 times to find something good */ + for (tries=0;tries < 20;tries++) { + state->samples = 0; + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + state->pos++; + } + + if (ast_test_flag(state->class, MOH_RANDOMIZE)) + state->pos = rand(); + + /* check to see if this file's format can be opened */ + if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) != -1) + break; + + } + } + + state->pos = state->pos % state->class->total_files; + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name); + return -1; + } + if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) { + ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno)); + state->pos++; + return -1; + } + + if (option_debug) + ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]); + + if (state->samples) + ast_seekstream(chan->stream, state->samples, SEEK_SET); + + return 0; +} + + +static struct ast_frame *moh_files_readframe(struct ast_channel *chan) +{ + struct ast_frame *f = NULL; + + if (!(chan->stream && (f = ast_readframe(chan->stream)))) { + if (!ast_moh_files_next(chan)) + f = ast_readframe(chan->stream); + } + + return f; +} + +static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct moh_files_state *state = chan->music_state; + struct ast_frame *f = NULL; + int res = 0; + + state->sample_queue += samples; + + while (state->sample_queue > 0) { + if ((f = moh_files_readframe(chan))) { + state->samples += f->samples; + res = ast_write(chan, f); + state->sample_queue -= f->samples; + ast_frfree(f); + if (res < 0) { + ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); + return -1; + } + } else + return -1; + } + return res; +} + + +static void *moh_files_alloc(struct ast_channel *chan, void *params) +{ + struct moh_files_state *state; + struct mohclass *class = params; + int allocated = 0; + + if (!chan->music_state && (state = malloc(sizeof(struct moh_files_state)))) { + chan->music_state = state; + allocated = 1; + } else + state = chan->music_state; + + if (state) { + if (allocated || state->class != class) { + /* initialize */ + memset(state, 0, sizeof(struct moh_files_state)); + state->class = class; + } + + state->origwfmt = chan->writeformat; + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name); + free(chan->music_state); + chan->music_state = NULL; + } else { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name); + } + } + + return chan->music_state; +} + +static struct ast_generator moh_file_stream = +{ + alloc: moh_files_alloc, + release: moh_files_release, + generate: moh_files_generator, +}; + +static int spawn_mp3(struct mohclass *class) +{ + int fds[2]; + int files = 0; + char fns[MAX_MP3S][80]; + char *argv[MAX_MP3S + 50]; + char xargs[256]; + char *argptr; + int argc = 0; + DIR *dir = NULL; + struct dirent *de; + + + if (!strcasecmp(class->dir, "nodir")) { + files = 1; + } else { + dir = opendir(class->dir); + if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) { + ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir); + return -1; + } + } + + if (!ast_test_flag(class, MOH_CUSTOM)) { + argv[argc++] = "mpg123"; + argv[argc++] = "-q"; + argv[argc++] = "-s"; + argv[argc++] = "--mono"; + argv[argc++] = "-r"; + argv[argc++] = "8000"; + + if (!ast_test_flag(class, MOH_SINGLE)) { + argv[argc++] = "-b"; + argv[argc++] = "2048"; + } + + argv[argc++] = "-f"; + + if (ast_test_flag(class, MOH_QUIET)) + argv[argc++] = "4096"; + else + argv[argc++] = "8192"; + + /* Look for extra arguments and add them to the list */ + strncpy(xargs, class->args, sizeof(xargs) - 1); + argptr = xargs; + while (!ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + argptr = strchr(argptr, ','); + if (argptr) { + *argptr = '\0'; + argptr++; + } + } + } else { + /* Format arguments for argv vector */ + strncpy(xargs, class->args, sizeof(xargs) - 1); + argptr = xargs; + while (!ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + argptr = strchr(argptr, ' '); + if (argptr) { + *argptr = '\0'; + argptr++; + } + } + } + + + if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) { + strncpy(fns[files], class->dir, sizeof(fns[files]) - 1); + argv[argc++] = fns[files]; + files++; + } else if (dir) { + while ((de = readdir(dir)) && (files < MAX_MP3S)) { + if ((strlen(de->d_name) > 3) && + ((ast_test_flag(class, MOH_CUSTOM) && + (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || + !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) || + !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) { + strncpy(fns[files], de->d_name, sizeof(fns[files]) - 1); + argv[argc++] = fns[files]; + files++; + } + } + } + argv[argc] = NULL; + if (dir) { + closedir(dir); + } + if (pipe(fds)) { + ast_log(LOG_WARNING, "Pipe failed\n"); + return -1; + } +#if 0 + printf("%d files total, %d args total\n", files, argc); + { + int x; + for (x=0;argv[x];x++) + printf("arg%d: %s\n", x, argv[x]); + } +#endif + if (!files) { + ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir); + close(fds[0]); + close(fds[1]); + return -1; + } + if (time(NULL) - class->start < respawn_time) { + sleep(respawn_time - (time(NULL) - class->start)); + } + time(&class->start); + class->pid = fork(); + if (class->pid < 0) { + close(fds[0]); + close(fds[1]); + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); + return -1; + } + if (!class->pid) { + int x; + close(fds[0]); + /* Stdout goes to pipe */ + dup2(fds[1], STDOUT_FILENO); + /* Close unused file descriptors */ + for (x=3;x<8192;x++) { + if (-1 != fcntl(x, F_GETFL)) { + close(x); + } + } + /* Child */ + chdir(class->dir); + if (ast_test_flag(class, MOH_CUSTOM)) { + execv(argv[0], argv); + } else { + /* Default install is /usr/local/bin */ + execv(LOCAL_MPG_123, argv); + /* Many places have it in /usr/bin */ + execv(MPG_123, argv); + /* Check PATH as a last-ditch effort */ + execvp("mpg123", argv); + } + ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno)); + close(fds[1]); + exit(1); + } else { + /* Parent */ + close(fds[1]); + } + return fds[0]; +} + +static void *monmp3thread(void *data) +{ +#define MOH_MS_INTERVAL 100 + + struct mohclass *class = data; + struct mohdata *moh; + char buf[8192]; + short sbuf[8192]; + int res, res2; + int len; + struct timeval tv, tv_tmp; + + tv.tv_sec = 0; + tv.tv_usec = 0; + for(;/* ever */;) { + /* Spawn mp3 player if it's not there */ + if (class->srcfd < 0) { + if ((class->srcfd = spawn_mp3(class)) < 0) { + ast_log(LOG_WARNING, "Unable to spawn mp3player\n"); + /* Try again later */ + sleep(500); + } + } + if (class->pseudofd > -1) { + /* Pause some amount of time */ + res = read(class->pseudofd, buf, sizeof(buf)); + } else { + long delta; + /* Reliable sleep */ + tv_tmp = ast_tvnow(); + if (ast_tvzero(tv)) + tv = tv_tmp; + delta = ast_tvdiff_ms(tv_tmp, tv); + if (delta < MOH_MS_INTERVAL) { /* too early */ + tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */ + usleep(1000 * (MOH_MS_INTERVAL - delta)); + } else { + ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n"); + tv = tv_tmp; + } + res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */ + } + if (!class->members) + continue; + /* Read mp3 audio */ + len = ast_codec_get_len(class->format, res); + + if ((res2 = read(class->srcfd, sbuf, len)) != len) { + if (!res2) { + close(class->srcfd); + class->srcfd = -1; + if (class->pid) { + kill(class->pid, SIGKILL); + class->pid = 0; + } + } else + ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len); + continue; + } + ast_mutex_lock(&moh_lock); + moh = class->members; + while (moh) { + /* Write data */ + if ((res = write(moh->pipe[1], sbuf, res2)) != res2) + if (option_debug) + ast_log(LOG_DEBUG, "Only wrote %d of %d bytes to pipe\n", res, res2); + moh = moh->next; + } + ast_mutex_unlock(&moh_lock); + } + return NULL; +} + +static int moh0_exec(struct ast_channel *chan, void *data) +{ + if (ast_moh_start(chan, data)) { + ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name); + return -1; + } + while (!ast_safe_sleep(chan, 10000)); + ast_moh_stop(chan); + return -1; +} + +static int moh1_exec(struct ast_channel *chan, void *data) +{ + int res; + if (!data || !atoi(data)) { + ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n"); + return -1; + } + if (ast_moh_start(chan, NULL)) { + ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name); + return -1; + } + res = ast_safe_sleep(chan, atoi(data) * 1000); + ast_moh_stop(chan); + return res; +} + +static int moh2_exec(struct ast_channel *chan, void *data) +{ + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n"); + return -1; + } + strncpy(chan->musicclass, data, sizeof(chan->musicclass) - 1); + return 0; +} + +static int moh3_exec(struct ast_channel *chan, void *data) +{ + char *class = NULL; + if (data && strlen(data)) + class = data; + if (ast_moh_start(chan, class)) + ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name); + + return 0; +} + +static int moh4_exec(struct ast_channel *chan, void *data) +{ + ast_moh_stop(chan); + + return 0; +} + +static struct mohclass *get_mohbyname(char *name) +{ + struct mohclass *moh; + moh = mohclasses; + while (moh) { + if (!strcasecmp(name, moh->name)) + return moh; + moh = moh->next; + } + return NULL; +} + +static struct mohdata *mohalloc(struct mohclass *cl) +{ + struct mohdata *moh; + long flags; + moh = malloc(sizeof(struct mohdata)); + if (!moh) + return NULL; + memset(moh, 0, sizeof(struct mohdata)); + if (pipe(moh->pipe)) { + ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno)); + free(moh); + return NULL; + } + /* Make entirely non-blocking */ + flags = fcntl(moh->pipe[0], F_GETFL); + fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK); + flags = fcntl(moh->pipe[1], F_GETFL); + fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK); + moh->parent = cl; + moh->next = cl->members; + cl->members = moh; + return moh; +} + +static void moh_release(struct ast_channel *chan, void *data) +{ + struct mohdata *moh = data, *prev, *cur; + int oldwfmt; + ast_mutex_lock(&moh_lock); + /* Unlink */ + prev = NULL; + cur = moh->parent->members; + while (cur) { + if (cur == moh) { + if (prev) + prev->next = cur->next; + else + moh->parent->members = cur->next; + break; + } + prev = cur; + cur = cur->next; + } + ast_mutex_unlock(&moh_lock); + close(moh->pipe[0]); + close(moh->pipe[1]); + oldwfmt = moh->origwfmt; + free(moh); + if (chan) { + if (oldwfmt && ast_set_write_format(chan, oldwfmt)) + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt)); + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name); + } +} + +static void *moh_alloc(struct ast_channel *chan, void *params) +{ + struct mohdata *res; + struct mohclass *class = params; + + res = mohalloc(class); + if (res) { + res->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, class->format)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format)); + moh_release(NULL, res); + res = NULL; + } + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name); + } + return res; +} + +static int moh_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + struct ast_frame f; + struct mohdata *moh = data; + short buf[1280 + AST_FRIENDLY_OFFSET / 2]; + int res; + + if (!moh->parent->pid) + return -1; + + len = ast_codec_get_len(moh->parent->format, samples); + + if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { + ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name); + len = sizeof(buf) - AST_FRIENDLY_OFFSET; + } + res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len); +#if 0 + if (res != len) { + ast_log(LOG_WARNING, "Read only %d of %d bytes: %s\n", res, len, strerror(errno)); + } +#endif + if (res <= 0) + return 0; + + memset(&f, 0, sizeof(f)); + + f.frametype = AST_FRAME_VOICE; + f.subclass = moh->parent->format; + f.mallocd = 0; + f.datalen = res; + f.data = buf + AST_FRIENDLY_OFFSET / 2; + f.offset = AST_FRIENDLY_OFFSET; + f.samples = ast_codec_get_samples(&f); + + if (ast_write(chan, &f) < 0) { + ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); + return -1; + } + + return 0; +} + +static struct ast_generator mohgen = +{ + alloc: moh_alloc, + release: moh_release, + generate: moh_generate, +}; + +static int moh_scan_files(struct mohclass *class) { + + DIR *files_DIR; + struct dirent *files_dirent; + char path[512]; + char filepath[MAX_MOHFILE_LEN]; + char *ext; + struct stat statbuf; + int dirnamelen; + int i; + + files_DIR = opendir(class->dir); + if (!files_DIR) { + ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist", class->dir); + return -1; + } + + class->total_files = 0; + dirnamelen = strlen(class->dir) + 2; + getcwd(path, 512); + chdir(class->dir); + memset(class->filearray, 0, MAX_MOHFILES*MAX_MOHFILE_LEN); + while ((files_dirent = readdir(files_DIR))) { + if ((strlen(files_dirent->d_name) < 4) || ((strlen(files_dirent->d_name) + dirnamelen) >= MAX_MOHFILE_LEN)) + continue; + + snprintf(filepath, MAX_MOHFILE_LEN, "%s/%s", class->dir, files_dirent->d_name); + + if (stat(filepath, &statbuf)) + continue; + + if (!S_ISREG(statbuf.st_mode)) + continue; + + if ((ext = strrchr(filepath, '.'))) { + *ext = '\0'; + ext++; + } + + /* if the file is present in multiple formats, ensure we only put it into the list once */ + for (i = 0; i < class->total_files; i++) + if (!strcmp(filepath, class->filearray[i])) + break; + + if (i == class->total_files) + strcpy(class->filearray[class->total_files++], filepath); + } + + closedir(files_DIR); + chdir(path); + return class->total_files; +} + +static int moh_register(struct mohclass *moh, int reload) +{ +#ifdef ZAPATA_MOH + int x; +#endif + ast_mutex_lock(&moh_lock); + if (get_mohbyname(moh->name)) { + if (reload) { + ast_log(LOG_DEBUG, "Music on Hold class '%s' left alone from initial load.\n", moh->name); + } else { + ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name); + } + free(moh); + ast_mutex_unlock(&moh_lock); + return -1; + } + ast_mutex_unlock(&moh_lock); + + time(&moh->start); + moh->start -= respawn_time; + + if (!strcasecmp(moh->mode, "files")) { + if (!moh_scan_files(moh)) { + ast_moh_free_class(&moh); + return -1; + } + if (strchr(moh->args, 'r')) + ast_set_flag(moh, MOH_RANDOMIZE); + } 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")) { + + if (!strcasecmp(moh->mode, "custom")) + ast_set_flag(moh, MOH_CUSTOM); + else if (!strcasecmp(moh->mode, "mp3nb")) + ast_set_flag(moh, MOH_SINGLE); + else if (!strcasecmp(moh->mode, "quietmp3nb")) + ast_set_flag(moh, MOH_SINGLE | MOH_QUIET); + else if (!strcasecmp(moh->mode, "quietmp3")) + ast_set_flag(moh, MOH_QUIET); + + moh->srcfd = -1; +#ifdef ZAPATA_MOH + /* Open /dev/zap/pseudo for timing... Is + there a better, yet reliable way to do this? */ + moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY); + if (moh->pseudofd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n"); + } else { + x = 320; + ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x); + } +#else + moh->pseudofd = -1; +#endif + if (ast_pthread_create(&moh->thread, NULL, monmp3thread, moh)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (moh->pseudofd > -1) + close(moh->pseudofd); + ast_moh_free_class(&moh); + return -1; + } + } else { + ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode); + ast_moh_free_class(&moh); + return -1; + } + ast_mutex_lock(&moh_lock); + moh->next = mohclasses; + mohclasses = moh; + ast_mutex_unlock(&moh_lock); + return 0; +} + +static void local_ast_moh_cleanup(struct ast_channel *chan) +{ + if (chan->music_state) { + free(chan->music_state); + chan->music_state = NULL; + } +} + +static int local_ast_moh_start(struct ast_channel *chan, char *class) +{ + struct mohclass *mohclass; + + if (ast_strlen_zero(class)) + class = chan->musicclass; + if (ast_strlen_zero(class)) + class = "default"; + ast_mutex_lock(&moh_lock); + mohclass = get_mohbyname(class); + ast_mutex_unlock(&moh_lock); + + if (!mohclass) { + ast_log(LOG_WARNING, "No class: %s\n", (char *)class); + return -1; + } + + ast_set_flag(chan, AST_FLAG_MOH); + if (mohclass->total_files) { + return ast_activate_generator(chan, &moh_file_stream, mohclass); + } else + return ast_activate_generator(chan, &mohgen, mohclass); +} + +static void local_ast_moh_stop(struct ast_channel *chan) +{ + ast_clear_flag(chan, AST_FLAG_MOH); + ast_deactivate_generator(chan); + + if (chan->music_state) { + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + } +} + +static struct mohclass *moh_class_malloc(void) +{ + struct mohclass *class; + + class = malloc(sizeof(struct mohclass)); + + if (!class) + return NULL; + + memset(class, 0, sizeof(struct mohclass)); + + class->format = AST_FORMAT_SLINEAR; + + return class; +} + +static int load_moh_classes(int reload) +{ + struct ast_config *cfg; + struct ast_variable *var; + struct mohclass *class; + char *data; + char *args; + char *cat; + int numclasses = 0; + static int dep_warning = 0; + + cfg = ast_config_load("musiconhold.conf"); + + if (!cfg) + return 0; + + cat = ast_category_browse(cfg, NULL); + for (; cat; cat = ast_category_browse(cfg, cat)) { + if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) { + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + break; + } + ast_copy_string(class->name, cat, sizeof(class->name)); + var = ast_variable_browse(cfg, cat); + while (var) { + if (!strcasecmp(var->name, "mode")) + ast_copy_string(class->mode, var->value, sizeof(class->mode)); + else if (!strcasecmp(var->name, "directory")) + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + else if (!strcasecmp(var->name, "application")) + ast_copy_string(class->args, var->value, sizeof(class->args)); + else if (!strcasecmp(var->name, "random")) + ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE); + else if (!strcasecmp(var->name, "format")) { + class->format = ast_getformatbyname(var->value); + if (!class->format) { + ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value); + class->format = AST_FORMAT_SLINEAR; + } + } + var = var->next; + } + + if (ast_strlen_zero(class->dir)) { + if (!strcasecmp(class->mode, "custom")) { + strcpy(class->dir, "nodir"); + } else { + ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + } + if (ast_strlen_zero(class->mode)) { + ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) { + ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + + /* Don't leak a class when it's already registered */ + moh_register(class, reload); + + numclasses++; + } + } + + + /* Deprecated Old-School Configuration */ + var = ast_variable_browse(cfg, "classes"); + while (var) { + if (!dep_warning) { + 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"); + dep_warning = 1; + } + data = strchr(var->value, ':'); + if (data) { + *data++ = '\0'; + args = strchr(data, ','); + if (args) + *args++ = '\0'; + if (!(get_mohbyname(var->name))) { + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return numclasses; + } + + ast_copy_string(class->name, var->name, sizeof(class->name)); + ast_copy_string(class->dir, data, sizeof(class->dir)); + ast_copy_string(class->mode, var->value, sizeof(class->mode)); + if (args) + ast_copy_string(class->args, args, sizeof(class->args)); + + moh_register(class, reload); + numclasses++; + } + } + var = var->next; + } + var = ast_variable_browse(cfg, "moh_files"); + while (var) { + if (!dep_warning) { + 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"); + dep_warning = 1; + } + if (!(get_mohbyname(var->name))) { + args = strchr(var->value, ','); + if (args) + *args++ = '\0'; + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return numclasses; + } + + ast_copy_string(class->name, var->name, sizeof(class->name)); + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + strcpy(class->mode, "files"); + if (args) + ast_copy_string(class->args, args, sizeof(class->args)); + + moh_register(class, reload); + numclasses++; + } + var = var->next; + } + + ast_config_destroy(cfg); + + return numclasses; +} + +static void ast_moh_destroy(void) +{ + struct mohclass *moh, *tmp; + char buff[8192]; + int bytes, tbytes=0, stime = 0, pid = 0; + + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n"); + ast_mutex_lock(&moh_lock); + moh = mohclasses; + + while (moh) { + if (moh->pid) { + ast_log(LOG_DEBUG, "killing %d!\n", moh->pid); + stime = time(NULL) + 2; + pid = moh->pid; + moh->pid = 0; + kill(pid, SIGKILL); + while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime) { + tbytes = tbytes + bytes; + } + ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes); + close(moh->srcfd); + } + tmp = moh; + moh = moh->next; + ast_moh_free_class(&tmp); + } + mohclasses = NULL; + ast_mutex_unlock(&moh_lock); +} + +static void moh_on_off(int on) +{ + struct ast_channel *chan = NULL; + + while ( (chan = ast_channel_walk_locked(chan)) != NULL) { + if (ast_test_flag(chan, AST_FLAG_MOH)) { + if (on) + local_ast_moh_start(chan, NULL); + else + ast_deactivate_generator(chan); + } + ast_mutex_unlock(&chan->lock); + } +} + +static int moh_cli(int fd, int argc, char *argv[]) +{ + int x; + + moh_on_off(0); + ast_moh_destroy(); + x = load_moh_classes(1); + moh_on_off(1); + ast_cli(fd, "\n%d class%s reloaded.\n", x, x == 1 ? "" : "es"); + return 0; +} + +static int cli_files_show(int fd, int argc, char *argv[]) +{ + int i; + struct mohclass *class; + + ast_mutex_lock(&moh_lock); + for (class = mohclasses; class; class = class->next) { + if (!class->total_files) + continue; + + ast_cli(fd, "Class: %s\n", class->name); + for (i = 0; i < class->total_files; i++) + ast_cli(fd, "\tFile: %s\n", class->filearray[i]); + } + ast_mutex_unlock(&moh_lock); + + return 0; +} + +static int moh_classes_show(int fd, int argc, char *argv[]) +{ + struct mohclass *class; + + ast_mutex_lock(&moh_lock); + for (class = mohclasses; class; class = class->next) { + ast_cli(fd, "Class: %s\n", class->name); + ast_cli(fd, "\tMode: %s\n", ast_strlen_zero(class->mode) ? "<none>" : class->mode); + ast_cli(fd, "\tDirectory: %s\n", ast_strlen_zero(class->dir) ? "<none>" : class->dir); + if (ast_test_flag(class, MOH_CUSTOM)) + ast_cli(fd, "\tApplication: %s\n", ast_strlen_zero(class->args) ? "<none>" : class->args); + ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format)); + } + ast_mutex_unlock(&moh_lock); + + return 0; +} + +static struct ast_cli_entry cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL}; + +static struct ast_cli_entry cli_moh_classes_show = { { "moh", "classes", "show"}, moh_classes_show, "List MOH classes", "Lists all MOH classes", NULL}; + +static struct ast_cli_entry cli_moh_files_show = { { "moh", "files", "show"}, cli_files_show, "List MOH file-based classes", "Lists all loaded file-based MOH classes and their files", NULL}; + +static int init_classes(int reload) +{ + struct mohclass *moh; + + if (!load_moh_classes(reload)) /* Load classes from config */ + return 0; /* Return if nothing is found */ + moh = mohclasses; + while (moh) { + if (moh->total_files) + moh_scan_files(moh); + moh = moh->next; + } + return 1; +} + +int load_module(void) +{ + int res; + + res = ast_register_application(app0, moh0_exec, synopsis0, descrip0); + ast_register_atexit(ast_moh_destroy); + ast_cli_register(&cli_moh); + ast_cli_register(&cli_moh_files_show); + ast_cli_register(&cli_moh_classes_show); + if (!res) + res = ast_register_application(app1, moh1_exec, synopsis1, descrip1); + if (!res) + res = ast_register_application(app2, moh2_exec, synopsis2, descrip2); + if (!res) + res = ast_register_application(app3, moh3_exec, synopsis3, descrip3); + if (!res) + res = ast_register_application(app4, moh4_exec, synopsis4, descrip4); + + if (!init_classes(0)) { /* No music classes configured, so skip it */ + ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold."); + } else { + ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); + } + + return 0; +} + +int reload(void) +{ + if (init_classes(1)) + ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); + + return 0; +} + +int unload_module(void) +{ + return -1; +} + +char *description(void) +{ + return "Music On Hold Resource"; +} + +int usecount(void) +{ + /* Never allow Music On Hold to be unloaded + unresolve needed symbols in the dialer */ +#if 0 + int res; + STANDARD_USECOUNT(res); + return res; +#else + return 1; +#endif +} + +char *key() +{ + return ASTERISK_GPL_KEY; +} |