aboutsummaryrefslogtreecommitdiffstats
path: root/res
diff options
context:
space:
mode:
authormmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2007-11-28 00:47:22 +0000
committermmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2007-11-28 00:47:22 +0000
commit8bca2b15a30717391de765f76a4ed88fbf06eb24 (patch)
treebffb8e8621b673bb130d3c6f9937ff99e88c20ba /res
parent78bd1dcd5f0d7e2e7d50a1a57a386b901f942ab3 (diff)
Adding support for realtime music on hold. The following are the main points:
1. When moh is started, we search first in memory to find the class. If we do not find it in memory, we search realtime instead. 2. When moh is restarted (as in, it had been started on this particular channel, stopped, and now we're starting it again), if using the "files" mode, then realtime will always be rechecked. If you are using other modes, however, we will simply reattach to the external running process which was playing moh earlier in the call. This is a necessary compromise so that we don't end up with too many background processes. 3. musiconhold.conf has a general section now. It has one option: cachertclasses. If set to yes, then moh classes found in realtime will be added to the in-memory list. This has the advantage of not requiring database lookups each time moh is started, but it has the disadvantage of not truly being realtime. I have tested this for functionality, and it passes. I also tested this under valgrind and there are no memory problems reported under typical use. Special thanks to Sergee for implementing this feature and enduring my complaints on the bugtracker! (closes issue #11196, reported and patched by sergee) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@89946 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'res')
-rw-r--r--res/res_musiconhold.c253
1 files changed, 235 insertions, 18 deletions
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index 4270f5708..027aed023 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -118,6 +118,10 @@ struct moh_files_state {
#define MOH_RANDOMIZE (1 << 3)
#define MOH_SORTALPHA (1 << 4)
+#define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */
+
+static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
+
struct mohclass {
char name[MAX_MUSICCLASS];
char dir[256];
@@ -143,6 +147,8 @@ struct mohclass {
int pseudofd;
/*! Number of users */
int inuse;
+ /*! Created on the fly, from RT engine */
+ int realtime;
unsigned int delete:1;
AST_LIST_HEAD_NOLOCK(, mohdata) members;
AST_LIST_ENTRY(mohclass) list;
@@ -668,7 +674,7 @@ static struct mohclass *get_mohbyname(const char *name, int warn)
}
if (!moh && warn)
- ast_log(LOG_WARNING, "Music on Hold class '%s' not found\n", name);
+ ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
return moh;
}
@@ -710,6 +716,7 @@ static void moh_release(struct ast_channel *chan, void *data)
{
struct mohdata *moh = data;
int oldwfmt;
+ struct moh_files_state *state;
AST_RWLIST_WRLOCK(&mohclasses);
AST_RWLIST_REMOVE(&moh->parent->members, moh, list);
@@ -718,8 +725,12 @@ static void moh_release(struct ast_channel *chan, void *data)
close(moh->pipe[0]);
close(moh->pipe[1]);
oldwfmt = moh->origwfmt;
+ state = chan->music_state;
if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse))
ast_moh_destroy_one(moh->parent);
+ if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete)
+ ast_moh_destroy_one(state->class);
+
ast_free(moh);
if (chan) {
if (oldwfmt && ast_set_write_format(chan, oldwfmt))
@@ -732,6 +743,19 @@ static void *moh_alloc(struct ast_channel *chan, void *params)
{
struct mohdata *res;
struct mohclass *class = params;
+ struct moh_files_state *state;
+
+ /* Initiating music_state for current channel. Channel should know name of moh class */
+ if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
+ chan->music_state = state;
+ memset(state, 0, sizeof(*state));
+ state->class = class;
+ } else
+ state = chan->music_state;
+ if (state && state->class != class) {
+ memset(state, 0, sizeof(*state));
+ state->class = class;
+ }
if ((res = mohalloc(class))) {
res->origwfmt = chan->writeformat;
@@ -827,7 +851,7 @@ static int moh_scan_files(struct mohclass *class) {
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);
@@ -978,15 +1002,51 @@ static int moh_register(struct mohclass *moh, int reload)
static void local_ast_moh_cleanup(struct ast_channel *chan)
{
- if (chan->music_state) {
+ struct moh_files_state *state = chan->music_state;
+
+ if (state) {
+ if (state->class->realtime) {
+ if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
+ /* We are cleaning out cached RT class, we should remove it from list, if no one else using it */
+ if (!(state->class->inuse)) {
+ /* Remove this class from list */
+ AST_RWLIST_WRLOCK(&mohclasses);
+ AST_RWLIST_REMOVE(&mohclasses, state->class, list);
+ AST_RWLIST_UNLOCK(&mohclasses);
+
+ /* Free some memory */
+ ast_moh_destroy_one(state->class);
+ }
+ } else {
+ ast_moh_destroy_one(state->class);
+ }
+ }
ast_free(chan->music_state);
chan->music_state = NULL;
}
}
+static struct mohclass *moh_class_malloc(void)
+{
+ struct mohclass *class;
+
+ if ((class = ast_calloc(1, sizeof(*class)))) {
+ class->format = AST_FORMAT_SLINEAR;
+ class->realtime = 0;
+ }
+
+ return class;
+}
+
static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
{
struct mohclass *mohclass = NULL;
+ struct ast_variable *var = NULL;
+ struct ast_variable *tmp = NULL;
+ struct moh_files_state *state = chan->music_state;
+#ifdef HAVE_ZAPTEL
+ int x;
+#endif
/* The following is the order of preference for which class to use:
* 1) The channels explicitly set musicclass, which should *only* be
@@ -999,6 +1059,8 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
* option.
* 4) The default class.
*/
+
+ /* First, let's check in memory for static and cached RT classes */
AST_RWLIST_RDLOCK(&mohclasses);
if (!ast_strlen_zero(chan->musicclass))
mohclass = get_mohbyname(chan->musicclass, 1);
@@ -1006,11 +1068,161 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
mohclass = get_mohbyname(mclass, 1);
if (!mohclass && !ast_strlen_zero(interpclass))
mohclass = get_mohbyname(interpclass, 1);
- if (!mohclass)
+ AST_RWLIST_UNLOCK(&mohclasses);
+
+ /* If no moh class found in memory, then check RT */
+ if (!mohclass && ast_check_realtime("musiconhold")) {
+ if (!ast_strlen_zero(chan->musicclass)) {
+ var = ast_load_realtime("musiconhold", "name", chan->musicclass, NULL);
+ }
+ if (!var && !ast_strlen_zero(mclass))
+ var = ast_load_realtime("musiconhold", "name", mclass, NULL);
+ if (!var && !ast_strlen_zero(interpclass))
+ var = ast_load_realtime("musiconhold", "name", interpclass, NULL);
+ if (!var)
+ var = ast_load_realtime("musiconhold", "name", "default", NULL);
+ if (var && (mohclass = moh_class_malloc())) {
+ mohclass->realtime = 1;
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "name"))
+ ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
+ else if (!strcasecmp(tmp->name, "mode"))
+ ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode));
+ else if (!strcasecmp(tmp->name, "directory"))
+ ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
+ else if (!strcasecmp(tmp->name, "application"))
+ ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
+ else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
+ mohclass->digit = *tmp->value;
+ else if (!strcasecmp(tmp->name, "random"))
+ ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
+ else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
+ ast_set_flag(mohclass, MOH_RANDOMIZE);
+ else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha"))
+ ast_set_flag(mohclass, MOH_SORTALPHA);
+ else if (!strcasecmp(tmp->name, "format")) {
+ mohclass->format = ast_getformatbyname(tmp->value);
+ if (!mohclass->format) {
+ ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
+ mohclass->format = AST_FORMAT_SLINEAR;
+ }
+ }
+ }
+ if (ast_strlen_zero(mohclass->dir)) {
+ if (!strcasecmp(mohclass->mode, "custom")) {
+ strcpy(mohclass->dir, "nodir");
+ } else {
+ ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
+ ast_free(mohclass);
+ return -1;
+ }
+ }
+ if (ast_strlen_zero(mohclass->mode)) {
+ ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
+ ast_free(mohclass);
+ return -1;
+ }
+ if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
+ ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
+ ast_free(mohclass);
+ return -1;
+ }
+
+ if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
+ /* CACHERTCLASSES enabled, let's add this class to default tree */
+ if (state && state->class) {
+ /* Class already exist for this channel */
+ ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
+ if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
+ /* we found RT class with the same name, seems like we should continue playing existing one */
+ ast_moh_free_class(&mohclass);
+ mohclass = state->class;
+ }
+ }
+ moh_register(mohclass, 0);
+ } else {
+
+ /* We don't register RT moh class, so let's init it manualy */
+
+ time(&mohclass->start);
+ mohclass->start -= respawn_time;
+
+ if (!strcasecmp(mohclass->mode, "files")) {
+ if (!moh_scan_files(mohclass)) {
+ ast_moh_free_class(&mohclass);
+ return -1;
+ }
+ if (strchr(mohclass->args, 'r'))
+ ast_set_flag(mohclass, MOH_RANDOMIZE);
+ } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) {
+
+ if (!strcasecmp(mohclass->mode, "custom"))
+ ast_set_flag(mohclass, MOH_CUSTOM);
+ else if (!strcasecmp(mohclass->mode, "mp3nb"))
+ ast_set_flag(mohclass, MOH_SINGLE);
+ else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
+ ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
+ else if (!strcasecmp(mohclass->mode, "quietmp3"))
+ ast_set_flag(mohclass, MOH_QUIET);
+
+ mohclass->srcfd = -1;
+#ifdef HAVE_ZAPTEL
+ /* Open /dev/zap/pseudo for timing... Is
+ there a better, yet reliable way to do this? */
+ mohclass->pseudofd = open("/dev/zap/pseudo", O_RDONLY);
+ if (mohclass->pseudofd < 0) {
+ ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
+ } else {
+ x = 320;
+ ioctl(mohclass->pseudofd, ZT_SET_BLOCKSIZE, &x);
+ }
+#else
+ mohclass->pseudofd = -1;
+#endif
+ /* Let's check if this channel already had a moh class before */
+ if (state && state->class) {
+ /* Class already exist for this channel */
+ ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
+ if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
+ /* we found RT class with the same name, seems like we should continue playing existing one */
+ ast_moh_free_class(&mohclass);
+ mohclass = state->class;
+
+ }
+ } else {
+ if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
+ ast_log(LOG_WARNING, "Unable to create moh...\n");
+ if (mohclass->pseudofd > -1)
+ close(mohclass->pseudofd);
+ ast_moh_free_class(&mohclass);
+ return -1;
+ }
+ }
+ } else {
+ ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
+ ast_moh_free_class(&mohclass);
+ return -1;
+ }
+
+ }
+
+ }
+ }
+
+
+
+ /* Requested MOH class not found, check for 'default' class in musiconhold.conf */
+ if (!mohclass) {
+ AST_RWLIST_RDLOCK(&mohclasses);
mohclass = get_mohbyname("default", 1);
- if (mohclass)
+ if (mohclass)
+ ast_atomic_fetchadd_int(&mohclass->inuse, +1);
+ AST_RWLIST_UNLOCK(&mohclasses);
+ } else {
+ AST_RWLIST_RDLOCK(&mohclasses);
ast_atomic_fetchadd_int(&mohclass->inuse, +1);
- AST_RWLIST_UNLOCK(&mohclasses);
+ AST_RWLIST_UNLOCK(&mohclasses);
+ }
if (!mohclass)
return -1;
@@ -1024,10 +1236,11 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
static void local_ast_moh_stop(struct ast_channel *chan)
{
+ struct moh_files_state *state = chan->music_state;
ast_clear_flag(chan, AST_FLAG_MOH);
ast_deactivate_generator(chan);
- if (chan->music_state) {
+ if (state) {
if (chan->stream) {
ast_closestream(chan->stream);
chan->stream = NULL;
@@ -1035,16 +1248,6 @@ static void local_ast_moh_stop(struct ast_channel *chan)
}
}
-static struct mohclass *moh_class_malloc(void)
-{
- struct mohclass *class;
-
- if ((class = ast_calloc(1, sizeof(*class))))
- class->format = AST_FORMAT_SLINEAR;
-
- return class;
-}
-
static int load_moh_classes(int reload)
{
struct ast_config *cfg;
@@ -1066,11 +1269,25 @@ static int load_moh_classes(int reload)
}
AST_RWLIST_UNLOCK(&mohclasses);
}
+
+ ast_clear_flag(global_flags, AST_FLAGS_ALL);
cat = ast_category_browse(cfg, NULL);
for (; cat; cat = ast_category_browse(cfg, cat)) {
+ /* Setup common options from [general] section */
+ if (!strcasecmp(cat, "general")) {
+ var = ast_variable_browse(cfg, cat);
+ while (var) {
+ if (!strcasecmp(var->name, "cachertclasses")) {
+ ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
+ } else {
+ ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
+ }
+ var = var->next;
+ }
+ }
/* These names were deprecated in 1.4 and should not be used until after the next major release. */
- if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) {
+ if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files") && strcasecmp(cat, "general")) {
if (!(class = moh_class_malloc()))
break;