2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Routines implementing music on hold
23 * \arg See also \ref Config_moh
25 * \author Mark Spencer <markster@digium.com>
29 <conflict>win32</conflict>
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40 #include <sys/signal.h>
41 #include <netinet/in.h>
44 #include <sys/ioctl.h>
47 #include <dahdi/user.h>
50 #include "asterisk/lock.h"
51 #include "asterisk/file.h"
52 #include "asterisk/channel.h"
53 #include "asterisk/pbx.h"
54 #include "asterisk/app.h"
55 #include "asterisk/module.h"
56 #include "asterisk/translate.h"
57 #include "asterisk/say.h"
58 #include "asterisk/musiconhold.h"
59 #include "asterisk/config.h"
60 #include "asterisk/utils.h"
61 #include "asterisk/cli.h"
62 #include "asterisk/stringfields.h"
63 #include "asterisk/linkedlists.h"
64 #include "asterisk/manager.h"
65 #include "asterisk/paths.h"
66 #include "asterisk/astobj2.h"
68 #define INITIAL_NUM_FILES 8
71 <application name="MusicOnHold" language="en_US">
73 Play Music On Hold indefinitely.
76 <parameter name="class" required="true" />
77 <parameter name="duration" />
80 <para>Plays hold music specified by class. If omitted, the default music
81 source for the channel will be used. Change the default class with
82 Set(CHANNEL(musicclass)=...). If duration is given, hold music will be played
83 specified number of seconds. If duration is ommited, music plays indefinitely.
84 Returns <literal>0</literal> when done, <literal>-1</literal> on hangup.</para>
87 <application name="WaitMusicOnHold" language="en_US">
89 Wait, playing Music On Hold.
92 <parameter name="delay" required="true" />
95 <para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
96 <para>Plays hold music specified number of seconds. Returns <literal>0</literal> when done,
97 or <literal>-1</literal> on hangup. If no hold music is available, the delay will still occur
99 <para> !!! DEPRECATED. Use MusicOnHold instead !!!</para>
102 <application name="SetMusicOnHold" language="en_US">
104 Set default Music On Hold class.
107 <parameter name="class" required="yes" />
110 <para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
111 <para>Sets the default class for music on hold for a given channel.
112 When music on hold is activated, this class will be used to select which
113 music is played.</para>
114 <para>!!! DEPRECATED. USe Set(CHANNEL(musicclass)=...) instead !!!</para>
117 <application name="StartMusicOnHold" language="en_US">
122 <parameter name="class" required="true" />
125 <para>Starts playing music on hold, uses default music class for channel.
126 Starts playing music specified by class. If omitted, the default music
127 source for the channel will be used. Always returns <literal>0</literal>.</para>
130 <application name="StopMusicOnHold" language="en_US">
132 Stop playing Music On Hold.
136 <para>Stops playing music on hold.</para>
141 static const char play_moh[] = "MusicOnHold";
142 static const char wait_moh[] = "WaitMusicOnHold";
143 static const char set_moh[] = "SetMusicOnHold";
144 static const char start_moh[] = "StartMusicOnHold";
145 static const char stop_moh[] = "StopMusicOnHold";
147 static int respawn_time = 20;
149 struct moh_files_state {
150 struct mohclass *class;
156 char *save_pos_filename;
159 #define MOH_QUIET (1 << 0)
160 #define MOH_SINGLE (1 << 1)
161 #define MOH_CUSTOM (1 << 2)
162 #define MOH_RANDOMIZE (1 << 3)
163 #define MOH_SORTALPHA (1 << 4)
165 #define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */
167 static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
170 char name[MAX_MUSICCLASS];
175 /*! A dynamically sized array to hold the list of filenames in "files" mode */
177 /*! The current size of the filearray */
179 /*! The current number of files loaded into the filearray */
182 /*! The format from the MOH source, not applicable to "files" mode */
184 /*! The pid of the external application delivering MOH */
188 /*! Source of audio */
190 /*! FD for timing source */
192 /*! Created on the fly, from RT engine */
194 unsigned int delete:1;
195 AST_LIST_HEAD_NOLOCK(, mohdata) members;
196 AST_LIST_ENTRY(mohclass) list;
202 struct mohclass *parent;
204 AST_LIST_ENTRY(mohdata) list;
207 static struct ao2_container *mohclasses;
209 #define LOCAL_MPG_123 "/usr/local/bin/mpg123"
210 #define MPG_123 "/usr/bin/mpg123"
213 static int reload(void);
215 #define mohclass_ref(class,string) (ao2_t_ref((class), +1, (string)), class)
216 #define mohclass_unref(class,string) (ao2_t_ref((class), -1, (string)), (struct mohclass *) NULL)
218 static void moh_files_release(struct ast_channel *chan, void *data)
220 struct moh_files_state *state;
222 if (!chan || !chan->music_state) {
226 state = chan->music_state;
229 ast_closestream(chan->stream);
233 if (option_verbose > 2) {
234 ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
237 if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) {
238 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt);
241 state->save_pos = state->pos;
243 mohclass_unref(state->class, "Unreffing channel's music class upon deactivation of generator");
246 static int ast_moh_files_next(struct ast_channel *chan)
248 struct moh_files_state *state = chan->music_state;
251 /* Discontinue a stream if it is running already */
253 ast_closestream(chan->stream);
257 if (!state->class->total_files) {
258 ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
262 /* If a specific file has been saved confirm it still exists and that it is still valid */
263 if (state->save_pos >= 0 && state->save_pos < state->class->total_files && state->class->filearray[state->save_pos] == state->save_pos_filename) {
264 state->pos = state->save_pos;
265 state->save_pos = -1;
266 } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) {
267 /* Get a random file and ensure we can open it */
268 for (tries = 0; tries < 20; tries++) {
269 state->pos = ast_random() % state->class->total_files;
270 if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0)
273 state->save_pos = -1;
276 /* This is easy, just increment our position and make sure we don't exceed the total file count */
278 state->pos %= state->class->total_files;
279 state->save_pos = -1;
283 if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) {
284 ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno));
286 state->pos %= state->class->total_files;
290 /* Record the pointer to the filename for position resuming later */
291 state->save_pos_filename = state->class->filearray[state->pos];
293 ast_debug(1, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]);
296 ast_seekstream(chan->stream, state->samples, SEEK_SET);
301 static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
303 struct ast_frame *f = NULL;
305 if (!(chan->stream && (f = ast_readframe(chan->stream)))) {
306 if (!ast_moh_files_next(chan))
307 f = ast_readframe(chan->stream);
313 static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
315 struct moh_files_state *state = chan->music_state;
316 struct ast_frame *f = NULL;
319 state->sample_queue += samples;
321 while (state->sample_queue > 0) {
322 if ((f = moh_files_readframe(chan))) {
323 state->samples += f->samples;
324 state->sample_queue -= f->samples;
325 res = ast_write(chan, f);
328 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
337 static void *moh_files_alloc(struct ast_channel *chan, void *params)
339 struct moh_files_state *state;
340 struct mohclass *class = params;
342 if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
343 chan->music_state = state;
345 state = chan->music_state;
352 if (state->class != class) {
353 memset(state, 0, sizeof(*state));
354 if (ast_test_flag(class, MOH_RANDOMIZE) && class->total_files) {
355 state->pos = ast_random() % class->total_files;
359 state->class = mohclass_ref(class, "Reffing music class for channel");
360 state->origwfmt = chan->writeformat;
362 ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name);
364 return chan->music_state;
367 static int moh_digit_match(void *obj, void *arg, int flags)
370 struct mohclass *class = obj;
372 return (*digit == class->digit) ? CMP_MATCH | CMP_STOP : 0;
375 /*! \note This function should be called with the mohclasses list locked */
376 static struct mohclass *get_mohbydigit(char digit)
378 return ao2_t_callback(mohclasses, 0, moh_digit_match, &digit, "digit callback");
381 static void moh_handle_digit(struct ast_channel *chan, char digit)
383 struct mohclass *class;
384 const char *classname = NULL;
386 if ((class = get_mohbydigit(digit))) {
387 classname = ast_strdupa(class->name);
388 class = mohclass_unref(class, "Unreffing ao2_find from finding by digit");
389 ast_string_field_set(chan,musicclass,classname);
391 ast_moh_start(chan, classname, NULL);
395 static struct ast_generator moh_file_stream =
397 .alloc = moh_files_alloc,
398 .release = moh_files_release,
399 .generate = moh_files_generator,
400 .digit = moh_handle_digit,
403 static int spawn_mp3(struct mohclass *class)
407 char fns[MAX_MP3S][80];
408 char *argv[MAX_MP3S + 50];
416 if (!strcasecmp(class->dir, "nodir")) {
419 dir = opendir(class->dir);
420 if (!dir && strncasecmp(class->dir, "http://", 7)) {
421 ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir);
426 if (!ast_test_flag(class, MOH_CUSTOM)) {
427 argv[argc++] = "mpg123";
430 argv[argc++] = "--mono";
432 argv[argc++] = "8000";
434 if (!ast_test_flag(class, MOH_SINGLE)) {
436 argv[argc++] = "2048";
441 if (ast_test_flag(class, MOH_QUIET))
442 argv[argc++] = "4096";
444 argv[argc++] = "8192";
446 /* Look for extra arguments and add them to the list */
447 ast_copy_string(xargs, class->args, sizeof(xargs));
449 while (!ast_strlen_zero(argptr)) {
450 argv[argc++] = argptr;
451 strsep(&argptr, ",");
454 /* Format arguments for argv vector */
455 ast_copy_string(xargs, class->args, sizeof(xargs));
457 while (!ast_strlen_zero(argptr)) {
458 argv[argc++] = argptr;
459 strsep(&argptr, " ");
463 if (!strncasecmp(class->dir, "http://", 7)) {
464 ast_copy_string(fns[files], class->dir, sizeof(fns[files]));
465 argv[argc++] = fns[files];
468 while ((de = readdir(dir)) && (files < MAX_MP3S)) {
469 if ((strlen(de->d_name) > 3) &&
470 ((ast_test_flag(class, MOH_CUSTOM) &&
471 (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") ||
472 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) ||
473 !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) {
474 ast_copy_string(fns[files], de->d_name, sizeof(fns[files]));
475 argv[argc++] = fns[files];
485 ast_log(LOG_WARNING, "Pipe failed\n");
489 ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir);
494 if (!strncasecmp(class->dir, "http://", 7) && time(NULL) - class->start < respawn_time) {
495 sleep(respawn_time - (time(NULL) - class->start));
499 class->pid = ast_safe_fork(0);
500 if (class->pid < 0) {
503 ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
507 if (ast_opt_high_priority)
511 /* Stdout goes to pipe */
512 dup2(fds[1], STDOUT_FILENO);
514 /* Close everything else */
515 ast_close_fds_above_n(STDERR_FILENO);
518 if (strcasecmp(class->dir, "nodir") && chdir(class->dir) < 0) {
519 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
522 setpgid(0, getpid());
523 if (ast_test_flag(class, MOH_CUSTOM)) {
524 execv(argv[0], argv);
526 /* Default install is /usr/local/bin */
527 execv(LOCAL_MPG_123, argv);
528 /* Many places have it in /usr/bin */
529 execv(MPG_123, argv);
530 /* Check PATH as a last-ditch effort */
531 execvp("mpg123", argv);
533 /* Can't use logger, since log FDs are closed */
534 fprintf(stderr, "MOH: exec failed: %s\n", strerror(errno));
544 static void *monmp3thread(void *data)
546 #define MOH_MS_INTERVAL 100
548 struct mohclass *class = data;
554 struct timeval deadline, tv_tmp;
557 deadline.tv_usec = 0;
559 pthread_testcancel();
560 /* Spawn mp3 player if it's not there */
561 if (class->srcfd < 0) {
562 if ((class->srcfd = spawn_mp3(class)) < 0) {
563 ast_log(LOG_WARNING, "Unable to spawn mp3player\n");
564 /* Try again later */
566 pthread_testcancel();
569 if (class->pseudofd > -1) {
573 /* Pause some amount of time */
574 res = read(class->pseudofd, buf, sizeof(buf));
575 pthread_testcancel();
579 tv_tmp = ast_tvnow();
580 if (ast_tvzero(deadline))
582 delta = ast_tvdiff_ms(tv_tmp, deadline);
583 if (delta < MOH_MS_INTERVAL) { /* too early */
584 deadline = ast_tvadd(deadline, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */
585 usleep(1000 * (MOH_MS_INTERVAL - delta));
586 pthread_testcancel();
588 ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n");
591 res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */
593 if ((strncasecmp(class->dir, "http://", 7) && strcasecmp(class->dir, "nodir")) && AST_LIST_EMPTY(&class->members))
596 len = ast_codec_get_len(class->format, res);
598 if ((res2 = read(class->srcfd, sbuf, len)) != len) {
602 pthread_testcancel();
603 if (class->pid > 1) {
604 killpg(class->pid, SIGHUP);
606 killpg(class->pid, SIGTERM);
608 killpg(class->pid, SIGKILL);
612 ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len);
617 pthread_testcancel();
620 AST_LIST_TRAVERSE(&class->members, moh, list) {
622 if ((res = write(moh->pipe[1], sbuf, res2)) != res2) {
623 ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2);
631 static int play_moh_exec(struct ast_channel *chan, const char *data)
637 AST_DECLARE_APP_ARGS(args,
639 AST_APP_ARG(duration);
642 parse = ast_strdupa(data);
644 AST_STANDARD_APP_ARGS(args, parse);
646 if (!ast_strlen_zero(args.duration)) {
647 if (sscanf(args.duration, "%30d", &timeout) == 1) {
650 ast_log(LOG_WARNING, "Invalid MusicOnHold duration '%s'. Will wait indefinitely.\n", args.duration);
654 class = S_OR(args.class, NULL);
655 if (ast_moh_start(chan, class, NULL)) {
656 ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
661 res = ast_safe_sleep(chan, timeout);
663 while (!(res = ast_safe_sleep(chan, 10000)));
671 static int wait_moh_exec(struct ast_channel *chan, const char *data)
673 static int deprecation_warning = 0;
676 if (!deprecation_warning) {
677 deprecation_warning = 1;
678 ast_log(LOG_WARNING, "WaitMusicOnHold application is deprecated and will be removed. Use MusicOnHold with duration parameter instead\n");
681 if (!data || !atoi(data)) {
682 ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n");
685 if (ast_moh_start(chan, NULL, NULL)) {
686 ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name);
689 res = ast_safe_sleep(chan, atoi(data) * 1000);
694 static int set_moh_exec(struct ast_channel *chan, const char *data)
696 static int deprecation_warning = 0;
698 if (!deprecation_warning) {
699 deprecation_warning = 1;
700 ast_log(LOG_WARNING, "SetMusicOnHold application is deprecated and will be removed. Use Set(CHANNEL(musicclass)=...) instead\n");
703 if (ast_strlen_zero(data)) {
704 ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n");
707 ast_string_field_set(chan, musicclass, data);
711 static int start_moh_exec(struct ast_channel *chan, const char *data)
715 AST_DECLARE_APP_ARGS(args,
719 parse = ast_strdupa(data);
721 AST_STANDARD_APP_ARGS(args, parse);
723 class = S_OR(args.class, NULL);
724 if (ast_moh_start(chan, class, NULL))
725 ast_log(LOG_WARNING, "Unable to start music on hold class '%s' on channel %s\n", class, chan->name);
730 static int stop_moh_exec(struct ast_channel *chan, const char *data)
737 static struct mohclass *get_mohbyname(const char *name, int warn)
739 struct mohclass *moh = NULL;
740 struct mohclass tmp_class = {
744 ast_copy_string(tmp_class.name, name, sizeof(tmp_class.name));
746 moh = ao2_t_find(mohclasses, &tmp_class, 0, "Finding by name");
749 ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name);
755 static struct mohdata *mohalloc(struct mohclass *cl)
760 if (!(moh = ast_calloc(1, sizeof(*moh))))
763 if (pipe(moh->pipe)) {
764 ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno));
769 /* Make entirely non-blocking */
770 flags = fcntl(moh->pipe[0], F_GETFL);
771 fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK);
772 flags = fcntl(moh->pipe[1], F_GETFL);
773 fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK);
775 moh->f.frametype = AST_FRAME_VOICE;
776 moh->f.subclass = cl->format;
777 moh->f.offset = AST_FRIENDLY_OFFSET;
779 moh->parent = mohclass_ref(cl, "Reffing music class for mohdata parent");
782 AST_LIST_INSERT_HEAD(&cl->members, moh, list);
788 static void moh_release(struct ast_channel *chan, void *data)
790 struct mohdata *moh = data;
791 struct mohclass *class = moh->parent;
795 AST_LIST_REMOVE(&moh->parent->members, moh, list);
801 oldwfmt = moh->origwfmt;
803 moh->parent = class = mohclass_unref(class, "unreffing moh->parent upon deactivation of generator");
808 if (oldwfmt && ast_set_write_format(chan, oldwfmt)) {
809 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n",
810 chan->name, ast_getformatname(oldwfmt));
813 ast_verb(3, "Stopped music on hold on %s\n", chan->name);
817 static void *moh_alloc(struct ast_channel *chan, void *params)
820 struct mohclass *class = params;
821 struct moh_files_state *state;
823 /* Initiating music_state for current channel. Channel should know name of moh class */
824 if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) {
825 chan->music_state = state;
826 state->class = class;
828 state = chan->music_state;
829 if (state && state->class != class) {
830 memset(state, 0, sizeof(*state));
831 state->class = class;
834 if ((res = mohalloc(class))) {
835 res->origwfmt = chan->writeformat;
836 if (ast_set_write_format(chan, class->format)) {
837 ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format));
838 moh_release(NULL, res);
841 ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name);
846 static int moh_generate(struct ast_channel *chan, void *data, int len, int samples)
848 struct mohdata *moh = data;
849 short buf[1280 + AST_FRIENDLY_OFFSET / 2];
852 len = ast_codec_get_len(moh->parent->format, samples);
854 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
855 ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name);
856 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
858 res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len);
862 moh->f.datalen = res;
863 moh->f.data.ptr = buf + AST_FRIENDLY_OFFSET / 2;
864 moh->f.samples = ast_codec_get_samples(&moh->f);
866 if (ast_write(chan, &moh->f) < 0) {
867 ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno));
874 static struct ast_generator mohgen = {
876 .release = moh_release,
877 .generate = moh_generate,
878 .digit = moh_handle_digit,
881 static int moh_add_file(struct mohclass *class, const char *filepath)
883 if (!class->allowed_files) {
884 if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray))))
886 class->allowed_files = INITIAL_NUM_FILES;
887 } else if (class->total_files == class->allowed_files) {
888 if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) {
889 class->allowed_files = 0;
890 class->total_files = 0;
893 class->allowed_files *= 2;
896 if (!(class->filearray[class->total_files] = ast_strdup(filepath)))
899 class->total_files++;
904 static int moh_sort_compare(const void *i1, const void *i2)
908 s1 = ((char **)i1)[0];
909 s2 = ((char **)i2)[0];
911 return strcasecmp(s1, s2);
914 static int moh_scan_files(struct mohclass *class) {
917 struct dirent *files_dirent;
918 char dir_path[PATH_MAX];
920 char filepath[PATH_MAX];
926 if (class->dir[0] != '/') {
927 ast_copy_string(dir_path, ast_config_AST_VAR_DIR, sizeof(dir_path));
928 strncat(dir_path, "/", sizeof(dir_path) - 1);
929 strncat(dir_path, class->dir, sizeof(dir_path) - 1);
931 ast_copy_string(dir_path, class->dir, sizeof(dir_path));
933 ast_debug(4, "Scanning '%s' for files for class '%s'\n", dir_path, class->name);
934 files_DIR = opendir(dir_path);
936 ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", dir_path);
940 for (i = 0; i < class->total_files; i++)
941 ast_free(class->filearray[i]);
943 class->total_files = 0;
944 dirnamelen = strlen(dir_path) + 2;
945 if (!getcwd(path, sizeof(path))) {
946 ast_log(LOG_WARNING, "getcwd() failed: %s\n", strerror(errno));
949 if (chdir(dir_path) < 0) {
950 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
953 while ((files_dirent = readdir(files_DIR))) {
954 /* The file name must be at least long enough to have the file type extension */
955 if ((strlen(files_dirent->d_name) < 4))
958 /* Skip files that starts with a dot */
959 if (files_dirent->d_name[0] == '.')
962 /* Skip files without extensions... they are not audio */
963 if (!strchr(files_dirent->d_name, '.'))
966 snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, files_dirent->d_name);
968 if (stat(filepath, &statbuf))
971 if (!S_ISREG(statbuf.st_mode))
974 if ((ext = strrchr(filepath, '.')))
977 /* if the file is present in multiple formats, ensure we only put it into the list once */
978 for (i = 0; i < class->total_files; i++)
979 if (!strcmp(filepath, class->filearray[i]))
982 if (i == class->total_files) {
983 if (moh_add_file(class, filepath))
989 if (chdir(path) < 0) {
990 ast_log(LOG_WARNING, "chdir() failed: %s\n", strerror(errno));
993 if (ast_test_flag(class, MOH_SORTALPHA))
994 qsort(&class->filearray[0], class->total_files, sizeof(char *), moh_sort_compare);
995 return class->total_files;
998 static int init_files_class(struct mohclass *class)
1002 res = moh_scan_files(class);
1009 if (option_verbose > 2) {
1010 ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n",
1011 class->dir, class->name);
1016 if (strchr(class->args, 'r')) {
1017 ast_set_flag(class, MOH_RANDOMIZE);
1024 static int moh_diff(struct mohclass *old, struct mohclass *new)
1030 if (strcmp(old->dir, new->dir)) {
1032 } else if (strcmp(old->mode, new->mode)) {
1034 } else if (strcmp(old->args, new->args)) {
1036 } else if (old->flags != new->flags) {
1043 static int init_app_class(struct mohclass *class)
1049 if (!strcasecmp(class->mode, "custom")) {
1050 ast_set_flag(class, MOH_CUSTOM);
1051 } else if (!strcasecmp(class->mode, "mp3nb")) {
1052 ast_set_flag(class, MOH_SINGLE);
1053 } else if (!strcasecmp(class->mode, "quietmp3nb")) {
1054 ast_set_flag(class, MOH_SINGLE | MOH_QUIET);
1055 } else if (!strcasecmp(class->mode, "quietmp3")) {
1056 ast_set_flag(class, MOH_QUIET);
1060 class->pseudofd = -1;
1063 /* Open /dev/zap/pseudo for timing... Is
1064 there a better, yet reliable way to do this? */
1065 class->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
1066 if (class->pseudofd < 0) {
1067 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
1070 ioctl(class->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
1074 if (ast_pthread_create_background(&class->thread, NULL, monmp3thread, class)) {
1075 ast_log(LOG_WARNING, "Unable to create moh thread...\n");
1076 if (class->pseudofd > -1) {
1077 close(class->pseudofd);
1078 class->pseudofd = -1;
1087 * \note This function owns the reference it gets to moh
1089 static int moh_register(struct mohclass *moh, int reload, int unref)
1091 struct mohclass *mohclass = NULL;
1093 if ((mohclass = get_mohbyname(moh->name, 0)) && !moh_diff(mohclass, moh)) {
1094 if (!mohclass->delete) {
1095 ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name);
1096 mohclass = mohclass_unref(mohclass, "unreffing mohclass we just found by name");
1098 moh = mohclass_unref(moh, "unreffing potential new moh class (it is a duplicate)");
1102 mohclass = mohclass_unref(mohclass, "Unreffing mohclass we just found by name");
1106 moh->start -= respawn_time;
1108 if (!strcasecmp(moh->mode, "files")) {
1109 if (init_files_class(moh)) {
1110 moh = mohclass_unref(moh, "unreffing potential new moh class (init_files_class failed)");
1113 } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") ||
1114 !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") ||
1115 !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) {
1116 if (init_app_class(moh)) {
1117 moh = mohclass_unref(moh, "unreffing potential new moh class (init_app_class_failed)");
1121 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode);
1122 moh = mohclass_unref(moh, "unreffing potential new moh class (unknown mode)");
1126 ao2_t_link(mohclasses, moh, "Adding class to container");
1129 moh = mohclass_unref(moh, "Unreffing new moh class because we just added it to the container");
1135 static void local_ast_moh_cleanup(struct ast_channel *chan)
1137 struct moh_files_state *state = chan->music_state;
1140 ast_free(chan->music_state);
1141 chan->music_state = NULL;
1145 static void moh_class_destructor(void *obj);
1147 static struct mohclass *moh_class_malloc(void)
1149 struct mohclass *class;
1151 if ((class = ao2_t_alloc(sizeof(*class), moh_class_destructor, "Allocating new moh class"))) {
1152 class->format = AST_FORMAT_SLINEAR;
1158 static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass)
1160 struct mohclass *mohclass = NULL;
1161 struct moh_files_state *state = chan->music_state;
1162 struct ast_variable *var = NULL;
1164 int realtime_possible = ast_check_realtime("musiconhold");
1166 /* The following is the order of preference for which class to use:
1167 * 1) The channels explicitly set musicclass, which should *only* be
1168 * set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan.
1169 * 2) The mclass argument. If a channel is calling ast_moh_start() as the
1170 * result of receiving a HOLD control frame, this should be the
1171 * payload that came with the frame.
1172 * 3) The interpclass argument. This would be from the mohinterpret
1173 * option from channel drivers. This is the same as the old musicclass
1175 * 4) The default class.
1177 if (!ast_strlen_zero(chan->musicclass)) {
1178 mohclass = get_mohbyname(chan->musicclass, 1);
1179 if (!mohclass && realtime_possible) {
1180 var = ast_load_realtime("musiconhold", "name", chan->musicclass, SENTINEL);
1183 if (!mohclass && !var && !ast_strlen_zero(mclass)) {
1184 mohclass = get_mohbyname(mclass, 1);
1185 if (!mohclass && realtime_possible) {
1186 var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL);
1189 if (!mohclass && !var && !ast_strlen_zero(interpclass)) {
1190 mohclass = get_mohbyname(interpclass, 1);
1191 if (!mohclass && realtime_possible) {
1192 var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL);
1196 if (!mohclass && !var) {
1197 mohclass = get_mohbyname("default", 1);
1198 if (!mohclass && realtime_possible) {
1199 var = ast_load_realtime("musiconhold", "name", "default", SENTINEL);
1203 /* If no moh class found in memory, then check RT. Note that the logic used
1204 * above guarantees that if var is non-NULL, then mohclass must be NULL.
1207 struct ast_variable *tmp = NULL;
1209 if ((mohclass = moh_class_malloc())) {
1210 mohclass->realtime = 1;
1211 for (tmp = var; tmp; tmp = tmp->next) {
1212 if (!strcasecmp(tmp->name, "name"))
1213 ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name));
1214 else if (!strcasecmp(tmp->name, "mode"))
1215 ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode));
1216 else if (!strcasecmp(tmp->name, "directory"))
1217 ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir));
1218 else if (!strcasecmp(tmp->name, "application"))
1219 ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args));
1220 else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value)))
1221 mohclass->digit = *tmp->value;
1222 else if (!strcasecmp(tmp->name, "random"))
1223 ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE);
1224 else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random"))
1225 ast_set_flag(mohclass, MOH_RANDOMIZE);
1226 else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha"))
1227 ast_set_flag(mohclass, MOH_SORTALPHA);
1228 else if (!strcasecmp(tmp->name, "format")) {
1229 mohclass->format = ast_getformatbyname(tmp->value);
1230 if (!mohclass->format) {
1231 ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value);
1232 mohclass->format = AST_FORMAT_SLINEAR;
1236 ast_variables_destroy(var);
1237 if (ast_strlen_zero(mohclass->dir)) {
1238 if (!strcasecmp(mohclass->mode, "custom")) {
1239 strcpy(mohclass->dir, "nodir");
1241 ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name);
1242 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no directory specified)");
1246 if (ast_strlen_zero(mohclass->mode)) {
1247 ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name);
1248 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no mode specified)");
1251 if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) {
1252 ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name);
1253 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no app specified for custom mode");
1257 if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) {
1258 /* CACHERTCLASSES enabled, let's add this class to default tree */
1259 if (state && state->class) {
1260 /* Class already exist for this channel */
1261 ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
1262 if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
1263 /* we found RT class with the same name, seems like we should continue playing existing one */
1264 /* XXX This code is impossible to reach */
1265 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has a class)");
1266 mohclass = state->class;
1269 /* We don't want moh_register to unref the mohclass because we do it at the end of this function as well.
1270 * If we allowed moh_register to unref the mohclass,too, then the count would be off by one. The result would
1271 * be that the destructor would be called when the generator on the channel is deactivated. The container then
1272 * has a pointer to a freed mohclass, so any operations involving the mohclass container would result in reading
1275 moh_register(mohclass, 0, 0);
1277 /* We don't register RT moh class, so let's init it manualy */
1279 time(&mohclass->start);
1280 mohclass->start -= respawn_time;
1282 if (!strcasecmp(mohclass->mode, "files")) {
1283 if (!moh_scan_files(mohclass)) {
1284 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
1287 if (strchr(mohclass->args, 'r'))
1288 ast_set_flag(mohclass, MOH_RANDOMIZE);
1289 } 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")) {
1291 if (!strcasecmp(mohclass->mode, "custom"))
1292 ast_set_flag(mohclass, MOH_CUSTOM);
1293 else if (!strcasecmp(mohclass->mode, "mp3nb"))
1294 ast_set_flag(mohclass, MOH_SINGLE);
1295 else if (!strcasecmp(mohclass->mode, "quietmp3nb"))
1296 ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET);
1297 else if (!strcasecmp(mohclass->mode, "quietmp3"))
1298 ast_set_flag(mohclass, MOH_QUIET);
1300 mohclass->srcfd = -1;
1302 /* Open /dev/dahdi/pseudo for timing... Is
1303 there a better, yet reliable way to do this? */
1304 mohclass->pseudofd = open("/dev/dahdi/pseudo", O_RDONLY);
1305 if (mohclass->pseudofd < 0) {
1306 ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n");
1309 ioctl(mohclass->pseudofd, DAHDI_SET_BLOCKSIZE, &x);
1312 mohclass->pseudofd = -1;
1314 /* Let's check if this channel already had a moh class before */
1315 if (state && state->class) {
1316 /* Class already exist for this channel */
1317 ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name);
1318 if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) {
1319 /* we found RT class with the same name, seems like we should continue playing existing one */
1320 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (channel already has one)");
1321 mohclass = state->class;
1324 if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) {
1325 ast_log(LOG_WARNING, "Unable to create moh...\n");
1326 if (mohclass->pseudofd > -1) {
1327 close(mohclass->pseudofd);
1328 mohclass->pseudofd = -1;
1330 mohclass = mohclass_unref(mohclass, "Unreffing potential mohclass (failed to create background thread)");
1335 ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode);
1336 mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (unknown mode)");
1341 ast_variables_destroy(var);
1349 manager_event(EVENT_FLAG_CALL, "MusicOnHold",
1353 chan->name, chan->uniqueid);
1355 ast_set_flag(chan, AST_FLAG_MOH);
1357 if (mohclass->total_files) {
1358 res = ast_activate_generator(chan, &moh_file_stream, mohclass);
1360 res = ast_activate_generator(chan, &mohgen, mohclass);
1363 mohclass = mohclass_unref(mohclass, "unreffing local reference to mohclass in local_ast_moh_start");
1368 static void local_ast_moh_stop(struct ast_channel *chan)
1370 struct moh_files_state *state = chan->music_state;
1371 ast_clear_flag(chan, AST_FLAG_MOH);
1372 ast_deactivate_generator(chan);
1376 ast_closestream(chan->stream);
1377 chan->stream = NULL;
1381 manager_event(EVENT_FLAG_CALL, "MusicOnHold",
1385 chan->name, chan->uniqueid);
1388 static void moh_class_destructor(void *obj)
1390 struct mohclass *class = obj;
1391 struct mohdata *member;
1393 ast_debug(1, "Destroying MOH class '%s'\n", class->name);
1395 if (class->pid > 1) {
1397 int bytes, tbytes = 0, stime = 0, pid = 0;
1399 ast_log(LOG_DEBUG, "killing %d!\n", class->pid);
1401 stime = time(NULL) + 2;
1405 /* Back when this was just mpg123, SIGKILL was fine. Now we need
1406 * to give the process a reason and time enough to kill off its
1408 killpg(pid, SIGHUP);
1410 killpg(pid, SIGTERM);
1412 killpg(pid, SIGKILL);
1414 while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
1415 (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
1416 tbytes = tbytes + bytes;
1419 ast_log(LOG_DEBUG, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
1421 close(class->srcfd);
1424 while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
1428 if (class->thread) {
1429 pthread_cancel(class->thread);
1430 pthread_join(class->thread, NULL);
1431 class->thread = AST_PTHREADT_NULL;
1434 if (class->filearray) {
1436 for (i = 0; i < class->total_files; i++) {
1437 free(class->filearray[i]);
1439 free(class->filearray);
1440 class->filearray = NULL;
1444 static int moh_class_mark(void *obj, void *arg, int flags)
1446 struct mohclass *class = obj;
1453 static int moh_classes_delete_marked(void *obj, void *arg, int flags)
1455 struct mohclass *class = obj;
1457 return class->delete ? CMP_MATCH : 0;
1460 static int load_moh_classes(int reload)
1462 struct ast_config *cfg;
1463 struct ast_variable *var;
1464 struct mohclass *class;
1467 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1469 cfg = ast_config_load("musiconhold.conf", config_flags);
1471 if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
1476 ao2_t_callback(mohclasses, OBJ_NODATA, moh_class_mark, NULL, "Mark deleted classes");
1479 ast_clear_flag(global_flags, AST_FLAGS_ALL);
1481 cat = ast_category_browse(cfg, NULL);
1482 for (; cat; cat = ast_category_browse(cfg, cat)) {
1483 /* Setup common options from [general] section */
1484 if (!strcasecmp(cat, "general")) {
1485 for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
1486 if (!strcasecmp(var->name, "cachertclasses")) {
1487 ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES);
1489 ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name);
1493 /* These names were deprecated in 1.4 and should not be used until after the next major release. */
1494 if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") ||
1495 !strcasecmp(cat, "general")) {
1499 if (!(class = moh_class_malloc())) {
1503 ast_copy_string(class->name, cat, sizeof(class->name));
1504 for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
1505 if (!strcasecmp(var->name, "mode"))
1506 ast_copy_string(class->mode, var->value, sizeof(class->mode));
1507 else if (!strcasecmp(var->name, "directory"))
1508 ast_copy_string(class->dir, var->value, sizeof(class->dir));
1509 else if (!strcasecmp(var->name, "application"))
1510 ast_copy_string(class->args, var->value, sizeof(class->args));
1511 else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value)))
1512 class->digit = *var->value;
1513 else if (!strcasecmp(var->name, "random"))
1514 ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
1515 else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random"))
1516 ast_set_flag(class, MOH_RANDOMIZE);
1517 else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha"))
1518 ast_set_flag(class, MOH_SORTALPHA);
1519 else if (!strcasecmp(var->name, "format")) {
1520 class->format = ast_getformatbyname(var->value);
1521 if (!class->format) {
1522 ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
1523 class->format = AST_FORMAT_SLINEAR;
1528 if (ast_strlen_zero(class->dir)) {
1529 if (!strcasecmp(class->mode, "custom")) {
1530 strcpy(class->dir, "nodir");
1532 ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name);
1533 class = mohclass_unref(class, "unreffing potential mohclass (no directory)");
1537 if (ast_strlen_zero(class->mode)) {
1538 ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name);
1539 class = mohclass_unref(class, "unreffing potential mohclass (no mode)");
1542 if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) {
1543 ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name);
1544 class = mohclass_unref(class, "unreffing potential mohclass (no app for custom mode)");
1548 /* Don't leak a class when it's already registered */
1549 if (!moh_register(class, reload, 1)) {
1554 ast_config_destroy(cfg);
1556 ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
1557 moh_classes_delete_marked, NULL, "Purge marked classes");
1562 static void ast_moh_destroy(void)
1564 ast_verb(2, "Destroying musiconhold processes\n");
1565 ao2_t_callback(mohclasses, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL, "Destroy callback");
1568 static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1572 e->command = "moh reload";
1574 "Usage: moh reload\n"
1575 " Reloads the MusicOnHold module.\n"
1576 " Alias for 'module reload res_musiconhold.so'\n";
1582 if (a->argc != e->args)
1583 return CLI_SHOWUSAGE;
1590 static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1592 struct mohclass *class;
1593 struct ao2_iterator i;
1597 e->command = "moh show files";
1599 "Usage: moh show files\n"
1600 " Lists all loaded file-based MusicOnHold classes and their\n"
1607 if (a->argc != e->args)
1608 return CLI_SHOWUSAGE;
1610 i = ao2_iterator_init(mohclasses, 0);
1612 for (; (class = ao2_t_iterator_next(&i, "Show files iterator")); mohclass_unref(class, "Unref iterator in moh show files")) {
1615 if (!class->total_files) {
1619 ast_cli(a->fd, "Class: %s\n", class->name);
1620 for (x = 0; x < class->total_files; x++) {
1621 ast_cli(a->fd, "\tFile: %s\n", class->filearray[x]);
1628 static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1630 struct mohclass *class;
1631 struct ao2_iterator i;
1635 e->command = "moh show classes";
1637 "Usage: moh show classes\n"
1638 " Lists all MusicOnHold classes.\n";
1644 if (a->argc != e->args)
1645 return CLI_SHOWUSAGE;
1647 i = ao2_iterator_init(mohclasses, 0);
1649 for (; (class = ao2_t_iterator_next(&i, "Show classes iterator")); mohclass_unref(class, "Unref iterator in moh show classes")) {
1650 ast_cli(a->fd, "Class: %s\n", class->name);
1651 ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>"));
1652 ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
1653 if (ast_test_flag(class, MOH_CUSTOM)) {
1654 ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
1656 if (strcasecmp(class->mode, "files")) {
1657 ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format));
1664 static struct ast_cli_entry cli_moh[] = {
1665 AST_CLI_DEFINE(handle_cli_moh_reload, "Reload MusicOnHold"),
1666 AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"),
1667 AST_CLI_DEFINE(handle_cli_moh_show_files, "List MusicOnHold file-based classes")
1670 static int moh_class_hash(const void *obj, const int flags)
1672 const struct mohclass *class = obj;
1674 return ast_str_case_hash(class->name);
1677 static int moh_class_cmp(void *obj, void *arg, int flags)
1679 struct mohclass *class = obj, *class2 = arg;
1681 return strcasecmp(class->name, class2->name) ? 0 : CMP_MATCH | CMP_STOP;
1684 static int load_module(void)
1688 if (!(mohclasses = ao2_t_container_alloc(53, moh_class_hash, moh_class_cmp, "Moh class container"))) {
1689 return AST_MODULE_LOAD_DECLINE;
1692 if (!load_moh_classes(0)) { /* No music classes configured, so skip it */
1693 ast_log(LOG_WARNING, "No music on hold classes configured, "
1694 "disabling music on hold.\n");
1696 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
1697 local_ast_moh_cleanup);
1700 res = ast_register_application_xml(play_moh, play_moh_exec);
1701 ast_register_atexit(ast_moh_destroy);
1702 ast_cli_register_multiple(cli_moh, ARRAY_LEN(cli_moh));
1704 res = ast_register_application_xml(wait_moh, wait_moh_exec);
1706 res = ast_register_application_xml(set_moh, set_moh_exec);
1708 res = ast_register_application_xml(start_moh, start_moh_exec);
1710 res = ast_register_application_xml(stop_moh, stop_moh_exec);
1712 return AST_MODULE_LOAD_SUCCESS;
1715 static int reload(void)
1717 if (load_moh_classes(1)) {
1718 ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop,
1719 local_ast_moh_cleanup);
1722 return AST_MODULE_LOAD_SUCCESS;
1725 static int moh_class_inuse(void *obj, void *arg, int flags)
1727 struct mohclass *class = obj;
1729 return AST_LIST_EMPTY(&class->members) ? 0 : CMP_MATCH | CMP_STOP;
1732 static int unload_module(void)
1735 struct mohclass *class = NULL;
1737 /* XXX This check shouldn't be required if module ref counting was being used
1739 if ((class = ao2_t_callback(mohclasses, 0, moh_class_inuse, NULL, "Module unload callback"))) {
1740 class = mohclass_unref(class, "unref of class from module unload callback");
1745 ast_log(LOG_WARNING, "Unable to unload res_musiconhold due to active MOH channels\n");
1749 ast_uninstall_music_functions();
1752 res = ast_unregister_application(play_moh);
1753 res |= ast_unregister_application(wait_moh);
1754 res |= ast_unregister_application(set_moh);
1755 res |= ast_unregister_application(start_moh);
1756 res |= ast_unregister_application(stop_moh);
1757 ast_cli_unregister_multiple(cli_moh, ARRAY_LEN(cli_moh));
1758 ast_unregister_atexit(ast_moh_destroy);
1763 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource",
1764 .load = load_module,
1765 .unload = unload_module,