diff --git a/CHANGES b/CHANGES index 7460a9280..0a1e361fb 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,13 @@ CODECS * Ability to define custom SILK formats in codecs.conf. * Addition of speex32 audio format with translation. +ConfBridge +-------------------------- + * New highly optimized and customizable ConfBridge application capable of + mixing audio at sample rates ranging from 8khz-96khz. + * CONFBRIDGE dialplan function capable of creating dynamic ConfBridge user + and bridge profiles on a channel. + Dialplan Variables ------------------ * Added ASTETCDIR, ASTMODDIR, ASTVARLIBDIR, ASTDBDIR, ASTKEYDIR, ASTDATADIR, diff --git a/UPGRADE.txt b/UPGRADE.txt index 57c490e1e..e7aa03442 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -21,6 +21,10 @@ From 1.8 to 1.10: +ConfBridge + - ConfBridge's dialplan arguments have changed and are not + backwards compatible. + HTTP: - A bindaddr must be specified in order for the HTTP server to run. Previous versions would default to 0.0.0.0 if no diff --git a/apps/Makefile b/apps/Makefile index 4343f3c30..7f8e7c9c2 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -27,6 +27,12 @@ all: _all include $(ASTTOPDIR)/Makefile.moddir_rules +clean:: + rm -f confbridge/*.o confbridge/*.i + +$(if $(filter app_confbridge,$(EMBEDDED_MODS)),modules.link,app_confbridge.so): $(subst .c,.o,$(wildcard confbridge/*.c)) +$(subst .c,.o,$(wildcard confbridge/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,app_confbridge) + ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) LIBS+= -lres_features.so -lres_ael_share.so -lres_monitor.so -lres_speech.so LIBS+= -lres_smdi.so diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index e533c80e7..8e8682109 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -4,6 +4,7 @@ * Copyright (C) 2007-2008, Digium, Inc. * * Joshua Colp + * David Vossel * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -21,6 +22,7 @@ * \brief Conference Bridge application * * \author\verbatim Joshua Colp \endverbatim + * \author\verbatim David Vossel \endverbatim * * This is a conference bridge application utilizing the bridging core. * \ingroup applications @@ -38,69 +40,183 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/file.h" -#include "asterisk/logger.h" #include "asterisk/channel.h" #include "asterisk/pbx.h" +#include "asterisk/pbx.h" #include "asterisk/module.h" #include "asterisk/lock.h" -#include "asterisk/app.h" #include "asterisk/bridging.h" #include "asterisk/musiconhold.h" #include "asterisk/say.h" #include "asterisk/audiohook.h" #include "asterisk/astobj2.h" +#include "confbridge/include/confbridge.h" +#include "asterisk/paths.h" +#include "asterisk/manager.h" /*** DOCUMENTATION - - - Conference bridge application. - - - - The conference number - - - - - - - - - - - - - - - - - Enters the user into a specified conference bridge. The user can exit the conference by hangup only. - The join sound can be set using the CONFBRIDGE_JOIN_SOUND variable and the leave sound can be set using the CONFBRIDGE_LEAVE_SOUND variable. These can be unique to the caller. - This application will not automatically answer the channel. - - + + + Conference bridge application. + + + + The conference number + + + The bridge profile name from confbridge.conf. When left blank, a dynamically built bridge profile created by the CONFBRIDGE dialplan function is searched for on the channel and used. If no dynamic profile is present, the 'default_bridge' profile found in confbridge.conf is used. + It is important to note that while user profiles may be unique for each participant, mixing bridge profiles on a single conference is _NOT_ recommended and will produce undefined results. + + + The user profile name from confbridge.conf. When left blank, a dynamically built user profile created by the CONFBRIDGE dialplan function is searched for on the channel and used. If no dynamic profile is present, the 'default_user' profile found in confbridge.conf is used. + + + The name of the DTMF menu in confbridge.conf to be applied to this channel. No menu is applied by default if this option is left blank. + + + + Enters the user into a specified conference bridge. The user can exit the conference by hangup or DTMF menu option. + + + + + Set a custom dynamic bridge and user profile on a channel for the ConfBridge application using the same options defined in confbridge.conf. + + + + Type refers to which type of profile the option belongs too. Type can be bridge or user. + + + Option refers to confbridge.conf option that is being set dynamically on this channel. + + + + ---- Example 1 ---- + In this example the custom set user profile on this channel will automatically be used by the ConfBridge app. + exten => 1,1,Answer() + exten => 1,n,Set(CONFBRIDGE(user,announce_join_leave)=yes) + exten => 1,n,Set(CONFBRIDGE(user,startmuted)=yes) + exten => 1,n,ConfBridge(1) + ---- Example 2 ---- + This example shows how to use a predefined user or bridge profile in confbridge.conf as a template for a dynamic profile. Here we make a admin/marked user out of the default_user profile that is already defined in confbridge.conf. + exten => 1,1,Answer() + exten => 1,n,Set(CONFBRIDGE(user,template)=default_user) + exten => 1,n,Set(CONFBRIDGE(user,admin)=yes) + exten => 1,n,Set(CONFBRIDGE(user,marked)=yes) + exten => 1,n,ConfBridge(1) + + + + + List participants in a conference. + + + + + Conference number. + + + + Lists all users in a particular ConfBridge conference. + ConfbridgeList will follow as separate events, followed by a final event called + ConfbridgeListComplete. + + + + + List active conferences. + + + + + + Lists data about all active conferences. + ConfbridgeListRooms will follow as separate events, followed by a final event called + ConfbridgeListRoomsComplete. + + + + + Mute a Confbridge user. + + + + + + + + + + + + Unmute a Confbridge user. + + + + + + + + + + + + Kick a Confbridge user. + + + + + + + + + + + + Lock a Confbridge conference. + + + + + + + + + + + Unlock a Confbridge conference. + + + + + + + + + + + Start recording a Confbridge conference. + + + + + + + + Start recording a conference. If recording is already present an error will be returned. If RecordFile is not provided, the default record file specified in the conference's bridge profile will be used, if that is not present either a file will automatically be generated in the monitor directory. + + + + + Stop recording a Confbridge conference. + + + + + + + + ***/ /*! @@ -115,69 +231,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") static const char app[] = "ConfBridge"; -enum { - OPTION_ADMIN = (1 << 0), /*!< Set if the caller is an administrator */ - OPTION_MENU = (1 << 1), /*!< Set if the caller should have access to the conference bridge IVR menu */ - OPTION_MUSICONHOLD = (1 << 2), /*!< Set if music on hold should be played if nobody else is in the conference bridge */ - OPTION_NOONLYPERSON = (1 << 3), /*!< Set if the "you are currently the only person in this conference" sound file should not be played */ - OPTION_STARTMUTED = (1 << 4), /*!< Set if the caller should be initially set muted */ - OPTION_ANNOUNCEUSERCOUNT = (1 << 5), /*!< Set if the number of users should be announced to the caller */ - OPTION_MARKEDUSER = (1 << 6), /*!< Set if the caller is a marked user */ - OPTION_WAITMARKED = (1 << 7), /*!< Set if the conference must wait for a marked user before starting */ - OPTION_QUIET = (1 << 8), /*!< Set if no audio prompts should be played */ -}; - -enum { - OPTION_MUSICONHOLD_CLASS, /*!< If the 'M' option is set, the music on hold class to play */ - /*This must be the last element */ - OPTION_ARRAY_SIZE, -}; - -AST_APP_OPTIONS(app_opts,{ - AST_APP_OPTION('A', OPTION_MARKEDUSER), - AST_APP_OPTION('a', OPTION_ADMIN), - AST_APP_OPTION('c', OPTION_ANNOUNCEUSERCOUNT), - AST_APP_OPTION('m', OPTION_STARTMUTED), - AST_APP_OPTION_ARG('M', OPTION_MUSICONHOLD, OPTION_MUSICONHOLD_CLASS), - AST_APP_OPTION('1', OPTION_NOONLYPERSON), - AST_APP_OPTION('s', OPTION_MENU), - AST_APP_OPTION('w', OPTION_WAITMARKED), - AST_APP_OPTION('q', OPTION_QUIET), -}); - -/* Maximum length of a conference bridge name */ -#define MAX_CONF_NAME 32 - /* Number of buckets our conference bridges container can have */ #define CONFERENCE_BRIDGE_BUCKETS 53 -/*! \brief The structure that represents a conference bridge */ -struct conference_bridge { - char name[MAX_CONF_NAME]; /*!< Name of the conference bridge */ - struct ast_bridge *bridge; /*!< Bridge structure doing the mixing */ - unsigned int users; /*!< Number of users present */ - unsigned int markedusers; /*!< Number of marked users present */ - unsigned int locked:1; /*!< Is this conference bridge locked? */ - AST_LIST_HEAD_NOLOCK(, conference_bridge_user) users_list; /*!< List of users participating in the conference bridge */ - struct ast_channel *playback_chan; /*!< Channel used for playback into the conference bridge */ - ast_mutex_t playback_lock; /*!< Lock used for playback channel */ -}; - -/*! \brief The structure that represents a conference bridge user */ -struct conference_bridge_user { - struct conference_bridge *conference_bridge; /*!< Conference bridge they are participating in */ - struct ast_channel *chan; /*!< Asterisk channel participating */ - struct ast_flags flags; /*!< Flags passed in when the application was called */ - char *opt_args[OPTION_ARRAY_SIZE]; /*!< Arguments to options passed when application was called */ - struct ast_bridge_features features; /*!< Bridge features structure */ - unsigned int kicked:1; /*!< User has been kicked from the conference */ - AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */ -}; - /*! \brief Container to hold all conference bridges in progress */ static struct ao2_container *conference_bridges; static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename); +static int play_sound_number(struct conference_bridge *conference_bridge, int say_number); +static int execute_menu_entry(struct conference_bridge *conference_bridge, + struct conference_bridge_user *conference_bridge_user, + struct ast_bridge_channel *bridge_channel, + struct conf_menu_entry *menu_entry, + struct conf_menu *menu); /*! \brief Hashing function used for conference bridges container */ static int conference_bridge_hash_cb(const void *obj, const int flags) @@ -193,34 +259,320 @@ static int conference_bridge_cmp_cb(void *obj, void *arg, int flags) return (!strcasecmp(conference_bridge0->name, conference_bridge1->name) ? CMP_MATCH | CMP_STOP : 0); } +const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds) +{ + switch (sound) { + case CONF_SOUND_HAS_JOINED: + return S_OR(custom_sounds->hasjoin, "conf-hasjoin"); + case CONF_SOUND_HAS_LEFT: + return S_OR(custom_sounds->hasleft, "conf-hasleft"); + case CONF_SOUND_KICKED: + return S_OR(custom_sounds->kicked, "conf-kicked"); + case CONF_SOUND_MUTED: + return S_OR(custom_sounds->muted, "conf-muted"); + case CONF_SOUND_UNMUTED: + return S_OR(custom_sounds->unmuted, "conf-unmuted"); + case CONF_SOUND_ONLY_ONE: + return S_OR(custom_sounds->onlyone, "conf-onlyone"); + case CONF_SOUND_THERE_ARE: + return S_OR(custom_sounds->thereare, "conf-thereare"); + case CONF_SOUND_OTHER_IN_PARTY: + return S_OR(custom_sounds->otherinparty, "conf-otherinparty"); + case CONF_SOUND_PLACE_IN_CONF: + return S_OR(custom_sounds->placeintoconf, "conf-placeintoconf"); + case CONF_SOUND_WAIT_FOR_LEADER: + return S_OR(custom_sounds->waitforleader, "conf-waitforleader"); + case CONF_SOUND_LEADER_HAS_LEFT: + return S_OR(custom_sounds->leaderhasleft, "conf-leaderhasleft"); + case CONF_SOUND_GET_PIN: + return S_OR(custom_sounds->getpin, "conf-getpin"); + case CONF_SOUND_INVALID_PIN: + return S_OR(custom_sounds->invalidpin, "conf-invalidpin"); + case CONF_SOUND_ONLY_PERSON: + return S_OR(custom_sounds->onlyperson, "conf-onlyperson"); + case CONF_SOUND_LOCKED: + return S_OR(custom_sounds->locked, "conf-locked"); + case CONF_SOUND_LOCKED_NOW: + return S_OR(custom_sounds->lockednow, "conf-lockednow"); + case CONF_SOUND_UNLOCKED_NOW: + return S_OR(custom_sounds->unlockednow, "conf-unlockednow"); + case CONF_SOUND_ERROR_MENU: + return S_OR(custom_sounds->errormenu, "conf-errormenu"); + case CONF_SOUND_JOIN: + return S_OR(custom_sounds->join, "beep"); + case CONF_SOUND_LEAVE: + return S_OR(custom_sounds->leave, "beeperr"); + } + + return ""; +} + +static struct ast_frame *rec_read(struct ast_channel *ast) +{ + return &ast_null_frame; +} +static int rec_write(struct ast_channel *ast, struct ast_frame *f) +{ + return 0; +} +static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, void *data, int *cause); +static struct ast_channel_tech record_tech = { + .type = "ConfBridgeRec", + .description = "Conference Bridge Recording Channel", + .requester = rec_request, + .read = rec_read, + .write = rec_write, +}; +static struct ast_channel *rec_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, void *data, int *cause) +{ + struct ast_channel *tmp; + struct ast_format fmt; + const char *conf_name = data; + if (!(tmp = ast_channel_alloc(1, AST_STATE_UP, 0, 0, "", "", "", NULL, 0, + "ConfBridgeRecorder/conf-%s-uid-%d", + conf_name, + (int) ast_random()))) { + return NULL; + } + ast_format_set(&fmt, AST_FORMAT_SLINEAR, 0); + tmp->tech = &record_tech; + ast_format_cap_add_all(tmp->nativeformats); + ast_format_copy(&tmp->writeformat, &fmt); + ast_format_copy(&tmp->rawwriteformat, &fmt); + ast_format_copy(&tmp->readformat, &fmt); + ast_format_copy(&tmp->rawreadformat, &fmt); + return tmp; +} + +static void *record_thread(void *obj) +{ + struct conference_bridge *conference_bridge = obj; + struct ast_app *mixmonapp = pbx_findapp("MixMonitor"); + struct ast_channel *chan; + struct ast_str *filename = ast_str_alloca(PATH_MAX); + + if (!mixmonapp) { + ao2_ref(conference_bridge, -1); + return NULL; + } + + ao2_lock(conference_bridge); + if (!(conference_bridge->record_chan)) { + conference_bridge->record_thread = AST_PTHREADT_NULL; + ao2_unlock(conference_bridge); + ao2_ref(conference_bridge, -1); + return NULL; + } + chan = ast_channel_ref(conference_bridge->record_chan); + + if (!(ast_strlen_zero(conference_bridge->b_profile.rec_file))) { + ast_str_append(&filename, 0, "%s", conference_bridge->b_profile.rec_file); + } else { + time_t now; + time(&now); + ast_str_append(&filename, 0, "confbridge-%s-%u.wav", + conference_bridge->name, + (unsigned int) now); + } + ao2_unlock(conference_bridge); + + ast_answer(chan); + pbx_exec(chan, mixmonapp, ast_str_buffer(filename)); + ast_bridge_join(conference_bridge->bridge, chan, NULL, NULL, NULL); + + ao2_lock(conference_bridge); + conference_bridge->record_thread = AST_PTHREADT_NULL; + ao2_unlock(conference_bridge); + + ast_hangup(chan); /* This will eat this threads reference to the channel as well */ + ao2_ref(conference_bridge, -1); + return NULL; +} + +/*! + * \internal + * \brief Returns whether or not conference is being recorded. + * \retval 1, conference is recording. + * \retval 0, conference is NOT recording. + */ +static int conf_is_recording(struct conference_bridge *conference_bridge) +{ + int res = 0; + ao2_lock(conference_bridge); + if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) { + res = 1; + } + ao2_unlock(conference_bridge); + return res; +} + +/*! + * \internal + * \brief Stops the confbridge recording thread. + * + * \note do not call this function with any locks + */ +static int conf_stop_record(struct conference_bridge *conference_bridge) +{ + ao2_lock(conference_bridge); + + if (conference_bridge->record_thread != AST_PTHREADT_NULL) { + struct ast_channel *chan = ast_channel_ref(conference_bridge->record_chan); + pthread_t thread = conference_bridge->record_thread; + ao2_unlock(conference_bridge); + + ast_bridge_remove(conference_bridge->bridge, chan); + ast_queue_frame(chan, &ast_null_frame); + + chan = ast_channel_unref(chan); + pthread_join(thread, NULL); + + ao2_lock(conference_bridge); + } + + /* this is the reference given to the channel during the channel alloc */ + if (conference_bridge->record_chan) { + conference_bridge->record_chan = ast_channel_unref(conference_bridge->record_chan); + } + + ao2_unlock(conference_bridge); + return 0; +} + +static int conf_start_record(struct conference_bridge *conference_bridge) +{ + struct ast_format_cap *cap = ast_format_cap_alloc_nolock(); + struct ast_format tmpfmt; + int cause; + + ao2_lock(conference_bridge); + if (conference_bridge->record_chan || conference_bridge->record_thread != AST_PTHREADT_NULL) { + ao2_unlock(conference_bridge); + return -1; /* already recording */ + } + if (!cap) { + ao2_unlock(conference_bridge); + return -1; + } + if (!pbx_findapp("MixMonitor")) { + ast_log(LOG_WARNING, "Can not record ConfBridge, MixMonitor app is not installed\n"); + cap = ast_format_cap_destroy(cap); + ao2_unlock(conference_bridge); + return -1; + } + ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); + if (!(conference_bridge->record_chan = ast_request("ConfBridgeRec", cap, NULL, conference_bridge->name, &cause))) { + cap = ast_format_cap_destroy(cap); + ao2_unlock(conference_bridge); + return -1; + } + + cap = ast_format_cap_destroy(cap); + ao2_ref(conference_bridge, +1); /* give the record thread a ref */ + + if (ast_pthread_create_background(&conference_bridge->record_thread, NULL, record_thread, conference_bridge)) { + ast_log(LOG_WARNING, "Failed to create recording channel for conference %s\n", conference_bridge->name); + + ao2_unlock(conference_bridge); + ao2_ref(conference_bridge, -1); /* error so remove ref */ + return -1; + } + + ao2_unlock(conference_bridge); + return 0; +} + +static void send_conf_start_event(const char *conf_name) +{ + manager_event(EVENT_FLAG_CALL, "ConfbridgeStart", "Conference: %s\r\n", conf_name); +} + +static void send_conf_end_event(const char *conf_name) +{ + manager_event(EVENT_FLAG_CALL, "ConfbridgeEnd", "Conference: %s\r\n", conf_name); +} + +static void send_join_event(struct ast_channel *chan, const char *conf_name) +{ + ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeJoin", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Conference: %s\r\n" + "CallerIDnum: %s\r\n" + "CallerIDname: %s\r\n", + chan->name, + chan->uniqueid, + conf_name, + S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, ""), + S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, "") + ); +} + +static void send_leave_event(struct ast_channel *chan, const char *conf_name) +{ + ast_manager_event(chan, EVENT_FLAG_CALL, "ConfbridgeLeave", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Conference: %s\r\n" + "CallerIDnum: %s\r\n" + "CallerIDname: %s\r\n", + chan->name, + chan->uniqueid, + conf_name, + S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, ""), + S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, "") + ); +} + /*! * \brief Announce number of users in the conference bridge to the caller * * \param conference_bridge Conference bridge to peek at - * \param conference_bridge_user Caller + * \param (OPTIONAL) conference_bridge_user Caller * + * \note if caller is NULL, the announcment will be sent to all participants in the conference. * \return Returns nothing */ static void announce_user_count(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) { + const char *other_in_party = conf_get_sound(CONF_SOUND_OTHER_IN_PARTY, conference_bridge->b_profile.sounds); + const char *only_one = conf_get_sound(CONF_SOUND_ONLY_ONE, conference_bridge->b_profile.sounds); + const char *there_are = conf_get_sound(CONF_SOUND_THERE_ARE, conference_bridge->b_profile.sounds); + if (conference_bridge->users == 1) { /* Awww we are the only person in the conference bridge */ return; } else if (conference_bridge->users == 2) { - /* Eep, there is one other person */ - if (ast_stream_and_wait(conference_bridge_user->chan, "conf-onlyone", "")) { - return; + if (conference_bridge_user) { + /* Eep, there is one other person */ + if (ast_stream_and_wait(conference_bridge_user->chan, + only_one, + "")) { + return; + } + } else { + play_sound_file(conference_bridge, only_one); } } else { /* Alas multiple others in here */ - if (ast_stream_and_wait(conference_bridge_user->chan, "conf-thereare", "")) { - return; - } - if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", conference_bridge_user->chan->language, NULL)) { - return; - } - if (ast_stream_and_wait(conference_bridge_user->chan, "conf-otherinparty", "")) { - return; + if (conference_bridge_user) { + if (ast_stream_and_wait(conference_bridge_user->chan, + there_are, + "")) { + return; + } + if (ast_say_number(conference_bridge_user->chan, conference_bridge->users - 1, "", conference_bridge_user->chan->language, NULL)) { + return; + } + if (ast_stream_and_wait(conference_bridge_user->chan, + other_in_party, + "")) { + return; + } + } else { + play_sound_file(conference_bridge, there_are); + play_sound_number(conference_bridge, conference_bridge->users - 1); + play_sound_file(conference_bridge, other_in_party); } } } @@ -253,7 +605,7 @@ static void play_prompt_to_channel(struct conference_bridge *conference_bridge, */ static void post_join_marked(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) { - if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) { struct conference_bridge_user *other_conference_bridge_user = NULL; /* If we are not the first marked user to join just bail out now */ @@ -266,17 +618,18 @@ static void post_join_marked(struct conference_bridge *conference_bridge, struct if (other_conference_bridge_user == conference_bridge_user) { continue; } - if (ast_test_flag(&other_conference_bridge_user->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan)) { + if (ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_conference_bridge_user->chan)) { ast_moh_stop(other_conference_bridge_user->chan); ast_bridge_unsuspend(conference_bridge->bridge, other_conference_bridge_user->chan); } } /* Next play the audio file stating they are going to be placed into the conference */ - if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { + if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) { ao2_unlock(conference_bridge); ast_autoservice_start(conference_bridge_user->chan); - play_sound_file(conference_bridge, "conf-placeintoconf"); + play_sound_file(conference_bridge, + conf_get_sound(CONF_SOUND_PLACE_IN_CONF, conference_bridge_user->b_profile.sounds)); ast_autoservice_stop(conference_bridge_user->chan); ao2_lock(conference_bridge); } @@ -286,7 +639,10 @@ static void post_join_marked(struct conference_bridge *conference_bridge, struct if (other_conference_bridge_user == conference_bridge_user) { continue; } - other_conference_bridge_user->features.mute = 0; + /* only unmute them if they are not supposed to start muted */ + if (!ast_test_flag(&other_conference_bridge_user->u_profile, USER_OPT_STARTMUTED)) { + other_conference_bridge_user->features.mute = 0; + } } } else { @@ -297,15 +653,17 @@ static void post_join_marked(struct conference_bridge *conference_bridge, struct /* Be sure we are muted so we can't talk to anybody else waiting */ conference_bridge_user->features.mute = 1; /* If we have not been quieted play back that they are waiting for the leader */ - if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { - play_prompt_to_channel(conference_bridge, conference_bridge_user->chan, "conf-waitforleader"); + if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) { + play_prompt_to_channel(conference_bridge, + conference_bridge_user->chan, + conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, conference_bridge_user->b_profile.sounds)); } /* Start music on hold if needed */ /* We need to recheck the markedusers value here. play_prompt_to_channel unlocks the conference bridge, potentially * allowing a marked user to enter while the prompt was playing */ - if (!conference_bridge->markedusers && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { - ast_moh_start(conference_bridge_user->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); + if (!conference_bridge->markedusers && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL); } } } @@ -323,21 +681,23 @@ static void post_join_unmarked(struct conference_bridge *conference_bridge, stru /* Play back audio prompt and start MOH if need be if we are the first participant */ if (conference_bridge->users == 1) { /* If audio prompts have not been quieted or this prompt quieted play it on out */ - if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET | OPTION_NOONLYPERSON)) { - play_prompt_to_channel(conference_bridge, conference_bridge_user->chan, "conf-onlyperson"); + if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) { + play_prompt_to_channel(conference_bridge, + conference_bridge_user->chan, + conf_get_sound(CONF_SOUND_ONLY_PERSON, conference_bridge_user->b_profile.sounds)); } /* If we need to start music on hold on the channel do so now */ /* We need to re-check the number of users in the conference bridge here because another conference bridge * participant could have joined while the above prompt was playing for the first user. */ - if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { - ast_moh_start(conference_bridge_user->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); + if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + ast_moh_start(conference_bridge_user->chan, conference_bridge_user->u_profile.moh_class, NULL); } return; } /* Announce number of users if need be */ - if (ast_test_flag(&conference_bridge_user->flags, OPTION_ANNOUNCEUSERCOUNT)) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNT)) { ao2_unlock(conference_bridge); announce_user_count(conference_bridge, conference_bridge_user); ao2_lock(conference_bridge); @@ -348,11 +708,18 @@ static void post_join_unmarked(struct conference_bridge *conference_bridge, stru struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list); /* Temporarily suspend the above participant from the bridge so we have control to stop MOH if needed */ - if (ast_test_flag(&first_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { + if (ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { ast_moh_stop(first_participant->chan); ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan); } } + + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ANNOUNCEUSERCOUNTALL) && + (conference_bridge->users > conference_bridge_user->u_profile.announce_user_count_all_after)) { + ao2_unlock(conference_bridge); + announce_user_count(conference_bridge, NULL); + ao2_lock(conference_bridge); + } } /*! @@ -382,6 +749,7 @@ static void destroy_conference_bridge(void *obj) ast_bridge_destroy(conference_bridge->bridge); conference_bridge->bridge = NULL; } + conf_bridge_profile_destroy(&conference_bridge->b_profile); } /*! @@ -396,6 +764,8 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct { struct conference_bridge *conference_bridge = NULL; struct conference_bridge tmp; + int start_record = 0; + int max_members_reached = 0; ast_copy_string(tmp.name, name, sizeof(tmp.name)); @@ -407,12 +777,18 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct /* Attempt to find an existing conference bridge */ conference_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (conference_bridge && conference_bridge->b_profile.max_members) { + max_members_reached = conference_bridge->b_profile.max_members > conference_bridge->users ? 0 : 1; + } + /* When finding a conference bridge that already exists make sure that it is not locked, and if so that we are not an admin */ - if (conference_bridge && conference_bridge->locked && !ast_test_flag(&conference_bridge_user->flags, OPTION_ADMIN)) { + if (conference_bridge && (max_members_reached || conference_bridge->locked) && !ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN)) { ao2_unlock(conference_bridges); ao2_ref(conference_bridge, -1); ast_debug(1, "Conference bridge '%s' is locked and caller is not an admin\n", name); - ast_stream_and_wait(conference_bridge_user->chan, "conf-locked", ""); + ast_stream_and_wait(conference_bridge_user->chan, + conf_get_sound(CONF_SOUND_LOCKED, conference_bridge_user->b_profile.sounds), + ""); return NULL; } @@ -426,10 +802,12 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct } /* Setup conference bridge parameters */ + conference_bridge->record_thread = AST_PTHREADT_NULL; ast_copy_string(conference_bridge->name, name, sizeof(conference_bridge->name)); + conf_bridge_profile_copy(&conference_bridge->b_profile, &conference_bridge_user->b_profile); /* Create an actual bridge that will do the audio mixing */ - if (!(conference_bridge->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_SMART))) { + if (!(conference_bridge->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_MULTIMIX, 0))) { ao2_ref(conference_bridge, -1); conference_bridge = NULL; ao2_unlock(conference_bridges); @@ -437,12 +815,19 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct return NULL; } + /* Set the internal sample rate on the bridge from the bridge profile */ + ast_bridge_set_internal_sample_rate(conference_bridge->bridge, conference_bridge->b_profile.internal_sample_rate); + /* Set the internal mixing interval on the bridge from the bridge profile */ + ast_bridge_set_mixing_interval(conference_bridge->bridge, conference_bridge->b_profile.mix_interval); + /* Setup lock for playback channel */ ast_mutex_init(&conference_bridge->playback_lock); /* Link it into the conference bridges container */ ao2_link(conference_bridges, conference_bridge); + + send_conf_start_event(conference_bridge->name); ast_debug(1, "Created conference bridge '%s' and linked to container '%p'\n", name, conference_bridges); } @@ -460,7 +845,7 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct conference_bridge->users++; /* If the caller is a marked user bump up the count */ - if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) { conference_bridge->markedusers++; } @@ -470,14 +855,23 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct } /* If the caller is a marked user or is waiting for a marked user to enter pass 'em off, otherwise pass them off to do regular joining stuff */ - if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER | OPTION_WAITMARKED)) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER | USER_OPT_WAITMARKED)) { post_join_marked(conference_bridge, conference_bridge_user); } else { post_join_unmarked(conference_bridge, conference_bridge_user); } + /* check to see if recording needs to be started or not */ + if (ast_test_flag(&conference_bridge->b_profile, BRIDGE_OPT_RECORD_CONFERENCE) && !conf_is_recording(conference_bridge)) { + start_record = 1; + } + ao2_unlock(conference_bridge); + if (start_record) { + conf_start_record(conference_bridge); + } + return conference_bridge; } @@ -488,12 +882,12 @@ static struct conference_bridge *join_conference_bridge(const char *name, struct * \param conference_bridge_user The conference bridge user structure * */ -static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) +static void leave_conference_bridge(struct conference_bridge *conference_bridge, struct conference_bridge_user *conference_bridge_user) { ao2_lock(conference_bridge); /* If this caller is a marked user bump down the count */ - if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER)) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER)) { conference_bridge->markedusers--; } @@ -505,7 +899,7 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge /* If there are still users in the conference bridge we may need to do things (such as start MOH on them) */ if (conference_bridge->users) { - if (ast_test_flag(&conference_bridge_user->flags, OPTION_MARKEDUSER) && !conference_bridge->markedusers) { + if (ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MARKEDUSER) && !conference_bridge->markedusers) { struct conference_bridge_user *other_participant = NULL; /* Start out with muting everyone */ @@ -514,18 +908,22 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge } /* Play back the audio prompt saying the leader has left the conference */ - if (!ast_test_flag(&conference_bridge_user->flags, OPTION_QUIET)) { + if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_QUIET)) { ao2_unlock(conference_bridge); ast_autoservice_start(conference_bridge_user->chan); - play_sound_file(conference_bridge, "conf-leaderhasleft"); + play_sound_file(conference_bridge, + conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, conference_bridge_user->b_profile.sounds)); ast_autoservice_stop(conference_bridge_user->chan); ao2_lock(conference_bridge); } - /* Now on to starting MOH if needed */ + /* Now on to starting MOH or kick if needed */ AST_LIST_TRAVERSE(&conference_bridge->users_list, other_participant, list) { - if (ast_test_flag(&other_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_participant->chan)) { - ast_moh_start(other_participant->chan, other_participant->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); + if (ast_test_flag(&other_participant->u_profile, USER_OPT_ENDMARKED)) { + other_participant->kicked = 1; + ast_bridge_remove(conference_bridge->bridge, other_participant->chan); + } else if (ast_test_flag(&other_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, other_participant->chan)) { + ast_moh_start(other_participant->chan, other_participant->u_profile.moh_class, NULL); ast_bridge_unsuspend(conference_bridge->bridge, other_participant->chan); } } @@ -533,8 +931,8 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge /* Of course if there is one other person in here we may need to start up MOH on them */ struct conference_bridge_user *first_participant = AST_LIST_FIRST(&conference_bridge->users_list); - if (ast_test_flag(&first_participant->flags, OPTION_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { - ast_moh_start(first_participant->chan, first_participant->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); + if (ast_test_flag(&first_participant->u_profile, USER_OPT_MUSICONHOLD) && !ast_bridge_suspend(conference_bridge->bridge, first_participant->chan)) { + ast_moh_start(first_participant->chan, first_participant->u_profile.moh_class, NULL); ast_bridge_unsuspend(conference_bridge->bridge, first_participant->chan); } } @@ -543,14 +941,87 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge ast_devstate_changed(AST_DEVICE_NOT_INUSE, "confbridge:%s", conference_bridge->name); ao2_unlink(conference_bridges, conference_bridge); + send_conf_end_event(conference_bridge->name); } /* Done mucking with the conference bridge, huzzah */ ao2_unlock(conference_bridge); + if (!conference_bridge->users) { + conf_stop_record(conference_bridge); + } + ao2_ref(conference_bridge, -1); } +/*! + * \internal + * \brief allocates playback chan on a channel + * \pre expects conference to be locked before calling this function + */ +static int alloc_playback_chan(struct conference_bridge *conference_bridge) +{ + int cause; + struct ast_format_cap *cap; + struct ast_format tmpfmt; + + if (conference_bridge->playback_chan) { + return 0; + } + if (!(cap = ast_format_cap_alloc_nolock())) { + return -1; + } + ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); + if (!(conference_bridge->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) { + cap = ast_format_cap_destroy(cap); + return -1; + } + cap = ast_format_cap_destroy(cap); + + conference_bridge->playback_chan->bridge = conference_bridge->bridge; + + if (ast_call(conference_bridge->playback_chan, "", 0)) { + ast_hangup(conference_bridge->playback_chan); + conference_bridge->playback_chan = NULL; + return -1; + } + + ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name); + return 0; +} + +static int play_sound_helper(struct conference_bridge *conference_bridge, const char *filename, int say_number) +{ + struct ast_channel *underlying_channel; + + ast_mutex_lock(&conference_bridge->playback_lock); + if (!(conference_bridge->playback_chan)) { + if (alloc_playback_chan(conference_bridge)) { + ast_mutex_unlock(&conference_bridge->playback_lock); + return -1; + } + underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); + } else { + /* Channel was already available so we just need to add it back into the bridge */ + underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); + ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL); + } + + /* The channel is all under our control, in goes the prompt */ + if (!ast_strlen_zero(filename)) { + ast_stream_and_wait(conference_bridge->playback_chan, filename, ""); + } else { + ast_say_number(conference_bridge->playback_chan, say_number, "", conference_bridge->playback_chan->language, NULL); + } + + ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", underlying_channel->name, conference_bridge->bridge); + ast_bridge_depart(conference_bridge->bridge, underlying_channel); + + ast_mutex_unlock(&conference_bridge->playback_lock); + + return 0; +} + /*! * \brief Play sound file into conference bridge * @@ -562,161 +1033,154 @@ static void leave_conference_bridge(struct conference_bridge *conference_bridge */ static int play_sound_file(struct conference_bridge *conference_bridge, const char *filename) { - struct ast_channel *underlying_channel; - - ast_mutex_lock(&conference_bridge->playback_lock); - - if (!(conference_bridge->playback_chan)) { - int cause; - struct ast_format_cap *cap = ast_format_cap_alloc_nolock(); - struct ast_format tmpfmt; - if (!cap) { - return -1; - } - ast_format_cap_add(cap, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0)); - if (!(conference_bridge->playback_chan = ast_request("Bridge", cap, NULL, "", &cause))) { - ast_mutex_unlock(&conference_bridge->playback_lock); - cap = ast_format_cap_destroy(cap); - return -1; - } - cap = ast_format_cap_destroy(cap); - - conference_bridge->playback_chan->bridge = conference_bridge->bridge; - - if (ast_call(conference_bridge->playback_chan, "", 0)) { - ast_hangup(conference_bridge->playback_chan); - conference_bridge->playback_chan = NULL; - ast_mutex_unlock(&conference_bridge->playback_lock); - return -1; - } - - ast_debug(1, "Created a playback channel to conference bridge '%s'\n", conference_bridge->name); - - underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); - } else { - /* Channel was already available so we just need to add it back into the bridge */ - underlying_channel = conference_bridge->playback_chan->tech->bridged_channel(conference_bridge->playback_chan, NULL); - ast_bridge_impart(conference_bridge->bridge, underlying_channel, NULL, NULL); - } - - /* The channel is all under our control, in goes the prompt */ - ast_stream_and_wait(conference_bridge->playback_chan, filename, ""); - - ast_debug(1, "Departing underlying channel '%s' from bridge '%p'\n", underlying_channel->name, conference_bridge->bridge); - ast_bridge_depart(conference_bridge->bridge, underlying_channel); - - ast_mutex_unlock(&conference_bridge->playback_lock); - - return 0; + return play_sound_helper(conference_bridge, filename, 0); } /*! - * \brief DTMF Menu Callback + * \brief Play number into the conference bridge * - * \param bridge Bridge this is involving - * \param bridge_channel Bridged channel this is involving - * \param hook_pvt User's conference bridge structure + * \param conference_bridge The conference bridge to say the number into + * \param number to say * * \retval 0 success * \retval -1 failure */ -static int menu_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +static int play_sound_number(struct conference_bridge *conference_bridge, int say_number) { - struct conference_bridge_user *conference_bridge_user = hook_pvt; - struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge; - int digit, res = 0, isadmin = ast_test_flag(&conference_bridge_user->flags, OPTION_ADMIN); + return play_sound_helper(conference_bridge, NULL, say_number); +} - /* See if music on hold is playing */ - ao2_lock(conference_bridge); - if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { - /* Just us so MOH is probably indeed going, let's stop it */ - ast_moh_stop(bridge_channel->chan); - } - ao2_unlock(conference_bridge); +static void conf_handle_talker_destructor(void *pvt_data) +{ + ast_free(pvt_data); +} - /* Try to play back the user menu, if it fails pass this back up so the bridging core will act on it */ - if (ast_streamfile(bridge_channel->chan, (isadmin ? "conf-adminmenu" : "conf-usermenu"), bridge_channel->chan->language)) { - res = -1; - goto finished; +static void conf_handle_talker_cb(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data) +{ + char *conf_name = pvt_data; + int talking; + + switch (bridge_channel->state) { + case AST_BRIDGE_CHANNEL_STATE_START_TALKING: + talking = 1; + break; + case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING: + talking = 0; + break; + default: + return; /* uhh this shouldn't happen, but bail if it does. */ } - /* Wait for them to enter a digit from the user menu options */ - digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY); - ast_stopstream(bridge_channel->chan); + /* notify AMI someone is has either started or stopped talking */ + ast_manager_event(bridge_channel->chan, EVENT_FLAG_CALL, "ConfbridgeTalking", + "Channel: %s\r\n" + "Uniqueid: %s\r\n" + "Conference: %s\r\n" + "TalkingStatus: %s\r\n", + bridge_channel->chan->name, bridge_channel->chan->uniqueid, conf_name, talking ? "on" : "off"); +} - if (digit == '1') { - /* 1 - Mute or unmute yourself, note we only allow manipulation if they aren't waiting for a marked user or if marked users exist */ - if (!ast_test_flag(&conference_bridge_user->flags, OPTION_WAITMARKED) || conference_bridge->markedusers) { - conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0); +static int conf_get_pin(struct ast_channel *chan, struct conference_bridge_user *conference_bridge_user) +{ + char pin_guess[MAX_PIN+1] = { 0, }; + const char *pin = conference_bridge_user->u_profile.pin; + char *tmp = pin_guess; + int i, res; + unsigned int len = MAX_PIN ; + + /* give them three tries to get the pin right */ + for (i = 0; i < 3; i++) { + if (ast_app_getdata(chan, + conf_get_sound(CONF_SOUND_GET_PIN, conference_bridge_user->b_profile.sounds), + tmp, len, 0) >= 0) { + if (!strcasecmp(pin, pin_guess)) { + return 0; + } } - res = ast_stream_and_wait(bridge_channel->chan, (conference_bridge_user->features.mute ? "conf-muted" : "conf-unmuted"), ""); - } else if (isadmin && digit == '2') { - /* 2 - Unlock or lock conference */ - conference_bridge->locked = (!conference_bridge->locked ? 1 : 0); - res = ast_stream_and_wait(bridge_channel->chan, (conference_bridge->locked ? "conf-lockednow" : "conf-unlockednow"), ""); - } else if (isadmin && digit == '3') { - /* 3 - Eject last user */ - struct conference_bridge_user *last_participant = NULL; - - ao2_lock(conference_bridge); - if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user) || (ast_test_flag(&last_participant->flags, OPTION_ADMIN))) { - ao2_unlock(conference_bridge); - res = ast_stream_and_wait(bridge_channel->chan, "conf-errormenu", ""); + ast_streamfile(chan, + conf_get_sound(CONF_SOUND_INVALID_PIN, conference_bridge_user->b_profile.sounds), + chan->language); + res = ast_waitstream(chan, AST_DIGIT_ANY); + if (res > 0) { + /* Account for digit already read during ivalid pin playback + * resetting pin buf. */ + pin_guess[0] = res; + pin_guess[1] = '\0'; + tmp = pin_guess + 1; + len = MAX_PIN - 1; } else { - last_participant->kicked = 1; - ast_bridge_remove(conference_bridge->bridge, last_participant->chan); - ao2_unlock(conference_bridge); + /* reset pin buf as empty buffer. */ + tmp = pin_guess; + len = MAX_PIN; } - } else if (digit == '4') { - /* 4 - Decrease listening volume */ - ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, -1); - } else if (digit == '6') { - /* 6 - Increase listening volume */ - ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 1); - } else if (digit == '7') { - /* 7 - Decrease talking volume */ - ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, -1); - } else if (digit == '8') { - /* 8 - Exit the IVR */ - } else if (digit == '9') { - /* 9 - Increase talking volume */ - ast_audiohook_volume_adjust(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 1); - } else { - /* No valid option was selected */ - res = ast_stream_and_wait(bridge_channel->chan, "conf-errormenu", ""); } + return -1; +} - finished: - /* See if music on hold needs to be started back up again */ - ao2_lock(conference_bridge); - if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->flags, OPTION_MUSICONHOLD)) { - ast_moh_start(bridge_channel->chan, conference_bridge_user->opt_args[OPTION_MUSICONHOLD_CLASS], NULL); +static int conf_rec_name(struct conference_bridge_user *user, const char *conf_name) +{ + char destdir[PATH_MAX]; + int res; + int duration = 20; + + snprintf(destdir, sizeof(destdir), "%s/confbridge", ast_config_AST_SPOOL_DIR); + + if (ast_mkdir(destdir, 0777) != 0) { + ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", destdir, strerror(errno)); + return -1; } - ao2_unlock(conference_bridge); + snprintf(user->name_rec_location, sizeof(user->name_rec_location), + "%s/confbridge-name-%s-%s", destdir, + conf_name, user->chan->uniqueid); - bridge_channel->state = AST_BRIDGE_CHANNEL_STATE_WAIT; + res = ast_play_and_record(user->chan, + "vm-rec-name", + user->name_rec_location, + 10, + "sln", + &duration, + ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE), + 0, + NULL); - return res; + if (res == -1) { + user->name_rec_location[0] = '\0'; + return -1; + } + return 0; } /*! \brief The ConfBridge application */ static int confbridge_exec(struct ast_channel *chan, const char *data) { int res = 0, volume_adjustments[2]; + int quiet = 0; char *parse; + const char *b_profile_name = DEFAULT_BRIDGE_PROFILE; + const char *u_profile_name = DEFAULT_USER_PROFILE; struct conference_bridge *conference_bridge = NULL; struct conference_bridge_user conference_bridge_user = { .chan = chan, + .tech_args.talking_threshold = DEFAULT_TALKING_THRESHOLD, + .tech_args.silence_threshold = DEFAULT_SILENCE_THRESHOLD, + .tech_args.drop_silence = 0, }; - const char *tmp, *join_sound = NULL, *leave_sound = NULL; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(conf_name); - AST_APP_ARG(options); + AST_APP_ARG(b_profile_name); + AST_APP_ARG(u_profile_name); + AST_APP_ARG(menu_name); ); + ast_bridge_features_init(&conference_bridge_user.features); + + if (chan->_state != AST_STATE_UP) { + ast_answer(chan); + } if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "%s requires an argument (conference name[,options])\n", app); - return -1; + res = -1; /* invalid PIN */ + goto confbridge_cleanup; } /* We need to make a copy of the input string if we are going to modify it! */ @@ -724,54 +1188,146 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) AST_STANDARD_APP_ARGS(args, parse); - if (args.argc == 2) { - ast_app_parse_options(app_opts, &conference_bridge_user.flags, conference_bridge_user.opt_args, args.options); + /* bridge profile name */ + if (args.argc > 1 && !ast_strlen_zero(args.b_profile_name)) { + b_profile_name = args.b_profile_name; + } + conf_find_bridge_profile(chan, b_profile_name, &conference_bridge_user.b_profile); + + /* user profile name */ + if (args.argc > 2 && !ast_strlen_zero(args.u_profile_name)) { + u_profile_name = args.u_profile_name; + } + + conf_find_user_profile(chan, u_profile_name, &conference_bridge_user.u_profile); + quiet = ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_QUIET); + + /* ask for a PIN immediately after finding user profile. This has to be + * prompted for requardless of quiet setting. */ + if (!ast_strlen_zero(conference_bridge_user.u_profile.pin)) { + if (conf_get_pin(chan, &conference_bridge_user)) { + res = -1; /* invalid PIN */ + goto confbridge_cleanup; + } + } + + /* See if we need them to record a intro name */ + if (!quiet && ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE)) { + conf_rec_name(&conference_bridge_user, args.conf_name); + } + + /* menu name */ + if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) { + ast_copy_string(conference_bridge_user.menu_name, args.menu_name, sizeof(conference_bridge_user.menu_name)); + if (conf_set_menu_to_user(conference_bridge_user.menu_name, &conference_bridge_user)) { + ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n", + args.menu_name); + res = -1; /* invalid PIN */ + goto confbridge_cleanup; + } + } + + /* Set if DTMF should pass through for this user or not */ + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DTMF_PASS)) { + conference_bridge_user.features.dtmf_passthrough = 1; + } + + /* Set dsp threshold values if present */ + if (conference_bridge_user.u_profile.talking_threshold) { + conference_bridge_user.tech_args.talking_threshold = conference_bridge_user.u_profile.talking_threshold; + } + if (conference_bridge_user.u_profile.silence_threshold) { + conference_bridge_user.tech_args.silence_threshold = conference_bridge_user.u_profile.silence_threshold; + } + + /* Set a talker indicate call back if talking detection is requested */ + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_TALKER_DETECT)) { + char *conf_name = ast_strdup(args.conf_name); /* this is freed during feature cleanup */ + if (!(conf_name)) { + res = -1; /* invalid PIN */ + goto confbridge_cleanup; + } + ast_bridge_features_set_talk_detector(&conference_bridge_user.features, + conf_handle_talker_cb, + conf_handle_talker_destructor, + conf_name); } /* Look for a conference bridge matching the provided name */ if (!(conference_bridge = join_conference_bridge(args.conf_name, &conference_bridge_user))) { - return -1; + res = -1; /* invalid PIN */ + goto confbridge_cleanup; } /* Keep a copy of volume adjustments so we can restore them later if need be */ volume_adjustments[0] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_READ); volume_adjustments[1] = ast_audiohook_volume_get(chan, AST_AUDIOHOOK_DIRECTION_WRITE); - /* Always initialize the features structure, we are in most cases always going to need it. */ - ast_bridge_features_init(&conference_bridge_user.features); - - /* If the menu option is enabled provide a user or admin menu as a custom feature hook */ - if (ast_test_flag(&conference_bridge_user.flags, OPTION_MENU)) { - ast_bridge_features_hook(&conference_bridge_user.features, "#", menu_callback, &conference_bridge_user); - } - /* If the caller should be joined already muted, make it so */ - if (ast_test_flag(&conference_bridge_user.flags, OPTION_STARTMUTED)) { + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_STARTMUTED)) { conference_bridge_user.features.mute = 1; } - /* Grab join/leave sounds from the channel */ - ast_channel_lock(chan); - if ((tmp = pbx_builtin_getvar_helper(chan, "CONFBRIDGE_JOIN_SOUND"))) { - join_sound = ast_strdupa(tmp); + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DROP_SILENCE)) { + conference_bridge_user.tech_args.drop_silence = 1; } - if ((tmp = pbx_builtin_getvar_helper(chan, "CONFBRIDGE_LEAVE_SOUND"))) { - leave_sound = ast_strdupa(tmp); - } - ast_channel_unlock(chan); - /* If there is 1 or more people already in the conference then play our join sound unless overridden */ - if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && !ast_strlen_zero(join_sound) && conference_bridge->users >= 2) { + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_JITTERBUFFER)) { + char *func_jb; + if ((func_jb = ast_module_helper("", "func_jitterbuffer", 0, 0, 0, 0))) { + ast_free(func_jb); + ast_func_write(chan, "JITTERBUFFER(adaptive)", "default"); + } + } + + if (ast_test_flag(&conference_bridge_user.u_profile, USER_OPT_DENOISE)) { + char *mod_speex; + /* Reduce background noise from each participant */ + if ((mod_speex = ast_module_helper("", "codec_speex", 0, 0, 0, 0))) { + ast_free(mod_speex); + ast_func_write(chan, "DENOISE(rx)", "on"); + } + } + + /* if this user has a intro, play it before entering */ + if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) { + ast_autoservice_start(chan); + play_sound_file(conference_bridge, conference_bridge_user.name_rec_location); + play_sound_file(conference_bridge, + conf_get_sound(CONF_SOUND_HAS_JOINED, conference_bridge_user.b_profile.sounds)); + ast_autoservice_stop(chan); + } + + /* Play the Join sound to both the conference and the user entering. */ + if (!quiet) { + const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference_bridge_user.b_profile.sounds); + ast_stream_and_wait(chan, join_sound, ""); ast_autoservice_start(chan); play_sound_file(conference_bridge, join_sound); ast_autoservice_stop(chan); } /* Join our conference bridge for real */ - ast_bridge_join(conference_bridge->bridge, chan, NULL, &conference_bridge_user.features); + send_join_event(conference_bridge_user.chan, conference_bridge->name); + ast_bridge_join(conference_bridge->bridge, + chan, + NULL, + &conference_bridge_user.features, + &conference_bridge_user.tech_args); + send_leave_event(conference_bridge_user.chan, conference_bridge->name); - /* If there is 1 or more people (not including us) already in the conference then play our leave sound unless overridden */ - if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && !ast_strlen_zero(leave_sound) && conference_bridge->users >= 2) { + /* if this user has a intro, play it when leaving */ + if (!quiet && !ast_strlen_zero(conference_bridge_user.name_rec_location)) { + ast_autoservice_start(chan); + play_sound_file(conference_bridge, conference_bridge_user.name_rec_location); + play_sound_file(conference_bridge, + conf_get_sound(CONF_SOUND_HAS_LEFT, conference_bridge_user.b_profile.sounds)); + ast_autoservice_stop(chan); + } + + /* play the leave sound */ + if (!quiet) { + const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference_bridge_user.b_profile.sounds); ast_autoservice_start(chan); play_sound_file(conference_bridge, leave_sound); ast_autoservice_stop(chan); @@ -785,8 +1341,10 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) ast_bridge_features_cleanup(&conference_bridge_user.features); /* If the user was kicked from the conference play back the audio prompt for it */ - if (!ast_test_flag(&conference_bridge_user.flags, OPTION_QUIET) && conference_bridge_user.kicked) { - res = ast_stream_and_wait(chan, "conf-kicked", ""); + if (!quiet && conference_bridge_user.kicked) { + res = ast_stream_and_wait(chan, + conf_get_sound(CONF_SOUND_KICKED, conference_bridge_user.b_profile.sounds), + ""); } /* Restore volume adjustments to previous values in case they were changed */ @@ -797,9 +1355,305 @@ static int confbridge_exec(struct ast_channel *chan, const char *data) ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_WRITE, volume_adjustments[1]); } + if (!ast_strlen_zero(conference_bridge_user.name_rec_location)) { + ast_filedelete(conference_bridge_user.name_rec_location, NULL); + } + +confbridge_cleanup: + ast_bridge_features_cleanup(&conference_bridge_user.features); + conf_bridge_profile_destroy(&conference_bridge_user.b_profile); return res; } +static int action_toggle_mute(struct conference_bridge *conference_bridge, + struct conference_bridge_user *conference_bridge_user, + struct ast_channel *chan) +{ + /* Mute or unmute yourself, note we only allow manipulation if they aren't waiting for a marked user or if marked users exist */ + if (!ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_WAITMARKED) || conference_bridge->markedusers) { + conference_bridge_user->features.mute = (!conference_bridge_user->features.mute ? 1 : 0); + } + return ast_stream_and_wait(chan, (conference_bridge_user->features.mute ? + conf_get_sound(CONF_SOUND_MUTED, conference_bridge_user->b_profile.sounds) : + conf_get_sound(CONF_SOUND_UNMUTED, conference_bridge_user->b_profile.sounds)), + ""); +} + +static int action_playback(struct ast_bridge_channel *bridge_channel, const char *playback_file) +{ + char *file_copy = ast_strdupa(playback_file); + char *file = NULL; + + while ((file = strsep(&file_copy, "&"))) { + if (ast_stream_and_wait(bridge_channel->chan, file, "")) { + ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file); + return -1; + } + } + return 0; +} + +static int action_playback_and_continue(struct conference_bridge *conference_bridge, + struct conference_bridge_user *conference_bridge_user, + struct ast_bridge_channel *bridge_channel, + struct conf_menu *menu, + const char *playback_file, + const char *cur_dtmf, + int *stop_prompts) +{ + int i; + int digit = 0; + char dtmf[MAXIMUM_DTMF_FEATURE_STRING]; + struct conf_menu_entry new_menu_entry = { { 0, }, }; + char *file_copy = ast_strdupa(playback_file); + char *file = NULL; + + while ((file = strsep(&file_copy, "&"))) { + if (ast_streamfile(bridge_channel->chan, file, bridge_channel->chan->language)) { + ast_log(LOG_WARNING, "Failed to playback file %s to channel\n", file); + return -1; + } + + /* now wait for more digits. */ + if (!(digit = ast_waitstream(bridge_channel->chan, AST_DIGIT_ANY))) { + /* streaming finished and no DTMF was entered */ + continue; + } else if (digit == -1) { + /* error */ + return -1; + } else { + break; /* dtmf was entered */ + } + } + if (!digit) { + /* streaming finished on all files and no DTMF was entered */ + return -1; + } + ast_stopstream(bridge_channel->chan); + + /* If we get here, then DTMF has been entered, This means no + * additional prompts should be played for this menu entry */ + *stop_prompts = 1; + + /* If a digit was pressed during the payback, update + * the dtmf string and look for a new menu entry in the + * menu structure */ + ast_copy_string(dtmf, cur_dtmf, sizeof(dtmf)); + for (i = 0; i < (MAXIMUM_DTMF_FEATURE_STRING - 1); i++) { + dtmf[i] = cur_dtmf[i]; + if (!dtmf[i]) { + dtmf[i] = (char) digit; + dtmf[i + 1] = '\0'; + i = -1; + break; + } + } + /* If i is not -1 then the new dtmf digit was _NOT_ added to the string. + * If this is the case, no new DTMF sequence should be looked for. */ + if (i != -1) { + return 0; + } + + if (conf_find_menu_entry_by_sequence(dtmf, menu, &new_menu_entry)) { + execute_menu_entry(conference_bridge, + conference_bridge_user, + bridge_channel, + &new_menu_entry, menu); + conf_menu_entry_destroy(&new_menu_entry); + } + return 0; +} + +static int action_kick_last(struct conference_bridge *conference_bridge, + struct ast_bridge_channel *bridge_channel, + struct conference_bridge_user *conference_bridge_user) +{ + struct conference_bridge_user *last_participant = NULL; + int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN); + + if (!isadmin) { + ast_stream_and_wait(bridge_channel->chan, + conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds), + ""); + ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n", + bridge_channel->chan->name, + conference_bridge->name); + return -1; + } + + ao2_lock(conference_bridge); + if (((last_participant = AST_LIST_LAST(&conference_bridge->users_list)) == conference_bridge_user) + || (ast_test_flag(&last_participant->u_profile, USER_OPT_ADMIN))) { + ao2_unlock(conference_bridge); + ast_stream_and_wait(bridge_channel->chan, + conf_get_sound(CONF_SOUND_ERROR_MENU, conference_bridge_user->b_profile.sounds), + ""); + } else if (last_participant) { + last_participant->kicked = 1; + ast_bridge_remove(conference_bridge->bridge, last_participant->chan); + ao2_unlock(conference_bridge); + } + return 0; +} + +static int action_dialplan_exec(struct ast_bridge_channel *bridge_channel, struct conf_menu_action *menu_action) +{ + struct ast_pbx_args args; + struct ast_pbx *pbx; + char *exten; + char *context; + int priority; + int res; + + memset(&args, 0, sizeof(args)); + args.no_hangup_chan = 1; + + ast_channel_lock(bridge_channel->chan); + + /*save off*/ + exten = ast_strdupa(bridge_channel->chan->exten); + context = ast_strdupa(bridge_channel->chan->context); + priority = bridge_channel->chan->priority; + pbx = bridge_channel->chan->pbx; + bridge_channel->chan->pbx = NULL; + + /*set new*/ + ast_copy_string(bridge_channel->chan->exten, menu_action->data.dialplan_args.exten, sizeof(bridge_channel->chan->exten)); + ast_copy_string(bridge_channel->chan->context, menu_action->data.dialplan_args.context, sizeof(bridge_channel->chan->context)); + bridge_channel->chan->priority = menu_action->data.dialplan_args.priority; + + ast_channel_unlock(bridge_channel->chan); + + /*execute*/ + res = ast_pbx_run_args(bridge_channel->chan, &args); + + /*restore*/ + ast_channel_lock(bridge_channel->chan); + + ast_copy_string(bridge_channel->chan->exten, exten, sizeof(bridge_channel->chan->exten)); + ast_copy_string(bridge_channel->chan->context, context, sizeof(bridge_channel->chan->context)); + bridge_channel->chan->priority = priority; + bridge_channel->chan->pbx = pbx; + + ast_channel_unlock(bridge_channel->chan); + + return res; +} + +static int execute_menu_entry(struct conference_bridge *conference_bridge, + struct conference_bridge_user *conference_bridge_user, + struct ast_bridge_channel *bridge_channel, + struct conf_menu_entry *menu_entry, + struct conf_menu *menu) +{ + struct conf_menu_action *menu_action; + int isadmin = ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_ADMIN); + int stop_prompts = 0; + int res = 0; + + AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) { + switch (menu_action->id) { + case MENU_ACTION_TOGGLE_MUTE: + res |= action_toggle_mute(conference_bridge, + conference_bridge_user, + bridge_channel->chan); + break; + case MENU_ACTION_PLAYBACK: + if (!stop_prompts) { + res |= action_playback(bridge_channel, menu_action->data.playback_file); + } + break; + case MENU_ACTION_RESET_LISTENING: + ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_WRITE, 0); + break; + case MENU_ACTION_RESET_TALKING: + ast_audiohook_volume_set(conference_bridge_user->chan, AST_AUDIOHOOK_DIRECTION_READ, 0); + break; + case MENU_ACTION_INCREASE_LISTENING: + ast_audiohook_volume_adjust(conference_bridge_user->chan, + AST_AUDIOHOOK_DIRECTION_WRITE, 1); + break; + case MENU_ACTION_DECREASE_LISTENING: + ast_audiohook_volume_adjust(conference_bridge_user->chan, + AST_AUDIOHOOK_DIRECTION_WRITE, -1); + break; + case MENU_ACTION_INCREASE_TALKING: + ast_audiohook_volume_adjust(conference_bridge_user->chan, + AST_AUDIOHOOK_DIRECTION_READ, 1); + break; + case MENU_ACTION_DECREASE_TALKING: + ast_audiohook_volume_adjust(conference_bridge_user->chan, + AST_AUDIOHOOK_DIRECTION_READ, -1); + break; + case MENU_ACTION_PLAYBACK_AND_CONTINUE: + if (!(stop_prompts)) { + res |= action_playback_and_continue(conference_bridge, + conference_bridge_user, + bridge_channel, + menu, + menu_action->data.playback_file, + menu_entry->dtmf, + &stop_prompts); + } + break; + case MENU_ACTION_DIALPLAN_EXEC: + res |= action_dialplan_exec(bridge_channel, menu_action); + break; + case MENU_ACTION_ADMIN_TOGGLE_LOCK: + if (!isadmin) { + break; + } + conference_bridge->locked = (!conference_bridge->locked ? 1 : 0); + res |= ast_stream_and_wait(bridge_channel->chan, + (conference_bridge->locked ? + conf_get_sound(CONF_SOUND_LOCKED_NOW, conference_bridge_user->b_profile.sounds) : + conf_get_sound(CONF_SOUND_UNLOCKED_NOW, conference_bridge_user->b_profile.sounds)), + ""); + + break; + case MENU_ACTION_ADMIN_KICK_LAST: + res |= action_kick_last(conference_bridge, bridge_channel, conference_bridge_user); + break; + case MENU_ACTION_LEAVE: + ao2_lock(conference_bridge); + ast_bridge_remove(conference_bridge->bridge, bridge_channel->chan); + ao2_unlock(conference_bridge); + break; + case MENU_ACTION_NOOP: + break; + } + } + return res; +} + +int conf_handle_dtmf(struct ast_bridge_channel *bridge_channel, + struct conference_bridge_user *conference_bridge_user, + struct conf_menu_entry *menu_entry, + struct conf_menu *menu) +{ + struct conference_bridge *conference_bridge = conference_bridge_user->conference_bridge; + + /* See if music on hold is playing */ + ao2_lock(conference_bridge); + if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + /* Just us so MOH is probably indeed going, let's stop it */ + ast_moh_stop(bridge_channel->chan); + } + ao2_unlock(conference_bridge); + + /* execute the list of actions associated with this menu entry */ + execute_menu_entry(conference_bridge, conference_bridge_user, bridge_channel, menu_entry, menu); + + /* See if music on hold needs to be started back up again */ + ao2_lock(conference_bridge); + if (conference_bridge->users == 1 && ast_test_flag(&conference_bridge_user->u_profile, USER_OPT_MUSICONHOLD)) { + ast_moh_start(bridge_channel->chan, conference_bridge_user->u_profile.moh_class, NULL); + } + ao2_unlock(conference_bridge); + + return 0; +} + static char *complete_confbridge_name(const char *line, const char *word, int pos, int state) { int which = 0; @@ -824,19 +1678,18 @@ static char *complete_confbridge_name(const char *line, const char *word, int po static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct conference_bridge *bridge = NULL; struct conference_bridge tmp; struct conference_bridge_user *participant = NULL; - switch (cmd) { - case CLI_INIT: - e->command = "confbridge kick"; - e->usage = - "Usage: confbridge kick \n" - " Kicks a channel out of the conference bridge.\n"; - return NULL; - case CLI_GENERATE: + switch (cmd) { + case CLI_INIT: + e->command = "confbridge kick"; + e->usage = + "Usage: confbridge kick \n" + " Kicks a channel out of the conference bridge.\n"; + return NULL; + case CLI_GENERATE: if (a->pos == 2) { return complete_confbridge_name(a->line, a->word, a->pos, a->n); } @@ -845,8 +1698,8 @@ static char *handle_cli_confbridge_kick(struct ast_cli_entry *e, int cmd, struct return complete_confbridge_channel(a->line, a->word, a->pos, a->n); } */ - return NULL; - } + return NULL; + } if (a->argc != 4) { return CLI_SHOWUSAGE; @@ -881,19 +1734,19 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct struct conference_bridge tmp; struct conference_bridge_user *participant = NULL; - switch (cmd) { - case CLI_INIT: - e->command = "confbridge list"; - e->usage = - "Usage: confbridge list []\n" - " Lists all currently active conference bridges.\n"; - return NULL; - case CLI_GENERATE: + switch (cmd) { + case CLI_INIT: + e->command = "confbridge list"; + e->usage = + "Usage: confbridge list []\n" + " Lists all currently active conference bridges.\n"; + return NULL; + case CLI_GENERATE: if (a->pos == 2) { return complete_confbridge_name(a->line, a->word, a->pos, a->n); } - return NULL; - } + return NULL; + } if (a->argc == 2) { ast_cli(a->fd, "Conference Bridge Name Users Marked Locked?\n"); @@ -914,38 +1767,14 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]); return CLI_SUCCESS; } - ast_cli(a->fd, "Channel Flags\n"); - ast_cli(a->fd, "================================ ================\n"); + ast_cli(a->fd, "Channel User Profile Bridge Profile Menu\n"); + ast_cli(a->fd, "============================= ================ ================ ================\n"); ao2_lock(bridge); AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { - ast_cli(a->fd, "%-32s ", participant->chan->name); - if (ast_test_flag(&participant->flags, OPTION_MARKEDUSER)) { - ast_cli(a->fd, "A"); - } - if (ast_test_flag(&participant->flags, OPTION_ADMIN)) { - ast_cli(a->fd, "a"); - } - if (ast_test_flag(&participant->flags, OPTION_ANNOUNCEUSERCOUNT)) { - ast_cli(a->fd, "c"); - } - if (ast_test_flag(&participant->flags, OPTION_MENU)) { - ast_cli(a->fd, "m"); - } - if (ast_test_flag(&participant->flags, OPTION_MUSICONHOLD)) { - ast_cli(a->fd, "M(%s)", participant->opt_args[OPTION_MUSICONHOLD_CLASS]); - } - if (ast_test_flag(&participant->flags, OPTION_NOONLYPERSON)) { - ast_cli(a->fd, "1"); - } - if (ast_test_flag(&participant->flags, OPTION_STARTMUTED)) { - ast_cli(a->fd, "s"); - } - if (ast_test_flag(&participant->flags, OPTION_WAITMARKED)) { - ast_cli(a->fd, "w"); - } - if (ast_test_flag(&participant->flags, OPTION_QUIET)) { - ast_cli(a->fd, "q"); - } + ast_cli(a->fd, "%-29s ", participant->chan->name); + ast_cli(a->fd, "%-17s", participant->u_profile.name); + ast_cli(a->fd, "%-17s", participant->b_profile.name); + ast_cli(a->fd, "%-17s", participant->menu_name); ast_cli(a->fd, "\n"); } ao2_unlock(bridge); @@ -956,44 +1785,670 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct return CLI_SHOWUSAGE; } +/* \internal + * \brief finds a conference by name and locks/unlocks. + * + * \retval 0 success + * \retval -1 conference not found + */ +static int generic_lock_unlock_helper(int lock, const char *conference) +{ + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + int res = 0; + + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + return -1; + } + ao2_lock(bridge); + bridge->locked = lock; + ao2_unlock(bridge); + ao2_ref(bridge, -1); + + return res; +} + +/* \internal + * \brief finds a conference user by channel name and mutes/unmutes them. + * + * \retval 0 success + * \retval -1 conference not found + * \retval -2 user not found + */ +static int generic_mute_unmute_helper(int mute, const char *conference, const char *user) +{ + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + struct conference_bridge_user *participant = NULL; + int res = 0; + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + return -1; + } + ao2_lock(bridge); + AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + if (!strncmp(user, participant->chan->name, strlen(user))) { + break; + } + } + if (participant) { + participant->features.mute = mute; + } else { + res = -2;; + } + ao2_unlock(bridge); + ao2_ref(bridge, -1); + + return res; +} + +static int cli_mute_unmute_helper(int mute, struct ast_cli_args *a) +{ + int res = generic_mute_unmute_helper(mute, a->argv[2], a->argv[3]); + + if (res == -1) { + ast_cli(a->fd, "No conference bridge named '%s' found!\n", a->argv[2]); + return -1; + } else if (res == -2) { + ast_cli(a->fd, "No channel named '%s' found in conference %s\n", a->argv[3], a->argv[2]); + return -1; + } + ast_cli(a->fd, "%s %s from confbridge %s\n", mute ? "Muting" : "Unmuting", a->argv[3], a->argv[2]); + return 0; +} + +static char *handle_cli_confbridge_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge mute"; + e->usage = + "Usage: confbridge mute \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + cli_mute_unmute_helper(1, a); + + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_unmute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge unmute"; + e->usage = + "Usage: confbridge unmute \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + cli_mute_unmute_helper(0, a); + + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_lock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge lock"; + e->usage = + "Usage: confbridge lock \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + if (generic_lock_unlock_helper(1, a->argv[2])) { + ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]); + } else { + ast_cli(a->fd, "Conference %s is locked.\n", a->argv[2]); + } + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_unlock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "confbridge unlock"; + e->usage = + "Usage: confbridge unlock \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + if (generic_lock_unlock_helper(0, a->argv[2])) { + ast_cli(a->fd, "Conference %s is not found\n", a->argv[2]); + } else { + ast_cli(a->fd, "Conference %s is unlocked.\n", a->argv[2]); + } + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_start_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + const char *rec_file = NULL; + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge record start"; + e->usage = + "Usage: confbridge record start \n" + " is optional, Otherwise the bridge profile\n" + " record file will be used. If the bridge profile\n" + " has no record file specified, a file will automatically\n" + " be generated in the monitor directory\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + if (a->argc == 5) { + rec_file = a->argv[4]; + } + + ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + ast_cli(a->fd, "Conference not found.\n"); + return CLI_FAILURE; + } + if (conf_is_recording(bridge)) { + ast_cli(a->fd, "Conference is already being recorded.\n"); + ao2_ref(bridge, -1); + return CLI_SUCCESS; + } + if (!ast_strlen_zero(rec_file)) { + ao2_lock(bridge); + ast_copy_string(bridge->b_profile.rec_file, rec_file, sizeof(bridge->b_profile.rec_file)); + ao2_unlock(bridge); + } + if (conf_start_record(bridge)) { + ast_cli(a->fd, "Could not start recording due to internal error.\n"); + ao2_ref(bridge, -1); + return CLI_FAILURE; + } + ast_cli(a->fd, "Recording started\n"); + ao2_ref(bridge, -1); + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_stop_record(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge record stop"; + e->usage = + "Usage: confbridge record stop \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_confbridge_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + ast_cli(a->fd, "Conference not found.\n"); + return CLI_SUCCESS; + } + conf_stop_record(bridge); + ast_cli(a->fd, "Recording stopped.\n"); + ao2_ref(bridge, -1); + return CLI_SUCCESS; +} + static struct ast_cli_entry cli_confbridge[] = { AST_CLI_DEFINE(handle_cli_confbridge_list, "List conference bridges and participants."), - AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges.") + AST_CLI_DEFINE(handle_cli_confbridge_kick, "Kick participants out of conference bridges."), + AST_CLI_DEFINE(handle_cli_confbridge_mute, "Mute a participant."), + AST_CLI_DEFINE(handle_cli_confbridge_unmute, "Mute a participant."), + AST_CLI_DEFINE(handle_cli_confbridge_lock, "Lock a conference."), + AST_CLI_DEFINE(handle_cli_confbridge_unlock, "Unlock a conference."), + AST_CLI_DEFINE(handle_cli_confbridge_start_record, "Start recording a conference"), + AST_CLI_DEFINE(handle_cli_confbridge_stop_record, "Stop recording a conference."), }; +static struct ast_custom_function confbridge_function = { + .name = "CONFBRIDGE", + .write = func_confbridge_helper, +}; + +static int action_confbridgelist(struct mansession *s, const struct message *m) +{ + const char *actionid = astman_get_header(m, "ActionID"); + const char *conference = astman_get_header(m, "Conference"); + struct conference_bridge_user *participant = NULL; + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + char id_text[80] = ""; + int total = 0; + + if (!ast_strlen_zero(actionid)) { + snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid); + } + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } + + astman_send_listack(s, m, "Confbridge user list will follow", "start"); + + ao2_lock(bridge); + AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + total++; + astman_append(s, + "Event: ConfbridgeList\r\n" + "%s" + "Conference: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "Channel: %s\r\n" + "Admin: %s\r\n" + "MarkedUser: %s\r\n" + "\r\n", + id_text, + bridge->name, + S_COR(participant->chan->caller.id.number.valid, participant->chan->caller.id.number.str, ""), + S_COR(participant->chan->caller.id.name.valid, participant->chan->caller.id.name.str, ""), + participant->chan->name, + ast_test_flag(&participant->u_profile, USER_OPT_ADMIN) ? "Yes" : "No", + ast_test_flag(&participant->u_profile, USER_OPT_MARKEDUSER) ? "Yes" : "No"); + } + ao2_unlock(bridge); + ao2_ref(bridge, -1); + + astman_append(s, + "Event: ConfbridgeListComplete\r\n" + "EventList: Complete\r\n" + "ListItems: %d\r\n" + "%s" + "\r\n", total, id_text); + + return 0; +} + +static int action_confbridgelistrooms(struct mansession *s, const struct message *m) +{ + const char *actionid = astman_get_header(m, "ActionID"); + struct conference_bridge *bridge = NULL; + struct ao2_iterator i; + char id_text[512] = ""; + int totalitems = 0; + + if (!ast_strlen_zero(actionid)) { + snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", actionid); + } + + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + astman_send_listack(s, m, "Confbridge conferences will follow", "start"); + + /* Traverse the conference list */ + i = ao2_iterator_init(conference_bridges, 0); + while ((bridge = ao2_iterator_next(&i))) { + totalitems++; + + ao2_lock(bridge); + astman_append(s, + "Event: ConfbridgeListRooms\r\n" + "%s" + "Conference: %s\r\n" + "Parties: %d\r\n" + "Marked: %d\r\n" + "Locked: %s\r\n" + "\r\n", + id_text, + bridge->name, + bridge->users, + bridge->markedusers, + bridge->locked ? "Yes" : "No"); + ao2_unlock(bridge); + + ao2_ref(bridge, -1); + } + ao2_iterator_destroy(&i); + + /* Send final confirmation */ + astman_append(s, + "Event: ConfbridgeListRoomsComplete\r\n" + "EventList: Complete\r\n" + "ListItems: %d\r\n" + "%s" + "\r\n", totalitems, id_text); + return 0; +} + +static int action_mute_unmute_helper(struct mansession *s, const struct message *m, int mute) +{ + const char *conference = astman_get_header(m, "Conference"); + const char *channel = astman_get_header(m, "Channel"); + int res = 0; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (ast_strlen_zero(channel)) { + astman_send_error(s, m, "No channel name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + res = generic_mute_unmute_helper(mute, conference, channel); + + if (res == -1) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } else if (res == -2) { + astman_send_error(s, m, "No Channel by that name found in Conference."); + return 0; + } + + astman_send_ack(s, m, mute ? "User muted" : "User unmuted"); + return 0; +} + +static int action_confbridgeunmute(struct mansession *s, const struct message *m) +{ + return action_mute_unmute_helper(s, m, 0); +} +static int action_confbridgemute(struct mansession *s, const struct message *m) +{ + return action_mute_unmute_helper(s, m, 1); +} + +static int action_lock_unlock_helper(struct mansession *s, const struct message *m, int lock) +{ + const char *conference = astman_get_header(m, "Conference"); + int res = 0; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + if ((res = generic_lock_unlock_helper(lock, conference))) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } + astman_send_ack(s, m, lock ? "Conference locked" : "Conference unlocked"); + return 0; +} +static int action_confbridgeunlock(struct mansession *s, const struct message *m) +{ + return action_lock_unlock_helper(s, m, 0); +} +static int action_confbridgelock(struct mansession *s, const struct message *m) +{ + return action_lock_unlock_helper(s, m, 1); +} + +static int action_confbridgekick(struct mansession *s, const struct message *m) +{ + const char *conference = astman_get_header(m, "Conference"); + const char *channel = astman_get_header(m, "Channel"); + struct conference_bridge_user *participant = NULL; + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + int found = 0; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } + + ao2_lock(bridge); + AST_LIST_TRAVERSE(&bridge->users_list, participant, list) { + if (!strcasecmp(participant->chan->name, channel)) { + participant->kicked = 1; + ast_bridge_remove(bridge->bridge, participant->chan); + found = 1; + break; + } + } + ao2_unlock(bridge); + ao2_ref(bridge, -1); + + if (found) { + astman_send_ack(s, m, "User kicked"); + } else { + astman_send_error(s, m, "No Channel by that name found in Conference."); + } + return 0; +} + +static int action_confbridgestartrecord(struct mansession *s, const struct message *m) +{ + const char *conference = astman_get_header(m, "Conference"); + const char *recordfile = astman_get_header(m, "RecordFile"); + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } + + if (conf_is_recording(bridge)) { + astman_send_error(s, m, "Conference is already being recorded."); + ao2_ref(bridge, -1); + return 0; + } + + if (!ast_strlen_zero(recordfile)) { + ao2_lock(bridge); + ast_copy_string(bridge->b_profile.rec_file, recordfile, sizeof(bridge->b_profile.rec_file)); + ao2_unlock(bridge); + } + + if (conf_start_record(bridge)) { + astman_send_error(s, m, "Internal error starting conference recording."); + ao2_ref(bridge, -1); + return 0; + } + + ao2_ref(bridge, -1); + astman_send_ack(s, m, "Conference Recording Started."); + return 0; +} +static int action_confbridgestoprecord(struct mansession *s, const struct message *m) +{ + const char *conference = astman_get_header(m, "Conference"); + struct conference_bridge *bridge = NULL; + struct conference_bridge tmp; + + if (ast_strlen_zero(conference)) { + astman_send_error(s, m, "No Conference name provided."); + return 0; + } + if (!ao2_container_count(conference_bridges)) { + astman_send_error(s, m, "No active conferences."); + return 0; + } + + ast_copy_string(tmp.name, conference, sizeof(tmp.name)); + bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER); + if (!bridge) { + astman_send_error(s, m, "No Conference by that name found."); + return 0; + } + + if (conf_stop_record(bridge)) { + astman_send_error(s, m, "Internal error while stopping recording."); + ao2_ref(bridge, -1); + return 0; + } + + ao2_ref(bridge, -1); + astman_send_ack(s, m, "Conference Recording Stopped."); + return 0; +} + + /*! \brief Called when module is being unloaded */ static int unload_module(void) { int res = ast_unregister_application(app); + ast_custom_function_unregister(&confbridge_function); + ast_cli_unregister_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry)); /* Get rid of the conference bridges container. Since we only allow dynamic ones none will be active. */ ao2_ref(conference_bridges, -1); + conf_destroy_config(); + + ast_channel_unregister(&record_tech); + record_tech.capabilities = ast_format_cap_destroy(record_tech.capabilities); + + res |= ast_manager_unregister("ConfbridgeList"); + res |= ast_manager_unregister("ConfbridgeListRooms"); + res |= ast_manager_unregister("ConfbridgeMute"); + res |= ast_manager_unregister("ConfbridgeUnmute"); + res |= ast_manager_unregister("ConfbridgeKick"); + res |= ast_manager_unregister("ConfbridgeUnlock"); + res |= ast_manager_unregister("ConfbridgeLock"); + res |= ast_manager_unregister("ConfbridgeStartRecord"); + res |= ast_manager_unregister("ConfbridgeStopRecord"); + return res; } /*! \brief Called when module is being loaded */ static int load_module(void) { + int res = 0; + if ((ast_custom_function_register(&confbridge_function))) { + return AST_MODULE_LOAD_FAILURE; + } + if (!(record_tech.capabilities = ast_format_cap_alloc())) { + return AST_MODULE_LOAD_FAILURE; + } + ast_format_cap_add_all(record_tech.capabilities); + if (ast_channel_register(&record_tech)) { + ast_log(LOG_ERROR, "Unable to register ConfBridge recorder.\n"); + return AST_MODULE_LOAD_FAILURE; + } /* Create a container to hold the conference bridges */ if (!(conference_bridges = ao2_container_alloc(CONFERENCE_BRIDGE_BUCKETS, conference_bridge_hash_cb, conference_bridge_cmp_cb))) { - return AST_MODULE_LOAD_DECLINE; + return AST_MODULE_LOAD_FAILURE; } - if (ast_register_application_xml(app, confbridge_exec)) { ao2_ref(conference_bridges, -1); - return AST_MODULE_LOAD_DECLINE; + return AST_MODULE_LOAD_FAILURE; } - ast_cli_register_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry)); + res |= ast_cli_register_multiple(cli_confbridge, sizeof(cli_confbridge) / sizeof(struct ast_cli_entry)); + res |= ast_manager_register_xml("ConfbridgeList", EVENT_FLAG_REPORTING, action_confbridgelist); + res |= ast_manager_register_xml("ConfbridgeListRooms", EVENT_FLAG_REPORTING, action_confbridgelistrooms); + res |= ast_manager_register_xml("ConfbridgeMute", EVENT_FLAG_CALL, action_confbridgemute); + res |= ast_manager_register_xml("ConfbridgeUnmute", EVENT_FLAG_CALL, action_confbridgeunmute); + res |= ast_manager_register_xml("ConfbridgeKick", EVENT_FLAG_CALL, action_confbridgekick); + res |= ast_manager_register_xml("ConfbridgeUnlock", EVENT_FLAG_CALL, action_confbridgeunlock); + res |= ast_manager_register_xml("ConfbridgeLock", EVENT_FLAG_CALL, action_confbridgelock); + res |= ast_manager_register_xml("ConfbridgeStartRecord", EVENT_FLAG_CALL, action_confbridgestartrecord); + res |= ast_manager_register_xml("ConfbridgeStopRecord", EVENT_FLAG_CALL, action_confbridgestoprecord); - return AST_MODULE_LOAD_SUCCESS; + conf_load_config(0); + return res; +} + +static int reload(void) +{ + return conf_load_config(1); } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Conference Bridge Application", .load = load_module, .unload = unload_module, + .reload = reload, .load_pri = AST_MODPRI_DEVSTATE_PROVIDER, ); diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c new file mode 100644 index 000000000..dbec32bba --- /dev/null +++ b/apps/confbridge/conf_config_parser.c @@ -0,0 +1,1444 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011, Digium, Inc. + * + * David Vossel + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ConfBridge config parser + * + * \author David Vossel + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "asterisk/logger.h" +#include "asterisk/config.h" +#include "include/confbridge.h" +#include "asterisk/astobj2.h" +#include "asterisk/cli.h" +#include "asterisk/bridging_features.h" +#include "asterisk/stringfields.h" +#include "asterisk/pbx.h" + +#define CONFBRIDGE_CONFIG "confbridge.conf" + +static struct ao2_container *user_profiles; +static struct ao2_container *bridge_profiles; +static struct ao2_container *menus; + +/*! bridge profile container functions */ +static int bridge_cmp_cb(void *obj, void *arg, int flags) +{ + const struct bridge_profile *entry1 = obj; + const struct bridge_profile *entry2 = arg; + return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; +} +static int bridge_hash_cb(const void *obj, const int flags) +{ + const struct bridge_profile *b_profile = obj; + return ast_str_case_hash(b_profile->name); +} +static int bridge_mark_delme_cb(void *obj, void *arg, int flag) +{ + struct bridge_profile *entry = obj; + entry->delme = 1; + return 0; +} +static int match_bridge_delme_cb(void *obj, void *arg, int flag) +{ + const struct bridge_profile *entry = obj; + return entry->delme ? CMP_MATCH : 0; +} + +/*! menu container functions */ +static int menu_cmp_cb(void *obj, void *arg, int flags) +{ + const struct conf_menu *entry1 = obj; + const struct conf_menu *entry2 = arg; + return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; +} +static int menu_hash_cb(const void *obj, const int flags) +{ + const struct conf_menu *menu = obj; + return ast_str_case_hash(menu->name); +} +static int menu_mark_delme_cb(void *obj, void *arg, int flag) +{ + struct conf_menu *entry = obj; + entry->delme = 1; + return 0; +} +static int match_menu_delme_cb(void *obj, void *arg, int flag) +{ + const struct conf_menu *entry = obj; + return entry->delme ? CMP_MATCH : 0; +} +static void menu_destructor(void *obj) +{ + struct conf_menu *menu = obj; + struct conf_menu_entry *entry = NULL; + + while ((entry = AST_LIST_REMOVE_HEAD(&menu->entries, entry))) { + conf_menu_entry_destroy(entry); + ast_free(entry); + } +} + +/*! User profile container functions */ +static int user_cmp_cb(void *obj, void *arg, int flags) +{ + const struct user_profile *entry1 = obj; + const struct user_profile *entry2 = arg; + return (!strcasecmp(entry1->name, entry2->name)) ? CMP_MATCH | CMP_STOP : 0; +} +static int user_hash_cb(const void *obj, const int flags) +{ + const struct user_profile *u_profile = obj; + return ast_str_case_hash(u_profile->name); +} +static int user_mark_delme_cb(void *obj, void *arg, int flag) +{ + struct user_profile *entry = obj; + entry->delme = 1; + return 0; +} +static int match_user_delme_cb(void *obj, void *arg, int flag) +{ + const struct user_profile *entry = obj; + return entry->delme ? CMP_MATCH : 0; +} + +/*! Bridge Profile Sounds functions */ +static void bridge_profile_sounds_destroy_cb(void *obj) +{ + struct bridge_profile_sounds *sounds = obj; + ast_string_field_free_memory(sounds); +} + +static struct bridge_profile_sounds *bridge_profile_sounds_alloc(void) +{ + struct bridge_profile_sounds *sounds = ao2_alloc(sizeof(*sounds), bridge_profile_sounds_destroy_cb); + + if (!sounds) { + return NULL; + } + if (ast_string_field_init(sounds, 512)) { + ao2_ref(sounds, -1); + return NULL; + } + + return sounds; +} + +static int set_user_option(const char *name, const char *value, struct user_profile *u_profile) +{ + if (!strcasecmp(name, "admin")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_ADMIN); + } else if (!strcasecmp(name, "marked")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_MARKEDUSER); + } else if (!strcasecmp(name, "startmuted")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_STARTMUTED); + } else if (!strcasecmp(name, "music_on_hold_when_empty")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_MUSICONHOLD); + } else if (!strcasecmp(name, "quiet")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_QUIET); + } else if (!strcasecmp(name, "announce_user_count_all")) { + if (ast_true(value)) { + u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; + } else if (ast_false(value)) { + u_profile->flags = u_profile->flags & ~USER_OPT_ANNOUNCEUSERCOUNTALL; + } else if (sscanf(value, "%30u", &u_profile->announce_user_count_all_after) == 1) { + u_profile->flags = u_profile->flags | USER_OPT_ANNOUNCEUSERCOUNTALL; + } else { + return -1; + } + } else if (!strcasecmp(name, "announce_user_count")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_ANNOUNCEUSERCOUNT); + } else if (!strcasecmp(name, "announce_only_user")) { + u_profile->flags = ast_true(value) ? + u_profile->flags & ~USER_OPT_NOONLYPERSON : + u_profile->flags | USER_OPT_NOONLYPERSON; + } else if (!strcasecmp(name, "wait_marked")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_WAITMARKED); + } else if (!strcasecmp(name, "end_marked")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_ENDMARKED); + } else if (!strcasecmp(name, "talk_detection_events")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_TALKER_DETECT); + } else if (!strcasecmp(name, "dtmf_passthrough")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_DTMF_PASS); + } else if (!strcasecmp(name, "announce_join_leave")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_ANNOUNCE_JOIN_LEAVE); + } else if (!strcasecmp(name, "pin")) { + ast_copy_string(u_profile->pin, value, sizeof(u_profile->pin)); + } else if (!strcasecmp(name, "music_on_hold_class")) { + ast_copy_string(u_profile->moh_class, value, sizeof(u_profile->moh_class)); + } else if (!strcasecmp(name, "denoise")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_DENOISE); + } else if (!strcasecmp(name, "dsp_talking_threshold")) { + if (sscanf(value, "%30u", &u_profile->talking_threshold) != 1) { + return -1; + } + } else if (!strcasecmp(name, "dsp_silence_threshold")) { + if (sscanf(value, "%30u", &u_profile->silence_threshold) != 1) { + return -1; + } + } else if (!strcasecmp(name, "dsp_drop_silence")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_DROP_SILENCE); + } else if (!strcasecmp(name, "template")) { + if (!(conf_find_user_profile(NULL, value, u_profile))) { + return -1; + } + } else if (!strcasecmp(name, "jitterbuffer")) { + ast_set2_flag(u_profile, ast_true(value), USER_OPT_JITTERBUFFER); + } else { + return -1; + } + return 0; +} + +static int set_sound(const char *sound_name, const char *sound_file, struct bridge_profile_sounds *sounds) +{ + if (ast_strlen_zero(sound_file)) { + return -1; + } + + if (!strcasecmp(sound_name, "sound_only_person")) { + ast_string_field_set(sounds, onlyperson, sound_file); + } else if (!strcasecmp(sound_name, "sound_has_joined")) { + ast_string_field_set(sounds, hasjoin, sound_file); + } else if (!strcasecmp(sound_name, "sound_has_left")) { + ast_string_field_set(sounds, hasleft, sound_file); + } else if (!strcasecmp(sound_name, "sound_kicked")) { + ast_string_field_set(sounds, kicked, sound_file); + } else if (!strcasecmp(sound_name, "sound_muted")) { + ast_string_field_set(sounds, muted, sound_file); + } else if (!strcasecmp(sound_name, "sound_unmuted")) { + ast_string_field_set(sounds, unmuted, sound_file); + } else if (!strcasecmp(sound_name, "sound_there_are")) { + ast_string_field_set(sounds, thereare, sound_file); + } else if (!strcasecmp(sound_name, "sound_other_in_party")) { + ast_string_field_set(sounds, otherinparty, sound_file); + } else if (!strcasecmp(sound_name, "sound_place_into_conference")) { + ast_string_field_set(sounds, placeintoconf, sound_file); + } else if (!strcasecmp(sound_name, "sound_wait_for_leader")) { + ast_string_field_set(sounds, waitforleader, sound_file); + } else if (!strcasecmp(sound_name, "sound_get_pin")) { + ast_string_field_set(sounds, getpin, sound_file); + } else if (!strcasecmp(sound_name, "sound_invalid_pin")) { + ast_string_field_set(sounds, invalidpin, sound_file); + } else if (!strcasecmp(sound_name, "sound_locked")) { + ast_string_field_set(sounds, locked, sound_file); + } else if (!strcasecmp(sound_name, "sound_unlocked_now")) { + ast_string_field_set(sounds, unlockednow, sound_file); + } else if (!strcasecmp(sound_name, "sound_locked_now")) { + ast_string_field_set(sounds, lockednow, sound_file); + } else if (!strcasecmp(sound_name, "sound_error_menu")) { + ast_string_field_set(sounds, errormenu, sound_file); + } else if (!strcasecmp(sound_name, "sound_join")) { + ast_string_field_set(sounds, join, sound_file); + } else if (!strcasecmp(sound_name, "sound_leave")) { + ast_string_field_set(sounds, leave, sound_file); + } else { + return -1; + } + + return 0; +} +static int set_bridge_option(const char *name, const char *value, struct bridge_profile *b_profile) +{ + if (!strcasecmp(name, "internal_sample_rate")) { + if (!strcasecmp(value, "auto")) { + b_profile->internal_sample_rate = 0; + } else if (sscanf(value, "%30u", &b_profile->internal_sample_rate) != 1) { + return -1; + } + } else if (!strcasecmp(name, "mixing_interval")) { + if (sscanf(value, "%30u", &b_profile->mix_interval) != 1) { + return -1; + } + switch (b_profile->mix_interval) { + case 10: + case 20: + case 40: + case 80: + break; + default: + ast_log(LOG_WARNING, "invalid mixing interval %u\n", b_profile->mix_interval); + b_profile->mix_interval = 0; + return -1; + } + } else if (!strcasecmp(name, "record_conference")) { + ast_set2_flag(b_profile, ast_true(value), BRIDGE_OPT_RECORD_CONFERENCE); + } else if (!strcasecmp(name, "max_members")) { + if (sscanf(value, "%30u", &b_profile->max_members) != 1) { + return -1; + } + } else if (!strcasecmp(name, "record_file")) { + ast_copy_string(b_profile->rec_file, value, sizeof(b_profile->rec_file)); + } else if (strlen(name) >= 5 && !strncasecmp(name, "sound", 5)) { + if (set_sound(name, value, b_profile->sounds)) { + return -1; + } + } else if (!strcasecmp(name, "template")) { /* Only documented for use in CONFBRIDGE dialplan function */ + struct bridge_profile *tmp = b_profile; + struct bridge_profile_sounds *sounds = bridge_profile_sounds_alloc(); + struct bridge_profile_sounds *oldsounds = b_profile->sounds; + if (!sounds) { + return -1; + } + if (!(conf_find_bridge_profile(NULL, value, tmp))) { + ao2_ref(sounds, -1); + return -1; + } + /* Using a bridge profile as a template is a little complicated due to the sounds. Since the sounds + * structure of a dynamic profile will need to be altered, a completely new sounds structure must be + * create instead of simply holding a reference to the one built by the config file. */ + ast_string_field_set(sounds, onlyperson, tmp->sounds->onlyperson); + ast_string_field_set(sounds, hasjoin, tmp->sounds->hasjoin); + ast_string_field_set(sounds, hasleft, tmp->sounds->hasleft); + ast_string_field_set(sounds, kicked, tmp->sounds->kicked); + ast_string_field_set(sounds, muted, tmp->sounds->muted); + ast_string_field_set(sounds, unmuted, tmp->sounds->unmuted); + ast_string_field_set(sounds, thereare, tmp->sounds->thereare); + ast_string_field_set(sounds, otherinparty, tmp->sounds->otherinparty); + ast_string_field_set(sounds, placeintoconf, tmp->sounds->placeintoconf); + ast_string_field_set(sounds, waitforleader, tmp->sounds->waitforleader); + ast_string_field_set(sounds, getpin, tmp->sounds->getpin); + ast_string_field_set(sounds, invalidpin, tmp->sounds->invalidpin); + ast_string_field_set(sounds, locked, tmp->sounds->locked); + ast_string_field_set(sounds, unlockednow, tmp->sounds->unlockednow); + ast_string_field_set(sounds, lockednow, tmp->sounds->lockednow); + ast_string_field_set(sounds, errormenu, tmp->sounds->errormenu); + + ao2_ref(tmp->sounds, -1); /* sounds struct copied over to it from the template by reference only. */ + ao2_ref(oldsounds,-1); /* original sounds struct we don't need anymore */ + tmp->sounds = sounds; /* the new sounds struct that is a deep copy of the one from the template. */ + } else { + return -1; + } + + return 0; +} + +/*! CONFBRIDGE dialplan function functions and channel datastore. */ +struct func_confbridge_data { + struct bridge_profile b_profile; + struct user_profile u_profile; + unsigned int b_usable:1; /*!< Tells if bridge profile is usable or not */ + unsigned int u_usable:1; /*!< Tells if user profile is usable or not */ +}; +static void func_confbridge_destroy_cb(void *data) +{ + struct func_confbridge_data *b_data = data; + conf_bridge_profile_destroy(&b_data->b_profile); + ast_free(b_data); +}; +static const struct ast_datastore_info confbridge_datastore = { + .type = "confbridge", + .destroy = func_confbridge_destroy_cb +}; +int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct ast_datastore *datastore = NULL; + struct func_confbridge_data *b_data = NULL; + char *parse = NULL; + int new = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(type); + AST_APP_ARG(option); + ); + + /* parse all the required arguments and make sure they exist. */ + if (ast_strlen_zero(data) || ast_strlen_zero(value)) { + return -1; + } + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + if (ast_strlen_zero(args.type) || ast_strlen_zero(args.option)) { + return -1; + } + + ast_channel_lock(chan); + if (!(datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { + ast_channel_unlock(chan); + + if (!(datastore = ast_datastore_alloc(&confbridge_datastore, NULL))) { + return 0; + } + if (!(b_data = ast_calloc(1, sizeof(*b_data)))) { + ast_datastore_free(datastore); + return 0; + } + if (!(b_data->b_profile.sounds = bridge_profile_sounds_alloc())) { + ast_datastore_free(datastore); + ast_free(b_data); + return 0; + } + datastore->data = b_data; + new = 1; + } else { + ast_channel_unlock(chan); + b_data = datastore->data; + } + + /* SET(CONFBRIDGE(type,option)=value) */ + if (!strcasecmp(args.type, "bridge") && !set_bridge_option(args.option, value, &b_data->b_profile)) { + b_data->b_usable = 1; + } else if (!strcasecmp(args.type, "user") && !set_user_option(args.option, value, &b_data->u_profile)) { + b_data->u_usable = 1; + } else { + ast_log(LOG_WARNING, "Profile type \"%s\" can not be set in CONFBRIDGE function with option \"%s\" and value \"%s\"\n", + args.type, args.option, value); + goto cleanup_error; + } + if (new) { + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + } + return 0; + +cleanup_error: + ast_log(LOG_ERROR, "Invalid argument provided to the %s function\n", cmd); + if (new) { + ast_datastore_free(datastore); + } + return -1; +} + +/*! + * \brief Parse the bridge profile options + */ +static int parse_bridge(const char *cat, struct ast_config *cfg) +{ + struct ast_variable *var; + struct bridge_profile tmp; + struct bridge_profile *b_profile; + + ast_copy_string(tmp.name, cat, sizeof(tmp.name)); + if ((b_profile = ao2_find(bridge_profiles, &tmp, OBJ_POINTER))) { + b_profile->delme = 0; + } else if ((b_profile = ao2_alloc(sizeof(*b_profile), NULL))) { + ast_copy_string(b_profile->name, cat, sizeof(b_profile->name)); + ao2_link(bridge_profiles, b_profile); + } else { + return -1; + } + + ao2_lock(b_profile); + /* set defaults */ + b_profile->internal_sample_rate = 0; + b_profile->flags = 0; + b_profile->max_members = 0; + b_profile->mix_interval = 0; + memset(b_profile->rec_file, 0, sizeof(b_profile->rec_file)); + if (b_profile->sounds) { + ao2_ref(b_profile->sounds, -1); /* sounds is read only. Once it has been created + * it can never be altered. This prevents having to + * do any locking after it is built from the config. */ + b_profile->sounds = NULL; + } + + if (!(b_profile->sounds = bridge_profile_sounds_alloc())) { + ao2_unlock(b_profile); + ao2_ref(b_profile, -1); + ao2_unlink(bridge_profiles, b_profile); + return -1; + } + + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "type")) { + continue; + } else if (set_bridge_option(var->name, var->value, b_profile)) { + ast_log(LOG_WARNING, "Invalid: '%s' at line %d of %s is not supported.\n", + var->name, var->lineno, CONFBRIDGE_CONFIG); + } + } + ao2_unlock(b_profile); + + ao2_ref(b_profile, -1); + return 0; +} + +static int parse_user(const char *cat, struct ast_config *cfg) +{ + struct ast_variable *var; + struct user_profile tmp; + struct user_profile *u_profile; + + ast_copy_string(tmp.name, cat, sizeof(tmp.name)); + if ((u_profile = ao2_find(user_profiles, &tmp, OBJ_POINTER))) { + u_profile->delme = 0; + } else if ((u_profile = ao2_alloc(sizeof(*u_profile), NULL))) { + ast_copy_string(u_profile->name, cat, sizeof(u_profile->name)); + ao2_link(user_profiles, u_profile); + } else { + return -1; + } + + ao2_lock(u_profile); + /* set defaults */ + u_profile->flags = 0; + u_profile->announce_user_count_all_after = 0; + u_profile->silence_threshold = DEFAULT_SILENCE_THRESHOLD; + u_profile->talking_threshold = DEFAULT_TALKING_THRESHOLD; + memset(u_profile->pin, 0, sizeof(u_profile->pin)); + memset(u_profile->moh_class, 0, sizeof(u_profile->moh_class)); + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "type")) { + continue; + } else if (set_user_option(var->name, var->value, u_profile)) { + ast_log(LOG_WARNING, "Invalid option '%s' at line %d of %s is not supported.\n", + var->name, var->lineno, CONFBRIDGE_CONFIG); + } + } + ao2_unlock(u_profile); + + ao2_ref(u_profile, -1); + return 0; +} + +static int add_action_to_menu_entry(struct conf_menu_entry *menu_entry, enum conf_menu_action_id id, char *databuf) +{ + struct conf_menu_action *menu_action = ast_calloc(1, sizeof(*menu_action)); + + if (!menu_action) { + return -1; + } + menu_action->id = id; + + switch (id) { + case MENU_ACTION_NOOP: + case MENU_ACTION_TOGGLE_MUTE: + case MENU_ACTION_INCREASE_LISTENING: + case MENU_ACTION_DECREASE_LISTENING: + case MENU_ACTION_INCREASE_TALKING: + case MENU_ACTION_DECREASE_TALKING: + case MENU_ACTION_RESET_LISTENING: + case MENU_ACTION_RESET_TALKING: + case MENU_ACTION_ADMIN_TOGGLE_LOCK: + case MENU_ACTION_ADMIN_KICK_LAST: + case MENU_ACTION_LEAVE: + break; + case MENU_ACTION_PLAYBACK: + case MENU_ACTION_PLAYBACK_AND_CONTINUE: + if (!(ast_strlen_zero(databuf))) { + ast_copy_string(menu_action->data.playback_file, databuf, sizeof(menu_action->data.playback_file)); + } else { + ast_free(menu_action); + return -1; + } + break; + case MENU_ACTION_DIALPLAN_EXEC: + if (!(ast_strlen_zero(databuf))) { + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(context); + AST_APP_ARG(exten); + AST_APP_ARG(priority); + ); + AST_STANDARD_APP_ARGS(args, databuf); + if (!ast_strlen_zero(args.context)) { + ast_copy_string(menu_action->data.dialplan_args.context, + args.context, + sizeof(menu_action->data.dialplan_args.context)); + } + if (!ast_strlen_zero(args.exten)) { + ast_copy_string(menu_action->data.dialplan_args.exten, + args.exten, + sizeof(menu_action->data.dialplan_args.exten)); + } + menu_action->data.dialplan_args.priority = 1; /* 1 by default */ + if (!ast_strlen_zero(args.priority) && + (sscanf(args.priority, "%30u", &menu_action->data.dialplan_args.priority) != 1)) { + /* invalid priority */ + ast_free(menu_action); + return -1; + } + } else { + ast_free(menu_action); + return -1; + } + }; + + AST_LIST_INSERT_TAIL(&menu_entry->actions, menu_action, action); + + return 0; +} + +static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names) +{ + struct conf_menu_entry *menu_entry = NULL; + int res = 0; + char *tmp_action_names = ast_strdupa(action_names); + char *action = NULL; + char *action_args; + char *tmp; + char buf[PATH_MAX]; + char *delimiter = ","; + + if (!(menu_entry = ast_calloc(1, sizeof(*menu_entry)))) { + return -1; + } + + for (;;) { + char *comma; + char *startbrace; + char *endbrace; + unsigned int action_len; + + if (ast_strlen_zero(tmp_action_names)) { + break; + } + startbrace = strchr(tmp_action_names, '('); + endbrace = strchr(tmp_action_names, ')'); + comma = strchr(tmp_action_names, ','); + + /* If the next action has brackets with comma delimited arguments in it, + * make the delimeter ')' instead of a comma to preserve the argments */ + if (startbrace && endbrace && comma && (comma > startbrace && comma < endbrace)) { + delimiter = ")"; + } else { + delimiter = ","; + } + + if (!(action = strsep(&tmp_action_names, delimiter))) { + break; + } + + action = ast_strip(action); + if (ast_strlen_zero(action)) { + continue; + } + + action_len = strlen(action); + ast_copy_string(menu_entry->dtmf, dtmf, sizeof(menu_entry->dtmf)); + if (!strcasecmp(action, "toggle_mute")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_TOGGLE_MUTE, NULL); + } else if (!strcasecmp(action, "no_op")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_NOOP, NULL); + } else if (!strcasecmp(action, "increase_listening_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_INCREASE_LISTENING, NULL); + } else if (!strcasecmp(action, "decrease_listening_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DECREASE_LISTENING, NULL); + } else if (!strcasecmp(action, "increase_talking_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_INCREASE_TALKING, NULL); + } else if (!strcasecmp(action, "reset_listening_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_RESET_LISTENING, NULL); + } else if (!strcasecmp(action, "reset_talking_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_RESET_TALKING, NULL); + } else if (!strcasecmp(action, "decrease_talking_volume")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DECREASE_TALKING, NULL); + } else if (!strcasecmp(action, "admin_toggle_conference_lock")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_ADMIN_TOGGLE_LOCK, NULL); + } else if (!strcasecmp(action, "admin_kick_last")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_ADMIN_KICK_LAST, NULL); + } else if (!strcasecmp(action, "leave_conference")) { + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_LEAVE, NULL); + } else if (!strncasecmp(action, "dialplan_exec(", 14)) { + ast_copy_string(buf, action, sizeof(buf)); + action_args = buf; + if ((action_args = strchr(action, '('))) { + action_args++; + } + /* it is possible that this argument may or may not + * have a closing brace at this point, it all depends on if + * comma delimited arguments were provided */ + if ((tmp = strchr(action, ')'))) { + *tmp = '\0'; + } + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_DIALPLAN_EXEC, action_args); + } else if (action_len >= 21 && !strncasecmp(action, "playback_and_continue(", 22)) { + ast_copy_string(buf, action, sizeof(buf)); + action_args = buf; + if ((action_args = strchr(action, '(')) && (tmp = strrchr(action_args, ')'))) { + *tmp = '\0'; + action_args++; + } + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_PLAYBACK_AND_CONTINUE, action_args); + } else if (action_len >= 8 && !strncasecmp(action, "playback(", 9)) { + ast_copy_string(buf, action, sizeof(buf)); + action_args = buf; + if ((action_args = strchr(action, '(')) && (tmp = strrchr(action_args, ')'))) { + *tmp = '\0'; + action_args++; + } + res |= add_action_to_menu_entry(menu_entry, MENU_ACTION_PLAYBACK, action_args); + } + } + + /* if adding any of the actions failed, bail */ + if (res) { + struct conf_menu_action *action; + while ((action = AST_LIST_REMOVE_HEAD(&menu_entry->actions, action))) { + ast_free(action); + } + ast_free(menu_entry); + return -1; + } + + AST_LIST_INSERT_TAIL(&menu->entries, menu_entry, entry); + + return 0; +} +static int parse_menu(const char *cat, struct ast_config *cfg) +{ + struct ast_variable *var; + struct conf_menu tmp; + struct conf_menu *menu; + + ast_copy_string(tmp.name, cat, sizeof(tmp.name)); + if ((menu = ao2_find(menus, &tmp, OBJ_POINTER))) { + menu->delme = 0; + } else if ((menu = ao2_alloc(sizeof(*menu), menu_destructor))) { + ast_copy_string(menu->name, cat, sizeof(menu->name)); + ao2_link(menus, menu); + } else { + return -1; + } + + ao2_lock(menu); + /* this isn't freeing the menu, just destroying the menu list so it can be rebuilt.*/ + menu_destructor(menu); + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "type")) { + continue; + } else if (add_menu_entry(menu, var->name, var->value)) { + ast_log(LOG_WARNING, "Unknown option '%s' at line %d of %s is not supported.\n", + var->name, var->lineno, CONFBRIDGE_CONFIG); + } + } + ao2_unlock(menu); + + ao2_ref(menu, -1); + return 0; +} + +static char *complete_user_profile_name(const char *line, const char *word, int pos, int state) +{ + int which = 0; + char *res = NULL; + int wordlen = strlen(word); + struct ao2_iterator i; + struct user_profile *u_profile = NULL; + + i = ao2_iterator_init(user_profiles, 0); + while ((u_profile = ao2_iterator_next(&i))) { + if (!strncasecmp(u_profile->name, word, wordlen) && ++which > state) { + res = ast_strdup(u_profile->name); + ao2_ref(u_profile, -1); + break; + } + ao2_ref(u_profile, -1); + } + ao2_iterator_destroy(&i); + + return res; +} + +static char *handle_cli_confbridge_show_user_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator it; + struct user_profile *u_profile; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show profile users"; + e->usage = + "Usage confbridge show profile users\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd,"--------- User Profiles -----------\n"); + ao2_lock(user_profiles); + it = ao2_iterator_init(user_profiles, 0); + while ((u_profile = ao2_iterator_next(&it))) { + ast_cli(a->fd,"%s\n", u_profile->name); + ao2_ref(u_profile, -1); + } + ao2_iterator_destroy(&it); + ao2_unlock(user_profiles); + + return CLI_SUCCESS; +} +static char *handle_cli_confbridge_show_user_profile(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct user_profile u_profile; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show profile user"; + e->usage = + "Usage confbridge show profile user []\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 4) { + return complete_user_profile_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + + if (!(conf_find_user_profile(NULL, a->argv[4], &u_profile))) { + ast_cli(a->fd, "No conference user profile named '%s' found!\n", a->argv[4]); + return CLI_SUCCESS; + } + + ast_cli(a->fd,"--------------------------------------------\n"); + ast_cli(a->fd,"Name: %s\n", + u_profile.name); + ast_cli(a->fd,"Admin: %s\n", + u_profile.flags & USER_OPT_ADMIN ? + "true" : "false"); + ast_cli(a->fd,"Marked User: %s\n", + u_profile.flags & USER_OPT_MARKEDUSER ? + "true" : "false"); + ast_cli(a->fd,"Start Muted: %s\n", + u_profile.flags & USER_OPT_STARTMUTED? + "true" : "false"); + ast_cli(a->fd,"MOH When Empty: %s\n", + u_profile.flags & USER_OPT_MUSICONHOLD ? + "enabled" : "disabled"); + ast_cli(a->fd,"MOH Class: %s\n", + ast_strlen_zero(u_profile.moh_class) ? + "default" : u_profile.moh_class); + ast_cli(a->fd,"Quiet: %s\n", + u_profile.flags & USER_OPT_QUIET ? + "enabled" : "disabled"); + ast_cli(a->fd,"Wait Marked: %s\n", + u_profile.flags & USER_OPT_WAITMARKED ? + "enabled" : "disabled"); + ast_cli(a->fd,"END Marked: %s\n", + u_profile.flags & USER_OPT_ENDMARKED ? + "enabled" : "disabled"); + ast_cli(a->fd,"Drop_silence: %s\n", + u_profile.flags & USER_OPT_DROP_SILENCE ? + "enabled" : "disabled"); + ast_cli(a->fd,"Silence Threshold: %dms\n", + u_profile.silence_threshold); + ast_cli(a->fd,"Talking Threshold: %dms\n", + u_profile.talking_threshold); + ast_cli(a->fd,"Denoise: %s\n", + u_profile.flags & USER_OPT_DENOISE ? + "enabled" : "disabled"); + ast_cli(a->fd,"Jitterbuffer: %s\n", + u_profile.flags & USER_OPT_JITTERBUFFER ? + "enabled" : "disabled"); + ast_cli(a->fd,"Talk Detect Events: %s\n", + u_profile.flags & USER_OPT_TALKER_DETECT ? + "enabled" : "disabled"); + ast_cli(a->fd,"DTMF Pass Through: %s\n", + u_profile.flags & USER_OPT_DTMF_PASS ? + "enabled" : "disabled"); + ast_cli(a->fd,"PIN: %s\n", + ast_strlen_zero(u_profile.pin) ? + "None" : u_profile.pin); + ast_cli(a->fd,"Announce User Count: %s\n", + u_profile.flags & USER_OPT_ANNOUNCEUSERCOUNT ? + "enabled" : "disabled"); + ast_cli(a->fd,"Announce join/leave: %s\n", + u_profile.flags & USER_OPT_ANNOUNCE_JOIN_LEAVE ? + "enabled" : "disabled"); + ast_cli(a->fd,"Announce User Count all: %s\n", + u_profile.flags & USER_OPT_ANNOUNCEUSERCOUNTALL ? + "enabled" : "disabled"); + ast_cli(a->fd,"\n"); + + return CLI_SUCCESS; +} + +static char *complete_bridge_profile_name(const char *line, const char *word, int pos, int state) +{ + int which = 0; + char *res = NULL; + int wordlen = strlen(word); + struct ao2_iterator i; + struct bridge_profile *b_profile = NULL; + + i = ao2_iterator_init(bridge_profiles, 0); + while ((b_profile = ao2_iterator_next(&i))) { + if (!strncasecmp(b_profile->name, word, wordlen) && ++which > state) { + res = ast_strdup(b_profile->name); + ao2_ref(b_profile, -1); + break; + } + ao2_ref(b_profile, -1); + } + ao2_iterator_destroy(&i); + + return res; +} + +static char *handle_cli_confbridge_show_bridge_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator it; + struct bridge_profile *b_profile; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show profile bridges"; + e->usage = + "Usage confbridge show profile bridges\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd,"--------- Bridge Profiles -----------\n"); + ao2_lock(bridge_profiles); + it = ao2_iterator_init(bridge_profiles, 0); + while ((b_profile = ao2_iterator_next(&it))) { + ast_cli(a->fd,"%s\n", b_profile->name); + ao2_ref(b_profile, -1); + } + ao2_iterator_destroy(&it); + ao2_unlock(bridge_profiles); + + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct bridge_profile b_profile; + char tmp[64]; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show profile bridge"; + e->usage = + "Usage confbridge show profile bridge \n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 4) { + return complete_bridge_profile_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + + if (!(conf_find_bridge_profile(NULL, a->argv[4], &b_profile))) { + ast_cli(a->fd, "No conference bridge profile named '%s' found!\n", a->argv[4]); + return CLI_SUCCESS; + } + + ast_cli(a->fd,"--------------------------------------------\n"); + ast_cli(a->fd,"Name: %s\n", b_profile.name); + + if (b_profile.internal_sample_rate) { + snprintf(tmp, sizeof(tmp), "%d", b_profile.internal_sample_rate); + } else { + ast_copy_string(tmp, "auto", sizeof(tmp)); + } + ast_cli(a->fd,"Internal Sample Rate: %s\n", tmp); + + if (b_profile.mix_interval) { + ast_cli(a->fd,"Mixing Interval: %d\n", b_profile.mix_interval); + } else { + ast_cli(a->fd,"Mixing Interval: Default 20ms\n"); + } + + ast_cli(a->fd,"Record Conference: %s\n", + b_profile.flags & BRIDGE_OPT_RECORD_CONFERENCE ? + "yes" : "no"); + + ast_cli(a->fd,"Record File: %s\n", + ast_strlen_zero(b_profile.rec_file) ? "Auto Generated" : + b_profile.rec_file); + + if (b_profile.max_members) { + ast_cli(a->fd,"Max Members: %d\n", b_profile.max_members); + } else { + ast_cli(a->fd,"Max Members: No Limit\n"); + } + + ast_cli(a->fd,"sound_join: %s\n", conf_get_sound(CONF_SOUND_JOIN, b_profile.sounds)); + ast_cli(a->fd,"sound_leave: %s\n", conf_get_sound(CONF_SOUND_LEAVE, b_profile.sounds)); + ast_cli(a->fd,"sound_only_person: %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds)); + ast_cli(a->fd,"sound_has_joined: %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds)); + ast_cli(a->fd,"sound_has_left: %s\n", conf_get_sound(CONF_SOUND_HAS_LEFT, b_profile.sounds)); + ast_cli(a->fd,"sound_kicked: %s\n", conf_get_sound(CONF_SOUND_KICKED, b_profile.sounds)); + ast_cli(a->fd,"sound_muted: %s\n", conf_get_sound(CONF_SOUND_MUTED, b_profile.sounds)); + ast_cli(a->fd,"sound_unmuted: %s\n", conf_get_sound(CONF_SOUND_UNMUTED, b_profile.sounds)); + ast_cli(a->fd,"sound_there_are: %s\n", conf_get_sound(CONF_SOUND_THERE_ARE, b_profile.sounds)); + ast_cli(a->fd,"sound_other_in_party: %s\n", conf_get_sound(CONF_SOUND_OTHER_IN_PARTY, b_profile.sounds)); + ast_cli(a->fd,"sound_place_into_conference: %s\n", conf_get_sound(CONF_SOUND_PLACE_IN_CONF, b_profile.sounds)); + ast_cli(a->fd,"sound_wait_for_leader: %s\n", conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, b_profile.sounds)); + ast_cli(a->fd,"sound_get_pin: %s\n", conf_get_sound(CONF_SOUND_GET_PIN, b_profile.sounds)); + ast_cli(a->fd,"sound_invalid_pin: %s\n", conf_get_sound(CONF_SOUND_INVALID_PIN, b_profile.sounds)); + ast_cli(a->fd,"sound_locked: %s\n", conf_get_sound(CONF_SOUND_LOCKED, b_profile.sounds)); + ast_cli(a->fd,"sound_unlocked_now: %s\n", conf_get_sound(CONF_SOUND_UNLOCKED_NOW, b_profile.sounds)); + ast_cli(a->fd,"sound_lockednow: %s\n", conf_get_sound(CONF_SOUND_LOCKED_NOW, b_profile.sounds)); + ast_cli(a->fd,"sound_error_menu: %s\n", conf_get_sound(CONF_SOUND_ERROR_MENU, b_profile.sounds)); + ast_cli(a->fd,"\n"); + + conf_bridge_profile_destroy(&b_profile); + return CLI_SUCCESS; +} + +static char *complete_menu_name(const char *line, const char *word, int pos, int state) +{ + int which = 0; + char *res = NULL; + int wordlen = strlen(word); + struct ao2_iterator i; + struct conf_menu *menu = NULL; + + i = ao2_iterator_init(menus, 0); + while ((menu = ao2_iterator_next(&i))) { + if (!strncasecmp(menu->name, word, wordlen) && ++which > state) { + res = ast_strdup(menu->name); + ao2_ref(menu, -1); + break; + } + ao2_ref(menu, -1); + } + ao2_iterator_destroy(&i); + + return res; +} + +static char *handle_cli_confbridge_show_menus(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator it; + struct conf_menu *menu; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show menus"; + e->usage = + "Usage confbridge show profile menus\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd,"--------- Menus -----------\n"); + ao2_lock(menus); + it = ao2_iterator_init(menus, 0); + while ((menu = ao2_iterator_next(&it))) { + ast_cli(a->fd,"%s\n", menu->name); + ao2_ref(menu, -1); + } + ao2_iterator_destroy(&it); + ao2_unlock(menus); + + return CLI_SUCCESS; +} + +static char *handle_cli_confbridge_show_menu(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct conf_menu tmp; + struct conf_menu *menu; + struct conf_menu_entry *menu_entry = NULL; + struct conf_menu_action *menu_action = NULL; + + switch (cmd) { + case CLI_INIT: + e->command = "confbridge show menu"; + e->usage = + "Usage confbridge show menu []\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_menu_name(a->line, a->word, a->pos, a->n); + } + return NULL; + } + + if (a->argc != 4) { + return CLI_SHOWUSAGE; + } + + ast_copy_string(tmp.name, a->argv[3], sizeof(tmp.name)); + if (!(menu = ao2_find(menus, &tmp, OBJ_POINTER))) { + ast_cli(a->fd, "No conference menu named '%s' found!\n", a->argv[3]); + return CLI_SUCCESS; + } + ao2_lock(menu); + + ast_cli(a->fd,"Name: %s\n", menu->name); + AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { + int action_num = 0; + ast_cli(a->fd, "%s=", menu_entry->dtmf); + AST_LIST_TRAVERSE(&menu_entry->actions, menu_action, action) { + if (action_num) { + ast_cli(a->fd, ", "); + } + switch (menu_action->id) { + case MENU_ACTION_TOGGLE_MUTE: + ast_cli(a->fd, "toggle_mute"); + break; + case MENU_ACTION_NOOP: + ast_cli(a->fd, "no_op"); + break; + case MENU_ACTION_INCREASE_LISTENING: + ast_cli(a->fd, "increase_listening_volume"); + break; + case MENU_ACTION_DECREASE_LISTENING: + ast_cli(a->fd, "decrease_listening_volume"); + break; + case MENU_ACTION_RESET_LISTENING: + ast_cli(a->fd, "reset_listening_volume"); + break; + case MENU_ACTION_RESET_TALKING: + ast_cli(a->fd, "reset_talking_volume"); + break; + case MENU_ACTION_INCREASE_TALKING: + ast_cli(a->fd, "increase_talking_volume"); + break; + case MENU_ACTION_DECREASE_TALKING: + ast_cli(a->fd, "decrease_talking_volume"); + break; + case MENU_ACTION_PLAYBACK: + ast_cli(a->fd, "playback(%s)", menu_action->data.playback_file); + break; + case MENU_ACTION_PLAYBACK_AND_CONTINUE: + ast_cli(a->fd, "playback_and_continue(%s)", menu_action->data.playback_file); + break; + case MENU_ACTION_DIALPLAN_EXEC: + ast_cli(a->fd, "dialplan_exec(%s,%s,%d)", + menu_action->data.dialplan_args.context, + menu_action->data.dialplan_args.exten, + menu_action->data.dialplan_args.priority); + break; + case MENU_ACTION_ADMIN_TOGGLE_LOCK: + ast_cli(a->fd, "admin_toggle_conference_lock"); + break; + case MENU_ACTION_ADMIN_KICK_LAST: + ast_cli(a->fd, "admin_kick_last"); + break; + case MENU_ACTION_LEAVE: + ast_cli(a->fd, "leave_conference"); + break; + } + action_num++; + } + ast_cli(a->fd,"\n"); + } + + + ao2_unlock(menu); + ao2_ref(menu, -1); + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_confbridge_parser[] = { + AST_CLI_DEFINE(handle_cli_confbridge_show_user_profile, "Show a conference user profile."), + AST_CLI_DEFINE(handle_cli_confbridge_show_bridge_profile, "Show a conference bridge profile."), + AST_CLI_DEFINE(handle_cli_confbridge_show_menu, "Show a conference menu"), + AST_CLI_DEFINE(handle_cli_confbridge_show_user_profiles, "Show a list of conference user profiles."), + AST_CLI_DEFINE(handle_cli_confbridge_show_bridge_profiles, "Show a list of conference bridge profiles."), + AST_CLI_DEFINE(handle_cli_confbridge_show_menus, "Show a list of conference menus"), + +}; + +static int conf_parse_init(void) +{ + if (!(user_profiles = ao2_container_alloc(283, user_hash_cb, user_cmp_cb))) { + conf_destroy_config(); + return -1; + } + + if (!(bridge_profiles = ao2_container_alloc(283, bridge_hash_cb, bridge_cmp_cb))) { + conf_destroy_config(); + return -1; + } + + if (!(menus = ao2_container_alloc(283, menu_hash_cb, menu_cmp_cb))) { + conf_destroy_config(); + return -1; + } + + ast_cli_register_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser)); + + return 0; +} + +void conf_destroy_config() +{ + if (user_profiles) { + ao2_ref(user_profiles, -1); + user_profiles = NULL; + } + if (bridge_profiles) { + ao2_ref(bridge_profiles, -1); + bridge_profiles = NULL; + } + + if (menus) { + ao2_ref(menus, -1); + menus = NULL; + } + ast_cli_unregister_multiple(cli_confbridge_parser, sizeof(cli_confbridge_parser) / sizeof(struct ast_cli_entry)); +} + +static void remove_all_delme(void) +{ + ao2_callback(user_profiles, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_user_delme_cb, NULL); + ao2_callback(bridge_profiles, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_bridge_delme_cb, NULL); + ao2_callback(menus, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, match_menu_delme_cb, NULL); +} + +static void mark_all_delme(void) +{ + ao2_callback(user_profiles, OBJ_NODATA | OBJ_MULTIPLE, user_mark_delme_cb, NULL); + ao2_callback(bridge_profiles, OBJ_NODATA | OBJ_MULTIPLE, bridge_mark_delme_cb, NULL); + ao2_callback(menus, OBJ_NODATA | OBJ_MULTIPLE, menu_mark_delme_cb, NULL); +} + +int conf_load_config(int reload) +{ + struct ast_flags config_flags = { 0, }; + struct ast_config *cfg = ast_config_load(CONFBRIDGE_CONFIG, config_flags); + const char *type = NULL; + char *cat = NULL; + + if (!reload) { + conf_parse_init(); + } + + if (!cfg || cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { + return 0; + } + + mark_all_delme(); + + while ((cat = ast_category_browse(cfg, cat))) { + if (!(type = (ast_variable_retrieve(cfg, cat, "type")))) { + if (strcasecmp(cat, "general")) { + ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat); + } + continue; + } + if (!strcasecmp(type, "bridge")) { + parse_bridge(cat, cfg); + } else if (!strcasecmp(type, "user")) { + parse_user(cat, cfg); + } else if (!strcasecmp(type, "menu")) { + parse_menu(cat, cfg); + } else { + continue; + } + } + + remove_all_delme(); + + return 0; +} + +static void conf_user_profile_copy(struct user_profile *dst, struct user_profile *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result) +{ + struct user_profile tmp; + struct user_profile *tmp2; + struct ast_datastore *datastore = NULL; + struct func_confbridge_data *b_data = NULL; + ast_copy_string(tmp.name, user_profile_name, sizeof(tmp.name)); + + if (chan) { + ast_channel_lock(chan); + if ((datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { + ast_channel_unlock(chan); + b_data = datastore->data; + if (b_data->u_usable) { + conf_user_profile_copy(result, &b_data->u_profile); + return result; + } + } + ast_channel_unlock(chan); + } + + if (ast_strlen_zero(user_profile_name)) { + user_profile_name = DEFAULT_USER_PROFILE; + } + if (!(tmp2 = ao2_find(user_profiles, &tmp, OBJ_POINTER))) { + return NULL; + } + ao2_lock(tmp2); + conf_user_profile_copy(result, tmp2); + ao2_unlock(tmp2); + ao2_ref(tmp2, -1); + + return result; +} + +void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src) +{ + memcpy(dst, src, sizeof(*dst)); + if (src->sounds) { + ao2_ref(src->sounds, +1); + } +} + +void conf_bridge_profile_destroy(struct bridge_profile *b_profile) +{ + if (b_profile->sounds) { + ao2_ref(b_profile->sounds, -1); + b_profile->sounds = NULL; + } +} + +const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result) +{ + struct bridge_profile tmp; + struct bridge_profile *tmp2; + struct ast_datastore *datastore = NULL; + struct func_confbridge_data *b_data = NULL; + + if (chan) { + ast_channel_lock(chan); + if ((datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL))) { + ast_channel_unlock(chan); + b_data = datastore->data; + if (b_data->b_usable) { + conf_bridge_profile_copy(result, &b_data->b_profile); + return result; + } + } + ast_channel_unlock(chan); + } + if (ast_strlen_zero(bridge_profile_name)) { + bridge_profile_name = DEFAULT_BRIDGE_PROFILE; + } + ast_copy_string(tmp.name, bridge_profile_name, sizeof(tmp.name)); + if (!(tmp2 = ao2_find(bridge_profiles, &tmp, OBJ_POINTER))) { + return NULL; + } + ao2_lock(tmp2); + conf_bridge_profile_copy(result, tmp2); + ao2_unlock(tmp2); + ao2_ref(tmp2, -1); + + return result; +} + +struct dtmf_menu_hook_pvt { + struct conference_bridge_user *conference_bridge_user; + struct conf_menu_entry menu_entry; + struct conf_menu *menu; +}; + +static void menu_hook_destroy(void *hook_pvt) +{ + struct dtmf_menu_hook_pvt *pvt = hook_pvt; + struct conf_menu_action *action = NULL; + + ao2_ref(pvt->menu, -1); + + while ((action = AST_LIST_REMOVE_HEAD(&pvt->menu_entry.actions, action))) { + ast_free(action); + } + ast_free(pvt); +} + +static int menu_hook_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct dtmf_menu_hook_pvt *pvt = hook_pvt; + return conf_handle_dtmf(bridge_channel, pvt->conference_bridge_user, &pvt->menu_entry, pvt->menu); +} + +static int copy_menu_entry(struct conf_menu_entry *dst, struct conf_menu_entry *src) +{ + struct conf_menu_action *menu_action = NULL; + struct conf_menu_action *new_menu_action = NULL; + + memcpy(dst, src, sizeof(*dst)); + AST_LIST_HEAD_INIT_NOLOCK(&dst->actions); + + AST_LIST_TRAVERSE(&src->actions, menu_action, action) { + if (!(new_menu_action = ast_calloc(1, sizeof(*new_menu_action)))) { + return -1; + } + memcpy(new_menu_action, menu_action, sizeof(*new_menu_action)); + AST_LIST_INSERT_HEAD(&dst->actions, new_menu_action, action); + } + return 0; +} + +void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry) +{ + struct conf_menu_action *menu_action = NULL; + while ((menu_action = AST_LIST_REMOVE_HEAD(&menu_entry->actions, action))) { + ast_free(menu_action); + } +} + +int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result) +{ + struct conf_menu_entry *menu_entry = NULL; + + ao2_lock(menu); + AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { + if (!strcasecmp(menu_entry->dtmf, dtmf_sequence)) { + copy_menu_entry(result, menu_entry); + ao2_unlock(menu); + return 1; + } + } + ao2_unlock(menu); + + return 0; +} + +int conf_set_menu_to_user(const char *menu_name, struct conference_bridge_user *conference_bridge_user) +{ + struct conf_menu tmp; + struct conf_menu *menu; + struct conf_menu_entry *menu_entry = NULL; + ast_copy_string(tmp.name, menu_name, sizeof(tmp.name)); + + if (!(menu = ao2_find(menus, &tmp, OBJ_POINTER))) { + return -1; + } + ao2_lock(menu); + AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) { + struct dtmf_menu_hook_pvt *pvt; + if (!(pvt = ast_calloc(1, sizeof(*pvt)))) { + ao2_unlock(menu); + ao2_ref(menu, -1); + return -1; + } + if (copy_menu_entry(&pvt->menu_entry, menu_entry)) { + ast_free(pvt); + ao2_unlock(menu); + ao2_ref(menu, -1); + return -1; + } + pvt->conference_bridge_user = conference_bridge_user; + ao2_ref(menu, +1); + pvt->menu = menu; + + ast_bridge_features_hook(&conference_bridge_user->features, pvt->menu_entry.dtmf, menu_hook_callback, pvt, menu_hook_destroy); + } + + ao2_unlock(menu); + ao2_ref(menu, -1); + + return 0; +} diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h new file mode 100644 index 000000000..4b6c0615e --- /dev/null +++ b/apps/confbridge/include/confbridge.h @@ -0,0 +1,320 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011, Digium, Inc. + * + * David Vossel + * Joshua Colp + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + + +#ifndef _CONFBRIDGE_H +#define _CONFBRIDGE_H + +#include "asterisk.h" +#include "asterisk/app.h" +#include "asterisk/logger.h" +#include "asterisk/linkedlists.h" +#include "asterisk/channel.h" +#include "asterisk/bridging.h" +#include "asterisk/bridging_features.h" + +/* Maximum length of a conference bridge name */ +#define MAX_CONF_NAME 32 +/* Maximum length of a conference pin */ +#define MAX_PIN 80 + +#define DEFAULT_USER_PROFILE "default_user" +#define DEFAULT_BRIDGE_PROFILE "default_bridge" + +#define DEFAULT_TALKING_THRESHOLD 160 +#define DEFAULT_SILENCE_THRESHOLD 2500 + +enum user_profile_flags { + USER_OPT_ADMIN = (1 << 0), /*!< Set if the caller is an administrator */ + USER_OPT_NOONLYPERSON = (1 << 1), /*!< Set if the "you are currently the only person in this conference" sound file should not be played */ + USER_OPT_MARKEDUSER = (1 << 2), /*!< Set if the caller is a marked user */ + USER_OPT_STARTMUTED = (1 << 3), /*!< Set if the caller should be initially set muted */ + USER_OPT_MUSICONHOLD = (1 << 4), /*!< Set if music on hold should be played if nobody else is in the conference bridge */ + USER_OPT_QUIET = (1 << 5), /*!< Set if no audio prompts should be played */ + USER_OPT_ANNOUNCEUSERCOUNT = (1 << 6), /*!< Set if the number of users should be announced to the caller */ + USER_OPT_WAITMARKED = (1 << 7), /*!< Set if the user must wait for a marked user before starting */ + USER_OPT_ENDMARKED = (1 << 8), /*!< Set if the user should be kicked after the last Marked user exits */ + USER_OPT_DENOISE = (1 << 9), /*!< Sets if denoise filter should be used on audio before mixing. */ + USER_OPT_ANNOUNCE_JOIN_LEAVE = (1 << 10), /*!< Sets if the user's name should be recorded and announced on join and leave. */ + USER_OPT_TALKER_DETECT = (1 << 11), /*!< Sets if start and stop talking events should generated for this user over AMI. */ + USER_OPT_DROP_SILENCE = (1 << 12), /*!< Sets if silence should be dropped from the mix or not. */ + USER_OPT_DTMF_PASS = (1 << 13), /*!< Sets if dtmf should be passed into the conference or not */ + USER_OPT_ANNOUNCEUSERCOUNTALL = (1 << 14), /*!< Sets if the number of users should be announced to everyone. */ + USER_OPT_JITTERBUFFER = (1 << 15), /*!< Places a jitterbuffer on the user. */ +}; + +enum bridge_profile_flags { + BRIDGE_OPT_RECORD_CONFERENCE = (1 << 0), /*!< Set if the conference should be recorded */ +}; + +enum conf_menu_action_id { + MENU_ACTION_TOGGLE_MUTE = 1, + MENU_ACTION_PLAYBACK, + MENU_ACTION_PLAYBACK_AND_CONTINUE, + MENU_ACTION_INCREASE_LISTENING, + MENU_ACTION_DECREASE_LISTENING, + MENU_ACTION_RESET_LISTENING, + MENU_ACTION_RESET_TALKING, + MENU_ACTION_INCREASE_TALKING, + MENU_ACTION_DECREASE_TALKING, + MENU_ACTION_DIALPLAN_EXEC, + MENU_ACTION_ADMIN_TOGGLE_LOCK, + MENU_ACTION_ADMIN_KICK_LAST, + MENU_ACTION_LEAVE, + MENU_ACTION_NOOP, +}; + +/*! The conference menu action contains both + * the action id that represents the action that + * must take place, along with any data associated + * with that action. */ +struct conf_menu_action { + enum conf_menu_action_id id; + union { + char playback_file[PATH_MAX]; + struct { + char context[AST_MAX_CONTEXT]; + char exten[AST_MAX_EXTENSION]; + int priority; + } dialplan_args; + } data; + AST_LIST_ENTRY(conf_menu_action) action; +}; + +/*! Conference menu entries contain the DTMF sequence + * and the list of actions that are associated with that + * sequence. */ +struct conf_menu_entry { + /*! the DTMF sequence that triggers the actions */ + char dtmf[MAXIMUM_DTMF_FEATURE_STRING]; + /*! The actions associated with this menu entry. */ + AST_LIST_HEAD_NOLOCK(, conf_menu_action) actions; + AST_LIST_ENTRY(conf_menu_entry) entry; +}; + +/*! Conference menu structure. Contains a list + * of DTMF sequences coupled with the actions those + * sequences invoke.*/ +struct conf_menu { + char name[128]; + int delme; + AST_LIST_HEAD_NOLOCK(, conf_menu_entry) entries; +}; + +struct user_profile { + char name[128]; + char pin[MAX_PIN]; + char moh_class[128]; + unsigned int flags; + unsigned int announce_user_count_all_after; + /*! The time in ms of talking before a user is considered to be talking by the dsp. */ + unsigned int talking_threshold; + /*! The time in ms of silence before a user is considered to be silent by the dsp. */ + unsigned int silence_threshold; + int delme; +}; + +enum conf_sounds { + CONF_SOUND_HAS_JOINED, + CONF_SOUND_HAS_LEFT, + CONF_SOUND_KICKED, + CONF_SOUND_MUTED, + CONF_SOUND_UNMUTED, + CONF_SOUND_ONLY_ONE, + CONF_SOUND_THERE_ARE, + CONF_SOUND_OTHER_IN_PARTY, + CONF_SOUND_PLACE_IN_CONF, + CONF_SOUND_WAIT_FOR_LEADER, + CONF_SOUND_LEADER_HAS_LEFT, + CONF_SOUND_GET_PIN, + CONF_SOUND_INVALID_PIN, + CONF_SOUND_ONLY_PERSON, + CONF_SOUND_LOCKED, + CONF_SOUND_LOCKED_NOW, + CONF_SOUND_UNLOCKED_NOW, + CONF_SOUND_ERROR_MENU, + CONF_SOUND_JOIN, + CONF_SOUND_LEAVE, +}; + +struct bridge_profile_sounds { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(hasjoin); + AST_STRING_FIELD(hasleft); + AST_STRING_FIELD(kicked); + AST_STRING_FIELD(muted); + AST_STRING_FIELD(unmuted); + AST_STRING_FIELD(onlyone); + AST_STRING_FIELD(thereare); + AST_STRING_FIELD(otherinparty); + AST_STRING_FIELD(placeintoconf); + AST_STRING_FIELD(waitforleader); + AST_STRING_FIELD(leaderhasleft); + AST_STRING_FIELD(getpin); + AST_STRING_FIELD(invalidpin); + AST_STRING_FIELD(onlyperson); + AST_STRING_FIELD(locked); + AST_STRING_FIELD(lockednow); + AST_STRING_FIELD(unlockednow); + AST_STRING_FIELD(errormenu); + AST_STRING_FIELD(leave); + AST_STRING_FIELD(join); + ); +}; + +struct bridge_profile { + char name[64]; + char rec_file[PATH_MAX]; + unsigned int flags; + unsigned int max_members; /*!< The maximum number of participants allowed in the conference */ + unsigned int internal_sample_rate; /*!< The internal sample rate of the bridge. 0 when set to auto adjust mode. */ + unsigned int mix_interval; /*!< The internal mixing interval used by the bridge. When set to 0 the bridgewill use a default interval. */ + struct bridge_profile_sounds *sounds; + int delme; +}; + +/*! \brief The structure that represents a conference bridge */ +struct conference_bridge { + char name[MAX_CONF_NAME]; /*!< Name of the conference bridge */ + struct ast_bridge *bridge; /*!< Bridge structure doing the mixing */ + struct bridge_profile b_profile; /*!< The Bridge Configuration Profile */ + unsigned int users; /*!< Number of users present */ + unsigned int markedusers; /*!< Number of marked users present */ + unsigned int locked:1; /*!< Is this conference bridge locked? */ + struct ast_channel *playback_chan; /*!< Channel used for playback into the conference bridge */ + struct ast_channel *record_chan; /*!< Channel used for recording the conference */ + pthread_t record_thread; /*!< The thread the recording chan lives in */ + ast_mutex_t playback_lock; /*!< Lock used for playback channel */ + AST_LIST_HEAD_NOLOCK(, conference_bridge_user) users_list; /*!< List of users participating in the conference bridge */ +}; + +/*! \brief The structure that represents a conference bridge user */ +struct conference_bridge_user { + struct conference_bridge *conference_bridge; /*!< Conference bridge they are participating in */ + struct bridge_profile b_profile; /*!< The Bridge Configuration Profile */ + struct user_profile u_profile; /*!< The User Configuration Profile */ + char menu_name[64]; /*!< The name of the DTMF menu assigned to this user */ + char name_rec_location[PATH_MAX]; /*!< Location of the User's name recorded file if it exists */ + struct ast_channel *chan; /*!< Asterisk channel participating */ + struct ast_bridge_features features; /*!< Bridge features structure */ + struct ast_bridge_tech_optimizations tech_args; /*!< Bridge technology optimizations for talk detection */ + unsigned int kicked:1; /*!< User has been kicked from the conference */ + AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */ +}; + +/*! \brief load confbridge.conf file */ +int conf_load_config(int reload); + +/*! \brief destroy the information loaded from the confbridge.conf file*/ +void conf_destroy_config(void); + +/*! + * \brief find a user profile given a user profile's name and store + * that profile in result structure. + * + * \details This function first attempts to find any custom user + * profile that might exist on a channel datastore, if that doesn't + * exist it looks up the provided user profile name, if that doesn't + * exist either the default_user profile is used. + + * \retval user profile on success + * \retval NULL on failure + */ +const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result); + +/*! + * \brief Find a bridge profile + * + * \details Any bridge profile found using this function must be + * destroyed using conf_bridge_profile_destroy. This function first + * attempts to find any custom bridge profile that might exist on + * a channel datastore, if that doesn't exist it looks up the + * provided bridge profile name, if that doesn't exist either + * the default_bridge profile is used. + * + * \retval Bridge profile on success + * \retval NULL on failure + */ +const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result); + +/*! + * \brief Destroy a bridge profile found by 'conf_find_bridge_profile' + */ +void conf_bridge_profile_destroy(struct bridge_profile *b_profile); + +/*! + * \brief copies a bridge profile + * \note conf_bridge_profile_destroy must be called on the dst structure + */ +void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src); + +/*! + * \brief Set a DTMF menu to a conference user by menu name. + * + * \retval 0 on success, menu was found and set + * \retval -1 on error, menu was not found + */ +int conf_set_menu_to_user(const char *menu_name, struct conference_bridge_user *conference_bridge_user); + +/*! + * \brief Finds a menu_entry in a menu structure matched by DTMF sequence. + * + * \note the menu entry found must be destroyed using conf_menu_entry_destroy() + * + * \retval 1 success, entry is found and stored in result + * \retval 0 failure, no entry found for given DTMF sequence + */ +int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result); + +/*! + * \brief Destroys and frees all the actions stored in a menu_entry structure + */ +void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry); + +/*! + * \brief Once a DTMF sequence matches a sequence in the user's DTMF menu, this function will get + * called to perform the menu action. + * + * \param bridge_channel, Bridged channel this is involving + * \param conference_bridge_user, the conference user to perform the action on. + * \param menu_entry, the menu entry that invoked this callback to occur. + * \param menu, an AO2 referenced pointer to the entire menu structure the menu_entry + * derived from. + * + * \note The menu_entry is a deep copy of the entry found in the menu structure. This allows + * for the menu_entry to be accessed without requiring the menu lock. If the menu must + * be accessed, the menu lock must be held. Reference counting of the menu structure is + * handled outside of the scope of this function. + * + * \retval 0 success + * \retval -1 failure + */ +int conf_handle_dtmf( + struct ast_bridge_channel *bridge_channel, + struct conference_bridge_user *conference_bridge_user, + struct conf_menu_entry *menu_entry, + struct conf_menu *menu); + + +/*! \brief Looks to see if sound file is stored in bridge profile sounds, if not + * default sound is provided.*/ +const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds); + +int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value); +#endif diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c index 67da6489b..2b21933e2 100644 --- a/bridges/bridge_builtin_features.c +++ b/bridges/bridge_builtin_features.c @@ -198,12 +198,12 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg ast_bridge_features_enable(&caller_features, AST_BRIDGE_BUILTIN_HANGUP, (attended_transfer && !ast_strlen_zero(attended_transfer->complete) ? attended_transfer->complete : "*1"), NULL); ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->threeway) ? attended_transfer->threeway : "*2"), - attended_threeway_transfer, NULL); + attended_threeway_transfer, NULL, NULL); ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->abort) ? attended_transfer->abort : "*3"), - attended_abort_transfer, NULL); + attended_abort_transfer, NULL, NULL); /* But for the caller we want to join the bridge in a blocking fashion so we don't spin around in this function doing nothing while waiting */ - attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features); + attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL); /* Since the above returned the caller features structure is of no more use */ ast_bridge_features_cleanup(&caller_features); diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 1ac2780de..eb476932f 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -1,9 +1,10 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 2007, Digium, Inc. + * Copyright (C) 2011, Digium, Inc. * * Joshua Colp + * David Vossel * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -21,12 +22,9 @@ * \brief Multi-party software based channel mixing * * \author Joshua Colp + * \author David Vossel * * \ingroup bridges - * - * \todo This bridge operates in 8 kHz mode unless a define is uncommented. - * This needs to be improved so the bridge moves between the dominant codec as needed depending - * on channels present in the bridge and transcoding capabilities. */ #include "asterisk.h" @@ -51,20 +49,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/slinfactory.h" #include "asterisk/astobj2.h" #include "asterisk/timing.h" +#include "asterisk/translate.h" -#define MAX_DATALEN 3840 +#define MAX_DATALEN 8096 /*! \brief Interval at which mixing will take place. Valid options are 10, 20, and 40. */ -#define SOFTMIX_INTERVAL 20 +#define DEFAULT_SOFTMIX_INTERVAL 20 /*! \brief Size of the buffer used for sample manipulation */ -#define SOFTMIX_DATALEN(rate) ((rate/50) * (SOFTMIX_INTERVAL / 10)) +#define SOFTMIX_DATALEN(rate, interval) ((rate/50) * (interval / 10)) /*! \brief Number of samples we are dealing with */ -#define SOFTMIX_SAMPLES(rate) (SOFTMIX_DATALEN(rate) / 2) +#define SOFTMIX_SAMPLES(rate, interval) (SOFTMIX_DATALEN(rate, interval) / 2) -/*! \brief Define used to turn on 16 kHz audio support */ -/* #define SOFTMIX_16_SUPPORT */ +/*! \brief Number of mixing iterations to perform between gathering statistics. */ +#define SOFTMIX_STAT_INTERVAL 100 + +/* This is the threshold in ms at which a channel's own audio will stop getting + * mixed out its own write audio stream because it is not talking. */ +#define DEFAULT_SOFTMIX_SILENCE_THRESHOLD 2500 +#define DEFAULT_SOFTMIX_TALKING_THRESHOLD 160 /*! \brief Structure which contains per-channel mixing information */ struct softmix_channel { @@ -73,7 +77,14 @@ struct softmix_channel { /*! Factory which contains audio read in from the channel */ struct ast_slinfactory factory; /*! Frame that contains mixed audio to be written out to the channel */ - struct ast_frame frame; + struct ast_frame write_frame; + /*! Frame that contains mixed audio read from the channel */ + struct ast_frame read_frame; + /*! DSP for detecting silence */ + struct ast_dsp *dsp; + /*! Bit used to indicate if a channel is talking or not. This affects how + * the channel's audio is mixed back to it. */ + int talking:1; /*! Bit used to indicate that the channel provided audio for this mixing interval */ int have_audio:1; /*! Bit used to indicate that a frame is available to be written out to the channel */ @@ -87,66 +98,268 @@ struct softmix_channel { struct softmix_bridge_data { struct ast_timer *timer; unsigned int internal_rate; + unsigned int internal_mixing_interval; }; +struct softmix_stats { + /*! Each index represents a sample rate used above the internal rate. */ + unsigned int sample_rates[16]; + /*! Each index represents the number of channels using the same index in the sample_rates array. */ + unsigned int num_channels[16]; + /*! the number of channels above the internal sample rate */ + unsigned int num_above_internal_rate; + /*! the number of channels at the internal sample rate */ + unsigned int num_at_internal_rate; + /*! the absolute highest sample rate supported by any channel in the bridge */ + unsigned int highest_supported_rate; + /*! Is the sample rate locked by the bridge, if so what is that rate.*/ + unsigned int locked_rate; +}; + +struct softmix_mixing_array { + int max_num_entries; + int used_entries; + int16_t **buffers; +}; + +struct softmix_translate_helper_entry { + int num_times_requested; /*!< Once this entry is no longer requested, free the trans_pvt + and re-init if it was usable. */ + struct ast_format dst_format; /*!< The destination format for this helper */ + struct ast_trans_pvt *trans_pvt; /*!< the translator for this slot. */ + struct ast_frame *out_frame; /*!< The output frame from the last translation */ + AST_LIST_ENTRY(softmix_translate_helper_entry) entry; +}; + +struct softmix_translate_helper { + struct ast_format slin_src; /*!< the source format expected for all the translators */ + AST_LIST_HEAD_NOLOCK(, softmix_translate_helper_entry) entries; +}; + +static struct softmix_translate_helper_entry *softmix_translate_helper_entry_alloc(struct ast_format *dst) +{ + struct softmix_translate_helper_entry *entry; + if (!(entry = ast_calloc(1, sizeof(*entry)))) { + return NULL; + } + ast_format_copy(&entry->dst_format, dst); + return entry; +} + +static void *softmix_translate_helper_free_entry(struct softmix_translate_helper_entry *entry) +{ + if (entry->trans_pvt) { + ast_translator_free_path(entry->trans_pvt); + } + if (entry->out_frame) { + ast_frfree(entry->out_frame); + } + ast_free(entry); + return NULL; +} + +static void softmix_translate_helper_init(struct softmix_translate_helper *trans_helper, unsigned int sample_rate) +{ + memset(trans_helper, 0, sizeof(*trans_helper)); + ast_format_set(&trans_helper->slin_src, ast_format_slin_by_rate(sample_rate), 0); +} + +static void softmix_translate_helper_destroy(struct softmix_translate_helper *trans_helper) +{ + struct softmix_translate_helper_entry *entry; + + while ((entry = AST_LIST_REMOVE_HEAD(&trans_helper->entries, entry))) { + softmix_translate_helper_free_entry(entry); + } +} + +static void softmix_translate_helper_change_rate(struct softmix_translate_helper *trans_helper, unsigned int sample_rate) +{ + struct softmix_translate_helper_entry *entry; + + ast_format_set(&trans_helper->slin_src, ast_format_slin_by_rate(sample_rate), 0); + AST_LIST_TRAVERSE_SAFE_BEGIN(&trans_helper->entries, entry, entry) { + if (entry->trans_pvt) { + ast_translator_free_path(entry->trans_pvt); + if (!(entry->trans_pvt = ast_translator_build_path(&entry->dst_format, &trans_helper->slin_src))) { + AST_LIST_REMOVE_CURRENT(entry); + entry = softmix_translate_helper_free_entry(entry); + } + } + } + AST_LIST_TRAVERSE_SAFE_END; +} + +/*! + * \internal + * \brief Get the next available audio on the softmix channel's read stream + * and determine if it should be mixed out or not on the write stream. + * + * \retval pointer to buffer containing the exact number of samples requested on success. + * \retval NULL if no samples are present + */ +static int16_t *softmix_process_read_audio(struct softmix_channel *sc, unsigned int num_samples) +{ + if ((ast_slinfactory_available(&sc->factory) >= num_samples) && + ast_slinfactory_read(&sc->factory, sc->our_buf, num_samples)) { + sc->have_audio = 1; + return sc->our_buf; + } + sc->have_audio = 0; + return NULL; +} + +/*! + * \internal + * \brief Process a softmix channel's write audio + * + * \details This function will remove the channel's talking from its own audio if present and + * possibly even do the channel's write translation for it depending on how many other + * channels use the same write format. + */ +static void softmix_process_write_audio(struct softmix_translate_helper *trans_helper, + struct ast_format *raw_write_fmt, + struct softmix_channel *sc) +{ + struct softmix_translate_helper_entry *entry = NULL; + int i; + + /* If we provided audio that was not determined to be silence, + * then take it out while in slinear format. */ + if (sc->have_audio && sc->talking) { + for (i = 0; i < sc->write_frame.samples; i++) { + ast_slinear_saturated_subtract(&sc->final_buf[i], &sc->our_buf[i]); + } + /* do not do any special write translate optimization if we had to make + * a special mix for them to remove their own audio. */ + return; + } + + AST_LIST_TRAVERSE(&trans_helper->entries, entry, entry) { + if (ast_format_cmp(&entry->dst_format, raw_write_fmt) == AST_FORMAT_CMP_EQUAL) { + entry->num_times_requested++; + } else { + continue; + } + if (!entry->trans_pvt && (entry->num_times_requested > 1)) { + entry->trans_pvt = ast_translator_build_path(&entry->dst_format, &trans_helper->slin_src); + } + if (entry->trans_pvt && !entry->out_frame) { + entry->out_frame = ast_translate(entry->trans_pvt, &sc->write_frame, 0); + } + if (entry->out_frame && (entry->out_frame->datalen < MAX_DATALEN)) { + ast_format_copy(&sc->write_frame.subclass.format, &entry->out_frame->subclass.format); + memcpy(sc->final_buf, entry->out_frame->data.ptr, entry->out_frame->datalen); + sc->write_frame.datalen = entry->out_frame->datalen; + sc->write_frame.samples = entry->out_frame->samples; + } + break; + } + + /* add new entry into list if this format destination was not matched. */ + if (!entry && (entry = softmix_translate_helper_entry_alloc(raw_write_fmt))) { + AST_LIST_INSERT_HEAD(&trans_helper->entries, entry, entry); + } +} + +static void softmix_translate_helper_cleanup(struct softmix_translate_helper *trans_helper) +{ + struct softmix_translate_helper_entry *entry = NULL; + AST_LIST_TRAVERSE(&trans_helper->entries, entry, entry) { + if (entry->out_frame) { + ast_frfree(entry->out_frame); + entry->out_frame = NULL; + } + entry->num_times_requested = 0; + } +} + +static void softmix_bridge_data_destroy(void *obj) +{ + struct softmix_bridge_data *softmix_data = obj; + ast_timer_close(softmix_data->timer); +} + /*! \brief Function called when a bridge is created */ static int softmix_bridge_create(struct ast_bridge *bridge) { - struct softmix_bridge_data *bridge_data; + struct softmix_bridge_data *softmix_data; - if (!(bridge_data = ast_calloc(1, sizeof(*bridge_data)))) { + if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) { return -1; } - if (!(bridge_data->timer = ast_timer_open())) { - ast_free(bridge_data); + if (!(softmix_data->timer = ast_timer_open())) { + ao2_ref(softmix_data, -1); return -1; } /* start at 8khz, let it grow from there */ - bridge_data->internal_rate = 8000; + softmix_data->internal_rate = 8000; + softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL; - bridge->bridge_pvt = bridge_data; + bridge->bridge_pvt = softmix_data; return 0; } /*! \brief Function called when a bridge is destroyed */ static int softmix_bridge_destroy(struct ast_bridge *bridge) { - struct softmix_bridge_data *bridge_data = bridge->bridge_pvt; + struct softmix_bridge_data *softmix_data = bridge->bridge_pvt; if (!bridge->bridge_pvt) { return -1; } - ast_timer_close(bridge_data->timer); - ast_free(bridge_data); + ao2_ref(softmix_data, -1); + bridge->bridge_pvt = NULL; return 0; } -static void set_softmix_bridge_data(int rate, struct ast_bridge_channel *bridge_channel, int reset) +static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset) { struct softmix_channel *sc = bridge_channel->bridge_pvt; + unsigned int channel_read_rate = ast_format_rate(&bridge_channel->chan->rawreadformat); + + ast_mutex_lock(&sc->lock); if (reset) { ast_slinfactory_destroy(&sc->factory); + ast_dsp_free(sc->dsp); } - /* Setup frame parameters */ - sc->frame.frametype = AST_FRAME_VOICE; + /* Setup read/write frame parameters */ + sc->write_frame.frametype = AST_FRAME_VOICE; + ast_format_set(&sc->write_frame.subclass.format, ast_format_slin_by_rate(rate), 0); + sc->write_frame.data.ptr = sc->final_buf; + sc->write_frame.datalen = SOFTMIX_DATALEN(rate, interval); + sc->write_frame.samples = SOFTMIX_SAMPLES(rate, interval); - ast_format_set(&sc->frame.subclass.format, ast_format_slin_by_rate(rate), 0); - sc->frame.data.ptr = sc->final_buf; - sc->frame.datalen = SOFTMIX_DATALEN(rate); - sc->frame.samples = SOFTMIX_SAMPLES(rate); + sc->read_frame.frametype = AST_FRAME_VOICE; + ast_format_set(&sc->read_frame.subclass.format, ast_format_slin_by_rate(channel_read_rate), 0); + sc->read_frame.data.ptr = sc->our_buf; + sc->read_frame.datalen = SOFTMIX_DATALEN(channel_read_rate, interval); + sc->read_frame.samples = SOFTMIX_SAMPLES(channel_read_rate, interval); /* Setup smoother */ - ast_slinfactory_init_with_format(&sc->factory, &sc->frame.subclass.format); + ast_slinfactory_init_with_format(&sc->factory, &sc->write_frame.subclass.format); - ast_set_read_format(bridge_channel->chan, &sc->frame.subclass.format); - ast_set_write_format(bridge_channel->chan, &sc->frame.subclass.format); + /* set new read and write formats on channel. */ + ast_set_read_format(bridge_channel->chan, &sc->read_frame.subclass.format); + ast_set_write_format(bridge_channel->chan, &sc->write_frame.subclass.format); + + /* set up new DSP. This is on the read side only right before the read frame enters the smoother. */ + sc->dsp = ast_dsp_new_with_rate(channel_read_rate); + /* we want to aggressively detect silence to avoid feedback */ + if (bridge_channel->tech_args.talking_threshold) { + ast_dsp_set_threshold(sc->dsp, bridge_channel->tech_args.talking_threshold); + } else { + ast_dsp_set_threshold(sc->dsp, DEFAULT_SOFTMIX_TALKING_THRESHOLD); + } + + ast_mutex_unlock(&sc->lock); } /*! \brief Function called when a channel is joined into the bridge */ static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { struct softmix_channel *sc = NULL; - struct softmix_bridge_data *bridge_data = bridge->bridge_pvt; + struct softmix_bridge_data *softmix_data = bridge->bridge_pvt; /* Create a new softmix_channel structure and allocate various things on it */ if (!(sc = ast_calloc(1, sizeof(*sc)))) { @@ -159,7 +372,9 @@ static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chan /* Can't forget to record our pvt structure within the bridged channel structure */ bridge_channel->bridge_pvt = sc; - set_softmix_bridge_data(bridge_data->internal_rate, bridge_channel, 0); + set_softmix_bridge_data(softmix_data->internal_rate, + softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL, + bridge_channel, 0); return 0; } @@ -169,44 +384,102 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha { struct softmix_channel *sc = bridge_channel->bridge_pvt; + if (!(bridge_channel->bridge_pvt)) { + return 0; + } + bridge_channel->bridge_pvt = NULL; + /* Drop mutex lock */ ast_mutex_destroy(&sc->lock); /* Drop the factory */ ast_slinfactory_destroy(&sc->factory); + /* Drop the DSP */ + ast_dsp_free(sc->dsp); + /* Eep! drop ourselves */ ast_free(sc); return 0; } +/*! + * \internal + * \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here. + */ +static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +{ + struct ast_bridge_channel *tmp; + AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) { + if (tmp == bridge_channel) { + continue; + } + ast_write(tmp->chan, frame); + } +} + /*! \brief Function called when a channel writes a frame into the bridge */ static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) { struct softmix_channel *sc = bridge_channel->bridge_pvt; + struct softmix_bridge_data *softmix_data = bridge->bridge_pvt; + int totalsilence = 0; + int silence_threshold = bridge_channel->tech_args.silence_threshold ? + bridge_channel->tech_args.silence_threshold : + DEFAULT_SOFTMIX_SILENCE_THRESHOLD; + char update_talking = -1; /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */ /* Only accept audio frames, all others are unsupported */ - if (frame->frametype != AST_FRAME_VOICE) { + if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) { + softmix_pass_dtmf(bridge, bridge_channel, frame); + return AST_BRIDGE_WRITE_SUCCESS; + } else if (frame->frametype != AST_FRAME_VOICE) { return AST_BRIDGE_WRITE_UNSUPPORTED; } ast_mutex_lock(&sc->lock); - /* If a frame was provided add it to the smoother */ - if (frame->frametype == AST_FRAME_VOICE && ast_format_is_slinear(&frame->subclass.format)) { + ast_dsp_silence(sc->dsp, frame, &totalsilence); + if (totalsilence < silence_threshold) { + if (!sc->talking) { + update_talking = 1; + } + sc->talking = 1; /* tell the write process we have audio to be mixed out */ + } else { + if (sc->talking) { + update_talking = 0; + } + sc->talking = 0; + } + + /* Before adding audio in, make sure we haven't fallen behind. If audio has fallen + * behind 4 times the amount of samples mixed on every iteration of the mixer, Re-sync + * the audio by flushing the buffer before adding new audio in. */ + if (ast_slinfactory_available(&sc->factory) > (4 * SOFTMIX_SAMPLES(softmix_data->internal_rate, softmix_data->internal_mixing_interval))) { + ast_slinfactory_flush(&sc->factory); + } + + /* If a frame was provided add it to the smoother, unless drop silence is enabled and this frame + * is not determined to be talking. */ + if (!(bridge_channel->tech_args.drop_silence && !sc->talking) && + (frame->frametype == AST_FRAME_VOICE && ast_format_is_slinear(&frame->subclass.format))) { ast_slinfactory_feed(&sc->factory, frame); } /* If a frame is ready to be written out, do so */ if (sc->have_frame) { - ast_write(bridge_channel->chan, &sc->frame); + ast_write(bridge_channel->chan, &sc->write_frame); sc->have_frame = 0; } /* Alllll done */ ast_mutex_unlock(&sc->lock); + if (update_talking != -1) { + ast_bridge_notify_talking(bridge, bridge_channel, update_talking); + } + return AST_BRIDGE_WRITE_SUCCESS; } @@ -218,7 +491,7 @@ static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_chan ast_mutex_lock(&sc->lock); if (sc->have_frame) { - ast_write(bridge_channel->chan, &sc->frame); + ast_write(bridge_channel->chan, &sc->write_frame); sc->have_frame = 0; } @@ -227,167 +500,306 @@ static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_chan return 0; } +static void gather_softmix_stats(struct softmix_stats *stats, + const struct softmix_bridge_data *softmix_data, + struct ast_bridge_channel *bridge_channel) +{ + int channel_native_rate; + int i; + /* Gather stats about channel sample rates. */ + channel_native_rate = MAX(ast_format_rate(&bridge_channel->chan->rawwriteformat), + ast_format_rate(&bridge_channel->chan->rawreadformat)); + + if (channel_native_rate > stats->highest_supported_rate) { + stats->highest_supported_rate = channel_native_rate; + } + if (channel_native_rate > softmix_data->internal_rate) { + for (i = 0; i < ARRAY_LEN(stats->sample_rates); i++) { + if (stats->sample_rates[i] == channel_native_rate) { + stats->num_channels[i]++; + break; + } else if (!stats->sample_rates[i]) { + stats->sample_rates[i] = channel_native_rate; + stats->num_channels[i]++; + break; + } + } + stats->num_above_internal_rate++; + } else if (channel_native_rate == softmix_data->internal_rate) { + stats->num_at_internal_rate++; + } +} +/*! + * \internal + * \brief Analyse mixing statistics and change bridges internal rate + * if necessary. + * + * \retval 0, no changes to internal rate + * \ratval 1, internal rate was changed, update all the channels on the next mixing iteration. + */ +static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data) +{ + int i; + /* Re-adjust the internal bridge sample rate if + * 1. The bridge's internal sample rate is locked in at a sample + * rate other than the current sample rate being used. + * 2. two or more channels support a higher sample rate + * 3. no channels support the current sample rate or a higher rate + */ + if (stats->locked_rate) { + /* if the rate is locked by the bridge, only update it if it differs + * from the current rate we are using. */ + if (softmix_data->internal_rate != stats->locked_rate) { + softmix_data->internal_rate = stats->locked_rate; + ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate); + return 1; + } + } else if (stats->num_above_internal_rate >= 2) { + /* the highest rate is just used as a starting point */ + unsigned int best_rate = stats->highest_supported_rate; + int best_index = -1; + + for (i = 0; i < ARRAY_LEN(stats->num_channels); i++) { + if (stats->num_channels[i]) { + break; + } + /* best_rate starts out being the first sample rate + * greater than the internal sample rate that 2 or + * more channels support. */ + if (stats->num_channels[i] >= 2 && (best_index == -1)) { + best_rate = stats->sample_rates[i]; + best_index = i; + /* If it has been detected that multiple rates above + * the internal rate are present, compare those rates + * to each other and pick the highest one two or more + * channels support. */ + } else if (((best_index != -1) && + (stats->num_channels[i] >= 2) && + (stats->sample_rates[best_index] < stats->sample_rates[i]))) { + best_rate = stats->sample_rates[i]; + best_index = i; + /* It is possible that multiple channels exist with native sample + * rates above the internal sample rate, but none of those channels + * have the same rate in common. In this case, the lowest sample + * rate among those channels is picked. Over time as additional + * statistic runs are made the internal sample rate number will + * adjust to the most optimal sample rate, but it may take multiple + * iterations. */ + } else if (best_index == -1) { + best_rate = MIN(best_rate, stats->sample_rates[i]); + } + } + + ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate); + softmix_data->internal_rate = best_rate; + return 1; + } else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) { + /* In this case, the highest supported rate is actually lower than the internal rate */ + softmix_data->internal_rate = stats->highest_supported_rate; + ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate); + return 1; + } + return 0; +} + +static int softmix_mixing_array_init(struct softmix_mixing_array *mixing_array, unsigned int starting_num_entries) +{ + memset(mixing_array, 0, sizeof(*mixing_array)); + mixing_array->max_num_entries = starting_num_entries; + if (!(mixing_array->buffers = ast_calloc(mixing_array->max_num_entries, sizeof(int16_t *)))) { + ast_log(LOG_NOTICE, "Failed to allocate softmix mixing structure. \n"); + return -1; + } + return 0; +} + +static void softmix_mixing_array_destroy(struct softmix_mixing_array *mixing_array) +{ + ast_free(mixing_array->buffers); +} + +static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array, unsigned int num_entries) +{ + int16_t **tmp; + /* give it some room to grow since memory is cheap but allocations can be expensive */ + mixing_array->max_num_entries = num_entries; + if (!(tmp = ast_realloc(mixing_array->buffers, (mixing_array->max_num_entries * sizeof(int16_t *))))) { + ast_log(LOG_NOTICE, "Failed to re-allocate softmix mixing structure. \n"); + return -1; + } + mixing_array->buffers = tmp; + return 0; +} + /*! \brief Function which acts as the mixing thread */ static int softmix_bridge_thread(struct ast_bridge *bridge) { - struct { - /*! Each index represents a sample rate used above the internal rate. */ - unsigned int sample_rates[8]; - /*! Each index represents the number of channels using the same index in the sample_rates array. */ - unsigned int num_channels[8]; - /*! the number of channels above the internal sample rate */ - unsigned int num_above_internal_rate; - /*! the number of channels at the internal sample rate */ - unsigned int num_at_internal_rate; - /*! the absolute highest sample rate supported by any channel in the bridge */ - unsigned int highest_supported_rate; - } stats; - struct softmix_bridge_data *bridge_data = bridge->bridge_pvt; - struct ast_timer *timer = bridge_data->timer; - int timingfd = ast_timer_fd(timer); + struct softmix_stats stats = { { 0 }, }; + struct softmix_mixing_array mixing_array; + struct softmix_bridge_data *softmix_data = bridge->bridge_pvt; + struct ast_timer *timer; + struct softmix_translate_helper trans_helper; + int16_t buf[MAX_DATALEN] = { 0, }; + unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */ + int timingfd; int update_all_rates = 0; /* set this when the internal sample rate has changed */ - int i; + int i, x; + int res = -1; - ast_timer_set_rate(timer, (1000 / SOFTMIX_INTERVAL)); + if (!(softmix_data = bridge->bridge_pvt)) { + goto softmix_cleanup; + } + + ao2_ref(softmix_data, 1); + timer = softmix_data->timer; + timingfd = ast_timer_fd(timer); + softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate); + ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval)); + + /* Give the mixing array room to grow, memory is cheap but allocations are expensive. */ + if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) { + ast_log(LOG_NOTICE, "Failed to allocate softmix mixing structure. \n"); + goto softmix_cleanup; + } while (!bridge->stop && !bridge->refresh && bridge->array_num) { struct ast_bridge_channel *bridge_channel = NULL; - short buf[MAX_DATALEN] = {0, }; int timeout = -1; + enum ast_format_id cur_slin_id = ast_format_slin_by_rate(softmix_data->internal_rate); + unsigned int softmix_samples = SOFTMIX_SAMPLES(softmix_data->internal_rate, softmix_data->internal_mixing_interval); + unsigned int softmix_datalen = SOFTMIX_DATALEN(softmix_data->internal_rate, softmix_data->internal_mixing_interval); - /* these variables help determine if a rate change is required */ - memset(&stats, 0, sizeof(stats)); - stats.highest_supported_rate = 8000; + if (softmix_datalen > MAX_DATALEN) { + /* This should NEVER happen, but if it does we need to know about it. Almost + * all the memcpys used during this process depend on this assumption. Rather + * than checking this over and over again through out the code, this single + * verification is done on each iteration. */ + ast_log(LOG_WARNING, "Conference mixing error, requested mixing length greater than mixing buffer.\n"); + goto softmix_cleanup; + } + + /* Grow the mixing array buffer as participants are added. */ + if (mixing_array.max_num_entries < bridge->num && softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) { + goto softmix_cleanup; + } + + /* init the number of buffers stored in the mixing array to 0. + * As buffers are added for mixing, this number is incremented. */ + mixing_array.used_entries = 0; + + /* These variables help determine if a rate change is required */ + if (!stat_iteration_counter) { + memset(&stats, 0, sizeof(stats)); + stats.locked_rate = bridge->internal_sample_rate; + } + + /* If the sample rate has changed, update the translator helper */ + if (update_all_rates) { + softmix_translate_helper_change_rate(&trans_helper, softmix_data->internal_rate); + } /* Go through pulling audio from each factory that has it available */ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { struct softmix_channel *sc = bridge_channel->bridge_pvt; - int channel_native_rate; - - ast_mutex_lock(&sc->lock); + /* Update the sample rate to match the bridge's native sample rate if necessary. */ if (update_all_rates) { - set_softmix_bridge_data(bridge_data->internal_rate, bridge_channel, 1); + set_softmix_bridge_data(softmix_data->internal_rate, softmix_data->internal_mixing_interval, bridge_channel, 1); + } + + /* If stat_iteration_counter is 0, then collect statistics during this mixing interation */ + if (!stat_iteration_counter) { + gather_softmix_stats(&stats, softmix_data, bridge_channel); + } + + /* if the channel is suspended, don't check for audio, but still gather stats */ + if (bridge_channel->suspended) { + continue; } /* Try to get audio from the factory if available */ - if (ast_slinfactory_available(&sc->factory) >= SOFTMIX_SAMPLES(bridge_data->internal_rate) && - ast_slinfactory_read(&sc->factory, sc->our_buf, SOFTMIX_SAMPLES(bridge_data->internal_rate))) { - short *data1, *data2; - int i; - - /* Put into the local final buffer */ - for (i = 0, data1 = buf, data2 = sc->our_buf; i < SOFTMIX_DATALEN(bridge_data->internal_rate); i++, data1++, data2++) - ast_slinear_saturated_add(data1, data2); - /* Yay we have our own audio */ - sc->have_audio = 1; - } else { - /* Awww we don't have audio ;( */ - sc->have_audio = 0; + ast_mutex_lock(&sc->lock); + if ((mixing_array.buffers[mixing_array.used_entries] = softmix_process_read_audio(sc, softmix_samples))) { + mixing_array.used_entries++; } - - /* Gather stats about channel sample rates. */ - channel_native_rate = MAX(ast_format_rate(&bridge_channel->chan->rawwriteformat), - ast_format_rate(&bridge_channel->chan->rawreadformat)); - - if (channel_native_rate > stats.highest_supported_rate) { - stats.highest_supported_rate = channel_native_rate; - } - if (channel_native_rate > bridge_data->internal_rate) { - for (i = 0; i < ARRAY_LEN(stats.sample_rates); i++) { - if (stats.sample_rates[i] == channel_native_rate) { - stats.num_channels[i]++; - break; - } else if (!stats.sample_rates[i]) { - stats.sample_rates[i] = channel_native_rate; - stats.num_channels[i]++; - break; - } - } - stats.num_above_internal_rate++; - } else if (channel_native_rate == bridge_data->internal_rate) { - stats.num_at_internal_rate++; - } - ast_mutex_unlock(&sc->lock); } + /* mix it like crazy */ + memset(buf, 0, softmix_datalen); + for (i = 0; i < mixing_array.used_entries; i++) { + for (x = 0; x < softmix_samples; x++) { + ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x); + } + } + /* Next step go through removing the channel's own audio and creating a good frame... */ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { struct softmix_channel *sc = bridge_channel->bridge_pvt; - int i = 0; - /* Copy from local final buffer to our final buffer */ - memcpy(sc->final_buf, buf, sizeof(sc->final_buf)); - - /* If we provided audio then take it out */ - if (sc->have_audio) { - for (i = 0; i < SOFTMIX_DATALEN(bridge_data->internal_rate); i++) { - ast_slinear_saturated_subtract(&sc->final_buf[i], &sc->our_buf[i]); - } + if (bridge_channel->suspended) { + continue; } + ast_mutex_lock(&sc->lock); + + /* Make SLINEAR write frame from local buffer */ + if (sc->write_frame.subclass.format.id != cur_slin_id) { + ast_format_set(&sc->write_frame.subclass.format, cur_slin_id, 0); + } + sc->write_frame.datalen = softmix_datalen; + sc->write_frame.samples = softmix_samples; + memcpy(sc->final_buf, buf, softmix_datalen); + + /* process the softmix channel's new write audio */ + softmix_process_write_audio(&trans_helper, &bridge_channel->chan->rawwriteformat, sc); + /* The frame is now ready for use... */ sc->have_frame = 1; + ast_mutex_unlock(&sc->lock); + /* Poke bridged channel thread just in case */ pthread_kill(bridge_channel->thread, SIGURG); } - /* Re-adjust the internal bridge sample rate if - * 1. two or more channels support a higher sample rate - * 2. no channels support the current sample rate or a higher rate - */ - if (stats.num_above_internal_rate >= 2) { - /* the highest rate is just used as a starting point */ - unsigned int best_rate = stats.highest_supported_rate; - int best_index = -1; - - /* 1. pick the best sample rate two or more channels support - * 2. if two or more channels do not support the same rate, pick the - * lowest sample rate that is still above the internal rate. */ - for (i = 0; ((i < ARRAY_LEN(stats.num_channels)) && stats.num_channels[i]); i++) { - if ((stats.num_channels[i] >= 2 && (best_index == -1)) || - ((best_index != -1) && - (stats.num_channels[i] >= 2) && - (stats.sample_rates[best_index] < stats.sample_rates[i]))) { - - best_rate = stats.sample_rates[i]; - best_index = i; - } else if (best_index == -1) { - best_rate = MIN(best_rate, stats.sample_rates[i]); - } - } - - ast_debug(1, " Bridge changed from %d To %d\n", bridge_data->internal_rate, best_rate); - bridge_data->internal_rate = best_rate; - update_all_rates = 1; - } else if (!stats.num_at_internal_rate && !stats.num_above_internal_rate) { - update_all_rates = 1; - /* in this case, the highest supported rate is actually lower than the internal rate */ - bridge_data->internal_rate = stats.highest_supported_rate; - ast_debug(1, " Bridge changed from %d to %d\n", bridge_data->internal_rate, stats.highest_supported_rate); - update_all_rates = 1; - } else { - update_all_rates = 0; + update_all_rates = 0; + if (!stat_iteration_counter) { + update_all_rates = analyse_softmix_stats(&stats, softmix_data); + stat_iteration_counter = SOFTMIX_STAT_INTERVAL; } + stat_iteration_counter--; ao2_unlock(bridge); - + /* cleanup any translation frame data from the previous mixing iteration. */ + softmix_translate_helper_cleanup(&trans_helper); /* Wait for the timing source to tell us to wake up and get things done */ ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL); - ast_timer_ack(timer, 1); - ao2_lock(bridge); + + /* make sure to detect mixing interval changes if they occur. */ + if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) { + softmix_data->internal_mixing_interval = bridge->internal_mixing_interval; + ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval)); + update_all_rates = 1; /* if the interval changes, the rates must be adjusted as well just to be notified new interval.*/ + } } - return 0; + res = 0; + +softmix_cleanup: + softmix_translate_helper_destroy(&trans_helper); + softmix_mixing_array_destroy(&mixing_array); + if (softmix_data) { + ao2_ref(softmix_data, -1); + } + return res; } static struct ast_bridge_technology softmix_bridge = { .name = "softmix", - .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED, + .capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE, .preference = AST_BRIDGE_PREFERENCE_LOW, .create = softmix_bridge_create, .destroy = softmix_bridge_destroy, @@ -410,11 +822,7 @@ static int load_module(void) if (!(softmix_bridge.format_capabilities = ast_format_cap_alloc())) { return AST_MODULE_LOAD_DECLINE; } -#ifdef SOFTMIX_16_SUPPORT - ast_format_cap_add(softmix_bridge.format_capabilities, ast_format_set(&tmp, AST_FORMAT_SLINEAR16, 0)); -#else ast_format_cap_add(softmix_bridge.format_capabilities, ast_format_set(&tmp, AST_FORMAT_SLINEAR, 0)); -#endif return ast_bridge_technology_register(&softmix_bridge); } diff --git a/configs/confbridge.conf.sample b/configs/confbridge.conf.sample new file mode 100644 index 000000000..1781b88a0 --- /dev/null +++ b/configs/confbridge.conf.sample @@ -0,0 +1,302 @@ +[general] +; The general section of this config +; is not currently used, but reserved +; for future use. + +; +; --- Default Information --- +; The default_user and default_bridge sections are applied +; automatically to all ConfBridge instances invoked without +; a user, or bridge argument. No menu is applied by default. +; + +; --- ConfBridge User Profile Options --- +[default_user] +type=user +;admin=yes ; Sets if the user is an admin or not. Off by default. +;marked=yes ; Sets if this is a marked user or not. Off by default. +;startmuted=yes; Sets if all users should start out muted. Off by default +;music_on_hold_when_empty=yes ; Sets whether MOH should be played when only + ; one person is in the conference or when the + ; the user is waiting on a marked user to enter + ; the conference. Off by default. +;music_on_hold_class=default ; The MOH class to use for this user. +;quiet=yes ; When enabled enter/leave prompts and user intros are not played. + ; There are some prompts, such as the prompt to enter a PIN number, + ; that must be played regardless of what this option is set to. + ; Off by default +;announce_user_count=yes ; Sets if the number of users should be announced to the + ; caller. Off by default. +;announce_user_count_all=yes ; Sets if the number of users should be announced to + ; all the other users in the conference when someone joins. + ; This option can be either set to 'yes' or a number. + ; When set to a number, the announcement will only occur + ; once the user count is above the specified number. +;announce_only_user=yes ; Sets if the only user announcement should be played + ; when a channel enters a empty conference. On by default. +;wait_marked=yes ; Sets if the user must wait for a marked user to enter before + ; joining the conference. Off by default. +;end_marked=yes ; This option will kick every user with this option set in their + ; user profile after the last Marked user exists the conference. + +;dsp_drop_silence=yes ; This option drops what Asterisk detects as silence from + ; entering into the bridge. Enabling this option will drastically + ; improve performance and help remove the buildup of background + ; noise from the conference. Highly recommended for large conferences + ; due to its performance enhancements. + +;dsp_talking_threshold=128 ; The time in milliseconds of sound above what the dsp has + ; established as base line silence for a user before a user + ; is considered to be talking. This value affects several + ; operations and should not be changed unless the impact on + ; call quality is fully understood. + ; + ; What this value affects internally: + ; + ; 1. Audio is only mixed out of a user's incoming audio stream + ; if talking is detected. If this value is set too + ; loose the user will hear themselves briefly each + ; time they begin talking until the dsp has time to + ; establish that they are in fact talking. + ; 2. When talk detection AMI events are enabled, this value + ; determines when talking has begun which results in + ; an AMI event to fire. If this value is set too tight + ; AMI events may be falsely triggered by variants in + ; room noise. + ; 3. The drop_silence option depends on this value to determine + ; when the user's audio should be mixed into the bridge + ; after periods of silence. If this value is too loose + ; the beginning of a user's speech will get cut off as they + ; transition from silence to talking. + ; + ; By default this value is 160 ms. Valid values are 1 through 2^31 + +;dsp_silence_threshold=2000 ; The time in milliseconds of sound falling within the what + ; the dsp has established as baseline silence before a user + ; is considered be silent. This value affects several + ; operations and should not be changed unless the impact + ; on call quality is fully understood. + ; + ; What this value affects internally: + ; + ; 1. When talk detection AMI events are enabled, this value + ; determines when the user has stopped talking after a + ; period of talking. If this value is set too low + ; AMI events indicating the user has stopped talking + ; may get falsely sent out when the user briefly pauses + ; during mid sentence. + ; 2. The drop_silence option depends on this value to + ; determine when the user's audio should begin to be + ; dropped from the conference bridge after the user + ; stops talking. If this value is set too low the user's + ; audio stream may sound choppy to the other participants. + ; This is caused by the user transitioning constantly from + ; silence to talking during mid sentence. + ; + ; The best way to approach this option is to set it slightly above + ; the maximum amount of ms of silence a user may generate during + ; natural speech. + ; + ; By default this value is 2500ms. Valid values are 1 through 2^31 + +;talk_detection_events=yes ; This option sets whether or not notifications of when a user + ; begins and ends talking should be sent out as events over AMI. + ; By default this option is off. + +;denoise=yes ; Sets whether or not a denoise filter should be applied + ; to the audio before mixing or not. Off by default. Requires + ; codec_speex to be built and installed. Do not confuse this option + ; with drop_silence. Denoise is useful if there is a lot of background + ; noise for a user as it attempts to remove the noise while preserving + ; the speech. This option does NOT remove silence from being mixed into + ; the conference and does come at the cost of a slight performance hit. + +;jitterbuffer=yes ; Enabling this option places a jitterbuffer on the user's audio stream + ; before audio mixing is performed. This is highly recommended but will + ; add a slight delay to the audio. This option is using the JITTERBUFFER + ; dialplan function's default adaptive jitterbuffer. For a more fine tuned + ; jitterbuffer, disable this option and use the JITTERBUFFER dialplan function + ; on the user before entering the ConfBridge application. + +;pin=1234 ; Sets if this user must enter a PIN number before entering + ; the conference. The PIN will be prompted for. +;announce_join_leave=yes ; When enabled, this option will prompt the user for a + ; name when entering the conference. After the name is + ; recorded, it will be played as the user enters and exists + ; the conference. This option is off by default. +;dtmf_passthrough=yes ; Sets whether or not DTMF should pass through the conference. + ; This option is off by default. + +; --- ConfBridge Bridge Profile Options --- +[default_bridge] +type=bridge +;max_members=50 ; This option limits the number of participants for a single + ; conference to a specific number. By default conferences + ; have no participant limit. After the limit is reached, the + ; conference will be locked until someone leaves. Note however + ; that an Admin user will always be alowed to join the conference + ; regardless if this limit is reached or not. + +;record_conference=yes ; Records the conference call starting when the first user + ; enters the room, and ending when the last user exits the room. + ; The default recorded filename is + ; 'confbridge--.wav + ; and the default format is 8khz slinear. This file will be + ; located in the configured monitoring directory in asterisk.conf. + +;record_file= ; When record_conference is set to yes, the specific name of the + ; record file can be set using this option. Note that since multiple + ; conferences may use the same bridge profile, this may cause issues + ; depending on the configuration. It is recommended to only use this + ; option dynamically with the CONFBRIDGE() dialplan function. This + ; allows the record name to be specified and a unique name to be chosen. + ; By default, the record_file is stored in Asterisk's spool/monitor directory + ; with a unique filename starting with the 'confbridge' prefix. + +;internal_sample_rate=auto ; Sets the internal native sample rate the + ; conference is mixed at. This is set to automatically + ; adjust the sample rate to the best quality by default. + ; Other values can be anything from 8000-192000. If a + ; sample rate is set that Asterisk does not support, the + ; closest sample rate Asterisk does support to the one requested + ; will be used. + +;mixing_interval=40 ; Sets the internal mixing interval in milliseconds for the bridge. This + ; number reflects how tight or loose the mixing will be for the conference. + ; In order to improve performance a larger mixing interval such as 40ms may + ; be chosen. Using a larger mixing interval comes at the cost of introducing + ; larger amounts of delay into the bridge. Valid values here are 10, 20, 40, + ; or 80. By default 20ms is used. + +; All sounds in the conference are customizable using the bridge profile options below. +; Simply state the option followed by the filename or full path of the filename after +; the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin +; sound file found in the sounds directory when announcing someone's name is joining the +; conference. + +;sound_join ; The sound played to everyone when someone enters the conference. +;sound_leave ; The sound played to everyone when someone leaves the conference. +;sound_has_joined ; The sound played before announcing someone's name has + ; joined the conference. This is used for user intros. + ; Example "_____ has joined the conference" +;sound_has_left ; The sound played when announcing someone's name has + ; left the conference. This is used for user intros. + ; Example "_____ has left the conference" +;sound_kicked ; The sound played to a user who has been kicked from the conference. +;sound_muted ; The sound played when the mute option it toggled on. +;sound_unmuted ; The sound played when the mute option it toggled off. +;sound_only_person ; The sound played when the user is the only person in the conference. +;sound_only_one ; The sound played to a user when there is only one other + ; person is in the conference. +;sound_there_are ; The sound played when announcing how many users there + ; are in a conference. +;sound_other_in_party; ; This file is used in conjunction with 'sound_there_are" + ; when announcing how many users there are in the conference. + ; The sounds are stringed together like this. + ; "sound_there_are" "sound_other_in_party" +;sound_place_into_conference ; The sound played when someone is placed into the conference + ; after waiting for a marked user. +;sound_wait_for_leader ; The sound played when a user is placed into a conference that + ; can not start until a marked user enters. +;sound_leader_has_left ; The sound played when the last marked user leaves the conference. +;sound_get_pin ; The sound played when prompting for a conference pin number. +;sound_invalid_pin ; The sound played when an invalid pin is entered too many times. +;sound_locked ; The sound played to a user trying to join a locked conference. +;sound_locked_now ; The sound played to an admin after toggling the conference to locked mode. +;sound_unlocked_now; The sound played to an admin after toggling the conference to unlocked mode. +;sound_error_menu ; The sound played when an invalid menu option is entered. + +; --- ConfBridge Menu Options --- +; The ConfBridge application also has the ability to +; apply custom DTMF menus to each channel using the +; application. Like the User and Bridge profiles +; a menu is passed in to ConfBridge as an argument in +; the dialplan. +; +; Below is a list of menu actions that can be assigned +; to a DTMF sequence. +; +; A single DTMF sequence can have multiple actions associated with it. This is +; accomplished by stringing the actions together and using a ',' as the delimiter. +; Example: Both listening and talking volume is reset when '5' is pressed. +; 5=reset_talking_volume, reset_listening_volume +; +; playback(&) + ; Playback will play back an audio file to a channel + ; and then immediately return to the conference. + ; This file can not be interupted by DTMF. + ; Mutliple files can be chained together using the + ; '&' character. +; playback_and_continue(&) + ; playback_and_continue will + ; play back a prompt while continuing to + ; collect the dtmf sequence. This is useful + ; when using a menu prompt that describes all + ; the menu options. Note however that any DTMF + ; during this action will terminate the prompts + ; playback. Prompt files can be chained together + ; using the '&' character as a delimiter. +; toggle_mute ; Toggle turning on and off mute. Mute will make the user silent + ; to everyone else, but the user will still be able to listen in. + ; continue to collect the dtmf sequence. +; no_op ; This action does nothing (No Operation). Its only real purpose exists for + ; being able to reserve a sequence in the config as a menu exit sequence. +; decrease_listening_volume ; Decreases the channel's listening volume. +; increase_listening_volume ; Increases the channel's listening volume. +; reset_listening_volume ; Reset channel's listening volume to default level. + +; decrease_talking_volume ; Decreases the channel's talking volume. +; increase_talking_volume ; Icreases the channel's talking volume. +; reset_talking_volume ; Reset channel's talking volume to default level. +; +; dialplan_exec(context,exten,priority) ; The dialplan_exec action allows a user + ; to escape from the conference and execute + ; commands in the dialplan. Once the dialplan + ; exits the user will be put back into the + ; conference. The possibilities are endless! +; leave_conference ; This action allows a user to exit the conference and continue + ; execution in the dialplan. +; +; admin_kick_last ; This action allows an Admin to kick the last participant from the + ; conference. This action will only work for admins which allows + ; a single menu to be used for both users and admins. +; +; admin_toggle_conference_lock ; This action allows an Admin to toggle locking and + ; unlocking the conference. Non admins can not use + ; this action even if it is in their menu. + +[sample_user_menu] +type=menu +*=playback_and_continue(conf-usermenu) +*1=toggle_mute +1=toggle_mute +*4=decrease_listening_volume +4=decrease_listening_volume +*6=increase_listening_volume +6=increase_listening_volume +*7=decrease_talking_volume +7=decrease_talking_volume +*8=no_op +8=no_op +*9=increase_talking_volume +9=increase_talking_volume + +[sample_admin_menu] +type=menu +*=playback_and_continue(conf-adminmenu) +*1=toggle_mute +1=toggle_mute +*2=admin_toggle_conference_lock ; only applied to admin users +2=admin_toggle_conference_lock ; only applied to admin users +*3=admin_kick_last ; only applied to admin users +3=admin_kick_last ; only applied to admin users +*4=decrease_listening_volume +4=decrease_listening_volume +*6=increase_listening_volume +6=increase_listening_volume +*7=decrease_talking_volume +7=decrease_talking_volume +*8=no_op +8=no_op +*9=increase_talking_volume +9=increase_talking_volume diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h index 810bc3705..58f61d6fd 100644 --- a/include/asterisk/bridging.h +++ b/include/asterisk/bridging.h @@ -63,6 +63,7 @@ extern "C" { #endif #include "asterisk/bridging_features.h" +#include "asterisk/dsp.h" /*! \brief Capabilities for a bridge technology */ enum ast_bridge_capability { @@ -96,6 +97,10 @@ enum ast_bridge_channel_state { AST_BRIDGE_CHANNEL_STATE_FEATURE, /*! Bridged channel is sending a DTMF stream out */ AST_BRIDGE_CHANNEL_STATE_DTMF, + /*! Bridged channel began talking */ + AST_BRIDGE_CHANNEL_STATE_START_TALKING, + /*! Bridged channel has stopped talking */ + AST_BRIDGE_CHANNEL_STATE_STOP_TALKING, }; /*! \brief Return values for bridge technology write function */ @@ -111,6 +116,22 @@ enum ast_bridge_write_result { struct ast_bridge_technology; struct ast_bridge; +/*! + * \brief Structure specific to bridge technologies capable of + * performing talking optimizations. + */ +struct ast_bridge_tech_optimizations { + /*! The amount of time in ms that talking must be detected before + * the dsp determines that talking has occurred */ + unsigned int talking_threshold; + /*! The amount of time in ms that silence must be detected before + * the dsp determines that talking has stopped */ + unsigned int silence_threshold; + /*! Whether or not the bridging technology should drop audio + * detected as silence from the mix. */ + unsigned int drop_silence:1; +}; + /*! * \brief Structure that contains information regarding a channel in a bridge */ @@ -137,6 +158,9 @@ struct ast_bridge_channel { unsigned int suspended:1; /*! Features structure for features that are specific to this channel */ struct ast_bridge_features *features; + /*! Technology optimization parameters used by bridging technologies capable of + * optimizing based upon talk detection. */ + struct ast_bridge_tech_optimizations tech_args; /*! Queue of DTMF digits used for DTMF streaming */ char dtmf_stream_q[8]; /*! Linked list information */ @@ -149,6 +173,13 @@ struct ast_bridge_channel { struct ast_bridge { /*! Number of channels participating in the bridge */ int num; + /*! The internal sample rate this bridge is mixed at when multiple channels are being mixed. + * If this value is 0, the bridge technology may auto adjust the internal mixing rate. */ + unsigned int internal_sample_rate; + /*! The mixing interval indicates how quickly the bridges internal mixing should occur + * for bridge technologies that mix audio. When set to 0, the bridge tech must choose a + * default interval for itself. */ + unsigned int internal_mixing_interval; /*! Bit to indicate that the bridge thread is waiting on channels in the bridge array */ unsigned int waiting:1; /*! Bit to indicate the bridge thread should stop */ @@ -236,6 +267,7 @@ int ast_bridge_destroy(struct ast_bridge *bridge); * \param chan Channel to join * \param swap Channel to swap out if swapping * \param features Bridge features structure + * \param (Optional) Bridging tech optimization parameters for this channel. * * \retval state that channel exited the bridge with * @@ -256,7 +288,11 @@ int ast_bridge_destroy(struct ast_bridge *bridge); * If channel specific features are enabled a pointer to the features structure * can be specified in the features parameter. */ -enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features); +enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, + struct ast_channel *chan, + struct ast_channel *swap, + struct ast_bridge_features *features, + struct ast_bridge_tech_optimizations *tech_args); /*! \brief Impart (non-blocking) a channel on a bridge * @@ -419,6 +455,27 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan); */ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state); +/*! \brief Adjust the internal mixing sample rate of a bridge used during + * multimix mode. + * + * \param bridge_channel Channel to change the sample rate on. + * \param sample rate, the sample rate to change to. If a + * value of 0 is passed here, the bridge will be free to pick + * what ever sample rate it chooses. + * + */ +void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate); + +/*! \brief Adjust the internal mixing interval of a bridge used during + * multimix mode. + * + * \param bridge_channel Channel to change the sample rate on. + * \param mixing_interval, the sample rate to change to. If 0 is set + * the bridge tech is free to choose any mixing interval it uses by default. + */ +void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval); + + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/include/asterisk/bridging_features.h b/include/asterisk/bridging_features.h index ee36a561c..e377ca6b4 100644 --- a/include/asterisk/bridging_features.h +++ b/include/asterisk/bridging_features.h @@ -63,6 +63,31 @@ struct ast_bridge_channel; */ typedef int (*ast_bridge_features_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt); +/*! + * \brief Features hook pvt destructor callback + * + * \param hook_pvt Private data passed in when the hook was create to destroy + */ +typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt); + +/*! + * \brief Talking indicator callback + * + * \details This callback can be registered with the bridge in order + * to receive updates on when a bridge_channel has started and stopped + * talking + * + * \param bridge The bridge that the channel is part of + * \param bridge_channel Channel executing the feature + * + * \retval 0 success + * \retval -1 failure + */ +typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data); + + +typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data); + /*! * \brief Maximum length of a DTMF feature string */ @@ -76,6 +101,8 @@ struct ast_bridge_features_hook { char dtmf[MAXIMUM_DTMF_FEATURE_STRING]; /*! Callback that is called when DTMF string is matched */ ast_bridge_features_hook_callback callback; + /*! Callback to destroy hook_pvt data right before destruction. */ + ast_bridge_features_hook_pvt_destructor destructor; /*! Unique data that was passed into us */ void *hook_pvt; /*! Linked list information */ @@ -88,12 +115,21 @@ struct ast_bridge_features_hook { struct ast_bridge_features { /*! Attached DTMF based feature hooks */ AST_LIST_HEAD_NOLOCK(, ast_bridge_features_hook) hooks; + /*! Callback to indicate when a bridge channel has started and stopped talking */ + ast_bridge_talking_indicate_callback talker_cb; + /*! Callback to destroy any pvt data stored for the talker. */ + ast_bridge_talking_indicate_destructor talker_destructor_cb; + /*! Talker callback pvt data */ + void *talker_pvt_data; /*! Feature flags that are enabled */ struct ast_flags feature_flags; - /*! Bit to indicate that this structure is useful and should be considered when looking for features */ + /*! Bit to indicate that the hook list is useful and should be considered when looking for DTMF features */ unsigned int usable:1; /*! Bit to indicate whether the channel/bridge is muted or not */ unsigned int mute:1; + /*! Bit to indicate whether DTMF should be passed into the bridge tech or not. */ + unsigned int dtmf_passthrough:1; + }; /*! @@ -161,6 +197,7 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature); * \param dtmf DTMF string to be activated upon * \param callback Function to execute upon activation * \param hook_pvt Unique data + * \param Optional destructor callback for hook_pvt data * * \retval 0 on success * \retval -1 on failure @@ -170,7 +207,7 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature); * \code * struct ast_bridge_features features; * ast_bridge_features_init(&features); - * ast_bridge_features_hook(&features, "#", pound_callback, NULL); + * ast_bridge_features_hook(&features, "#", pound_callback, NULL, NULL); * \endcode * * This makes the bridging core call pound_callback if a channel that has this @@ -180,7 +217,26 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature); * \note It is important that the callback set the bridge channel state back to * AST_BRIDGE_CHANNEL_STATE_WAIT or the bridge thread will not service the channel. */ -int ast_bridge_features_hook(struct ast_bridge_features *features, const char *dtmf, ast_bridge_features_hook_callback callback, void *hook_pvt); +int ast_bridge_features_hook(struct ast_bridge_features *features, + const char *dtmf, + ast_bridge_features_hook_callback callback, + void *hook_pvt, + ast_bridge_features_hook_pvt_destructor destructor); + +/*! \brief Set a callback on the features structure to receive talking notifications on. + * + * \param features Bridge features structure + * \param talker_cb, Callback function to execute when talking events occur in the bridge core. + * \param pvt_data Optional unique data that will be passed with the talking events. + * \param Optional destructor callback for pvt data. + * + * \retval 0, success + * \retval -1, failure + */ +int ast_bridge_features_set_talk_detector(struct ast_bridge_features *features, + ast_bridge_talking_indicate_callback talker_cb, + ast_bridge_talking_indicate_destructor talker_destructor, + void *pvt_data); /*! \brief Enable a built in feature on a bridge features structure * diff --git a/include/asterisk/bridging_technology.h b/include/asterisk/bridging_technology.h index c3e22975c..3d2e870b6 100644 --- a/include/asterisk/bridging_technology.h +++ b/include/asterisk/bridging_technology.h @@ -143,6 +143,21 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology); */ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd); +/*! \brief Lets the bridging indicate when a bridge channel has stopped or started talking. + * + * \note All DSP functionality on the bridge has been pushed down to the lowest possible + * layer, which in this case is the specific bridging technology being used. Since it + * is necessary for the knowledge of which channels are talking to make its way up to the + * application, this function has been created to allow the bridging technology to communicate + * that information with the bridging core. + * + * \param bridge The bridge that the channel is a part of. + * \param bridge_channel The bridge channel that has either started or stopped talking. + * \param started_talking, set to 1 when this indicates the channel has started talking, set to 0 + * when this indicates the channel has stopped talking. + */ +void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking); + /*! \brief Suspend a bridge technology from consideration * * \param technology The bridge technology to suspend diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 88e9db4bb..46039ac85 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -178,6 +178,7 @@ typedef unsigned long long ast_group_t; */ struct ast_generator { void *(*alloc)(struct ast_channel *chan, void *params); + /*! Channel is locked during this function callback. */ void (*release)(struct ast_channel *chan, void *data); /*! This function gets called with the channel unlocked, but is called in * the context of the channel thread so we know the channel is not going @@ -186,6 +187,9 @@ struct ast_generator { int (*generate)(struct ast_channel *chan, void *data, int len, int samples); /*! This gets called when DTMF_END frames are read from the channel */ void (*digit)(struct ast_channel *chan, char digit); + /*! This gets called when the write format on a channel is changed while + * generating. The channel is locked during this callback. */ + void (*write_format_change)(struct ast_channel *chan, void *data); }; /*! Party name character set enumeration values (values from Q.SIG) */ diff --git a/include/asterisk/dsp.h b/include/asterisk/dsp.h index 10ada9955..79e4da695 100644 --- a/include/asterisk/dsp.h +++ b/include/asterisk/dsp.h @@ -73,9 +73,19 @@ enum threshold { THRESHOLD_MAX = 1, }; +/*! \brief Allocates a new dsp with a specific internal sample rate used + * during processing. */ +struct ast_dsp *ast_dsp_new_with_rate(unsigned int sample_rate); + +/*! \brief Allocates a new dsp, assumes 8khz for internal sample rate */ struct ast_dsp *ast_dsp_new(void); + void ast_dsp_free(struct ast_dsp *dsp); +/*! \brief Retrieve the sample rate this DSP structure was + * created with */ +unsigned int ast_dsp_get_sample_rate(const struct ast_dsp *dsp); + /*! \brief Set threshold value for silence */ void ast_dsp_set_threshold(struct ast_dsp *dsp, int threshold); diff --git a/main/bridging.c b/main/bridging.c index f988d9694..444eea8d5 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -123,9 +123,9 @@ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast /* Only poke the channel's thread if it is not us */ if (!pthread_equal(pthread_self(), bridge_channel->thread)) { pthread_kill(bridge_channel->thread, SIGURG); - ast_mutex_lock(&bridge_channel->lock); + ao2_lock(bridge_channel); ast_cond_signal(&bridge_channel->cond); - ast_mutex_unlock(&bridge_channel->lock); + ao2_unlock(bridge_channel); } return; @@ -273,6 +273,15 @@ static int bridge_drop_control_frame(int subclass) } } +void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking) +{ + if (started_talking) { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING); + } else { + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING); + } +} + void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd) { /* If no bridge channel has been provided and the actual channel has been provided find it */ @@ -290,14 +299,21 @@ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END); } else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) { ast_debug(1, "Dropping control frame from bridge channel %p\n", bridge_channel); - } else { + } else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) { + int dtmf_passthrough = bridge_channel->features ? + bridge_channel->features->dtmf_passthrough : + bridge->features.dtmf_passthrough; + if (frame->frametype == AST_FRAME_DTMF_BEGIN) { frame = bridge_handle_dtmf(bridge, bridge_channel, frame); } - /* Simply write the frame out to the bridge technology if it still exists */ - if (frame) { + + if (frame && dtmf_passthrough) { bridge->technology->write(bridge, bridge_channel, frame); } + } else { + /* Simply write the frame out to the bridge technology if it still exists */ + bridge->technology->write(bridge, bridge_channel, frame); } if (frame) { @@ -681,9 +697,9 @@ static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_c /* Fourth we tell them to wake up so they become aware that they above has happened */ pthread_kill(bridge_channel2->thread, SIGURG); - ast_mutex_lock(&bridge_channel2->lock); + ao2_lock(bridge_channel2); ast_cond_signal(&bridge_channel2->cond); - ast_mutex_unlock(&bridge_channel2->lock); + ao2_unlock(bridge_channel2); } /* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */ @@ -724,10 +740,10 @@ static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct as ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms); } else { - ast_mutex_lock(&bridge_channel->lock); + ao2_lock(bridge_channel); ast_debug(10, "Going into a multithreaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); - ast_cond_wait(&bridge_channel->cond, &bridge_channel->lock); - ast_mutex_unlock(&bridge_channel->lock); + ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel)); + ao2_unlock(bridge_channel); } ao2_lock(bridge_channel->bridge); @@ -743,12 +759,12 @@ static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct as static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel) { ao2_unlock(bridge_channel->bridge); - ast_mutex_lock(&bridge_channel->lock); + ao2_lock(bridge_channel); if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) { ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge); - ast_cond_wait(&bridge_channel->cond, &bridge_channel->lock); + ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel)); } - ast_mutex_unlock(&bridge_channel->lock); + ao2_unlock(bridge_channel); ao2_lock(bridge_channel->bridge); return bridge_channel->state; @@ -782,7 +798,11 @@ static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridg return; } -/*! \brief Internal function that executes a feature on a bridge channel */ +/*! + * \brief Internal function that executes a feature on a bridge channel + * \note Neither the bridge nor the bridge_channel locks should be held when entering + * this function. + */ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features); @@ -819,6 +839,7 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_ /* If this hook matches just break out now */ if (!strcmp(hook->dtmf, dtmf)) { ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel); + look_for_dtmf = 0; break; } else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) { ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel); @@ -842,12 +863,26 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_ hook->callback(bridge, bridge_channel, hook->hook_pvt); } else { ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan); + } + + /* if the channel is still in feature state, revert it back to wait state */ + if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) { ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); } return; } +static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) +{ + struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features); + + if (features && features->talker_cb) { + features->talker_cb(bridge, bridge_channel, features->talker_pvt_data); + } + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT); +} + /*! \brief Internal function that plays back DTMF on a bridge channel */ static void bridge_channel_dtmf_stream(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { @@ -890,7 +925,10 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann if (bridge_channel->swap) { struct ast_bridge_channel *bridge_channel2 = NULL; - /* If we are performing a swap operation we do not need to execute the smart bridge operation as the actual number of channels involved will not have changed, we just need to tell the other channel to leave */ + /* If we are performing a swap operation we do not need + * to execute the smart bridge operation as the actual number + * of channels involved will not have changed, we just need to + * tell the other channel to leave */ if ((bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap))) { ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel); ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP); @@ -931,14 +969,27 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann /* Execute the threading model */ state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED ? bridge_channel_join_multithreaded(bridge_channel) : bridge_channel_join_singlethreaded(bridge_channel)); /* Depending on the above state see what we need to do */ - if (state == AST_BRIDGE_CHANNEL_STATE_FEATURE) { + switch (state) { + case AST_BRIDGE_CHANNEL_STATE_FEATURE: bridge_channel_suspend(bridge_channel->bridge, bridge_channel); + ao2_unlock(bridge_channel->bridge); bridge_channel_feature(bridge_channel->bridge, bridge_channel); + ao2_lock(bridge_channel->bridge); bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel); - } else if (state == AST_BRIDGE_CHANNEL_STATE_DTMF) { + break; + case AST_BRIDGE_CHANNEL_STATE_DTMF: bridge_channel_suspend(bridge_channel->bridge, bridge_channel); bridge_channel_dtmf_stream(bridge_channel->bridge, bridge_channel); bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel); + break; + case AST_BRIDGE_CHANNEL_STATE_START_TALKING: + case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING: + ao2_unlock(bridge_channel->bridge); + bridge_channel_talking(bridge_channel->bridge, bridge_channel); + ao2_lock(bridge_channel->bridge); + break; + default: + break; } } @@ -987,29 +1038,63 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann return bridge_channel->state; } -enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features) +static void bridge_channel_destroy(void *obj) { - struct ast_bridge_channel bridge_channel = { - .chan = chan, - .swap = swap, - .bridge = bridge, - .features = features, - }; - enum ast_bridge_channel_state state; + struct ast_bridge_channel *bridge_channel = obj; + + if (bridge_channel->bridge) { + ao2_ref(bridge_channel->bridge, -1); + bridge_channel->bridge = NULL; + } + /* Destroy elements of the bridge channel structure and the bridge channel structure itself */ + ast_cond_destroy(&bridge_channel->cond); +} + +static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge) +{ + struct ast_bridge_channel *bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy); + if (!(bridge_channel)) { + return NULL; + } + ast_cond_init(&bridge_channel->cond, NULL); + if (bridge) { + bridge_channel->bridge = bridge; + ao2_ref(bridge_channel->bridge, +1); + } + return bridge_channel; +} + +enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, + struct ast_channel *chan, + struct ast_channel *swap, + struct ast_bridge_features *features, + struct ast_bridge_tech_optimizations *tech_args) +{ + struct ast_bridge_channel *bridge_channel = bridge_channel_alloc(bridge); + enum ast_bridge_channel_state state = AST_BRIDGE_CHANNEL_STATE_HANGUP; + + if (!bridge_channel) { + return state; + } + if (tech_args) { + memcpy(&bridge_channel->tech_args, tech_args, sizeof(bridge_channel->tech_args)); + } /* Initialize various other elements of the bridge channel structure that we can't do above */ - ast_mutex_init(&bridge_channel.lock); - ast_cond_init(&bridge_channel.cond, NULL); + bridge_channel->chan = chan; + bridge_channel->swap = swap; + bridge_channel->features = features; - ao2_ref(bridge_channel.bridge, +1); + state = bridge_channel_join(bridge_channel); - state = bridge_channel_join(&bridge_channel); + /* Cleanup all the data in the bridge channel after it leaves the bridge. */ + ao2_lock(bridge_channel); + bridge_channel->chan = NULL; + bridge_channel->swap = NULL; + bridge_channel->features = NULL; + ao2_unlock(bridge_channel); - ao2_ref(bridge_channel.bridge, -1); - - /* Destroy some elements of the bridge channel structure above */ - ast_mutex_destroy(&bridge_channel.lock); - ast_cond_destroy(&bridge_channel.cond); + ao2_ref(bridge_channel, -1); return state; } @@ -1022,49 +1107,39 @@ static void *bridge_channel_thread(void *data) state = bridge_channel_join(bridge_channel); - ao2_ref(bridge_channel->bridge, -1); - /* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */ if (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP) { ast_hangup(bridge_channel->chan); } - /* Destroy elements of the bridge channel structure and the bridge channel structure itself */ - ast_mutex_destroy(&bridge_channel->lock); - ast_cond_destroy(&bridge_channel->cond); - ast_free(bridge_channel); + /* cleanup */ + ao2_lock(bridge_channel); + bridge_channel->chan = NULL; + bridge_channel->swap = NULL; + bridge_channel->features = NULL; + ao2_unlock(bridge_channel); + + ao2_ref(bridge_channel, -1); return NULL; } int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features) { - struct ast_bridge_channel *bridge_channel = NULL; - + struct ast_bridge_channel *bridge_channel = bridge_channel_alloc(bridge); /* Try to allocate a structure for the bridge channel */ - if (!(bridge_channel = ast_calloc(1, sizeof(*bridge_channel)))) { + if (!(bridge_channel)) { return -1; } /* Setup various parameters */ bridge_channel->chan = chan; bridge_channel->swap = swap; - bridge_channel->bridge = bridge; bridge_channel->features = features; - /* Initialize our mutex lock and condition */ - ast_mutex_init(&bridge_channel->lock); - ast_cond_init(&bridge_channel->cond, NULL); - - /* Bump up the reference count on the bridge, it'll get decremented later */ - ao2_ref(bridge, +1); - /* Actually create the thread that will handle the channel */ if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) { - ao2_ref(bridge, -1); - ast_cond_destroy(&bridge_channel->cond); - ast_mutex_destroy(&bridge_channel->lock); - ast_free(bridge_channel); + ao2_ref(bridge_channel, -1); return -1; } @@ -1181,9 +1256,9 @@ int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1) /* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */ pthread_kill(bridge_channel->thread, SIGURG); - ast_mutex_lock(&bridge_channel->lock); + ao2_lock(bridge_channel); ast_cond_signal(&bridge_channel->cond); - ast_mutex_unlock(&bridge_channel->lock); + ao2_unlock(bridge_channel); } ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0); @@ -1268,7 +1343,11 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature) return 0; } -int ast_bridge_features_hook(struct ast_bridge_features *features, const char *dtmf, ast_bridge_features_hook_callback callback, void *hook_pvt) +int ast_bridge_features_hook(struct ast_bridge_features *features, + const char *dtmf, + ast_bridge_features_hook_callback callback, + void *hook_pvt, + ast_bridge_features_hook_pvt_destructor destructor) { struct ast_bridge_features_hook *hook = NULL; @@ -1279,6 +1358,7 @@ int ast_bridge_features_hook(struct ast_bridge_features *features, const char *d ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf)); hook->callback = callback; + hook->destructor = destructor; hook->hook_pvt = hook_pvt; /* Once done we add it onto the list. Now it will be picked up when DTMF is used */ @@ -1289,6 +1369,17 @@ int ast_bridge_features_hook(struct ast_bridge_features *features, const char *d return 0; } +int ast_bridge_features_set_talk_detector(struct ast_bridge_features *features, + ast_bridge_talking_indicate_callback talker_cb, + ast_bridge_talking_indicate_destructor talker_destructor, + void *pvt_data) +{ + features->talker_cb = talker_cb; + features->talker_destructor_cb = talker_destructor; + features->talker_pvt_data = pvt_data; + return 0; +} + int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config) { /* If no alternate DTMF stream was provided use the default one */ @@ -1306,7 +1397,7 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br } /* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */ - return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config); + return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL); } int ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag) @@ -1333,8 +1424,15 @@ int ast_bridge_features_cleanup(struct ast_bridge_features *features) /* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */ while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) { + if (hook->destructor) { + hook->destructor(hook->hook_pvt); + } ast_free(hook); } + if (features->talker_destructor_cb && features->talker_pvt_data) { + features->talker_destructor_cb(features->talker_pvt_data); + features->talker_pvt_data = NULL; + } return 0; } @@ -1357,3 +1455,18 @@ int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct a return 0; } + +void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval) +{ + ao2_lock(bridge); + bridge->internal_mixing_interval = mixing_interval; + ao2_unlock(bridge); +} + +void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate) +{ + + ao2_lock(bridge); + bridge->internal_sample_rate = sample_rate; + ao2_unlock(bridge); +} diff --git a/main/channel.c b/main/channel.c index bb815e728..09be9395d 100644 --- a/main/channel.c +++ b/main/channel.c @@ -3011,6 +3011,15 @@ void ast_deactivate_generator(struct ast_channel *chan) ast_channel_unlock(chan); } +static void generator_write_format_change(struct ast_channel *chan) +{ + ast_channel_lock(chan); + if (chan->generator && chan->generator->write_format_change) { + chan->generator->write_format_change(chan, chan->generatordata); + } + ast_channel_unlock(chan); +} + static int generator_force(const void *data) { /* Called if generator doesn't have data */ @@ -5035,10 +5044,9 @@ static int set_format(struct ast_channel *chan, ast_debug(1, "Channel driver natively set channel %s to %s format %s\n", chan->name, direction ? "write" : "read", ast_getformatname(&best_set_fmt)); + ast_channel_lock(chan); ast_format_copy(format, &best_set_fmt); ast_format_copy(rawformat, &best_set_fmt); - - ast_channel_lock(chan); ast_format_cap_set(chan->nativeformats, &best_set_fmt); ast_channel_unlock(chan); @@ -5046,6 +5054,11 @@ static int set_format(struct ast_channel *chan, ast_translator_free_path(*trans); } *trans = NULL; + /* If there is a generator on the channel, it needs to know about this + * change if it is the write format. */ + if (direction && chan->generatordata) { + generator_write_format_change(chan); + } return 0; } @@ -5110,6 +5123,12 @@ static int set_format(struct ast_channel *chan, chan->name, direction ? "write" : "read", ast_getformatname(&best_set_fmt)); + + /* If there is a generator on the channel, it needs to know about this + * change if it is the write format. */ + if (direction && chan->generatordata) { + generator_write_format_change(chan); + } return res; } diff --git a/main/dsp.c b/main/dsp.c index a83adc2a0..5d5d1a2c2 100644 --- a/main/dsp.c +++ b/main/dsp.c @@ -192,15 +192,7 @@ enum gsamp_thresh { #define FAX_TONE_CED_DURATION 2600 #define FAX_TONE_CED_DB 16 -#define SAMPLE_RATE 8000 - -/* How many samples a frame has. This constant is used when calculating - * Goertzel block size for tone_detect. It is only important if we want to - * remove (squelch) the tone. In this case it is important to have block - * size not to exceed size of voice frame. Otherwise by the moment the tone - * is detected it is too late to squelch it from previous frames. - */ -#define SAMPLES_IN_FRAME 160 +#define DEFAULT_SAMPLE_RATE 8000 /* MF goertzel size */ #define MF_GSIZE 120 @@ -339,10 +331,10 @@ static inline float goertzel_result(goertzel_state_t *s) return (float)r.value * (float)(1 << r.power); } -static inline void goertzel_init(goertzel_state_t *s, float freq, int samples) +static inline void goertzel_init(goertzel_state_t *s, float freq, int samples, unsigned int sample_rate) { s->v2 = s->v3 = s->chunky = 0.0; - s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / SAMPLE_RATE)); + s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / sample_rate)); s->samples = samples; } @@ -394,6 +386,7 @@ struct ast_dsp { int display_inband_dtmf_warning; float genergy; int mute_fragments; + unsigned int sample_rate; fragment_t mute_data[5]; digit_detect_state_t digit_state; tone_detect_state_t cng_tone_state; @@ -410,7 +403,7 @@ static void mute_fragment(struct ast_dsp *dsp, fragment_t *fragment) dsp->mute_data[dsp->mute_fragments++] = *fragment; } -static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, int amp) +static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, int amp, unsigned int sample_rate) { int duration_samples; float x; @@ -419,16 +412,16 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, s->freq = freq; /* Desired tone duration in samples */ - duration_samples = duration * SAMPLE_RATE / 1000; + duration_samples = duration * sample_rate / 1000; /* We want to allow 10% deviation of tone duration */ duration_samples = duration_samples * 9 / 10; /* If we want to remove tone, it is important to have block size not to exceed frame size. Otherwise by the moment tone is detected it is too late - to squelch it from previous frames */ - s->block_size = SAMPLES_IN_FRAME; + to squelch it from previous frames. Block size is 20ms at the given sample rate.*/ + s->block_size = (20 * sample_rate) / 1000; - periods_in_block = s->block_size * freq / SAMPLE_RATE; + periods_in_block = s->block_size * freq / sample_rate; /* Make sure we will have at least 5 periods at target frequency for analisys. This may make block larger than expected packet and will make squelching impossible @@ -437,7 +430,7 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, periods_in_block = 5; /* Now calculate final block size. It will contain integer number of periods */ - s->block_size = periods_in_block * SAMPLE_RATE / freq; + s->block_size = periods_in_block * sample_rate / freq; /* tone_detect is currently only used to detect fax tones and we do not need suqlching the fax tones */ @@ -447,7 +440,7 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, and thus no tone will be detected in them */ s->hits_required = (duration_samples - (s->block_size - 1)) / s->block_size; - goertzel_init(&s->tone, freq, s->block_size); + goertzel_init(&s->tone, freq, s->block_size, sample_rate); s->samples_pending = s->block_size; s->hit_count = 0; @@ -472,19 +465,19 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, static void ast_fax_detect_init(struct ast_dsp *s) { - ast_tone_detect_init(&s->cng_tone_state, FAX_TONE_CNG_FREQ, FAX_TONE_CNG_DURATION, FAX_TONE_CNG_DB); - ast_tone_detect_init(&s->ced_tone_state, FAX_TONE_CED_FREQ, FAX_TONE_CED_DURATION, FAX_TONE_CED_DB); + ast_tone_detect_init(&s->cng_tone_state, FAX_TONE_CNG_FREQ, FAX_TONE_CNG_DURATION, FAX_TONE_CNG_DB, s->sample_rate); + ast_tone_detect_init(&s->ced_tone_state, FAX_TONE_CED_FREQ, FAX_TONE_CED_DURATION, FAX_TONE_CED_DB, s->sample_rate); } -static void ast_dtmf_detect_init (dtmf_detect_state_t *s) +static void ast_dtmf_detect_init (dtmf_detect_state_t *s, unsigned int sample_rate) { int i; s->lasthit = 0; s->current_hit = 0; for (i = 0; i < 4; i++) { - goertzel_init(&s->row_out[i], dtmf_row[i], DTMF_GSIZE); - goertzel_init(&s->col_out[i], dtmf_col[i], DTMF_GSIZE); + goertzel_init(&s->row_out[i], dtmf_row[i], DTMF_GSIZE, sample_rate); + goertzel_init(&s->col_out[i], dtmf_col[i], DTMF_GSIZE, sample_rate); s->energy = 0.0; } s->current_sample = 0; @@ -495,18 +488,18 @@ static void ast_dtmf_detect_init (dtmf_detect_state_t *s) s->misses_to_end = DTMF_MISSES_TO_END; } -static void ast_mf_detect_init (mf_detect_state_t *s) +static void ast_mf_detect_init (mf_detect_state_t *s, unsigned int sample_rate) { int i; s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0; for (i = 0; i < 6; i++) { - goertzel_init (&s->tone_out[i], mf_tones[i], 160); + goertzel_init (&s->tone_out[i], mf_tones[i], 160, sample_rate); } s->current_sample = 0; s->current_hit = 0; } -static void ast_digit_detect_init(digit_detect_state_t *s, int mf) +static void ast_digit_detect_init(digit_detect_state_t *s, int mf, unsigned int sample_rate) { s->current_digits = 0; s->detected_digits = 0; @@ -514,9 +507,9 @@ static void ast_digit_detect_init(digit_detect_state_t *s, int mf) s->digits[0] = '\0'; if (mf) { - ast_mf_detect_init(&s->td.mf); + ast_mf_detect_init(&s->td.mf, sample_rate); } else { - ast_dtmf_detect_init(&s->td.dtmf); + ast_dtmf_detect_init(&s->td.dtmf, sample_rate); } } @@ -1105,7 +1098,7 @@ int ast_dsp_call_progress(struct ast_dsp *dsp, struct ast_frame *inf) ast_log(LOG_WARNING, "Can't check call progress of non-voice frames\n"); return 0; } - if (inf->subclass.format.id != AST_FORMAT_SLINEAR) { + if (!ast_format_is_slinear(&inf->subclass.format)) { ast_log(LOG_WARNING, "Can only check call progress in signed-linear frames\n"); return 0; } @@ -1128,7 +1121,7 @@ static int __ast_dsp_silence_noise(struct ast_dsp *dsp, short *s, int len, int * accum /= len; if (accum < dsp->threshold) { /* Silent */ - dsp->totalsilence += len / 8; + dsp->totalsilence += len / (dsp->sample_rate / 1000); if (dsp->totalnoise) { /* Move and save history */ memmove(dsp->historicnoise + DSP_HISTORY - dsp->busycount, dsp->historicnoise + DSP_HISTORY - dsp->busycount + 1, dsp->busycount * sizeof(dsp->historicnoise[0])); @@ -1142,7 +1135,7 @@ static int __ast_dsp_silence_noise(struct ast_dsp *dsp, short *s, int len, int * res = 1; } else { /* Not silent */ - dsp->totalnoise += len / 8; + dsp->totalnoise += len / (dsp->sample_rate / 1000); if (dsp->totalsilence) { int silence1 = dsp->historicsilence[DSP_HISTORY - 1]; int silence2 = dsp->historicsilence[DSP_HISTORY - 2]; @@ -1321,7 +1314,7 @@ int ast_dsp_silence(struct ast_dsp *dsp, struct ast_frame *f, int *totalsilence) ast_log(LOG_WARNING, "Can't calculate silence on a non-voice frame\n"); return 0; } - if (f->subclass.format.id != AST_FORMAT_SLINEAR) { + if (!ast_format_is_slinear(&f->subclass.format)) { ast_log(LOG_WARNING, "Can only calculate silence on signed-linear frames :(\n"); return 0; } @@ -1339,7 +1332,7 @@ int ast_dsp_noise(struct ast_dsp *dsp, struct ast_frame *f, int *totalnoise) ast_log(LOG_WARNING, "Can't calculate noise on a non-voice frame\n"); return 0; } - if (f->subclass.format.id != AST_FORMAT_SLINEAR) { + if (!ast_format_is_slinear(&f->subclass.format)) { ast_log(LOG_WARNING, "Can only calculate noise on signed-linear frames :(\n"); return 0; } @@ -1370,30 +1363,31 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp, odata = af->data.ptr; len = af->datalen; /* Make sure we have short data */ - switch (af->subclass.format.id) { - case AST_FORMAT_SLINEAR: + if (ast_format_is_slinear(&af->subclass.format)) { shortdata = af->data.ptr; len = af->datalen / 2; - break; - case AST_FORMAT_ULAW: - case AST_FORMAT_TESTLAW: - shortdata = alloca(af->datalen * 2); - for (x = 0;x < len; x++) { - shortdata[x] = AST_MULAW(odata[x]); + } else { + switch (af->subclass.format.id) { + case AST_FORMAT_ULAW: + case AST_FORMAT_TESTLAW: + shortdata = alloca(af->datalen * 2); + for (x = 0;x < len; x++) { + shortdata[x] = AST_MULAW(odata[x]); + } + break; + case AST_FORMAT_ALAW: + shortdata = alloca(af->datalen * 2); + for (x = 0; x < len; x++) { + shortdata[x] = AST_ALAW(odata[x]); + } + break; + default: + /*Display warning only once. Otherwise you would get hundreds of warnings every second */ + if (dsp->display_inband_dtmf_warning) + ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(&af->subclass.format)); + dsp->display_inband_dtmf_warning = 0; + return af; } - break; - case AST_FORMAT_ALAW: - shortdata = alloca(af->datalen * 2); - for (x = 0; x < len; x++) { - shortdata[x] = AST_ALAW(odata[x]); - } - break; - default: - /*Display warning only once. Otherwise you would get hundreds of warnings every second */ - if (dsp->display_inband_dtmf_warning) - ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(&af->subclass.format)); - dsp->display_inband_dtmf_warning = 0; - return af; } /* Initially we do not want to mute anything */ @@ -1454,7 +1448,7 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp, if (dsp->features & DSP_FEATURE_DIGIT_DETECT) { event = AST_FRAME_DTMF_END; event_digit = dsp->digit_state.digits[0]; - event_len = dsp->digit_state.digitlen[0] * 1000 / SAMPLE_RATE; + event_len = dsp->digit_state.digitlen[0] * 1000 / dsp->sample_rate; } memmove(&dsp->digit_state.digits[0], &dsp->digit_state.digits[1], dsp->digit_state.current_digits); memmove(&dsp->digit_state.digitlen[0], &dsp->digit_state.digitlen[1], dsp->digit_state.current_digits * sizeof(dsp->digit_state.digitlen[0])); @@ -1521,8 +1515,6 @@ done: } switch (af->subclass.format.id) { - case AST_FORMAT_SLINEAR: - break; case AST_FORMAT_ULAW: for (x = 0; x < len; x++) { odata[x] = AST_LIN2MU((unsigned short) shortdata[x]); @@ -1557,7 +1549,7 @@ static void ast_dsp_prog_reset(struct ast_dsp *dsp) dsp->gsamps = 0; for (x = 0; x < ARRAY_LEN(modes[dsp->progmode].freqs); x++) { if (modes[dsp->progmode].freqs[x]) { - goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size); + goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size, dsp->sample_rate); max = x + 1; } } @@ -1565,7 +1557,12 @@ static void ast_dsp_prog_reset(struct ast_dsp *dsp) dsp->ringtimeout= 0; } -struct ast_dsp *ast_dsp_new(void) +unsigned int ast_dsp_get_sample_rate(const struct ast_dsp *dsp) +{ + return dsp->sample_rate; +} + +static struct ast_dsp *__ast_dsp_new(unsigned int sample_rate) { struct ast_dsp *dsp; @@ -1575,8 +1572,9 @@ struct ast_dsp *ast_dsp_new(void) dsp->busycount = DSP_HISTORY; dsp->digitmode = DSP_DIGITMODE_DTMF; dsp->faxmode = DSP_FAXMODE_DETECT_CNG; + dsp->sample_rate = sample_rate; /* Initialize digit detector */ - ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF); + ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF, dsp->sample_rate); dsp->display_inband_dtmf_warning = 1; /* Initialize initial DSP progress detect parameters */ ast_dsp_prog_reset(dsp); @@ -1586,6 +1584,16 @@ struct ast_dsp *ast_dsp_new(void) return dsp; } +struct ast_dsp *ast_dsp_new(void) +{ + return __ast_dsp_new(DEFAULT_SAMPLE_RATE); +} + +struct ast_dsp *ast_dsp_new_with_rate(unsigned int sample_rate) +{ + return __ast_dsp_new(sample_rate); +} + void ast_dsp_set_features(struct ast_dsp *dsp, int features) { dsp->features = features; @@ -1672,7 +1680,7 @@ int ast_dsp_set_digitmode(struct ast_dsp *dsp, int digitmode) new = digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX); if (old != new) { /* Must initialize structures if switching from MF to DTMF or vice-versa */ - ast_digit_detect_init(&dsp->digit_state, new & DSP_DIGITMODE_MF); + ast_digit_detect_init(&dsp->digit_state, new & DSP_DIGITMODE_MF, dsp->sample_rate); } dsp->digitmode = digitmode; return 0; diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index f5bc2ab35..feeabe312 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -153,6 +153,7 @@ struct moh_files_state { struct mohclass *class; char name[MAX_MUSICCLASS]; struct ast_format origwfmt; + struct ast_format mohwfmt; int samples; int sample_queue; int pos; @@ -267,6 +268,7 @@ static void moh_files_release(struct ast_channel *chan, void *data) ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name); } + ast_format_clear(&state->mohwfmt); /* make sure to clear this format before restoring the original format. */ if (state->origwfmt.id && 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)); } @@ -355,6 +357,25 @@ static struct ast_frame *moh_files_readframe(struct ast_channel *chan) return f; } +static void moh_files_write_format_change(struct ast_channel *chan, void *data) +{ + struct moh_files_state *state = chan->music_state; + + /* 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.id) { + struct ast_format tmp; + + ast_format_copy(&tmp, &chan->writeformat); + if (state->mohwfmt.id) { + ast_format_clear(&state->origwfmt); + ast_set_write_format(chan, &state->mohwfmt); + } + ast_format_copy(&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; @@ -375,6 +396,9 @@ static int moh_files_generator(struct ast_channel *chan, void *data, int len, in ast_channel_unlock(chan); state->samples += f->samples; state->sample_queue -= f->samples; + if (ast_format_cmp(&f->subclass.format, &state->mohwfmt) == AST_FORMAT_CMP_NOT_EQUAL) { + ast_format_copy(&state->mohwfmt, &f->subclass.format); + } res = ast_write(chan, f); ast_frfree(f); if (res < 0) { @@ -418,7 +442,9 @@ 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; + ast_format_copy(&state->origwfmt, &chan->writeformat); + ast_format_copy(&state->mohwfmt, &chan->writeformat); + /* For comparison on restart of MOH (see above) */ ast_copy_string(state->name, class->name, sizeof(state->name)); state->save_total = class->total_files; @@ -462,6 +488,7 @@ static struct ast_generator moh_file_stream = .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) @@ -929,7 +956,7 @@ static void *moh_alloc(struct ast_channel *chan, void *params) } if ((res = mohalloc(class))) { - res->origwfmt = chan->writeformat; + ast_format_copy(&res->origwfmt, &chan->writeformat); 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)); moh_release(NULL, res);