aboutsummaryrefslogtreecommitdiffstats
path: root/res
diff options
context:
space:
mode:
authormarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2002-06-29 21:46:57 +0000
committermarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2002-06-29 21:46:57 +0000
commit0721bdf320fce000fb0b44cb27df7f7337b251e4 (patch)
treefb036b7b0e927ae65ae3d54ef6fcfc2cacc5c303 /res
parent1162eacb83c84ca6f21363ac0d80a8038acc5f1e (diff)
Version 0.1.12 from FTP
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@474 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'res')
-rwxr-xr-xres/res_musiconhold.c569
1 files changed, 569 insertions, 0 deletions
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 <markster@linux-support.net>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#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/channel_pvt.h>
+#include <asterisk/musiconhold.h>
+#include <asterisk/config.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.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
+#include <linux/zaptel.h>
+#endif
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+#include <pthread.h>
+
+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;
+}