aboutsummaryrefslogtreecommitdiffstats
path: root/res/res_musiconhold.c
diff options
context:
space:
mode:
Diffstat (limited to 'res/res_musiconhold.c')
-rw-r--r--res/res_musiconhold.c1480
1 files changed, 1480 insertions, 0 deletions
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
new file mode 100644
index 000000000..6bf2837dc
--- /dev/null
+++ b/res/res_musiconhold.c
@@ -0,0 +1,1480 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, 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
+ *
+ * \author Mark Spencer <markster@digium.com>
+ */
+
+/*** MODULEINFO
+ <conflict>win32</conflict>
+ <use>dahdi</use>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#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>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#ifdef SOLARIS
+#include <thread.h>
+#endif
+
+#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"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/astobj2.h"
+
+#include "asterisk/dahdi_compat.h"
+
+#define INITIAL_NUM_FILES 8
+
+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;
+ int pos;
+ int save_pos;
+ char *save_pos_filename;
+};
+
+#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];
+ /*! A dynamically sized array to hold the list of filenames in "files" mode */
+ char **filearray;
+ /*! The current size of the filearray */
+ int allowed_files;
+ /*! The current number of files loaded into the filearray */
+ int total_files;
+ unsigned int flags;
+ /*! The format from the MOH source, not applicable to "files" mode */
+ int format;
+ /*! The pid of the external application delivering MOH */
+ int pid;
+ time_t start;
+ pthread_t thread;
+ /*! Source of audio */
+ int srcfd;
+ /*! FD for timing source */
+ int pseudofd;
+ unsigned int delete:1;
+ AST_LIST_HEAD_NOLOCK(, mohdata) members;
+ AST_LIST_ENTRY(mohclass) list;
+};
+
+struct mohdata {
+ int pipe[2];
+ int origwfmt;
+ struct mohclass *parent;
+ struct ast_frame f;
+ AST_LIST_ENTRY(mohdata) list;
+};
+
+static struct ao2_container *mohclasses;
+
+#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
+#define MPG_123 "/usr/bin/mpg123"
+#define MAX_MP3S 256
+
+static int reload(void);
+
+#define mohclass_ref(class) (ao2_ref((class), +1), class)
+#define mohclass_unref(class) (ao2_ref((class), -1), (struct mohclass *) NULL)
+
+static void moh_files_release(struct ast_channel *chan, void *data)
+{
+ struct moh_files_state *state;
+
+ if (!chan || !chan->music_state) {
+ return;
+ }
+
+ state = chan->music_state;
+
+ if (chan->stream) {
+ ast_closestream(chan->stream);
+ chan->stream = NULL;
+ }
+
+ 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;
+
+ state->class = mohclass_unref(state->class);
+}
+
+
+static int ast_moh_files_next(struct ast_channel *chan)
+{
+ struct moh_files_state *state = chan->music_state;
+ int tries;
+
+ /* Discontinue a stream if it is running already */
+ if (chan->stream) {
+ ast_closestream(chan->stream);
+ chan->stream = NULL;
+ }
+
+ if (!state->class->total_files) {
+ ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
+ return -1;
+ }
+
+ /* If a specific file has been saved confirm it still exists and that it is still valid */
+ if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
+ state->pos = state->save_pos;
+ state->save_pos = -1;
+ } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
+ /* Get a random file and ensure we can open it */
+ for (tries = 0; tries < 20; tries++) {
+ state->pos = ast_random() % state->class->total_files;
+ if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
+ break;
+ }
+ state->save_pos = -1;
+ state->samples = 0;
+ } else {
+ /* This is easy, just increment our position and make sure we don't exceed the total file count */
+ state->pos++;
+ state->pos %= state->class->total_files;
+ state->save_pos = -1;
+ state->samples = 0;
+ }
+
+ 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++;
+ state->pos %= state->class->total_files;
+ return -1;
+ }
+
+ /* Record the pointer to the filename for position resuming later */
+ state->save_pos_filename = state->class->filearray[state->pos];
+
+ 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;
+ state->sample_queue -= f->samples;
+ res = ast_write(chan, f);
+ 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;
+
+ if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
+ chan->music_state = state;
+ state->class = mohclass_ref(class);
+ state->save_pos = -1;
+ } else {
+ state = chan->music_state;
+ }
+
+ if (!state) {
+ return NULL;
+ }
+
+ if (state->class != class) {
+ /* (re-)initialize */
+ if (state->class) {
+ state->class = mohclass_unref(state->class);
+ }
+ memset(state, 0, sizeof(*state));
+ state->class = mohclass_ref(class);
+ if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files) {
+ state->pos = ast_random() % class->total_files;
+ }
+ }
+
+ state->origwfmt = chan->writeformat;
+
+ 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;
+ sigset_t signal_set, old_set;
+
+
+ if (!strcasecmp(class->dir, "nodir")) {
+ files = 1;
+ } else {
+ dir = opendir(class->dir);
+ if (!dir && strncasecmp(class->dir, "http://", 7)) {
+ 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 */
+ ast_copy_string(xargs, class->args, sizeof(xargs));
+ argptr = xargs;
+ while (!ast_strlen_zero(argptr)) {
+ argv[argc++] = argptr;
+ strsep(&argptr, ",");
+ }
+ } else {
+ /* Format arguments for argv vector */
+ ast_copy_string(xargs, class->args, sizeof(xargs));
+ argptr = xargs;
+ while (!ast_strlen_zero(argptr)) {
+ argv[argc++] = argptr;
+ strsep(&argptr, " ");
+ }
+ }
+
+ if (!strncasecmp(class->dir, "http://", 7)) {
+ ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
+ 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"))) {
+ ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
+ 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 (!files) {
+ ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+ }
+ if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
+ sleep(respawn_time - (time(NULL) - class->start));
+ }
+
+ /* Block signals during the fork() */
+ sigfillset(&signal_set);
+ pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
+
+ 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;
+
+ if (ast_opt_high_priority)
+ ast_set_priority(0);
+
+ /* Reset ignored signals back to default */
+ signal(SIGPIPE, SIG_DFL);
+ pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
+
+ 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 */
+ if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ _exit(1);
+ }
+ setpgid(0, getpid());
+ 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 */
+ pthread_sigmask(SIG_SETMASK, &old_set, NULL);
+ 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 */;) {
+ pthread_testcancel();
+ /* 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);
+ pthread_testcancel();
+ }
+ }
+ if (class->pseudofd > -1) {
+#ifdef SOLARIS
+ thr_yield();
+#endif
+ /* Pause some amount of time */
+ res = read(class->pseudofd, buf, sizeof(buf));
+ pthread_testcancel();
+ } 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));
+ pthread_testcancel();
+ } 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 (AST_LIST_EMPTY(&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;
+ pthread_testcancel();
+ if (class->pid > 1) {
+ killpg(class->pid, SIGHUP);
+ usleep(100000);
+ killpg(class->pid, SIGTERM);
+ usleep(100000);
+ killpg(class->pid, SIGKILL);
+ class->pid = 0;
+ }
+ } else
+ ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len);
+ continue;
+ }
+
+ pthread_testcancel();
+
+ ao2_lock(class);
+ AST_LIST_TRAVERSE(&class->members, moh, list) {
+ /* 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);
+ }
+ }
+ ao2_unlock(class);
+ }
+ return NULL;
+}
+
+static int moh0_exec(struct ast_channel *chan, void *data)
+{
+ if (ast_moh_start(chan, data, NULL)) {
+ ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name);
+ return 0;
+ }
+ 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, NULL)) {
+ ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
+ return 0;
+ }
+ 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;
+ }
+ ast_string_field_set(chan, musicclass, data);
+ 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, NULL))
+ 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(const char *name, int warn)
+{
+ struct mohclass *moh = NULL;
+ struct mohclass tmp_class = {
+ .flags = 0,
+ };
+
+ ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
+
+ moh = ao2_find(mohclasses, &tmp_class, 0);
+
+ if (!moh && warn) {
+ ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
+ }
+
+ return moh;
+}
+
+static struct mohdata *mohalloc(struct mohclass *cl)
+{
+ struct mohdata *moh;
+ long flags;
+
+ if (!(moh = ast_calloc(1, sizeof(*moh))))
+ return NULL;
+
+ 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->f.frametype = AST_FRAME_VOICE;
+ moh->f.subclass = cl->format;
+ moh->f.offset = AST_FRIENDLY_OFFSET;
+
+ moh->parent = mohclass_ref(cl);
+
+ ao2_lock(cl);
+ AST_LIST_INSERT_HEAD(&cl->members, moh, list);
+ ao2_unlock(cl);
+
+ return moh;
+}
+
+static void moh_release(struct ast_channel *chan, void *data)
+{
+ struct mohdata *moh = data;
+ struct mohclass *class = moh->parent;
+ int oldwfmt;
+
+ ao2_lock(class);
+ AST_LIST_REMOVE(&moh->parent->members, moh, list);
+ ao2_unlock(class);
+
+ close(moh->pipe[0]);
+ close(moh->pipe[1]);
+
+ oldwfmt = moh->origwfmt;
+
+ moh->parent = class = mohclass_unref(class);
+
+ 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;
+
+ if ((res = mohalloc(class))) {
+ 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 mohdata *moh = data;
+ short buf[1280 + AST_FRIENDLY_OFFSET / 2];
+ int res;
+
+ 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 (res <= 0)
+ return 0;
+
+ moh->f.datalen = res;
+ moh->f.data = buf + AST_FRIENDLY_OFFSET / 2;
+ moh->f.samples = ast_codec_get_samples(&moh->f);
+
+ if (ast_write(chan, &moh->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_add_file(struct mohclass *class, const char *filepath)
+{
+ if (!class->allowed_files) {
+ if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
+ return -1;
+ class->allowed_files = INITIAL_NUM_FILES;
+ } else if (class->total_files == class->allowed_files) {
+ if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
+ class->allowed_files = 0;
+ class->total_files = 0;
+ return -1;
+ }
+ class->allowed_files *= 2;
+ }
+
+ if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
+ return -1;
+
+ class->total_files++;
+
+ return 0;
+}
+
+static int moh_scan_files(struct mohclass *class) {
+
+ DIR *files_DIR;
+ struct dirent *files_dirent;
+ char path[PATH_MAX];
+ char filepath[PATH_MAX];
+ 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\n", class->dir);
+ return -1;
+ }
+
+ for (i = 0; i < class->total_files; i++)
+ free(class->filearray[i]);
+
+ class->total_files = 0;
+ dirnamelen = strlen(class->dir) + 2;
+ if (!getcwd(path, sizeof(path))) {
+ ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ if (chdir(class->dir) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ while ((files_dirent = readdir(files_DIR))) {
+ /* The file name must be at least long enough to have the file type extension */
+ if ((strlen(files_dirent->d_name) < 4))
+ continue;
+
+ /* Skip files that starts with a dot */
+ if (files_dirent->d_name[0] == '.')
+ continue;
+
+ /* Skip files without extensions... they are not audio */
+ if (!strchr(files_dirent->d_name, '.'))
+ continue;
+
+ snprintf(filepath, sizeof(filepath), "%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) {
+ if (moh_add_file(class, filepath))
+ break;
+ }
+ }
+
+ closedir(files_DIR);
+ if (chdir(path) < 0) {
+ ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return class->total_files;
+}
+
+static int init_files_class(struct mohclass *class)
+{
+ int res;
+
+ res = moh_scan_files(class);
+
+ if (res < 0) {
+ return -1;
+ }
+
+ if (!res) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
+ class->dir, class->name);
+ }
+ return -1;
+ }
+
+ if (strchr(class->args, 'r')) {
+ ast_set_flag(class, MOH_RANDOMIZE);
+ }
+
+ return 0;
+}
+
+static int init_app_class(struct mohclass *class)
+{
+#ifdef HAVE_DAHDI
+ int x;
+#endif
+
+ if (!strcasecmp(class->mode, "custom")) {
+ ast_set_flag(class, MOH_CUSTOM);
+ } else if (!strcasecmp(class->mode, "mp3nb")) {
+ ast_set_flag(class, MOH_SINGLE);
+ } else if (!strcasecmp(class->mode, "quietmp3nb")) {
+ ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
+ } else if (!strcasecmp(class->mode, "quietmp3")) {
+ ast_set_flag(class, MOH_QUIET);
+ }
+
+ class->srcfd = -1;
+ class->pseudofd = -1;
+
+#ifdef HAVE_DAHDI
+ /* Open /dev/zap/pseudo for timing... Is
+ there a better, yet reliable way to do this? */
+ class->pseudofd = open(DAHDI_FILE_PSEUDO, O_RDONLY);
+ if (class->pseudofd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
+ } else {
+ x = 320;
+ ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
+ }
+#endif
+
+ if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
+ ast_log(LOG_WARNING, "Unable to create moh thread...\n");
+ if (class->pseudofd > -1) {
+ close(class->pseudofd);
+ class->pseudofd = -1;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \note This function owns the reference it gets to moh
+ */
+static int moh_register(struct mohclass *moh, int reload)
+{
+ struct mohclass *mohclass = NULL;
+
+ if ((mohclass = get_mohbyname(moh->name, 0))) {
+ if (!mohclass->delete) {
+ ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
+ mohclass = mohclass_unref(mohclass);
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ mohclass = mohclass_unref(mohclass);
+ }
+
+ time(&moh->start);
+ moh->start -= respawn_time;
+
+ if (!strcasecmp(moh->mode, "files")) {
+ if (init_files_class(moh)) {
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ } 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 (init_app_class(moh)) {
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
+ moh = mohclass_unref(moh);
+ return -1;
+ }
+
+ ao2_link(mohclasses, moh);
+
+ moh = mohclass_unref(moh);
+
+ 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, const char *mclass, const char *interpclass)
+{
+ struct mohclass *mohclass = NULL;
+ int res;
+
+ /* The following is the order of preference for which class to use:
+ * 1) The channels explicitly set musicclass, which should *only* be
+ * set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
+ * 2) The mclass argument. If a channel is calling ast_moh_start() as the
+ * result of receiving a HOLD control frame, this should be the
+ * payload that came with the frame.
+ * 3) The interpclass argument. This would be from the mohinterpret
+ * option from channel drivers. This is the same as the old musicclass
+ * option.
+ * 4) The default class.
+ */
+ if (!ast_strlen_zero(chan->musicclass)) {
+ mohclass = get_mohbyname(chan->musicclass, 1);
+ }
+ if (!mohclass && !ast_strlen_zero(mclass)) {
+ mohclass = get_mohbyname(mclass, 1);
+ }
+ if (!mohclass && !ast_strlen_zero(interpclass)) {
+ mohclass = get_mohbyname(interpclass, 1);
+ }
+ if (!mohclass) {
+ mohclass = get_mohbyname("default", 1);
+ }
+
+ if (!mohclass) {
+ return -1;
+ }
+
+ ast_set_flag(chan, AST_FLAG_MOH);
+
+ if (mohclass->total_files) {
+ res = ast_activate_generator(chan, &moh_file_stream, mohclass);
+ } else {
+ res = ast_activate_generator(chan, &mohgen, mohclass);
+ }
+
+ mohclass = mohclass_unref(mohclass);
+
+ return res;
+}
+
+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 void moh_class_destructor(void *obj)
+{
+ struct mohclass *class = obj;
+ struct mohdata *member;
+
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "Destroying MOH class '%s'\n", class->name);
+ }
+
+ if (class->pid > 1) {
+ char buff[8192];
+ int bytes, tbytes = 0, stime = 0, pid = 0;
+
+ ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
+
+ stime = time(NULL) + 2;
+ pid = class->pid;
+ class->pid = 0;
+
+ /* Back when this was just mpg123, SIGKILL was fine. Now we need
+ * to give the process a reason and time enough to kill off its
+ * children. */
+ killpg(pid, SIGHUP);
+ usleep(100000);
+ killpg(pid, SIGTERM);
+ usleep(100000);
+ killpg(pid, SIGKILL);
+
+ while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
+ (bytes = read(class->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(class->srcfd);
+ }
+
+ while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
+ free(member);
+ }
+
+ if (class->thread) {
+ pthread_cancel(class->thread);
+ class->thread = AST_PTHREADT_NULL;
+ }
+
+ if (class->filearray) {
+ int i;
+ for (i = 0; i < class->total_files; i++) {
+ free(class->filearray[i]);
+ }
+ free(class->filearray);
+ class->filearray = NULL;
+ }
+}
+
+static struct mohclass *moh_class_malloc(void)
+{
+ struct mohclass *class;
+
+ if ((class = ao2_alloc(sizeof(*class), moh_class_destructor))) {
+ class->format = AST_FORMAT_SLINEAR;
+ }
+
+ return class;
+}
+
+static int moh_class_mark(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ class->delete = 1;
+
+ return 0;
+}
+
+static int moh_classes_delete_marked(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ return class->delete ? CMP_MATCH : 0;
+}
+
+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;
+ }
+
+ if (reload) {
+ ao2_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL);
+ }
+
+ cat = ast_category_browse(cfg, NULL);
+ for (; cat; cat = ast_category_browse(cfg, cat)) {
+ if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files")) {
+ continue;
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ ast_copy_string(class->name, cat, sizeof(class->name));
+
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ 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;
+ }
+ }
+ }
+
+ if (ast_strlen_zero(class->dir)) {
+ if (!strcasecmp(class->mode, "custom")) {
+ ast_copy_string(class->dir, "nodir", sizeof(class->dir));
+ } else {
+ ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
+ class = mohclass_unref(class);
+ continue;
+ }
+ }
+
+ if (ast_strlen_zero(class->mode)) {
+ ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
+ class = mohclass_unref(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);
+ class = mohclass_unref(class);
+ continue;
+ }
+
+ /* Don't leak a class when it's already registered */
+ moh_register(class, reload);
+
+ numclasses++;
+ }
+
+
+ /* Deprecated Old-School Configuration */
+ for (var = ast_variable_browse(cfg, "classes"); var; var = var->next) {
+ struct mohclass *tmp_class;
+
+ 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 (!(data = strchr(var->value, ':'))) {
+ continue;
+ }
+ *data++ = '\0';
+
+ if ((args = strchr(data, ','))) {
+ *args++ = '\0';
+ }
+
+ if ((tmp_class = get_mohbyname(var->name, 0))) {
+ tmp_class = mohclass_unref(tmp_class);
+ continue;
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ 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);
+ class = NULL;
+
+ numclasses++;
+ }
+
+ for (var = ast_variable_browse(cfg, "moh_files"); var; var = var->next) {
+ struct mohclass *tmp_class;
+
+ 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 ((tmp_class = get_mohbyname(var->name, 0))) {
+ tmp_class = mohclass_unref(tmp_class);
+ continue;
+ }
+
+ if ((args = strchr(var->value, ','))) {
+ *args++ = '\0';
+ }
+
+ if (!(class = moh_class_malloc())) {
+ break;
+ }
+
+ ast_copy_string(class->name, var->name, sizeof(class->name));
+ ast_copy_string(class->dir, var->value, sizeof(class->dir));
+ ast_copy_string(class->mode, "files", sizeof(class->mode));
+ if (args) {
+ ast_copy_string(class->args, args, sizeof(class->args));
+ }
+
+ moh_register(class, reload);
+ class = NULL;
+
+ numclasses++;
+ }
+
+ ast_config_destroy(cfg);
+
+ ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+ moh_classes_delete_marked, NULL);
+
+ return numclasses;
+}
+
+static void ast_moh_destroy(void)
+{
+ if (option_verbose > 1) {
+ ast_verbose(VERBOSE_PREFIX_2 "Destroying musiconhold processes\n");
+ }
+
+ ao2_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
+}
+
+static int moh_cli(int fd, int argc, char *argv[])
+{
+ reload();
+ return 0;
+}
+
+static int cli_files_show(int fd, int argc, char *argv[])
+{
+ struct mohclass *class;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(mohclasses, 0);
+
+ for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
+ int x;
+
+ if (!class->total_files) {
+ continue;
+ }
+
+ ast_cli(fd, "Class: %s\n", class->name);
+
+ for (x = 0; x < class->total_files; x++) {
+ ast_cli(fd, "\tFile: %s\n", class->filearray[x]);
+ }
+ }
+
+ return 0;
+}
+
+static int moh_classes_show(int fd, int argc, char *argv[])
+{
+ struct mohclass *class;
+ struct ao2_iterator i;
+
+ i = ao2_iterator_init(mohclasses, 0);
+
+ for (; (class = ao2_iterator_next(&i)); mohclass_unref(class)) {
+ ast_cli(fd, "Class: %s\n", class->name);
+ ast_cli(fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
+ ast_cli(fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
+ if (ast_test_flag(class, MOH_CUSTOM)) {
+ ast_cli(fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
+ }
+ if (strcasecmp(class->mode, "files")) {
+ ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format));
+ }
+ }
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_moh_classes_show_deprecated = {
+ { "moh", "classes", "show"},
+ moh_classes_show, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_moh_files_show_deprecated = {
+ { "moh", "files", "show"},
+ cli_files_show, NULL,
+ NULL };
+
+static struct ast_cli_entry cli_moh[] = {
+ { { "moh", "reload"},
+ moh_cli, "Music On Hold",
+ "Usage: moh reload\n Rereads configuration\n" },
+
+ { { "moh", "show", "classes"},
+ moh_classes_show, "List MOH classes",
+ "Usage: moh show classes\n Lists all MOH classes\n", NULL, &cli_moh_classes_show_deprecated },
+
+ { { "moh", "show", "files"},
+ cli_files_show, "List MOH file-based classes",
+ "Usage: moh show files\n Lists all loaded file-based MOH classes and their files\n", NULL, &cli_moh_files_show_deprecated },
+};
+
+static int moh_class_hash(const void *obj, const int flags)
+{
+ const struct mohclass *class = obj;
+
+ return ast_str_case_hash(class->name);
+}
+
+static int moh_class_cmp(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj, *class2 = arg;
+
+ return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int load_module(void)
+{
+ int res;
+
+ if (!(mohclasses = ao2_container_alloc(53, moh_class_hash, moh_class_cmp))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (!load_moh_classes(0)) { /* No music classes configured, so skip it */
+ ast_log(LOG_WARNING, "No music on hold classes configured, "
+ "disabling music on hold.\n");
+ } else {
+ ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
+ local_ast_moh_cleanup);
+ }
+
+ res = ast_register_application(app0, moh0_exec, synopsis0, descrip0);
+ ast_register_atexit(ast_moh_destroy);
+ ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
+ 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);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload(void)
+{
+ if (load_moh_classes(1)) {
+ ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
+ local_ast_moh_cleanup);
+ }
+
+ return 0;
+}
+
+static int moh_class_inuse(void *obj, void *arg, int flags)
+{
+ struct mohclass *class = obj;
+
+ return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int unload_module(void)
+{
+ int res = 0;
+ struct mohclass *class = NULL;
+
+ /* XXX This check shouldn't be required if module ref counting was being used
+ * properly ... */
+ if ((class = ao2_callback(mohclasses, 0, moh_class_inuse, NULL))) {
+ class = mohclass_unref(class);
+ res = -1;
+ }
+
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
+ return res;
+ }
+
+ ast_uninstall_music_functions();
+
+ ast_moh_destroy();
+
+ res = ast_unregister_application(app0);
+ res |= ast_unregister_application(app1);
+ res |= ast_unregister_application(app2);
+ res |= ast_unregister_application(app3);
+ res |= ast_unregister_application(app4);
+
+ ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
+
+ return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Music On Hold Resource",
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+);