summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/app_confbridge.c274
-rw-r--r--apps/app_macro.c36
-rw-r--r--apps/app_queue.c11
-rw-r--r--apps/confbridge/conf_chan_announce.c30
-rw-r--r--apps/confbridge/include/confbridge.h12
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
*