mirror of https://github.com/asterisk/asterisk
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@474 65c4cc65-6c06-0410-ace0-fbb531ad65f31.0
parent
8c63834523
commit
bab0cfd263
@ -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;
|
||||
}
|
Loading…
Reference in new issue