res_pjsip res_pjsip_mwi: Misc fixes and cleanups.
[asterisk/asterisk.git] / res / res_musiconhold.c
index 82e0534..3c7199e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2010, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
 /*! \file
  *
  * \brief Routines implementing music on hold
- *
- * \arg See also \ref Config_moh
  * 
  * \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
+ */
+
 /*** MODULEINFO
        <conflict>win32</conflict>
-       <use>dahdi</use>
+       <support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+ASTERISK_REGISTER_FILE()
 
 #include <ctype.h>
 #include <signal.h>
 #include <sys/time.h>
-#include <sys/signal.h>
+#include <signal.h>
 #include <netinet/in.h>
 #include <sys/stat.h>
 #include <dirent.h>
-#include <sys/ioctl.h>
 
 #ifdef SOLARIS
 #include <thread.h>
 #endif
 
-#ifdef HAVE_DAHDI
-#include <dahdi/user.h>
-#endif
-
 #include "asterisk/lock.h"
 #include "asterisk/file.h"
 #include "asterisk/channel.h"
@@ -65,9 +67,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/cli.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/linkedlists.h"
-#include "asterisk/manager.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
 #include "asterisk/paths.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/timing.h"
+#include "asterisk/time.h"
+#include "asterisk/poll-compat.h"
 
 #define INITIAL_NUM_FILES   8
 #define HANDLE_REF     1
@@ -88,36 +94,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        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>
-               </description>
-       </application>
-       <application name="WaitMusicOnHold" language="en_US">
-               <synopsis>
-                       Wait, playing Music On Hold.
-               </synopsis>
-               <syntax>
-                       <parameter name="delay" required="true" />
-               </syntax>
-               <description>
-                       <para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
-                       <para>Plays hold music specified number of seconds. Returns <literal>0</literal> when done,
-                       or <literal>-1</literal> on hangup. If no hold music is available, the delay will still occur
-                       with no sound.</para>
-                       <para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
-               </description>
-       </application>
-       <application name="SetMusicOnHold" language="en_US">
-               <synopsis>
-                       Set default Music On Hold class.
-               </synopsis>
-               <syntax>
-                       <parameter name="class" required="yes" />
-               </syntax>
-               <description>
-                       <para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
-                       <para>Sets the default class for music on hold for a given channel.
-                       When music on hold is activated, this class will be used to select which
-                       music is played.</para>
-                       <para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
+                       <para>This application does not automatically answer and should be preceeded by
+                       an application such as Answer() or Progress().</para>
                </description>
        </application>
        <application name="StartMusicOnHold" language="en_US">
@@ -145,23 +123,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  ***/
 
 static const char play_moh[] = "MusicOnHold";
-static const char wait_moh[] = "WaitMusicOnHold";
-static const char set_moh[] = "SetMusicOnHold";
 static const char start_moh[] = "StartMusicOnHold";
 static const char stop_moh[] = "StopMusicOnHold";
 
 static int respawn_time = 20;
 
 struct moh_files_state {
+       /*! Holds a reference to the MOH class. */
        struct mohclass *class;
-       char name[MAX_MUSICCLASS];
-       format_t origwfmt;
+       struct ast_format *origwfmt;
+       struct ast_format *mohwfmt;
+       int announcement;
        int samples;
        int sample_queue;
        int pos;
        int save_pos;
        int save_total;
-       char *save_pos_filename;
+       char name[MAX_MUSICCLASS];
+       char save_pos_filename[PATH_MAX];
 };
 
 #define MOH_QUIET              (1 << 0)
@@ -169,8 +148,12 @@ struct moh_files_state {
 #define MOH_CUSTOM             (1 << 2)
 #define MOH_RANDOMIZE          (1 << 3)
 #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_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 */
 
 /* Custom astobj2 flag */
 #define MOH_NOTDELETED          (1 << 30)       /*!< Find only records that aren't deleted? */
@@ -181,6 +164,7 @@ struct mohclass {
        char name[MAX_MUSICCLASS];
        char dir[256];
        char args[256];
+       char announcement[256];
        char mode[80];
        char digit;
        /*! A dynamically sized array to hold the list of filenames in "files" mode */
@@ -191,17 +175,17 @@ struct mohclass {
        int total_files;
        unsigned int flags;
        /*! The format from the MOH source, not applicable to "files" mode */
-       format_t format;
+       struct ast_format *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;
+       /*! Generic timer */
+       struct ast_timer *timer;
        /*! Created on the fly, from RT engine */
-       int realtime;
+       unsigned int realtime:1;
        unsigned int delete:1;
        AST_LIST_HEAD_NOLOCK(, mohdata) members;
        AST_LIST_ENTRY(mohclass) list;
@@ -209,7 +193,7 @@ struct mohclass {
 
 struct mohdata {
        int pipe[2];
-       format_t origwfmt;
+       struct ast_format *origwfmt;
        struct mohclass *parent;
        struct ast_frame f;
        AST_LIST_ENTRY(mohdata) list;
@@ -221,73 +205,126 @@ static struct ao2_container *mohclasses;
 #define MPG_123 "/usr/bin/mpg123"
 #define MAX_MP3S 256
 
+static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclass);
 static int reload(void);
 
 #define mohclass_ref(class,string)   (ao2_t_ref((class), +1, (string)), class)
 
-#ifndef REF_DEBUG
-#define mohclass_unref(class,string) (ao2_t_ref((class), -1, (string)), (struct mohclass *) NULL)
+#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)
 {
-       struct mohclass *dup;
-       if ((dup = ao2_find(mohclasses, class, OBJ_POINTER))) {
-               if (_ao2_ref_debug(dup, -1, (char *) tag, (char *) file, line, funcname) == 2) {
-                       FILE *ref = fopen("/tmp/refs", "a");
-                       if (ref) {
-                               fprintf(ref, "%p =1   %s:%d:%s (%s) BAD ATTEMPT!\n", class, file, line, funcname, tag);
-                               fclose(ref);
-                       }
+       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_t_ref(class, -1, (char *) tag);
+               __ao2_ref(class, -1, tag, file, line, funcname);
        }
        return NULL;
 }
 #endif
 
+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);
+
+               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);
+
+               stasis_publish(ast_channel_topic(chan), message);
+       }
+       ao2_cleanup(message);
+}
+
 static void moh_files_release(struct ast_channel *chan, void *data)
 {
        struct moh_files_state *state;
 
-       if (!chan || !chan->music_state) {
+       if (!chan || !ast_channel_music_state(chan)) {
                return;
        }
 
-       state = chan->music_state;
+       state = ast_channel_music_state(chan);
 
-       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 (ast_channel_stream(chan)) {
+               ast_closestream(ast_channel_stream(chan));
+               ast_channel_stream_set(chan, NULL);
        }
 
+       moh_post_stop(chan);
+
+       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", chan->name, ast_getformatname(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));
        }
+       ao2_cleanup(state->origwfmt);
+       state->origwfmt = NULL;
 
        state->save_pos = state->pos;
+       state->announcement = 0;
 
        state->class = mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
 }
 
 static int ast_moh_files_next(struct ast_channel *chan) 
 {
-       struct moh_files_state *state = chan->music_state;
+       struct moh_files_state *state = ast_channel_music_state(chan);
        int tries;
 
        /* Discontinue a stream if it is running already */
-       if (chan->stream) {
-               ast_closestream(chan->stream);
-               chan->stream = NULL;
+       if (ast_channel_stream(chan)) {
+               ast_closestream(ast_channel_stream(chan));
+               ast_channel_stream_set(chan, NULL);
+       }
+
+       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)) {
+                       ast_debug(1, "%s Opened announcement '%s'\n", ast_channel_name(chan), state->class->announcement);
+                       return 0;
+               }
+       } else {
+               state->announcement = 0;
        }
 
        if (!state->class->total_files) {
@@ -295,16 +332,20 @@ static int ast_moh_files_next(struct ast_channel *chan)
                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) {
+       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 < state->class->total_files && !strcmp(state->class->filearray[state->save_pos], state->save_pos_filename)) {
+               /* If a specific file has been saved confirm it still exists and that it is still valid */
                state->pos = state->save_pos;
                state->save_pos = -1;
-       } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
+       } 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() % state->class->total_files;
-                       if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
+                       if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0) {
                                break;
+                       }
                }
                state->save_pos = -1;
                state->samples = 0;
@@ -316,56 +357,106 @@ static int ast_moh_files_next(struct ast_channel *chan)
                state->samples = 0;
        }
 
-       if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
+       for (tries = 0; tries < state->class->total_files; ++tries) {
+               if (ast_openstream_full(chan, state->class->filearray[state->pos], ast_channel_language(chan), 1)) {
+                       break;
+               }
+
                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;
+       }
+
+       if (tries == 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];
-
-       ast_debug(1, "%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);
+       ast_copy_string(state->save_pos_filename, state->class->filearray[state->pos], sizeof(state->save_pos_filename));
+
+       ast_debug(1, "%s Opened file %d '%s'\n", ast_channel_name(chan), state->pos, state->class->filearray[state->pos]);
+
+       if (state->samples) {
+               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);
+               }
+       }
 
        return 0;
 }
 
-static struct ast_frame *moh_files_readframe(struct ast_channel *chan) 
+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_channel_stream(chan) && (f = ast_readframe(ast_channel_stream(chan))))) {
                if (!ast_moh_files_next(chan))
-                       f = ast_readframe(chan->stream);
+                       f = ast_readframe(ast_channel_stream(chan));
        }
 
        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;
+       }
+}
+
 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
 {
-       struct moh_files_state *state = chan->music_state;
+       struct moh_files_state *state = ast_channel_music_state(chan);
        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;      
+               ast_channel_lock(chan);
+               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;
+               }
+
+               state->samples += f->samples;
+               state->sample_queue -= 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;
+               }
        }
        return res;
 }
@@ -375,23 +466,25 @@ 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 = 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);
        } else {
-               state = chan->music_state;
-       }
-
-       if (!state) {
-               return NULL;
+               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");
+               }
        }
 
-       /* LOGIC: Comparing an unrefcounted pointer is a really bad idea, because
-        * malloc may allocate a different class to the same memory block.  This
-        * might only happen when two reloads are generated in a short period of
-        * time, but it's still important to protect against.
-        * PROG: Compare the quick operation first, to save CPU. */
+       /* Resume MOH from where we left off last time or start from scratch? */
        if (state->save_total != class->total_files || strcmp(state->name, class->name) != 0) {
+               /* Start MOH from scratch. */
+               ao2_cleanup(state->origwfmt);
+               ao2_cleanup(state->mohwfmt);
                memset(state, 0, sizeof(*state));
                if (ast_test_flag(class, MOH_RANDOMIZE) && class->total_files) {
                        state->pos = ast_random() % class->total_files;
@@ -399,14 +492,16 @@ static void *moh_files_alloc(struct ast_channel *chan, void *params)
        }
 
        state->class = mohclass_ref(class, "Reffing music class for channel");
-       state->origwfmt = chan->writeformat;
+       /* 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 = class->total_files;
 
-       ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
-       
-       return chan->music_state;
+       moh_post_start(chan, class->name);
+
+       return state;
 }
 
 static int moh_digit_match(void *obj, void *arg, int flags)
@@ -431,18 +526,18 @@ static void moh_handle_digit(struct ast_channel *chan, char digit)
        if ((class = get_mohbydigit(digit))) {
                classname = ast_strdupa(class->name);
                class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
-               ast_string_field_set(chan,musicclass,classname);
+               ast_channel_musicclass_set(chan, classname);
                ast_moh_stop(chan);
                ast_moh_start(chan, classname, NULL);
        }
 }
 
-static struct ast_generator moh_file_stream = 
-{
+static struct ast_generator moh_file_stream = {
        .alloc    = moh_files_alloc,
        .release  = moh_files_release,
        .generate = moh_files_generator,
        .digit    = moh_handle_digit,
+       .write_format_change = moh_files_write_format_change,
 };
 
 static int spawn_mp3(struct mohclass *class)
@@ -560,7 +655,7 @@ static int spawn_mp3(struct mohclass *class)
                ast_close_fds_above_n(STDERR_FILENO);
 
                /* Child */
-               if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
+               if (strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
                        ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
                        _exit(1);
                }
@@ -592,9 +687,8 @@ static void *monmp3thread(void *data)
 
        struct mohclass *class = data;
        struct mohdata *moh;
-       char buf[8192];
        short sbuf[8192];
-       int res, res2;
+       int res = 0, res2;
        int len;
        struct timeval deadline, tv_tmp;
 
@@ -608,15 +702,27 @@ static void *monmp3thread(void *data)
                                ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
                                /* Try again later */
                                sleep(500);
-                               pthread_testcancel();
+                               continue;
                        }
                }
-               if (class->pseudofd > -1) {
+               if (class->timer) {
+                       struct pollfd pfd = { .fd = ast_timer_fd(class->timer), .events = POLLIN | POLLPRI, };
+
 #ifdef SOLARIS
                        thr_yield();
 #endif
                        /* Pause some amount of time */
-                       res = read(class->pseudofd, buf, sizeof(buf));
+                       if (ast_poll(&pfd, 1, -1) > 0) {
+                               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 */
+                       } else {
+                               ast_log(LOG_WARNING, "poll() failed: %s\n", strerror(errno));
+                               res = 0;
+                       }
                        pthread_testcancel();
                } else {
                        long delta;
@@ -633,12 +739,16 @@ static void *monmp3thread(void *data)
                                ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
                                deadline = tv_tmp;
                        }
-                       res = 8 * MOH_MS_INTERVAL;      /* 8 samples per millisecond */
+                       /* 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))
                        continue;
                /* Read mp3 audio */
-               len = ast_codec_get_len(class->format, res);
+               len = ast_format_determine_length(class->format, res);
 
                if ((res2 = read(class->srcfd, sbuf, len)) != len) {
                        if (!res2) {
@@ -715,7 +825,7 @@ static int play_moh_exec(struct ast_channel *chan, const char *data)
 
        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, chan->name);
+               ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, ast_channel_name(chan));
                return 0;
        }
 
@@ -730,46 +840,6 @@ static int play_moh_exec(struct ast_channel *chan, const char *data)
        return res;
 }
 
-static int wait_moh_exec(struct ast_channel *chan, const char *data)
-{
-       static int deprecation_warning = 0;
-       int res;
-
-       if (!deprecation_warning) {
-               deprecation_warning = 1;
-               ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
-       }
-
-       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 set_moh_exec(struct ast_channel *chan, const char *data)
-{
-       static int deprecation_warning = 0;
-
-       if (!deprecation_warning) {
-               deprecation_warning = 1;
-               ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
-       }
-
-       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 start_moh_exec(struct ast_channel *chan, const char *data)
 {
        char *parse;
@@ -784,7 +854,7 @@ static int start_moh_exec(struct ast_channel *chan, const char *data)
 
        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, chan->name);
+               ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, ast_channel_name(chan));
 
        return 0;
 }
@@ -807,14 +877,11 @@ static struct mohclass *_get_mohbyname(const char *name, int warn, int flags, co
 
        ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
 
-#ifdef REF_DEBUG
-       moh = __ao2_find_debug(mohclasses, &tmp_class, flags, "get_mohbyname", (char *) file, lineno, funcname);
-#else
-       moh = __ao2_find(mohclasses, &tmp_class, flags);
-#endif
+       moh = __ao2_find(mohclasses, &tmp_class, flags,
+               "get_mohbyname", file, lineno, funcname);
 
        if (!moh && warn) {
-               ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
+               ast_log(LOG_WARNING, "Music on Hold class '%s' not found in memory. Verify your configuration.\n", name);
        }
 
        return moh;
@@ -823,11 +890,11 @@ static struct mohclass *_get_mohbyname(const char *name, int warn, int flags, co
 static struct mohdata *mohalloc(struct mohclass *cl)
 {
        struct mohdata *moh;
-       long flags;     
-       
+       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));
                ast_free(moh);
@@ -841,7 +908,7 @@ static struct mohdata *mohalloc(struct mohclass *cl)
        fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
 
        moh->f.frametype = AST_FRAME_VOICE;
-       moh->f.subclass.codec = cl->format;
+       moh->f.subclass.format = cl->format;
        moh->f.offset = AST_FRIENDLY_OFFSET;
 
        moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
@@ -857,7 +924,7 @@ static void moh_release(struct ast_channel *chan, void *data)
 {
        struct mohdata *moh = data;
        struct mohclass *class = moh->parent;
-       format_t oldwfmt;
+       struct ast_format *oldwfmt;
 
        ao2_lock(class);
        AST_LIST_REMOVE(&moh->parent->members, moh, list);      
@@ -873,13 +940,21 @@ static void moh_release(struct ast_channel *chan, void *data)
        ast_free(moh);
 
        if (chan) {
+               struct moh_files_state *state;
+
+               state = ast_channel_music_state(chan);
+               if (state && state->class) {
+                       state->class = mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
+               }
                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));
+                                       ast_channel_name(chan), ast_format_get_name(oldwfmt));
                }
 
-               ast_verb(3, "Stopped music on hold on %s\n", chan->name);
+               moh_post_stop(chan);
        }
+
+       ao2_cleanup(oldwfmt);
 }
 
 static void *moh_alloc(struct ast_channel *chan, void *params)
@@ -889,25 +964,34 @@ static void *moh_alloc(struct ast_channel *chan, void *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;
-               state->class = mohclass_ref(class, "Copying reference into state container");
+       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);
-       } else
-               state = chan->music_state;
-       if (state && state->class != class) {
+       } 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");
+               }
+               ao2_cleanup(state->origwfmt);
+               ao2_cleanup(state->mohwfmt);
                memset(state, 0, sizeof(*state));
-               state->class = class;
        }
 
        if ((res = mohalloc(class))) {
-               res->origwfmt = chan->writeformat;
+               res->origwfmt = ao2_bump(ast_channel_writeformat(chan));
                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));
+                       ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", ast_channel_name(chan),
+                               ast_format_get_name(class->format));
                        moh_release(NULL, res);
                        res = NULL;
+               } else {
+                       state->class = mohclass_ref(class, "Placing reference into state container");
+                       moh_post_start(chan, class->name);
                }
-               ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
        }
        return res;
 }
@@ -918,10 +1002,10 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl
        short buf[1280 + AST_FRIENDLY_OFFSET / 2];
        int res;
 
-       len = ast_codec_get_len(moh->parent->format, samples);
+       len = ast_format_determine_length(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);
+               ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, ast_channel_name(chan));
                len = sizeof(buf) - AST_FRIENDLY_OFFSET;
        }
        res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
@@ -930,10 +1014,10 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl
 
        moh->f.datalen = res;
        moh->f.data.ptr = buf + AST_FRIENDLY_OFFSET / 2;
-       moh->f.samples = ast_codec_get_samples(&moh->f);
+       moh->f.samples = ast_codec_samples_count(&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));
+               ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", ast_channel_name(chan), strerror(errno));
                return -1;
        }
 
@@ -947,23 +1031,67 @@ static struct ast_generator mohgen = {
        .digit    = moh_handle_digit,
 };
 
+static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclass)
+{
+       for (; var; var = var->next) {
+               if (!strcasecmp(var->name, "name")) {
+                       ast_copy_string(mohclass->name, var->value, sizeof(mohclass->name));
+               } else if (!strcasecmp(var->name, "mode")) {
+                       ast_copy_string(mohclass->mode, var->value, sizeof(mohclass->mode));
+               } else if (!strcasecmp(var->name, "directory")) {
+                       ast_copy_string(mohclass->dir, var->value, sizeof(mohclass->dir));
+               } else if (!strcasecmp(var->name, "application")) {
+                       ast_copy_string(mohclass->args, var->value, sizeof(mohclass->args));
+               } else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value))) {
+                       mohclass->digit = *var->value;
+               } else if (!strcasecmp(var->name, "random")) {
+                       static int deprecation_warning = 0;
+                       if (!deprecation_warning) {
+                               ast_log(LOG_WARNING, "Music on hold 'random' setting is deprecated in 14.  Please use 'sort=random' instead.\n");
+                               deprecation_warning = 1;
+                       }
+                       ast_set2_flag(mohclass, ast_true(var->value), MOH_RANDOMIZE);
+               } else if (!strcasecmp(var->name, "sort")) {
+                       if (!strcasecmp(var->value, "random")) {
+                               ast_set_flag(mohclass, MOH_RANDOMIZE);
+                       } else if (!strcasecmp(var->value, "alpha")) {
+                               ast_set_flag(mohclass, MOH_SORTALPHA);
+                       } else if (!strcasecmp(var->value, "randstart")) {
+                               ast_set_flag(mohclass, MOH_RANDSTART);
+                       }
+               } else if (!strcasecmp(var->name, "format")) {
+                       mohclass->format = ast_format_cache_get(var->value);
+                       if (!mohclass->format) {
+                               ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
+                               mohclass->format = ao2_bump(ast_format_slin);
+                       }
+               }
+       }
+}
+
 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))))
+               class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray));
+               if (!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;
+               char **new_array;
+
+               new_array = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2);
+               if (!new_array) {
                        return -1;
                }
+               class->filearray = new_array;
                class->allowed_files *= 2;
        }
 
-       if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
+       class->filearray[class->total_files] = ast_strdup(filepath);
+       if (!class->filearray[class->total_files]) {
                return -1;
+       }
 
        class->total_files++;
 
@@ -989,11 +1117,10 @@ static int moh_scan_files(struct mohclass *class) {
        char filepath[PATH_MAX];
        char *ext;
        struct stat statbuf;
-       int dirnamelen;
        int i;
 
        if (class->dir[0] != '/') {
-               ast_copy_string(dir_path, ast_config_AST_VAR_DIR, sizeof(dir_path));
+               ast_copy_string(dir_path, ast_config_AST_DATA_DIR, sizeof(dir_path));
                strncat(dir_path, "/", sizeof(dir_path) - 1);
                strncat(dir_path, class->dir, sizeof(dir_path) - 1);
        } else {
@@ -1006,17 +1133,19 @@ static int moh_scan_files(struct mohclass *class) {
                return -1;
        }
 
-       for (i = 0; i < class->total_files; i++)
+       for (i = 0; i < class->total_files; i++) {
                ast_free(class->filearray[i]);
-
+       }
        class->total_files = 0;
-       dirnamelen = strlen(dir_path) + 2;
+
        if (!getcwd(path, sizeof(path))) {
                ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
+               closedir(files_DIR);
                return -1;
        }
        if (chdir(dir_path) < 0) {
                ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
+               closedir(files_DIR);
                return -1;
        }
        while ((files_dirent = readdir(files_DIR))) {
@@ -1075,20 +1204,29 @@ static int init_files_class(struct mohclass *class)
        }
 
        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);
-               }
+               ast_verb(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 void moh_rescan_files(void) {
+       struct ao2_iterator i;
+       struct mohclass *c;
+
+       i = ao2_iterator_init(mohclasses, 0);
+
+       while ((c = ao2_iterator_next(&i))) {
+               if (!strcasecmp(c->mode, "files")) {
+                       moh_scan_files(c);
+               }
+               ao2_ref(c, -1);
+       }
+
+       ao2_iterator_destroy(&i);
+}
 
 static int moh_diff(struct mohclass *old, struct mohclass *new)
 {
@@ -1111,10 +1249,6 @@ static int moh_diff(struct mohclass *old, struct mohclass *new)
 
 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")) {
@@ -1124,27 +1258,24 @@ static int init_app_class(struct mohclass *class)
        } 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("/dev/dahdi/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);
+
+       if (!(class->timer = ast_timer_open())) {
+               ast_log(LOG_WARNING, "Unable to create timer: %s\n", strerror(errno));
+               return -1;
+       }
+       if (class->timer && ast_timer_set_rate(class->timer, 25)) {
+               ast_log(LOG_WARNING, "Unable to set 40ms frame rate: %s\n", strerror(errno));
+               ast_timer_close(class->timer);
+               class->timer = NULL;
        }
-#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;
+               if (class->timer) {
+                       ast_timer_close(class->timer);
+                       class->timer = NULL;
                }
                return -1;
        }
@@ -1155,12 +1286,14 @@ static int init_app_class(struct mohclass *class)
 /*!
  * \note This function owns the reference it gets to moh if unref is true
  */
-#define moh_register(a,b,c)    _moh_register(a,b,c,__FILE__,__LINE__,__PRETTY_FUNCTION__)
+#define moh_register(moh, reload, unref) _moh_register(moh, reload, unref, __FILE__, __LINE__, __PRETTY_FUNCTION__)
 static int _moh_register(struct mohclass *moh, int reload, int unref, const char *file, int line, const char *funcname)
 {
        struct mohclass *mohclass = NULL;
 
-       if ((mohclass = _get_mohbyname(moh->name, 0, MOH_NOTDELETED, file, line, funcname)) && !moh_diff(mohclass, moh)) {
+       mohclass = _get_mohbyname(moh->name, 0, MOH_NOTDELETED, file, line, funcname);
+
+       if (mohclass && !moh_diff(mohclass, moh)) {
                ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
                mohclass = mohclass_unref(mohclass, "unreffing mohclass we just found by name");
                if (unref) {
@@ -1174,7 +1307,7 @@ static int _moh_register(struct mohclass *moh, int reload, int unref, const char
 
        time(&moh->start);
        moh->start -= respawn_time;
-       
+
        if (!strcasecmp(moh->mode, "files")) {
                if (init_files_class(moh)) {
                        if (unref) {
@@ -1204,20 +1337,25 @@ static int _moh_register(struct mohclass *moh, int reload, int unref, const char
        if (unref) {
                moh = mohclass_unref(moh, "Unreffing new moh class because we just added it to the container");
        }
-       
+
        return 0;
 }
 
 static void local_ast_moh_cleanup(struct ast_channel *chan)
 {
-       struct moh_files_state *state = chan->music_state;
+       struct moh_files_state *state = ast_channel_music_state(chan);
 
        if (state) {
+               ast_channel_music_state_set(chan, NULL);
                if (state->class) {
-                       state->class = mohclass_unref(state->class, "Channel MOH state destruction");
+                       /* This should never happen.  We likely just leaked some resource. */
+                       state->class =
+                               mohclass_unref(state->class, "Uh Oh. Cleaning up MOH with an active class");
+                       ast_log(LOG_WARNING, "Uh Oh. Cleaning up MOH with an active class\n");
                }
-               ast_free(chan->music_state);
-               chan->music_state = NULL;
+               ao2_cleanup(state->origwfmt);
+               ao2_cleanup(state->mohwfmt);
+               ast_free(state);
                /* Only held a module reference if we had a music state */
                ast_module_unref(ast_module_info->self);
        }
@@ -1231,63 +1369,71 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char
 {
        struct mohclass *class;
 
-       if ((class =
-#ifdef REF_DEBUG
-                       __ao2_alloc_debug(sizeof(*class), moh_class_destructor, "Allocating new moh class", file, line, funcname, 1)
-#elif defined(__AST_DEBUG_MALLOC)
-                       __ao2_alloc_debug(sizeof(*class), moh_class_destructor, "Allocating new moh class", file, line, funcname, 0)
-#else
-                       ao2_alloc(sizeof(*class), moh_class_destructor)
-#endif
-               )) {
-               class->format = AST_FORMAT_SLINEAR;
+       class = __ao2_alloc(sizeof(*class), moh_class_destructor, AO2_ALLOC_OPT_LOCK_MUTEX,
+               "Allocating new moh class", file, line, funcname);
+       if (class) {
+               class->format = ao2_bump(ast_format_slin);
+               class->srcfd = -1;
        }
 
        return class;
 }
 
+static struct ast_variable *load_realtime_musiconhold(const char *name)
+{
+       struct ast_variable *var = ast_load_realtime("musiconhold", "name", name, SENTINEL);
+       if (!var) {
+               ast_log(LOG_WARNING,
+                       "Music on Hold class '%s' not found in memory/database. "
+                       "Verify your configuration.\n",
+                       name);
+       }
+       return var;
+}
+
 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
 {
        struct mohclass *mohclass = NULL;
-       struct moh_files_state *state = chan->music_state;
+       struct moh_files_state *state = ast_channel_music_state(chan);
        struct ast_variable *var = NULL;
-       int res;
+       int res = 0;
+       int i;
        int realtime_possible = ast_check_realtime("musiconhold");
+       int warn_if_not_in_memory = !realtime_possible;
+       const char *classes[] = {NULL, NULL, interpclass, "default"};
+
+       if (ast_test_flag(global_flags, MOH_PREFERCHANNELCLASS)) {
+               classes[0] = ast_channel_musicclass(chan);
+               classes[1] = mclass;
+       } else {
+               classes[0] = mclass;
+               classes[1] = ast_channel_musicclass(chan);
+       }
 
        /* 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.
+        *    Unless preferchannelclass in musiconhold.conf is false
         * 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
+        * 3) The channels explicitly set musicclass, which should *only* be
+        *    set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
+        * 4) 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.
+        * 5) The default class.
         */
-       if (!ast_strlen_zero(chan->musicclass)) {
-               mohclass = get_mohbyname(chan->musicclass, 1, 0);
-               if (!mohclass && realtime_possible) {
-                       var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
-               }
-       }
-       if (!mohclass && !var && !ast_strlen_zero(mclass)) {
-               mohclass = get_mohbyname(mclass, 1, 0);
-               if (!mohclass && realtime_possible) {
-                       var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL);
-               }
-       }
-       if (!mohclass && !var && !ast_strlen_zero(interpclass)) {
-               mohclass = get_mohbyname(interpclass, 1, 0);
-               if (!mohclass && realtime_possible) {
-                       var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL);
-               }
-       }
 
-       if (!mohclass && !var) {
-               mohclass = get_mohbyname("default", 1, 0);
-               if (!mohclass && realtime_possible) {
-                       var = ast_load_realtime("musiconhold", "name", "default", SENTINEL);
+       for (i = 0; i < ARRAY_LEN(classes); ++i) {
+               if (!ast_strlen_zero(classes[i])) {
+                       mohclass = get_mohbyname(classes[i], warn_if_not_in_memory, 0);
+                       if (!mohclass && realtime_possible) {
+                               var = load_realtime_musiconhold(classes[i]);
+                       }
+                       if (mohclass || var) {
+                               break;
+                       }
                }
        }
 
@@ -1295,36 +1441,12 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
         * above guarantees that if var is non-NULL, then mohclass must be NULL.
         */
        if (var) {
-               struct ast_variable *tmp = NULL;
-
                if ((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;
-                                       }
-                               }
-                       }
+
+                       moh_parse_options(var, mohclass);
                        ast_variables_destroy(var);
+
                        if (ast_strlen_zero(mohclass->dir)) {
                                if (!strcasecmp(mohclass->mode, "custom")) {
                                        strcpy(mohclass->dir, "nodir");
@@ -1350,12 +1472,6 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                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 */
-                                               /* XXX This code is impossible to reach */
-                                               mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has a class)");
-                                               mohclass = state->class;
-                                       }
                                }
                                /* We don't want moh_register to unref the mohclass because we do it at the end of this function as well.
                                 * If we allowed moh_register to unref the mohclass,too, then the count would be off by one. The result would
@@ -1363,20 +1479,29 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                 * has a pointer to a freed mohclass, so any operations involving the mohclass container would result in reading
                                 * invalid memory.
                                 */
-                               moh_register(mohclass, 0, DONT_UNREF);
+                               if (moh_register(mohclass, 0, DONT_UNREF) == -1) {
+                                       mohclass = mohclass_unref(mohclass, "unreffing mohclass failed to register");
+                                       return -1;
+                               }
                        } 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)) {
                                                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
                                                return -1;
                                        }
-                                       if (strchr(mohclass->args, 'r'))
+                                       if (strchr(mohclass->args, 'r')) {
+                                               static int deprecation_warning = 0;
+                                               if (!deprecation_warning) {
+                                                       ast_log(LOG_WARNING, "Music on hold 'application=r' setting is deprecated in 14.  Please use 'sort=random' instead.\n");
+                                                       deprecation_warning = 1;
+                                               }
                                                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"))
@@ -1387,21 +1512,17 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                                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_DAHDI
-                                       /* Open /dev/dahdi/pseudo for timing...  Is
-                                          there a better, yet reliable way to do this? */
-                                       mohclass->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
-                                       if (mohclass->pseudofd < 0) {
-                                               ast_log(LOG_WARNING, "Unable to open pseudo channel for timing...  Sound may be choppy.\n");
-                                       } else {
-                                               int x = 320;
-                                               ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
+                                       if (!(mohclass->timer = ast_timer_open())) {
+                                               ast_log(LOG_WARNING, "Unable to create timer: %s\n", strerror(errno));
                                        }
-#else
-                                       mohclass->pseudofd = -1;
-#endif
+                                       if (mohclass->timer && ast_timer_set_rate(mohclass->timer, 25)) {
+                                               ast_log(LOG_WARNING, "Unable to set 40ms frame rate: %s\n", strerror(errno));
+                                               ast_timer_close(mohclass->timer);
+                                               mohclass->timer = NULL;
+                                       }
+
                                        /* Let's check if this channel already had a moh class before */
                                        if (state && state->class) {
                                                /* Class already exist for this channel */
@@ -1409,14 +1530,14 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                                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 */
                                                        mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has one)");
-                                                       mohclass = state->class;
+                                                       mohclass = mohclass_ref(state->class, "using existing class from state");
                                                }
                                        } 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);
-                                                               mohclass->pseudofd = -1;
+                                                       if (mohclass->timer) {
+                                                               ast_timer_close(mohclass->timer);
+                                                               mohclass->timer = NULL;
                                                        }
                                                        mohclass = mohclass_unref(mohclass, "Unreffing potential mohclass (failed to create background thread)");
                                                        return -1;
@@ -1430,6 +1551,7 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                        }
                } else {
                        ast_variables_destroy(var);
+                       var = NULL;
                }
        }
 
@@ -1437,20 +1559,24 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                return -1;
        }
 
-       ast_manager_event(chan, EVENT_FLAG_CALL, "MusicOnHold",
-               "State: Start\r\n"
-               "Channel: %s\r\n"
-               "UniqueID: %s\r\n"
-               "Class: %s\r\n",
-               chan->name, chan->uniqueid,
-               mohclass->name);
-
-       ast_set_flag(chan, AST_FLAG_MOH);
+       /* If we are using a cached realtime class with files, re-scan the files */
+       if (!var && ast_test_flag(global_flags, MOH_CACHERTCLASSES) && mohclass->realtime && !strcasecmp(mohclass->mode, "files")) {
+               if (!moh_scan_files(mohclass)) {
+                       mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
+                       return -1;
+               }
+       }
 
-       if (mohclass->total_files) {
-               res = ast_activate_generator(chan, &moh_file_stream, mohclass);
-       } else {
-               res = ast_activate_generator(chan, &mohgen, mohclass);
+       if (!state || !state->class || strcmp(mohclass->name, state->class->name)) {
+               if (mohclass->total_files) {
+                       res = ast_activate_generator(chan, &moh_file_stream, mohclass);
+               } else {
+                       res = ast_activate_generator(chan, &mohgen, mohclass);
+               }
+       }
+       if (!res) {
+               ast_channel_latest_musicclass_set(chan, mohclass->name);
+               ast_set_flag(ast_channel_flags(chan), AST_FLAG_MOH);
        }
 
        mohclass = mohclass_unref(mohclass, "unreffing local reference to mohclass in local_ast_moh_start");
@@ -1460,22 +1586,17 @@ 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_clear_flag(ast_channel_flags(chan), AST_FLAG_MOH);
        ast_deactivate_generator(chan);
 
-       if (state) {
-               if (chan->stream) {
-                       ast_closestream(chan->stream);
-                       chan->stream = NULL;
+       ast_channel_lock(chan);
+       if (ast_channel_music_state(chan)) {
+               if (ast_channel_stream(chan)) {
+                       ast_closestream(ast_channel_stream(chan));
+                       ast_channel_stream_set(chan, NULL);
                }
        }
-
-       ast_manager_event(chan, EVENT_FLAG_CALL, "MusicOnHold",
-               "State: Stop\r\n"
-               "Channel: %s\r\n"
-               "UniqueID: %s\r\n",
-               chan->name, chan->uniqueid);
+       ast_channel_unlock(chan);
 }
 
 static void moh_class_destructor(void *obj)
@@ -1486,6 +1607,12 @@ static void moh_class_destructor(void *obj)
 
        ast_debug(1, "Destroying MOH class '%s'\n", class->name);
 
+       ao2_lock(class);
+       while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
+               ast_free(member);
+       }
+       ao2_unlock(class);
+
        /* Kill the thread first, so it cannot restart the child process while the
         * class is being destroyed */
        if (class->thread != AST_PTHREADT_NULL && class->thread != 0) {
@@ -1500,7 +1627,7 @@ static void moh_class_destructor(void *obj)
                char buff[8192];
                int bytes, tbytes = 0, stime = 0, pid = 0;
 
-               ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
+               ast_debug(1, "killing %d!\n", class->pid);
 
                stime = time(NULL) + 2;
                pid = class->pid;
@@ -1534,28 +1661,33 @@ static void moh_class_destructor(void *obj)
                        tbytes = tbytes + bytes;
                }
 
-               ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
+               ast_debug(1, "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);
+               class->srcfd = -1;
        }
 
        if (class->filearray) {
                int i;
                for (i = 0; i < class->total_files; i++) {
-                       free(class->filearray[i]);
+                       ast_free(class->filearray[i]);
                }
-               free(class->filearray);
+               ast_free(class->filearray);
                class->filearray = NULL;
        }
 
+       if (class->timer) {
+               ast_timer_close(class->timer);
+               class->timer = NULL;
+       }
+
+       ao2_cleanup(class->format);
+
        /* Finally, collect the exit status of the monitor thread */
        if (tid > 0) {
                pthread_join(tid, NULL);
        }
+
 }
 
 static int moh_class_mark(void *obj, void *arg, int flags)
@@ -1585,15 +1717,24 @@ static int load_moh_classes(int reload)
 
        cfg = ast_config_load("musiconhold.conf", config_flags);
 
-       if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
+       if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
+               if (ast_check_realtime("musiconhold") && reload) {
+                       ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
+                       ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, moh_classes_delete_marked, NULL, "Purge marked classes");
+               }
+               return 0;
+       }
+       if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+               moh_rescan_files();
                return 0;
        }
 
        if (reload) {
                ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
        }
-       
+
        ast_clear_flag(global_flags, AST_FLAGS_ALL);
+       ast_set2_flag(global_flags, 1, MOH_PREFERCHANNELCLASS);
 
        cat = ast_category_browse(cfg, NULL);
        for (; cat; cat = ast_category_browse(cfg, cat)) {
@@ -1602,45 +1743,22 @@ static int load_moh_classes(int reload)
                        for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
                                if (!strcasecmp(var->name, "cachertclasses")) {
                                        ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
+                               } else if (!strcasecmp(var->name, "preferchannelclass")) {
+                                       ast_set2_flag(global_flags, ast_true(var->value), MOH_PREFERCHANNELCLASS);
                                } else {
                                        ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
                                }
                        }
                }
-               /* 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") || 
-                               !strcasecmp(cat, "general")) {
-                       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, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
-                               class->digit = *var->value;
-                       else if (!strcasecmp(var->name, "random"))
-                               ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
-                       else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
-                               ast_set_flag(class, MOH_RANDOMIZE);
-                       else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) 
-                               ast_set_flag(class, MOH_SORTALPHA);
-                       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;
-                               }
-                       }
-               }
+               moh_parse_options(ast_variable_browse(cfg, cat), class);
+               /* For compatibility with the past, we overwrite any name=name
+                * with the context [name]. */
+               ast_copy_string(class->name, cat, sizeof(class->name));
 
                if (ast_strlen_zero(class->dir)) {
                        if (!strcasecmp(class->mode, "custom")) {
@@ -1679,7 +1797,11 @@ static int load_moh_classes(int reload)
 static void ast_moh_destroy(void)
 {
        ast_verb(2, "Destroying musiconhold processes\n");
-       ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
+       if (mohclasses) {
+               ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
+               ao2_ref(mohclasses, -1);
+               mohclasses = NULL;
+       }
 }
 
 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
@@ -1699,7 +1821,8 @@ static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_
        if (a->argc != e->args)
                return CLI_SHOWUSAGE;
 
-       reload();
+       /* The module loader will prevent concurrent reloads from occurring, so we delegate */
+       ast_module_reload("res_musiconhold");
 
        return CLI_SUCCESS;
 }
@@ -1770,7 +1893,7 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc
                        ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
                }
                if (strcasecmp(class->mode, "files")) {
-                       ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
+                       ast_cli(a->fd, "\tFormat: %s\n", ast_format_get_name(class->format));
                }
        }
        ao2_iterator_destroy(&i);
@@ -1800,6 +1923,16 @@ static int moh_class_cmp(void *obj, void *arg, int flags)
                CMP_MATCH | CMP_STOP;
 }
 
+/*!
+ * \brief Load the module
+ *
+ * Module loading including tests for configuration or dependencies.
+ * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
+ * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
+ * configuration file or other non-critical problem return 
+ * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ */
 static int load_module(void)
 {
        int res;
@@ -1808,7 +1941,7 @@ static int load_module(void)
                return AST_MODULE_LOAD_DECLINE;
        }
 
-       if (!load_moh_classes(0)) {     /* No music classes configured, so skip it */
+       if (!load_moh_classes(0) && ast_check_realtime("musiconhold") == 0) {   /* No music classes configured, so skip it */
                ast_log(LOG_WARNING, "No music on hold classes configured, "
                                "disabling music on hold.\n");
        } else {
@@ -1820,10 +1953,6 @@ static int load_module(void)
        ast_register_atexit(ast_moh_destroy);
        ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
        if (!res)
-               res = ast_register_application_xml(wait_moh, wait_moh_exec);
-       if (!res)
-               res = ast_register_application_xml(set_moh, set_moh_exec);
-       if (!res)
                res = ast_register_application_xml(start_moh, start_moh_exec);
        if (!res)
                res = ast_register_application_xml(stop_moh, stop_moh_exec);
@@ -1869,8 +1998,6 @@ static int unload_module(void)
 
        ast_moh_destroy();
        res = ast_unregister_application(play_moh);
-       res |= ast_unregister_application(wait_moh);
-       res |= ast_unregister_application(set_moh);
        res |= ast_unregister_application(start_moh);
        res |= ast_unregister_application(stop_moh);
        ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
@@ -1879,8 +2006,10 @@ static int unload_module(void)
        return res;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Music On Hold Resource",
+       .support_level = AST_MODULE_SUPPORT_CORE,
        .load = load_module,
        .unload = unload_module,
        .reload = reload,
+       .load_pri = AST_MODPRI_CHANNEL_DEPEND,
 );