diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/app_confbridge.c | 274 | ||||
-rw-r--r-- | apps/app_macro.c | 36 | ||||
-rw-r--r-- | apps/app_queue.c | 11 | ||||
-rw-r--r-- | apps/confbridge/conf_chan_announce.c | 30 | ||||
-rw-r--r-- | apps/confbridge/include/confbridge.h | 12 |
5 files changed, 270 insertions, 93 deletions
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index 991b3a307..ec4136580 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -71,6 +71,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_bridges.h" #include "asterisk/json.h" #include "asterisk/format_cache.h" +#include "asterisk/taskprocessor.h" /*** DOCUMENTATION <application name="ConfBridge" language="en_US"> @@ -959,6 +960,59 @@ static void handle_video_on_exit(struct confbridge_conference *conference, struc ao2_unlock(conference); } +struct hangup_data +{ + struct confbridge_conference *conference; + ast_mutex_t lock; + ast_cond_t cond; + int hungup; +}; + +/*! + * \brief Hang up the announcer channel + * + * This hangs up the announcer channel in the conference. This + * runs in the playback queue taskprocessor since we do not want + * to hang up the channel while it's trying to play an announcement. + * + * This task is performed synchronously, so there is no need to + * perform any cleanup on the passed-in data. + * + * \param data A hangup_data structure + * \return 0 + */ +static int hangup_playback(void *data) +{ + struct hangup_data *hangup = data; + + ast_autoservice_stop(hangup->conference->playback_chan); + + ast_hangup(hangup->conference->playback_chan); + hangup->conference->playback_chan = NULL; + + ast_mutex_lock(&hangup->lock); + hangup->hungup = 1; + ast_cond_signal(&hangup->cond); + ast_mutex_unlock(&hangup->lock); + + return 0; +} + +static void hangup_data_init(struct hangup_data *hangup, struct confbridge_conference *conference) +{ + ast_mutex_init(&hangup->lock); + ast_cond_init(&hangup->cond, NULL); + + hangup->conference = conference; + hangup->hungup = 0; +} + +static void hangup_data_destroy(struct hangup_data *hangup) +{ + ast_mutex_destroy(&hangup->lock); + ast_cond_destroy(&hangup->cond); +} + /*! * \brief Destroy a conference bridge * @@ -973,9 +1027,22 @@ static void destroy_conference_bridge(void *obj) ast_debug(1, "Destroying conference bridge '%s'\n", conference->name); if (conference->playback_chan) { - conf_announce_channel_depart(conference->playback_chan); - ast_hangup(conference->playback_chan); - conference->playback_chan = NULL; + if (conference->playback_queue) { + struct hangup_data hangup; + hangup_data_init(&hangup, conference); + ast_taskprocessor_push(conference->playback_queue, hangup_playback, &hangup); + + ast_mutex_lock(&hangup.lock); + while (!hangup.hungup) { + ast_cond_wait(&hangup.cond, &hangup.lock); + } + ast_mutex_unlock(&hangup.lock); + hangup_data_destroy(&hangup); + } else { + /* Playback queue is not yet allocated. Just hang up the channel straight */ + ast_hangup(conference->playback_chan); + conference->playback_chan = NULL; + } } /* Destroying a conference bridge is simple, all we have to do is destroy the bridging object */ @@ -989,7 +1056,7 @@ static void destroy_conference_bridge(void *obj) ast_free(conference->record_filename); conf_bridge_profile_destroy(&conference->b_profile); - ast_mutex_destroy(&conference->playback_lock); + ast_taskprocessor_unreference(conference->playback_queue); } /*! \brief Call the proper join event handler for the user for the conference bridge's current state @@ -1270,6 +1337,72 @@ void conf_ended(struct confbridge_conference *conference) } /*! + * \internal + * \brief Allocate playback channel for a conference. + * \pre expects conference to be locked before calling this function + */ +static int alloc_playback_chan(struct confbridge_conference *conference) +{ + struct ast_format_cap *cap; + char taskprocessor_name[AST_TASKPROCESSOR_MAX_NAME + 1]; + + cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!cap) { + return -1; + } + ast_format_cap_append(cap, ast_format_slin, 0); + conference->playback_chan = ast_request("CBAnn", cap, NULL, NULL, + conference->name, NULL); + ao2_ref(cap, -1); + if (!conference->playback_chan) { + return -1; + } + + /* To make sure playback_chan has the same language as the bridge */ + ast_channel_lock(conference->playback_chan); + ast_channel_language_set(conference->playback_chan, conference->b_profile.language); + ast_channel_unlock(conference->playback_chan); + + ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n", + ast_channel_name(conference->playback_chan), conference->name); + + ast_taskprocessor_build_name(taskprocessor_name, sizeof(taskprocessor_name), + "Confbridge/%s", conference->name); + conference->playback_queue = ast_taskprocessor_get(taskprocessor_name, TPS_REF_DEFAULT); + if (!conference->playback_queue) { + ast_hangup(conference->playback_chan); + conference->playback_chan = NULL; + return -1; + } + return 0; +} + +/*! + * \brief Push the announcer channel into the bridge + * + * This runs in the playback queue taskprocessor. + * + * \param data A confbridge_conference + * \retval 0 Success + * \retval -1 Failed to push the channel to the bridge + */ +static int push_announcer(void *data) +{ + struct confbridge_conference *conference = data; + + if (conf_announce_channel_push(conference->playback_chan)) { + ast_hangup(conference->playback_chan); + conference->playback_chan = NULL; + ao2_cleanup(conference); + return -1; + } + + ast_autoservice_start(conference->playback_chan); + ao2_cleanup(conference); + return 0; +} + +/*! * \brief Join a conference bridge * * \param conference_name The conference name @@ -1314,9 +1447,6 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen return NULL; } - /* Setup lock for playback channel */ - ast_mutex_init(&conference->playback_lock); - /* Setup for the record channel */ conference->record_filename = ast_str_create(RECORD_FILENAME_INITIAL_SPACE); if (!conference->record_filename) { @@ -1361,6 +1491,22 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen /* Set the initial state to EMPTY */ conference->state = CONF_STATE_EMPTY; + if (alloc_playback_chan(conference)) { + ao2_unlink(conference_bridges, conference); + ao2_ref(conference, -1); + ao2_unlock(conference_bridges); + ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name); + return NULL; + } + + if (ast_taskprocessor_push(conference->playback_queue, push_announcer, ao2_bump(conference))) { + ao2_unlink(conference_bridges, conference); + ao2_ref(conference, -1); + ao2_unlock(conference_bridges); + ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name); + return NULL; + } + if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_CONFERENCE)) { ao2_lock(conference); conf_start_record(conference); @@ -1481,67 +1627,105 @@ static void leave_conference(struct confbridge_user *user) user->conference = NULL; } +struct playback_task_data { + struct confbridge_conference *conference; + const char *filename; + int say_number; + int playback_finished; + ast_mutex_t lock; + ast_cond_t cond; +}; + /*! - * \internal - * \brief Allocate playback channel for a conference. - * \pre expects conference to be locked before calling this function + * \brief Play an announcement into a confbridge + * + * This runs in the playback queue taskprocessor. This ensures that + * all playbacks are handled in sequence and do not play over top one + * another. + * + * This task runs synchronously so there is no need for performing any + * sort of cleanup on the input parameter. + * + * \param data A playback_task_data + * \return 0 */ -static int alloc_playback_chan(struct confbridge_conference *conference) +static int playback_task(void *data) { - struct ast_format_cap *cap; + struct playback_task_data *ptd = data; - cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); - if (!cap) { - return -1; + /* Don't try to play if the playback channel has been hung up */ + if (!ptd->conference->playback_chan) { + goto end; } - ast_format_cap_append(cap, ast_format_slin, 0); - conference->playback_chan = ast_request("CBAnn", cap, NULL, NULL, - conference->name, NULL); - ao2_ref(cap, -1); - if (!conference->playback_chan) { - return -1; + + ast_autoservice_stop(ptd->conference->playback_chan); + + /* The channel is all under our control, in goes the prompt */ + if (!ast_strlen_zero(ptd->filename)) { + ast_stream_and_wait(ptd->conference->playback_chan, ptd->filename, ""); + } else if (ptd->say_number >= 0) { + ast_say_number(ptd->conference->playback_chan, ptd->say_number, "", + ast_channel_language(ptd->conference->playback_chan), NULL); } + ast_autoservice_start(ptd->conference->playback_chan); - /* To make sure playback_chan has the same language of that profile */ - ast_channel_lock(conference->playback_chan); - ast_channel_language_set(conference->playback_chan, conference->b_profile.language); - ast_channel_unlock(conference->playback_chan); +end: + ast_mutex_lock(&ptd->lock); + ptd->playback_finished = 1; + ast_cond_signal(&ptd->cond); + ast_mutex_unlock(&ptd->lock); - ast_debug(1, "Created announcer channel '%s' to conference bridge '%s'\n", - ast_channel_name(conference->playback_chan), conference->name); return 0; } +static void playback_task_data_init(struct playback_task_data *ptd, struct confbridge_conference *conference, + const char *filename, int say_number) +{ + ast_mutex_init(&ptd->lock); + ast_cond_init(&ptd->cond, NULL); + + ptd->filename = filename; + ptd->say_number = say_number; + ptd->conference = conference; + ptd->playback_finished = 0; +} + +static void playback_task_data_destroy(struct playback_task_data *ptd) +{ + ast_mutex_destroy(&ptd->lock); + ast_cond_destroy(&ptd->cond); +} + static int play_sound_helper(struct confbridge_conference *conference, const char *filename, int say_number) { + struct playback_task_data ptd; + /* Do not waste resources trying to play files that do not exist */ if (!ast_strlen_zero(filename) && !sound_file_exists(filename)) { return 0; } - ast_mutex_lock(&conference->playback_lock); - if (!conference->playback_chan && alloc_playback_chan(conference)) { - ast_mutex_unlock(&conference->playback_lock); - return -1; - } - if (conf_announce_channel_push(conference->playback_chan)) { - ast_mutex_unlock(&conference->playback_lock); + playback_task_data_init(&ptd, conference, filename, say_number); + if (ast_taskprocessor_push(conference->playback_queue, playback_task, &ptd)) { + if (!ast_strlen_zero(filename)) { + ast_log(LOG_WARNING, "Unable to play file '%s' to conference %s\n", + filename, conference->name); + } else { + ast_log(LOG_WARNING, "Unable to say number '%d' to conference %s\n", + say_number, conference->name); + } + playback_task_data_destroy(&ptd); return -1; } - /* The channel is all under our control, in goes the prompt */ - if (!ast_strlen_zero(filename)) { - ast_stream_and_wait(conference->playback_chan, filename, ""); - } else if (say_number >= 0) { - ast_say_number(conference->playback_chan, say_number, "", - ast_channel_language(conference->playback_chan), NULL); + /* Wait for the playback to complete */ + ast_mutex_lock(&ptd.lock); + while (!ptd.playback_finished) { + ast_cond_wait(&ptd.cond, &ptd.lock); } + ast_mutex_unlock(&ptd.lock); - ast_debug(1, "Departing announcer channel '%s' from conference bridge '%s'\n", - ast_channel_name(conference->playback_chan), conference->name); - conf_announce_channel_depart(conference->playback_chan); - - ast_mutex_unlock(&conference->playback_lock); + playback_task_data_destroy(&ptd); return 0; } diff --git a/apps/app_macro.c b/apps/app_macro.c index d8fd47fb1..ab80fbc38 100644 --- a/apps/app_macro.c +++ b/apps/app_macro.c @@ -243,7 +243,7 @@ static int _macro_exec(struct ast_channel *chan, const char *data, int exclusive int setmacrocontext=0; int autoloopflag, inhangup = 0; struct ast_str *tmp_subst = NULL; - + const char *my_macro_exten = NULL; char *save_macro_exten; char *save_macro_context; char *save_macro_priority; @@ -304,12 +304,32 @@ static int _macro_exec(struct ast_channel *chan, const char *data, int exclusive } snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro); - if (!ast_exists_extension(chan, fullmacro, "s", 1, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { - if (!ast_context_find(fullmacro)) - ast_log(LOG_WARNING, "No such context '%s' for macro '%s'. Was called by %s@%s\n", fullmacro, macro, ast_channel_exten(chan), ast_channel_context(chan)); - else - ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro); + + /* first search for the macro */ + if (!ast_context_find(fullmacro)) { + ast_log(LOG_WARNING, "No such context '%s' for macro '%s'. Was called by %s@%s\n", + fullmacro, macro, ast_channel_exten(chan), ast_channel_context(chan)); + return 0; + } + + /* now search for the right extension */ + if (ast_exists_extension(chan, fullmacro, "s", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL))) { + /* We have a normal macro */ + my_macro_exten = "s"; + } else if (ast_exists_extension(chan, fullmacro, "~~s~~", 1, + S_COR(ast_channel_caller(chan)->id.number.valid, + ast_channel_caller(chan)->id.number.str, NULL))) { + /* We have an AEL generated macro */ + my_macro_exten = "~~s~~"; + } + + /* do we have a valid exten? */ + if (!my_macro_exten) { + ast_log(LOG_WARNING, + "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", + fullmacro, macro); return 0; } @@ -361,7 +381,7 @@ static int _macro_exec(struct ast_channel *chan, const char *data, int exclusive ast_set_flag(ast_channel_flags(chan), AST_FLAG_SUBROUTINE_EXEC); /* Setup environment for new run */ - ast_channel_exten_set(chan, "s"); + ast_channel_exten_set(chan, my_macro_exten); ast_channel_context_set(chan, fullmacro); ast_channel_priority_set(chan, 1); diff --git a/apps/app_queue.c b/apps/app_queue.c index e04942f68..9b283d467 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -4077,6 +4077,17 @@ static void hangupcalls(struct queue_ent *qe, struct callattempt *outgoing, stru ast_channel_hangupcause_set(outgoing->chan, AST_CAUSE_ANSWERED_ELSEWHERE); } ast_channel_publish_dial(qe->chan, outgoing->chan, outgoing->interface, "CANCEL"); + + /* When dialing channels it is possible that they may not ever + * leave the not in use state (Local channels in particular) by + * the time we cancel them. If this occurs but we know they were + * dialed we explicitly remove them from the pending members + * container so that subsequent call attempts occur. + */ + if (outgoing->member->status == AST_DEVICE_NOT_INUSE) { + pending_members_remove(outgoing->member); + } + ast_hangup(outgoing->chan); } oo = outgoing; diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c index ee4660687..22ac4be39 100644 --- a/apps/confbridge/conf_chan_announce.c +++ b/apps/confbridge/conf_chan_announce.c @@ -143,31 +143,6 @@ struct ast_channel_tech *conf_announce_get_tech(void) return &announce_tech; } -void conf_announce_channel_depart(struct ast_channel *chan) -{ - struct announce_pvt *p = ast_channel_tech_pvt(chan); - - if (!p) { - return; - } - - ao2_ref(p, +1); - ao2_lock(p); - if (!ast_test_flag(&p->base, AST_UNREAL_CARETAKER_THREAD)) { - ao2_unlock(p); - ao2_ref(p, -1); - return; - } - ast_clear_flag(&p->base, AST_UNREAL_CARETAKER_THREAD); - chan = p->base.chan; - ao2_unlock(p); - ao2_ref(p, -1); - if (chan) { - ast_bridge_depart(chan); - ast_channel_unref(chan); - } -} - int conf_announce_channel_push(struct ast_channel *ast) { struct ast_bridge_features *features; @@ -186,20 +161,17 @@ int conf_announce_channel_push(struct ast_channel *ast) if (!chan) { return -1; } - ast_channel_ref(chan); } features = ast_bridge_features_new(); if (!features) { - ast_channel_unref(chan); return -1; } ast_set_flag(&features->feature_flags, AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE); /* Impart the output channel into the bridge */ if (ast_bridge_impart(p->bridge, chan, NULL, features, - AST_BRIDGE_IMPART_CHAN_DEPARTABLE)) { - ast_channel_unref(chan); + AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { return -1; } ao2_lock(p); diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index a1fa5a2b7..451d81098 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -225,9 +225,9 @@ struct confbridge_conference { struct ast_channel *record_chan; /*!< Channel used for recording the conference */ struct ast_str *record_filename; /*!< Recording filename. */ struct ast_str *orig_rec_file; /*!< Previous b_profile.rec_file. */ - ast_mutex_t playback_lock; /*!< Lock used for playback channel */ AST_LIST_HEAD_NOLOCK(, confbridge_user) active_list; /*!< List of users participating in the conference bridge */ AST_LIST_HEAD_NOLOCK(, confbridge_user) waiting_list; /*!< List of users waiting to join the conference bridge */ + struct ast_taskprocessor *playback_queue; /*!< Queue for playing back bridge announcements and managing the announcer channel */ }; extern struct ao2_container *conference_bridges; @@ -607,16 +607,6 @@ struct ast_channel_tech *conf_record_get_tech(void); struct ast_channel_tech *conf_announce_get_tech(void); /*! - * \brief Remove the announcer channel from the conference. - * \since 12.0.0 - * - * \param chan Either channel in the announcer channel pair. - * - * \return Nothing - */ -void conf_announce_channel_depart(struct ast_channel *chan); - -/*! * \brief Push the announcer channel into the conference. * \since 12.0.0 * |