Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2010, Digium, Inc.
Mark Spencer
committed
* 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
* \author Mark Spencer <markster@digium.com>
/*! \li \ref res_musiconhold.c uses the configuration file \ref musiconhold.conf
* \addtogroup configuration_file Configuration Files
*/
* \page musiconhold.conf musiconhold.conf
* \verbinclude musiconhold.conf.sample
*/
Kevin P. Fleming
committed
/*** MODULEINFO
<conflict>win32</conflict>
<support_level>core</support_level>
Kevin P. Fleming
committed
***/
Kevin P. Fleming
committed
#include "asterisk.h"
#include <ctype.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <dirent.h>
#ifdef SOLARIS
#include <thread.h>
#endif
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/app.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/stasis.h"
#include "asterisk/stasis_channels.h"
Kevin P. Fleming
committed
#include "asterisk/paths.h"
Russell Bryant
committed
#include "asterisk/astobj2.h"
#include "asterisk/timing.h"
#include "asterisk/time.h"
#include "asterisk/poll-compat.h"
Russell Bryant
committed
#define INITIAL_NUM_FILES 8
Tilghman Lesher
committed
#define HANDLE_REF 1
#define DONT_UNREF 0
/*** DOCUMENTATION
<application name="MusicOnHold" language="en_US">
<synopsis>
Play Music On Hold indefinitely.
</synopsis>
<syntax>
<parameter name="class" required="true" />
<parameter name="duration" />
</syntax>
<description>
<para>Plays hold music specified by class. If omitted, the default music
source for the channel will be used. Change the default class with
Set(CHANNEL(musicclass)=...). If duration is given, hold music will be played
specified number of seconds. If duration is ommited, music plays indefinitely.
Returns <literal>0</literal> when done, <literal>-1</literal> on hangup.</para>
<para>This application does not automatically answer and should be preceeded by
an application such as Answer() or Progress().</para>
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
</description>
</application>
<application name="StartMusicOnHold" language="en_US">
<synopsis>
Play Music On Hold.
</synopsis>
<syntax>
<parameter name="class" required="true" />
</syntax>
<description>
<para>Starts playing music on hold, uses default music class for channel.
Starts playing music specified by class. If omitted, the default music
source for the channel will be used. Always returns <literal>0</literal>.</para>
</description>
</application>
<application name="StopMusicOnHold" language="en_US">
<synopsis>
Stop playing Music On Hold.
</synopsis>
<syntax />
<description>
<para>Stops playing music on hold.</para>
</description>
</application>
***/
static const char play_moh[] = "MusicOnHold";
static const char start_moh[] = "StartMusicOnHold";
static const char stop_moh[] = "StopMusicOnHold";
static int respawn_time = 20;
Mark Spencer
committed
struct moh_files_state {
/*! Holds a reference to the MOH class. */
Mark Spencer
committed
struct mohclass *class;
struct ast_format *origwfmt;
struct ast_format *mohwfmt;
Jonathan Rose
committed
int announcement;
Mark Spencer
committed
int samples;
int sample_queue;
int save_total;
char name[MAX_MUSICCLASS];
char save_pos_filename[PATH_MAX];
Mark Spencer
committed
};
#define MOH_QUIET (1 << 0)
#define MOH_SINGLE (1 << 1)
#define MOH_CUSTOM (1 << 2)
#define MOH_RANDOMIZE (1 << 3)
Russell Bryant
committed
#define MOH_SORTALPHA (1 << 4)
#define MOH_RANDSTART (MOH_RANDOMIZE | MOH_SORTALPHA) /*!< Sorted but start at random position */
#define MOH_SORTMODE (3 << 3)
#define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */
#define MOH_ANNOUNCEMENT (1 << 6) /*!< Do we play announcement files between songs on this channel? */
#define MOH_PREFERCHANNELCLASS (1 << 7) /*!< Should queue moh override channel moh */
Mark Michelson
committed
Tilghman Lesher
committed
/* Custom astobj2 flag */
#define MOH_NOTDELETED (1 << 30) /*!< Find only records that aren't deleted? */
#define MOH_REALTIME (1 << 31) /*!< Find only records that are realtime */
Tilghman Lesher
committed
Mark Michelson
committed
static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
enum kill_methods {
KILL_METHOD_PROCESS_GROUP = 0,
KILL_METHOD_PROCESS
};
char name[MAX_MUSICCLASS];
Olle Johansson
committed
char dir[256];
char args[256];
Jonathan Rose
committed
char announcement[256];
char digit;
/*! An immutable vector of filenames in "files" mode */
struct ast_vector_string *files;
unsigned int flags;
Russell Bryant
committed
/*! The format from the MOH source, not applicable to "files" mode */
struct ast_format *format;
Russell Bryant
committed
/*! The pid of the external application delivering MOH */
int pid;
time_t start;
/*! Millisecond delay between kill attempts */
size_t kill_delay;
/*! Kill method */
enum kill_methods kill_method;
Russell Bryant
committed
/*! Source of audio */
/*! Generic timer */
struct ast_timer *timer;
Mark Michelson
committed
/*! Created on the fly, from RT engine */
AST_LIST_HEAD_NOLOCK(, mohdata) members;
AST_LIST_ENTRY(mohclass) list;
struct ast_format *origwfmt;
struct ast_frame f;
AST_LIST_ENTRY(mohdata) list;
Russell Bryant
committed
static struct ao2_container *mohclasses;
#define LOCAL_MPG_123 "/usr/local/bin/mpg123"
#define MPG_123 "/usr/bin/mpg123"
#define MAX_MP3S 256
static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclass);
#define mohclass_ref(class,string) (ao2_t_ref((class), +1, (string)), class)
#ifndef AST_DEVMODE
#define mohclass_unref(class,string) ({ ao2_t_ref((class), -1, (string)); (struct mohclass *) NULL; })
#else
#define mohclass_unref(class,string) _mohclass_unref(class, string, __FILE__,__LINE__,__PRETTY_FUNCTION__)
static struct mohclass *_mohclass_unref(struct mohclass *class, const char *tag, const char *file, int line, const char *funcname)
{
Jonathan Rose
committed
struct mohclass *dup = ao2_callback(mohclasses, OBJ_POINTER, ao2_match_by_addr, class);
if (dup) {
if (__ao2_ref(dup, -1, tag, file, line, funcname) == 2) {
ast_log(LOG_WARNING, "Attempt to unref mohclass %p (%s) when only 1 ref remained, and class is still in a container! (at %s:%d (%s))\n",
class, class->name, file, line, funcname);
} else {
ao2_ref(class, -1);
}
} else {
__ao2_ref(class, -1, tag, file, line, funcname);
}
return NULL;
}
#endif
Richard Mudgett
committed
static void moh_post_start(struct ast_channel *chan, const char *moh_class_name)
{
struct stasis_message *message;
struct ast_json *json_object;
ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n",
moh_class_name, ast_channel_name(chan));
json_object = ast_json_pack("{s: s}", "class", moh_class_name);
if (!json_object) {
return;
}
message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(chan),
ast_channel_moh_start_type(), json_object);
if (message) {
/* A channel snapshot must have been in the cache. */
ast_assert(((struct ast_channel_blob *) stasis_message_data(message))->snapshot != NULL);
Richard Mudgett
committed
stasis_publish(ast_channel_topic(chan), message);
}
ao2_cleanup(message);
ast_json_unref(json_object);
}
static void moh_post_stop(struct ast_channel *chan)
{
struct stasis_message *message;
ast_verb(3, "Stopped music on hold on %s\n", ast_channel_name(chan));
message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(chan),
ast_channel_moh_stop_type(), NULL);
if (message) {
/* A channel snapshot must have been in the cache. */
ast_assert(((struct ast_channel_blob *) stasis_message_data(message))->snapshot != NULL);
Richard Mudgett
committed
stasis_publish(ast_channel_topic(chan), message);
}
ao2_cleanup(message);
}
Mark Spencer
committed
static void moh_files_release(struct ast_channel *chan, void *data)
{
struct moh_files_state *state;
if (!chan || !ast_channel_music_state(chan)) {
Russell Bryant
committed
return;
}
state = ast_channel_music_state(chan);
Russell Bryant
committed
if (ast_channel_stream(chan)) {
ast_closestream(ast_channel_stream(chan));
ast_channel_stream_set(chan, NULL);
Russell Bryant
committed
}
Richard Mudgett
committed
moh_post_stop(chan);
Mark Spencer
committed
ao2_ref(state->mohwfmt, -1);
state->mohwfmt = NULL; /* make sure to clear this format before restoring the original format */
if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%s'\n", ast_channel_name(chan),
ast_format_get_name(state->origwfmt));
Mark Spencer
committed
}
ao2_cleanup(state->origwfmt);
state->origwfmt = NULL;
Mark Spencer
committed
Russell Bryant
committed
state->save_pos = state->pos;
Jonathan Rose
committed
state->announcement = 0;
Russell Bryant
committed
Tilghman Lesher
committed
state->class = mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
Russell Bryant
committed
}
Mark Spencer
committed
static int ast_moh_files_next(struct ast_channel *chan)
struct moh_files_state *state = ast_channel_music_state(chan);
struct ast_vector_string *files;
size_t file_count;
Mark Spencer
committed
/* Discontinue a stream if it is running already */
if (ast_channel_stream(chan)) {
ast_closestream(ast_channel_stream(chan));
ast_channel_stream_set(chan, NULL);
Kevin P. Fleming
committed
Jonathan Rose
committed
if (ast_test_flag(state->class, MOH_ANNOUNCEMENT) && state->announcement == 0) {
state->announcement = 1;
if (ast_openstream_full(chan, state->class->announcement, ast_channel_language(chan), 1)) {
Jonathan Rose
committed
ast_debug(1, "%s Opened announcement '%s'\n", ast_channel_name(chan), state->class->announcement);
return 0;
}
} else {
state->announcement = 0;
}
ao2_lock(state->class);
files = ao2_bump(state->class->files);
ao2_unlock(state->class);
file_count = AST_VECTOR_SIZE(files);
if (!file_count) {
ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
if (state->pos == 0 && ast_strlen_zero(state->save_pos_filename)) {
/* First time so lets play the file. */
state->save_pos = -1;
} else if (state->save_pos >= 0 && state->save_pos < file_count && !strcmp(AST_VECTOR_GET(files, state->save_pos), state->save_pos_filename)) {
/* If a specific file has been saved confirm it still exists and that it is still valid */
} else if (ast_test_flag(state->class, MOH_SORTMODE) == MOH_RANDOMIZE) {
/* Get a random file and ensure we can open it */
for (tries = 0; tries < 20; tries++) {
state->pos = ast_random() % file_count;
if (ast_fileexists(AST_VECTOR_GET(files, state->pos), NULL, NULL) > 0) {
Mark Spencer
committed
}
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 %= file_count;
Mark Spencer
committed
}
for (tries = 0; tries < file_count; ++tries) {
if (ast_openstream_full(chan, AST_VECTOR_GET(files, state->pos), ast_channel_language(chan), 1)) {
ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", AST_VECTOR_GET(files, state->pos), strerror(errno));
Mark Spencer
committed
state->pos++;
state->pos %= file_count;
if (tries == file_count) {
Mark Spencer
committed
return -1;
}
/* Record the pointer to the filename for position resuming later */
ast_copy_string(state->save_pos_filename, AST_VECTOR_GET(files, state->pos), sizeof(state->save_pos_filename));
ast_debug(1, "%s Opened file %d '%s'\n", ast_channel_name(chan), state->pos, state->save_pos_filename);
Mark Spencer
committed
size_t loc;
/* seek *SHOULD* be good since it's from a known location */
ast_seekstream(ast_channel_stream(chan), state->samples, SEEK_SET);
/* if the seek failed then recover because if there is not a valid read,
* moh_files_generate will return -1 and MOH will stop */
loc = ast_tellstream(ast_channel_stream(chan));
if (state->samples > loc && loc) {
/* seek one sample from the end for one guaranteed valid read */
ast_seekstream(ast_channel_stream(chan), 1, SEEK_END);
Mark Spencer
committed
Mark Spencer
committed
}
static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
struct ast_frame *f;
f = ast_readframe(ast_channel_stream(chan));
if (!f) {
/* Either there was no file stream setup or we reached EOF. */
if (!ast_moh_files_next(chan)) {
/*
* Either we resetup the previously saved file stream position
* or we started a new file stream.
*/
f = ast_readframe(ast_channel_stream(chan));
if (!f) {
/*
* We can get here if we were very unlucky because the
* resetup file stream was saved at EOF when MOH was
* previously stopped.
*/
if (!ast_moh_files_next(chan)) {
f = ast_readframe(ast_channel_stream(chan));
}
}
}
Mark Spencer
committed
}
return f;
}
static void moh_files_write_format_change(struct ast_channel *chan, void *data)
{
struct moh_files_state *state = ast_channel_music_state(chan);
/* In order to prevent a recursive call to this function as a result
* of setting the moh write format back on the channel. Clear
* the moh write format before setting the write format on the channel.*/
if (state->origwfmt) {
struct ast_format *tmp;
tmp = ao2_bump(ast_channel_writeformat(chan));
ao2_replace(state->origwfmt, NULL);
if (state->mohwfmt) {
ast_set_write_format(chan, state->mohwfmt);
state->origwfmt = tmp;
Mark Spencer
committed
static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
{
struct moh_files_state *state = ast_channel_music_state(chan);
Mark Spencer
committed
struct ast_frame *f = NULL;
int res = 0;
Mark Spencer
committed
state->sample_queue += samples;
f = moh_files_readframe(chan);
/* We need to be sure that we unlock
* the channel prior to calling
* ast_write. Otherwise, the recursive locking
* that occurs can cause deadlocks when using
* indirect channels, like local channels
*/
ast_channel_unlock(chan);
if (!f) {
return -1;
}
/* Only track our offset within the current file if we are not in the
* the middle of an announcement */
if (!state->announcement) {
state->samples += f->samples;
}
if (ast_format_cmp(f->subclass.format, state->mohwfmt) == AST_FORMAT_CMP_NOT_EQUAL) {
ao2_replace(state->mohwfmt, f->subclass.format);
}
res = ast_write(chan, f);
ast_frfree(f);
if (res < 0) {
ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", ast_channel_name(chan), strerror(errno));
return -1;
Mark Spencer
committed
}
return res;
}
static void *moh_files_alloc(struct ast_channel *chan, void *params)
{
struct moh_files_state *state;
struct mohclass *class = params;
size_t file_count;
Mark Spencer
committed
Richard Mudgett
committed
state = ast_channel_music_state(chan);
if (!state && (state = ast_calloc(1, sizeof(*state)))) {
ast_channel_music_state_set(chan, state);
ast_module_ref(ast_module_info->self);
Russell Bryant
committed
} else {
if (!state) {
return NULL;
}
if (state->class) {
mohclass_unref(state->class, "Uh Oh. Restarting MOH with an active class");
ast_log(LOG_WARNING, "Uh Oh. Restarting MOH with an active class\n");
}
Russell Bryant
committed
}
ao2_lock(class);
file_count = AST_VECTOR_SIZE(class->files);
ao2_unlock(class);
/* Resume MOH from where we left off last time or start from scratch? */
if (state->save_total != file_count || strcmp(state->name, class->name) != 0) {
/* Start MOH from scratch. */
ao2_cleanup(state->origwfmt);
ao2_cleanup(state->mohwfmt);
Russell Bryant
committed
memset(state, 0, sizeof(*state));
if (ast_test_flag(class, MOH_RANDOMIZE) && file_count) {
state->pos = ast_random() % file_count;
Russell Bryant
committed
}
}
Mark Spencer
committed
state->class = mohclass_ref(class, "Reffing music class for channel");
/* it's possible state is not a new allocation, don't leak old refs */
ao2_replace(state->origwfmt, ast_channel_writeformat(chan));
ao2_replace(state->mohwfmt, ast_channel_writeformat(chan));
/* For comparison on restart of MOH (see above) */
ast_copy_string(state->name, class->name, sizeof(state->name));
state->save_total = file_count;
Richard Mudgett
committed
moh_post_start(chan, class->name);
return state;
Mark Spencer
committed
}
Russell Bryant
committed
static int moh_digit_match(void *obj, void *arg, int flags)
{
Russell Bryant
committed
char *digit = arg;
struct mohclass *class = obj;
Russell Bryant
committed
return (*digit == class->digit) ? CMP_MATCH | CMP_STOP : 0;
}
Russell Bryant
committed
/*! \note This function should be called with the mohclasses list locked */
static struct mohclass *get_mohbydigit(char digit)
{
return ao2_t_callback(mohclasses, 0, moh_digit_match, &digit, "digit callback");
}
static void moh_handle_digit(struct ast_channel *chan, char digit)
{
Russell Bryant
committed
struct mohclass *class;
const char *classname = NULL;
Russell Bryant
committed
if ((class = get_mohbydigit(digit))) {
classname = ast_strdupa(class->name);
class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
ast_channel_musicclass_set(chan, classname);
Russell Bryant
committed
}
}
Richard Mudgett
committed
static struct ast_generator moh_file_stream = {
Russell Bryant
committed
.alloc = moh_files_alloc,
.release = moh_files_release,
.generate = moh_files_generator,
.digit = moh_handle_digit,
.write_format_change = moh_files_write_format_change,
Mark Spencer
committed
};
static int spawn_mp3(struct mohclass *class)
{
int fds[2];
char *argv[MAX_MP3S + 50];
char xargs[256];
char *argptr;
Mark Spencer
committed
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;
}
Mark Spencer
committed
}
Mark Spencer
committed
argv[argc++] = "mpg123";
argv[argc++] = "-q";
argv[argc++] = "-s";
argv[argc++] = "--mono";
argv[argc++] = "-r";
argv[argc++] = "8000";
Mark Spencer
committed
argv[argc++] = "-b";
argv[argc++] = "2048";
}
Mark Spencer
committed
argv[argc++] = "-f";
Mark Spencer
committed
argv[argc++] = "4096";
else
argv[argc++] = "8192";
Mark Spencer
committed
/* Look for extra arguments and add them to the list */
ast_copy_string(xargs, class->args, sizeof(xargs));
Mark Spencer
committed
argptr = xargs;
Mark Spencer
committed
argv[argc++] = argptr;
Mark Spencer
committed
}
Mark Spencer
committed
} else {
Mark Spencer
committed
/* Format arguments for argv vector */
ast_copy_string(xargs, class->args, sizeof(xargs));
Mark Spencer
committed
argptr = xargs;
Mark Spencer
committed
argv[argc++] = argptr;
if (!strncasecmp(class->dir, "http://", 7)) {
ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
argv[argc++] = fns[files];
files++;
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++;
}
if (dir) {
closedir(dir);
}
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]);
if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
sleep(respawn_time - (time(NULL) - class->start));
}
time(&class->start);
Tilghman Lesher
committed
class->pid = ast_safe_fork(0);
if (class->pid < 0) {
close(fds[0]);
close(fds[1]);
ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
return -1;
}
Kevin P. Fleming
committed
if (!class->pid) {
if (ast_opt_high_priority)
ast_set_priority(0);
Kevin P. Fleming
committed
close(fds[0]);
/* Stdout goes to pipe */
dup2(fds[1], STDOUT_FILENO);
Tilghman Lesher
committed
/* Close everything else */
ast_close_fds_above_n(STDERR_FILENO);
Kevin P. Fleming
committed
/* Child */
if (strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
_exit(1);
}
Mark Spencer
committed
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);
}
Tilghman Lesher
committed
/* Can't use logger, since log FDs are closed */
fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
Kevin P. Fleming
committed
close(fds[1]);
Kevin P. Fleming
committed
} else {
/* Parent */
close(fds[1]);
Kevin P. Fleming
committed
return fds[0];
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
static int killer(pid_t pid, int signum, enum kill_methods kill_method)
{
switch (kill_method) {
case KILL_METHOD_PROCESS_GROUP:
return killpg(pid, signum);
case KILL_METHOD_PROCESS:
return kill(pid, signum);
}
return -1;
}
static void killpid(int pid, size_t delay, enum kill_methods kill_method)
{
if (killer(pid, SIGHUP, kill_method) < 0) {
if (errno == ESRCH) {
return;
}
ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process '%d'?!!: %s\n", pid, strerror(errno));
} else {
ast_debug(1, "Sent HUP to pid %d%s\n", pid,
kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
}
usleep(delay);
if (killer(pid, SIGTERM, kill_method) < 0) {
if (errno == ESRCH) {
return;
}
ast_log(LOG_WARNING, "Unable to terminate MOH process '%d'?!!: %s\n", pid, strerror(errno));
} else {
ast_debug(1, "Sent TERM to pid %d%s\n", pid,
kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
}
usleep(delay);
if (killer(pid, SIGKILL, kill_method) < 0) {
if (errno == ESRCH) {
return;
}
ast_log(LOG_WARNING, "Unable to kill MOH process '%d'?!!: %s\n", pid, strerror(errno));
} else {
ast_debug(1, "Sent KILL to pid %d%s\n", pid,
kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
}
}
struct mohclass *class = data;
struct mohdata *moh;
short sbuf[8192];
struct timeval deadline, tv_tmp;
deadline.tv_sec = 0;
deadline.tv_usec = 0;
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
Matthew Jordan
committed
continue;
struct pollfd pfd = { .fd = ast_timer_fd(class->timer), .events = POLLIN | POLLPRI, };
#ifdef SOLARIS
thr_yield();
#endif
if (ast_poll(&pfd, 1, -1) > 0) {
Matthew Jordan
committed
if (ast_timer_ack(class->timer, 1) < 0) {
ast_log(LOG_ERROR, "Failed to acknowledge timer for mp3player\n");
return NULL;
}
/* 25 samples per second => 40ms framerate => 320 samples */
res = 320; /* 320/40 = 8 samples/ms */
ast_log(LOG_WARNING, "poll() failed: %s\n", strerror(errno));
Kevin P. Fleming
committed
long delta;
Kevin P. Fleming
committed
tv_tmp = ast_tvnow();
if (ast_tvzero(deadline))
deadline = tv_tmp;
delta = ast_tvdiff_ms(tv_tmp, deadline);
Kevin P. Fleming
committed
if (delta < MOH_MS_INTERVAL) { /* too early */
deadline = ast_tvadd(deadline, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */
Kevin P. Fleming
committed
usleep(1000 * (MOH_MS_INTERVAL - delta));
} else {
ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
deadline = tv_tmp;
/* 10 samples per second (MOH_MS_INTERVAL) => 100ms framerate => 800 samples */
res = 8 * MOH_MS_INTERVAL; /* 800/100 = 8 samples/ms */
/* For non-8000Hz formats, we need to alter the resolution */
res = res * ast_format_get_sample_rate(class->format) / 8000;
if ((strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir")) && AST_LIST_EMPTY(&class->members))
len = ast_format_determine_length(class->format, res);
if ((res2 = read(class->srcfd, sbuf, len)) != len) {
if (!res2) {
close(class->srcfd);
class->srcfd = -1;
if (class->pid > 1) {
killpid(class->pid, class->kill_delay, class->kill_method);
ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
Russell Bryant
committed
Russell Bryant
committed
ao2_lock(class);
AST_LIST_TRAVERSE(&class->members, moh, list) {
if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
Russell Bryant
committed
ao2_unlock(class);
static int play_moh_exec(struct ast_channel *chan, const char *data)
char *parse;
char *class;
int timeout = -1;
int res;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(class);
AST_APP_ARG(duration);
);
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (!ast_strlen_zero(args.duration)) {
timeout *= 1000;
} else {
ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
}
}
class = S_OR(args.class, NULL);
if (ast_moh_start(chan, class, NULL)) {
ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, ast_channel_name(chan));
if (timeout > 0)
res = ast_safe_sleep(chan, timeout);
else {
while (!(res = ast_safe_sleep(chan, 10000)));
}
Mark Spencer
committed
ast_moh_stop(chan);
static int start_moh_exec(struct ast_channel *chan, const char *data)
char *parse;
char *class;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(class);
);
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
class = S_OR(args.class, NULL);
if (ast_moh_start(chan, class, NULL))
ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, ast_channel_name(chan));
return 0;
}
static int stop_moh_exec(struct ast_channel *chan, const char *data)
{
ast_moh_stop(chan);
return 0;
}
Tilghman Lesher
committed
#define get_mohbyname(a,b,c) _get_mohbyname(a,b,c,__FILE__,__LINE__,__PRETTY_FUNCTION__)
static struct mohclass *_get_mohbyname(const char *name, int warn, int flags, const char *file, int lineno, const char *funcname)
Russell Bryant
committed
struct mohclass tmp_class = {
.flags = 0,
};
Russell Bryant
committed
ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
moh = __ao2_find(mohclasses, &tmp_class, flags,
"get_mohbyname", file, lineno, funcname);
Russell Bryant
committed
if (!moh && warn) {
ast_log(LOG_WARNING, "Music on Hold class '%s' not found in memory. Verify your configuration.\n", name);
Russell Bryant
committed
}
}
static struct mohdata *mohalloc(struct mohclass *cl)
{
struct mohdata *moh;
if (!(moh = ast_calloc(1, sizeof(*moh))))
if (ast_pipe_nonblock(moh->pipe)) {
ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
Tilghman Lesher
committed
ast_free(moh);
moh->f.frametype = AST_FRAME_VOICE;
moh->f.subclass.format = cl->format;
moh->f.offset = AST_FRIENDLY_OFFSET;
moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
Russell Bryant
committed
ao2_lock(cl);
AST_LIST_INSERT_HEAD(&cl->members, moh, list);
Russell Bryant
committed
ao2_unlock(cl);
return moh;
}
static void moh_release(struct ast_channel *chan, void *data)
{
Russell Bryant
committed
struct mohclass *class = moh->parent;
struct ast_format *oldwfmt;
Russell Bryant
committed
ao2_lock(class);
AST_LIST_REMOVE(&moh->parent->members, moh, list);
Russell Bryant
committed
ao2_unlock(class);
Russell Bryant
committed
oldwfmt = moh->origwfmt;
Russell Bryant
committed
moh->parent = class = mohclass_unref(class, "unreffing moh->parent upon deactivation of generator");
Mark Michelson
committed
Tilghman Lesher
committed
ast_free(moh);
Russell Bryant
committed