]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Adding support for realtime music on hold. The following are the main points:
authorMark Michelson <mmichelson@digium.com>
Wed, 28 Nov 2007 00:47:22 +0000 (00:47 +0000)
committerMark Michelson <mmichelson@digium.com>
Wed, 28 Nov 2007 00:47:22 +0000 (00:47 +0000)
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: https://origsvn.digium.com/svn/asterisk/trunk@89946 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
configs/extconfig.conf.sample
configs/musiconhold.conf.sample
res/res_musiconhold.c

diff --git a/CHANGES b/CHANGES
index f83cea2e9e86546d2a13112d113558d10da90962..00045eb65621d1eb83c66dd6593b0d42b7cd1499 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -234,6 +234,10 @@ Music On Hold Changes
      musiconhold.conf.  If this is set for a music on hold class, a caller
      listening to music on hold can press this digit to switch to listening
      to this music on hold class.
+  * Support for realtime music on hold has been added.
+  * In conjunction with the realtime music on hold, a general section has
+    been added to musiconhold.conf, its sole variable is cachertclasses. If this
+       is set, then music on hold classes found in realtime will be cached in memory.
 
 AEL Changes
 -----------
index 82f33f83dd53c879954fe3651e6c1fcd1d619574..3da9251bcc29904e1b78bd6329968e0a020f7d9d 100644 (file)
@@ -58,4 +58,5 @@
 ;extensions => odbc,asterisk
 ;queues => odbc,asterisk
 ;queue_members => odbc,asterisk
+;musiconhold => mysql,asterisk
 
index dd80f58ca02b041d6b2fd4890ad0e914e09f4b9e..4df1afd4f8dae9db2b9c8524a6d1e5c06cf400bc 100644 (file)
@@ -1,6 +1,11 @@
 ;
 ; Music on Hold -- Sample Configuration
 ;
+[general]
+;cachertclasses=yes ; use 1 instance of moh class for all users who are using it,
+                    ; decrease consumable cpu cycles and memory
+                    ; disabled by default
+
 
 ; valid mode options:
 ; files                -- read files from a directory in any Asterisk supported 
index 4270f5708f3104100ce0fb6e9b982b05b3b29595..027aed023688a17ff430a1d8bd3c92d05c16ba84 100644 (file)
@@ -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;