/* * 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; }