From bab0cfd2634aa0784266e3b773f149b3522d10b5 Mon Sep 17 00:00:00 2001 From: Mark Spencer Date: Sat, 29 Jun 2002 21:46:57 +0000 Subject: Version 0.1.12 from FTP git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@474 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- res/res_musiconhold.c | 569 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100755 res/res_musiconhold.c (limited to 'res') diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c new file mode 100755 index 000000000..c696bd55a --- /dev/null +++ b/res/res_musiconhold.c @@ -0,0 +1,569 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Routines implementing call parking + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef ZAPATA_MOH +#include +#endif +#include +#include +#include + +#include + +static char *app0 = "MusicOnHold"; +static char *app1 = "WaitMusicOnHold"; +static char *app2 = "SetMusicOnHold"; + +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 *descrip0 = "MusicOnHold(class): " +"Plays hold music specified by class. If omitted, the default\n" +"music source for the channel will be used. 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"; + +struct mohclass { + char class[80]; + char dir[256]; + char miscargs[256]; + int destroyme; + int pid; /* PID of mpg123 */ + int quiet; + 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 origrfmt; + int origwfmt; + struct mohclass *parent; + struct mohdata *next; +}; + +static struct mohclass *mohclasses; + +static pthread_mutex_t moh_lock = AST_MUTEX_INITIALIZER; + +#define MPG_123 "/usr/bin/mpg123" +#define MAX_MP3S 256 + +static void child_handler(int sig) +{ + int status; + if (wait4(-1,&status, WNOHANG, NULL)<1) + ast_log(LOG_NOTICE, "Huh? Child handler, but nobody there?\n"); +} + +static int spawn_mp3(struct mohclass *class) +{ + int fds[2]; + int files; + char fns[80][MAX_MP3S]; + char *argv[MAX_MP3S + 50]; + char xargs[256]; + char *argptr; + int argc; + DIR *dir; + struct dirent *de; + dir = opendir(class->dir); + if (!dir) { + ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir); + return -1; + } + argv[0] = MPG_123; + argv[1] = "-q"; + argv[2] = "-s"; + argv[3] = "--mono"; + argv[4] = "-r"; + argv[5] = "8000"; + argc = 6; + if (class->quiet) { + argv[argc++] = "-f"; + argv[argc++] = "8192"; + } + + /* Look for extra arguments and add them to the list */ + strncpy(xargs, class->miscargs, sizeof(xargs) - 1); + argptr = xargs; + while(argptr && strlen(argptr)) { + argv[argc++] = argptr; + argptr = strchr(argptr, ','); + if (argptr) { + *argptr = '\0'; + argptr++; + } + } + + files = 0; + while((de = readdir(dir)) && (files < MAX_MP3S)) { + if ((strlen(de->d_name) > 3) && !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3")) { + strncpy(fns[files], de->d_name, sizeof(fns[files])); + argv[argc++] = fns[files]; + files++; + } + } + argv[argc] = NULL; + 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); + return -1; + } + 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++) + close(x); + /* Child */ + chdir(class->dir); + execv(MPG_123, argv); + ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno)); + exit(1); + } else { + /* Parent */ + close(fds[1]); + } + return fds[0]; +} + +static void *monmp3thread(void *data) +{ + struct mohclass *class = data; + struct mohdata *moh; + char buf[8192]; + short sbuf[8192]; + int res, res2; + signal(SIGCHLD, child_handler); + 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 { + /* otherwise just sleep (unreliable) */ + usleep(250000); + res = 2000; + } + if (!class->members) + continue; + /* Read mp3 audio */ + if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) { + 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, res * 2); + continue; + } + ast_pthread_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; + } + pthread_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)); + 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 (class '%s') on channel %s\n", (char *)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 (!data || !strlen(data)) { + ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n"); + return -1; + } + strncpy(chan->musicclass, data, sizeof(chan->musicclass)); + return 0; +} + +static struct mohclass *get_mohbyname(char *name) +{ + struct mohclass *moh; + moh = mohclasses; + while(moh) { + if (!strcasecmp(name, moh->class)) + 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 oldrfmt, oldwfmt; + ast_pthread_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_pthread_mutex_unlock(&moh_lock); + close(moh->pipe[0]); + close(moh->pipe[1]); + oldrfmt = moh->origrfmt; + oldwfmt = moh->origwfmt; + free(moh); + if (chan) { + if (ast_set_write_format(chan, oldwfmt) || + ast_set_read_format(chan, oldrfmt)) + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %d/%d\n", chan->name, oldwfmt, oldrfmt); + 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; + ast_pthread_mutex_lock(&moh_lock); + class = get_mohbyname(params); + if (class) + res = mohalloc(class); + else { + if (strcasecmp(params, "default")) + ast_log(LOG_WARNING, "No class: %s\n", (char *)params); + res = NULL; + } + ast_pthread_mutex_unlock(&moh_lock); + if (res) { + res->origrfmt = chan->readformat; + res->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name); + moh_release(NULL, res); + res = NULL; + } else if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name); + moh_release(NULL, res); + res = NULL; + } + /* Allow writes to interrupt */ + chan->writeinterrupt = 1; + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", (char *)params, chan->name); + } + return res; +} + +static int moh_generate(struct ast_channel *chan, void *data, int len) +{ + struct ast_frame f; + struct mohdata *moh = data; + short buf[640 + AST_FRIENDLY_OFFSET / 2]; + int res; + if (len > sizeof(buf)) { + ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", sizeof(buf), len, chan->name); + len = sizeof(buf); + } + 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) { + memset(&f, 0, sizeof(f)); + f.frametype = AST_FRAME_VOICE; + f.subclass = AST_FORMAT_SLINEAR; + f.mallocd = 0; + f.datalen = res; + f.timelen = res / 8; + f.data = buf + AST_FRIENDLY_OFFSET / 2; + f.offset = AST_FRIENDLY_OFFSET; + 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_register(char *classname, char *mode, char *param, char *miscargs) +{ + struct mohclass *moh; + int x; + ast_pthread_mutex_lock(&moh_lock); + moh = get_mohbyname(classname); + ast_pthread_mutex_unlock(&moh_lock); + if (moh) { + ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname); + return -1; + } + moh = malloc(sizeof(struct mohclass)); + if (!moh) + return -1; + memset(moh, 0, sizeof(struct mohclass)); + + strncpy(moh->class, classname, sizeof(moh->class) - 1); + if (miscargs) + strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1); + if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "httpmp3")) { + if (!strcasecmp(mode, "quietmp3")) + moh->quiet = 1; + strncpy(moh->dir, param, sizeof(moh->dir)); + moh->srcfd = -1; +#ifdef ZAPATA_MOH + /* It's an MP3 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 (pthread_create(&moh->thread, NULL, monmp3thread, moh)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (moh->pseudofd > -1) + close(moh->pseudofd); + free(moh); + return -1; + } + } else { + ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode); + free(moh); + return -1; + } + ast_pthread_mutex_lock(&moh_lock); + moh->next = mohclasses; + mohclasses = moh; + ast_pthread_mutex_unlock(&moh_lock); + return 0; +} + +int ast_moh_start(struct ast_channel *chan, char *class) +{ + if (!class || !strlen(class)) + class = chan->musicclass; + if (!class || !strlen(class)) + class = "default"; + return ast_activate_generator(chan, &mohgen, class); +} + +void ast_moh_stop(struct ast_channel *chan) +{ + ast_deactivate_generator(chan); +} + +static void load_moh_classes(void) +{ + struct ast_config *cfg; + struct ast_variable *var; + char *data; + char *args; + cfg = ast_load("musiconhold.conf"); + if (cfg) { + var = ast_variable_browse(cfg, "classes"); + while(var) { + data = strchr(var->value, ':'); + if (data) { + *data = '\0'; + data++; + args = strchr(data, ','); + if (args) { + *args = '\0'; + args++; + } + moh_register(var->name, var->value, data,args); + } + var = var->next; + } + ast_destroy(cfg); + } +} + +int load_module(void) +{ + int res; + load_moh_classes(); + res = ast_register_application(app0, moh0_exec, synopsis0, descrip0); + if (!res) + res = ast_register_application(app1, moh1_exec, synopsis1, descrip1); + if (!res) + res = ast_register_application(app2, moh2_exec, synopsis2, descrip2); + return res; +} + +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; +} -- cgit v1.2.3