[asterisk-commits] rmudgett: branch rmudgett/bridge_tasks r383438 - in /team/rmudgett/bridge_tas...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Mar 20 11:55:17 CDT 2013


Author: rmudgett
Date: Wed Mar 20 11:55:13 2013
New Revision: 383438

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=383438
Log:
* Add support for bridge personality to support queue, parking,
confbridge and other bridge encapsulation applications.

* Convert ao2 locking for bridge and bridge_channel to wrapper functions
for easier where used queries and type safety.

Modified:
    team/rmudgett/bridge_tasks/apps/app_bridgewait.c
    team/rmudgett/bridge_tasks/apps/app_confbridge.c
    team/rmudgett/bridge_tasks/bridges/bridge_builtin_features.c
    team/rmudgett/bridge_tasks/bridges/bridge_softmix.c
    team/rmudgett/bridge_tasks/include/asterisk/bridging.h
    team/rmudgett/bridge_tasks/include/asterisk/bridging_features.h
    team/rmudgett/bridge_tasks/main/bridging.c
    team/rmudgett/bridge_tasks/main/features.c

Modified: team/rmudgett/bridge_tasks/apps/app_bridgewait.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/apps/app_bridgewait.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/apps/app_bridgewait.c (original)
+++ team/rmudgett/bridge_tasks/apps/app_bridgewait.c Wed Mar 20 11:55:13 2013
@@ -195,7 +195,9 @@
 
 	ast_mutex_lock(&bridgewait_lock);
 	if (!holding_bridge) {
-		holding_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_HOLDING, AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM);
+/* BUGBUG this holding bridge needs a personality to manage the timeout. Otherwise, the timer will move to the next bridge which is likely not desireable. */
+		holding_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_HOLDING,
+			AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM, NULL);
 	}
 	ast_mutex_unlock(&bridgewait_lock);
 

Modified: team/rmudgett/bridge_tasks/apps/app_confbridge.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/apps/app_confbridge.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/apps/app_confbridge.c (original)
+++ team/rmudgett/bridge_tasks/apps/app_confbridge.c Wed Mar 20 11:55:13 2013
@@ -1286,7 +1286,7 @@
 
 		/* Create an actual bridge that will do the audio mixing */
 		if (!(conference->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_MULTIMIX,
-			AST_BRIDGE_FLAG_MASQUERADE_ONLY))) {
+			AST_BRIDGE_FLAG_MASQUERADE_ONLY, NULL))) {
 			ao2_ref(conference, -1);
 			conference = NULL;
 			ao2_unlock(conference_bridges);

Modified: team/rmudgett/bridge_tasks/bridges/bridge_builtin_features.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/bridges/bridge_builtin_features.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/bridges/bridge_builtin_features.c (original)
+++ team/rmudgett/bridge_tasks/bridges/bridge_builtin_features.c Wed Mar 20 11:55:13 2013
@@ -277,7 +277,7 @@
 
 	/* Create a bridge to use to talk to the person we are calling */
 	attended_bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX,
-		AST_BRIDGE_FLAG_DISSOLVE_HANGUP);
+		AST_BRIDGE_FLAG_DISSOLVE_HANGUP, NULL);
 	if (!attended_bridge) {
 		ast_hangup(peer);
 /* BUGBUG beeperr needs to be configurable from features.conf */

Modified: team/rmudgett/bridge_tasks/bridges/bridge_softmix.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/bridges/bridge_softmix.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/bridges/bridge_softmix.c (original)
+++ team/rmudgett/bridge_tasks/bridges/bridge_softmix.c Wed Mar 20 11:55:13 2013
@@ -845,17 +845,17 @@
 		}
 		stat_iteration_counter--;
 
-		ao2_unlock(bridge);
+		ast_bridge_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);
 		if (ast_timer_ack(timer, 1) < 0) {
 			ast_log(LOG_ERROR, "Failed to acknowledge timer in softmix bridge.\n");
-			ao2_lock(bridge);
+			ast_bridge_lock(bridge);
 			goto softmix_cleanup;
 		}
-		ao2_lock(bridge);
+		ast_bridge_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)) {
@@ -887,7 +887,7 @@
 	struct ast_bridge *bridge = data;
 	struct softmix_bridge_data *softmix_data;
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	if (bridge->callid) {
 		ast_callid_threadassoc_add(bridge->callid);
 	}
@@ -898,13 +898,13 @@
 	while (!softmix_data->stop) {
 		if (!bridge->num_active) {
 			/* Wait for something to happen to the bridge. */
-			ao2_unlock(bridge);
+			ast_bridge_unlock(bridge);
 			ast_mutex_lock(&softmix_data->lock);
 			if (!softmix_data->stop) {
 				ast_cond_wait(&softmix_data->cond, &softmix_data->lock);
 			}
 			ast_mutex_unlock(&softmix_data->lock);
-			ao2_lock(bridge);
+			ast_bridge_lock(bridge);
 			continue;
 		}
 
@@ -913,13 +913,13 @@
 			 * A mixing error occurred.  Sleep and try again later so we
 			 * won't flood the logs.
 			 */
-			ao2_unlock(bridge);
+			ast_bridge_unlock(bridge);
 			sleep(1);
-			ao2_lock(bridge);
-		}
-	}
-
-	ao2_unlock(bridge);
+			ast_bridge_lock(bridge);
+		}
+	}
+
+	ast_bridge_unlock(bridge);
 
 	ast_debug(1, "Stopping mixing thread for bridge %p\n", bridge);
 

Modified: team/rmudgett/bridge_tasks/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/include/asterisk/bridging.h?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/include/asterisk/bridging.h (original)
+++ team/rmudgett/bridge_tasks/include/asterisk/bridging.h Wed Mar 20 11:55:13 2013
@@ -115,9 +115,138 @@
 };
 
 /*!
+ * \brief Create the bridge personality.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * Allocate and setup the self->personality_pvt.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+typedef int (*ast_personality_create_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Destroy the bridge personality.
+ *
+ * \param self Bridge to operate upon.
+ *
+ * \details
+ * Release any resources held by self->personality_pvt and
+ * release self->personality_pvt.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_personality_destroy_fn)(struct ast_bridge *self);
+
+/*!
+ * \brief Can this channel be pushed into its bridge.
+ *
+ * \param self Bridge channel to operate upon.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self->bridge is already locked.
+ *
+ * \retval TRUE if can push this channel into its bridge.
+ */
+typedef int (*ast_personality_channel_can_push_fn)(struct ast_bridge_channel *self, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Push this channel into its bridge.
+ *
+ * \param self Bridge channel to operate upon.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \details
+ * Setup any channel hooks controlled by the personality,
+ * allocate and setup the self->personality_pvt.  If there is a
+ * swap channel, use it as a guide to setting up the self
+ * channel.
+ *
+ * \note On entry, self->bridge is already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+typedef int (*ast_personality_channel_push_fn)(struct ast_bridge_channel *self, struct ast_bridge_channel *swap);
+
+/*!
+ * \brief Pull this channel from its bridge.
+ *
+ * \param self Bridge channel to operate upon.
+ *
+ * \details
+ * Remove any channel hooks controlled by the personality.
+ * Release any resources held by self->personality_pvt and
+ * release self->personality_pvt.
+ *
+ * \note On entry, self->bridge is already locked.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_personality_channel_pull_fn)(struct ast_bridge_channel *self);
+
+/*!
+ * \brief Masquerade push this channel into its bridge.
+ *
+ * \param self Bridge channel to operate upon.
+ *
+ * \note This is a virtual push into the bridge because a
+ * masquerade swaps the guts of the channel to give it a new
+ * identity.  This is mostly for external bridge monitor
+ * bookkeeping.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_personality_channel_push_masquerade_fn)(struct ast_bridge_channel *self);
+
+/*!
+ * \brief Masquerade pull this channel from its bridge.
+ *
+ * \param self Bridge channel to operate upon.
+ *
+ * \note This is a virtual pull from the bridge because a
+ * masquerade swaps the guts of the channel to give it a new
+ * identity.  This is mostly for external bridge monitor
+ * bookkeeping.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_personality_channel_pull_masquerade_fn)(struct ast_bridge_channel *self);
+
+/*! Bridge personality virtual methods table definition. */
+struct ast_personality_methods {
+	/*! Bridge personality name for log messages. */
+	const char *name;
+	/*! Create the bridge personality. */
+	ast_personality_create_fn create;
+	/*! Destroy the bridge personality. */
+	ast_personality_destroy_fn destroy;
+	/*! TRUE if can push the bridge channel into its bridge. */
+	ast_personality_channel_can_push_fn can_push;
+	/*! Push the bridge channel into its bridge. */
+	ast_personality_channel_push_fn push;
+	/*! Pull the bridge channel from its bridge. */
+	ast_personality_channel_pull_fn pull;
+/*
+ * BUGBUG push_masquerade may just have to change into masquerade_complete so the bridge will need to check compatibility again.
+ * Otherwise, the bridge channel will have to constantly check if the set formats have changed before reading frames.
+ * Race conditions may force the bridge channel to constantly check anyway.                                                                                                                            .
+ */
+	/*! Virtually push the bridge channel into its bridge. */
+	ast_personality_channel_push_masquerade_fn push_masquerade;
+	/*! Virtually pull the bridge channel from its bridge. */
+	ast_personality_channel_pull_masquerade_fn pull_masquerade;
+};
+
+/*!
  * \brief Structure that contains information regarding a channel in a bridge
  */
 struct ast_bridge_channel {
+/* BUGBUG cond is only here because of external party suspend/unsuspend support. */
 	/*! Condition, used if we want to wake up a thread waiting on the bridged channel */
 	ast_cond_t cond;
 	/*! Current bridged channel state */
@@ -128,7 +257,21 @@
 	struct ast_channel *swap;
 	/*! Bridge this channel is participating in */
 	struct ast_bridge *bridge;
-	/*! Private information unique to the bridge technology */
+	/*!
+	 * \brief Bridge personality private bridge channel data.
+	 *
+	 * \note This information is added when the channel is pushed
+	 * into the bridge and removed when it is pulled from the
+	 * bridge.
+	 */
+	void *personality_pvt;
+	/*!
+	 * \brief Private information unique to the bridge technology.
+	 *
+	 * \note This information is added when the channel joins the
+	 * bridge's technology and removed when it leaves the bridge's
+	 * technology.
+	 */
 	void *tech_pvt;
 	/*! Thread handling the bridged channel (Needed by ast_bridge_depart) */
 	pthread_t thread;
@@ -217,12 +360,32 @@
  * \brief Structure that contains information about a bridge
  */
 struct ast_bridge {
+	/*! Bridge personality virtual method table. */
+	const struct ast_personality_methods *personality;
+	/*!
+	 * \brief Bridge personality private bridge data.
+	 *
+	 * \note This information is added when the bridge is created.
+	 */
+	void *personality_pvt;
+	/*! Bridge technology that is handling the bridge */
+	struct ast_bridge_technology *technology;
+	/*! Private information unique to the bridge technology */
+	void *tech_pvt;
+	/*! Call ID associated with the bridge */
+	struct ast_callid *callid;
+	/*! Linked list of channels participating in the bridge */
+	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
+	/*! Queue of actions to perform on the bridge. */
+	AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue;
+	/*! The video mode this bridge is using */
+	struct ast_bridge_video_mode video_mode;
+	/*! Bridge flags to tweak behavior */
+	struct ast_flags feature_flags;
 	/*! Number of channels participating in the bridge */
 	unsigned int num_channels;
 	/*! Number of active channels in the bridge. */
 	unsigned int num_active;
-	/*! The video mode this bridge is using */
-	struct ast_bridge_video_mode video_mode;
 	/*!
 	 * \brief Count of the active temporary requests to inhibit bridge merges.
 	 * Zero if merges are allowed.
@@ -241,18 +404,6 @@
 	unsigned int reconfigured:1;
 	/*! TRUE if the bridge has been dissolved.  Any channel that now tries to join is immediately ejected. */
 	unsigned int dissolved:1;
-	/*! Bridge flags to tweak behavior */
-	struct ast_flags feature_flags;
-	/*! Bridge technology that is handling the bridge */
-	struct ast_bridge_technology *technology;
-	/*! Private information unique to the bridge technology */
-	void *tech_pvt;
-	/*! Call ID associated with the bridge */
-	struct ast_callid *callid;
-	/*! Linked list of channels participating in the bridge */
-	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
-	/*! Queue of actions to perform on the bridge. */
-	AST_LIST_HEAD_NOLOCK(, ast_frame) action_queue;
 };
 
 /*!
@@ -260,6 +411,7 @@
  *
  * \param capabilities The capabilities that we require to be used on the bridge
  * \param flags Flags that will alter the behavior of the bridge
+ * \param personality Personality of the new bridge (NULL if none)
  *
  * \retval a pointer to a new bridge on success
  * \retval NULL on failure
@@ -274,7 +426,21 @@
  * This creates a simple two party bridge that will be destroyed once one of
  * the channels hangs up.
  */
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags);
+struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags, const struct ast_personality_methods *personality);
+
+/*!
+ * \brief Try locking the bridge.
+ *
+ * \param bridge Bridge to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_trylock(bridge)	_ast_bridge_trylock(bridge, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge)
+static inline int _ast_bridge_trylock(struct ast_bridge *bridge, const char *file, const char *function, int line, const char *var)
+{
+	return __ao2_trylock(bridge, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
 
 /*!
  * \brief Lock the bridge.
@@ -558,6 +724,46 @@
 int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
 
 /*!
+ * \brief Try locking the bridge_channel.
+ *
+ * \param bridge_channel What to try locking
+ *
+ * \retval 0 on success.
+ * \retval non-zero on error.
+ */
+#define ast_bridge_channel_trylock(bridge_channel)	_ast_bridge_channel_trylock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline int _ast_bridge_channel_trylock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	return __ao2_trylock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Lock the bridge_channel.
+ *
+ * \param bridge_channel What to lock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_lock(bridge_channel)	_ast_bridge_channel_lock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_lock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	__ao2_lock(bridge_channel, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Unlock the bridge_channel.
+ *
+ * \param bridge_channel What to unlock
+ *
+ * \return Nothing
+ */
+#define ast_bridge_channel_unlock(bridge_channel)	_ast_bridge_channel_unlock(bridge_channel, __FILE__, __PRETTY_FUNCTION__, __LINE__, #bridge_channel)
+static inline void _ast_bridge_channel_unlock(struct ast_bridge_channel *bridge_channel, const char *file, const char *function, int line, const char *var)
+{
+	__ao2_unlock(bridge_channel, file, function, line, var);
+}
+
+/*!
  * \brief Set bridge channel state to leave bridge (if not leaving already) with no lock.
  *
  * \param bridge_channel Channel to change the state on

Modified: team/rmudgett/bridge_tasks/include/asterisk/bridging_features.h
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/include/asterisk/bridging_features.h?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/include/asterisk/bridging_features.h (original)
+++ team/rmudgett/bridge_tasks/include/asterisk/bridging_features.h Wed Mar 20 11:55:13 2013
@@ -32,16 +32,19 @@
 enum ast_bridge_feature_flags {
 	/*! Upon channel hangup all bridge participants should be kicked out. */
 	AST_BRIDGE_FLAG_DISSOLVE_HANGUP = (1 << 0),
+	/*! The last channel to leave the bridge dissolves it. */
+	AST_BRIDGE_FLAG_DISSOLVE_EMPTY = (1 << 1),
 	/*! Move between bridging technologies as needed. */
-	AST_BRIDGE_FLAG_SMART = (1 << 1),
-	/*! This channel leaves the bridge if all participants have this flag set. */
-	AST_BRIDGE_FLAG_LONELY = (1 << 2),
+	AST_BRIDGE_FLAG_SMART = (1 << 2),
 	/*! Bridge channels cannot be merged from this bridge. */
 	AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM = (1 << 3),
 	/*! Bridge channels cannot be merged to this bridge. */
 	AST_BRIDGE_FLAG_MERGE_INHIBIT_TO = (1 << 4),
 	/*! Bridge channels can be moved to another bridge only by masquerade (ConfBridge) */
 	AST_BRIDGE_FLAG_MASQUERADE_ONLY = (1 << 5),
+
+	/*! This channel leaves the bridge if all participants have this flag set. */
+	AST_BRIDGE_FLAG_LONELY = (1 << 10),
 };
 
 /*! \brief Built in DTMF features */

Modified: team/rmudgett/bridge_tasks/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/main/bridging.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/main/bridging.c (original)
+++ team/rmudgett/bridge_tasks/main/bridging.c Wed Mar 20 11:55:13 2013
@@ -216,21 +216,21 @@
 
 	for (;;) {
 		/* Safely get the bridge pointer */
-		ao2_lock(bridge_channel);
+		ast_bridge_channel_lock(bridge_channel);
 		bridge = bridge_channel->bridge;
 		ao2_ref(bridge, +1);
-		ao2_unlock(bridge_channel);
+		ast_bridge_channel_unlock(bridge_channel);
 
 		/*
 		 * The bridge pointer cannot change while the bridge or
 		 * bridge_channel is locked.
 		 */
-		ao2_lock(bridge);
+		ast_bridge_lock(bridge);
 		if (bridge == bridge_channel->bridge) {
 			ao2_ref(bridge, -1);
 			return;
 		}
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		ao2_ref(bridge, -1);
 	}
 }
@@ -252,9 +252,9 @@
 
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 	ast_bridge_change_state_nolock(bridge_channel, new_state);
-	ao2_unlock(bridge_channel);
+	ast_bridge_channel_unlock(bridge_channel);
 }
 
 /*!
@@ -272,9 +272,9 @@
 	ast_debug(1, "Queueing action type:%d sub:%d on bridge %p\n",
 		action->frametype, action->subclass.integer, bridge);
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	AST_LIST_INSERT_TAIL(&bridge->action_queue, action, frame_list);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 	ast_bridge_manager_service_req(bridge);
 }
 
@@ -302,13 +302,13 @@
 		return -1;
 	}
 
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 	AST_LIST_INSERT_TAIL(&bridge_channel->wr_queue, dup, frame_list);
 	if (write(bridge_channel->alert_pipe[1], &nudge, sizeof(nudge)) != sizeof(nudge)) {
 		ast_log(LOG_ERROR, "We couldn't write alert pipe for %p(%s)... something is VERY wrong\n",
 			bridge_channel, ast_channel_name(bridge_channel->chan));
 	}
-	ao2_unlock(bridge_channel);
+	ast_bridge_channel_unlock(bridge_channel);
 	return 0;
 }
 
@@ -366,20 +366,10 @@
 {
 	struct ast_bridge *bridge = bridge_channel->bridge;
 
-	ao2_lock(bridge_channel);
 	if (!bridge_channel->in_bridge) {
-		ao2_unlock(bridge_channel);
 		return;
 	}
 	bridge_channel->in_bridge = 0;
-
-	/* Remove channel from the bridge */
-	if (!bridge_channel->suspended) {
-		--bridge->num_active;
-	}
-	--bridge->num_channels;
-	AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
-	ao2_unlock(bridge_channel);
 
 	ast_debug(1, "Pulling bridge channel %p(%s) from bridge %p\n",
 		bridge_channel, ast_channel_name(bridge_channel->chan), bridge);
@@ -394,6 +384,16 @@
 		}
 	}
 
+	/* Remove channel from the bridge */
+	if (!bridge_channel->suspended) {
+		--bridge->num_active;
+	}
+	--bridge->num_channels;
+	AST_LIST_REMOVE(&bridge->channels, bridge_channel, entry);
+	if (bridge->personality && bridge->personality->pull) {
+		bridge->personality->pull(bridge_channel);
+	}
+
 	bridge->reconfigured = 1;
 }
 
@@ -411,50 +411,62 @@
 static void ast_bridge_channel_push(struct ast_bridge_channel *bridge_channel)
 {
 	struct ast_bridge *bridge = bridge_channel->bridge;
-	struct ast_channel *swap;
-
-	ao2_lock(bridge_channel);
+	struct ast_bridge_channel *swap;
+	int chan_leaving;
+
 	ast_assert(!bridge_channel->in_bridge);
 
-	if (bridge->dissolved) {
-		/* Force out channel being pushed into a dissolved bridge. */
+	swap = find_bridge_channel(bridge, bridge_channel->swap);
+	bridge_channel->swap = NULL;
+
+	chan_leaving = bridge->dissolved
+		|| (bridge->personality && bridge->personality->can_push
+			&& !bridge->personality->can_push(bridge_channel, swap));
+
+	ast_bridge_channel_lock(bridge_channel);
+	if (chan_leaving) {
+		/*
+		 * Force out channel being pushed into a dissolved bridge or it
+		 * is not allowed into the bridge.
+		 */
 		ast_bridge_change_state_nolock(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 	}
-	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
+	chan_leaving = bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT;
+	ast_bridge_channel_unlock(bridge_channel);
+	if (chan_leaving) {
 		/* Don't push a channel in the process of leaving. */
-		ao2_unlock(bridge_channel);
 		return;
 	}
 
+	if (swap) {
+		ast_debug(1, "Pushing bridge channel %p(%s) into bridge %p by swapping with %p(%s)\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan), bridge,
+			swap, ast_channel_name(swap->chan));
+	} else {
+		ast_debug(1, "Pushing bridge channel %p(%s) into bridge %p\n",
+			bridge_channel, ast_channel_name(bridge_channel->chan), bridge);
+	}
+
+	/* Add channel to the bridge */
+	if (bridge->personality && bridge->personality->push
+		&& bridge->personality->push(bridge_channel, swap)) {
+/* BUGBUG need to use bridge id in the diagnostic message */
+		ast_log(LOG_ERROR, "Pushing channel %s into bridge %p failed\n",
+			ast_channel_name(bridge_channel->chan), bridge);
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		return;
+	}
 	bridge_channel->in_bridge = 1;
 	bridge_channel->just_joined = 1;
-	swap = bridge_channel->swap;
-	bridge_channel->swap = NULL;
-
-	/* Add channel to the bridge */
 	AST_LIST_INSERT_TAIL(&bridge->channels, bridge_channel, entry);
 	++bridge->num_channels;
 	if (!bridge_channel->suspended) {
 		++bridge->num_active;
 	}
-	ao2_unlock(bridge_channel);
-
 	if (swap) {
-		struct ast_bridge_channel *bridge_channel2;
-
-		bridge_channel2 = find_bridge_channel(bridge, swap);
-		if (bridge_channel2) {
-			ast_debug(1, "Swapping bridge channel %p(%s) out from bridge %p so bridge channel %p(%s) can slip in\n",
-				bridge_channel2, ast_channel_name(bridge_channel2->chan), bridge,
-				bridge_channel, ast_channel_name(bridge_channel->chan));
-			ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP);
-
-			ast_bridge_channel_pull(bridge_channel2);
-		}
-	}
-
-	ast_debug(1, "Pushing bridge channel %p(%s) into bridge %p\n",
-		bridge_channel, ast_channel_name(bridge_channel->chan), bridge);
+		ast_bridge_change_state(swap, AST_BRIDGE_CHANNEL_STATE_HANGUP);
+		ast_bridge_channel_pull(swap);
+	}
 
 	bridge->reconfigured = 1;
 }
@@ -694,7 +706,7 @@
 	/* Simply write the frame out to the bridge technology. */
 	ast_bridge_channel_lock_bridge(bridge_channel);
 	bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
-	ao2_unlock(bridge_channel->bridge);
+	ast_bridge_unlock(bridge_channel->bridge);
 	ast_frfree(frame);
 }
 
@@ -743,9 +755,7 @@
 				ast_channel_name(bridge_channel->chan), bridge);
 		}
 
-		ao2_lock(bridge_channel);
 		bridge_channel->just_joined = 0;
-		ao2_unlock(bridge_channel);
 	}
 }
 
@@ -838,9 +848,9 @@
 
 	switch (action->subclass.integer) {
 	case AST_BRIDGE_ACTION_DEFERRED_TECH_DESTROY:
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		bridge_tech_deferred_destroy(bridge, action);
-		ao2_lock(bridge);
+		ast_bridge_lock(bridge);
 		break;
 	default:
 		/* Unexpected deferred action type.  Should never happen. */
@@ -886,9 +896,9 @@
 	ast_debug(1, "Actually destroying bridge %p, nobody wants it anymore\n", bridge);
 
 	/* Do any pending actions in the context of destruction. */
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	bridge_handle_actions(bridge);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	/* There should not be any channels left in the bridge. */
 	ast_assert(AST_LIST_EMPTY(&bridge->channels));
@@ -901,6 +911,14 @@
 	}
 	ast_module_unref(bridge->technology->mod);
 
+	if (bridge->personality) {
+		ast_debug(1, "Giving bridge personality %s the bridge structure %p to destroy\n",
+			bridge->personality->name, bridge);
+		if (bridge->personality->destroy) {
+			bridge->personality->destroy(bridge);
+		}
+	}
+
 	if (bridge->callid) {
 		bridge->callid = ast_callid_unref(bridge->callid);
 	}
@@ -908,7 +926,7 @@
 	cleanup_video_mode(bridge);
 }
 
-struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags)
+struct ast_bridge *ast_bridge_new(uint32_t capabilities, int flags, const struct ast_personality_methods *personality)
 {
 	struct ast_bridge *bridge;
 	struct ast_bridge_technology *bridge_technology;
@@ -942,9 +960,21 @@
 		return NULL;
 	}
 
+	bridge->personality = personality;
 	bridge->technology = bridge_technology;
 
 	ast_set_flag(&bridge->feature_flags, flags);
+
+	if (bridge->personality) {
+		ast_debug(1, "Giving bridge personality %s the bridge structure %p to setup\n",
+			bridge->personality->name, bridge);
+		if (bridge->personality->create && bridge->personality->create(bridge)) {
+			ast_debug(1, "Bridge personality %s failed to setup bridge structure %p\n",
+				bridge->personality->name, bridge);
+			ao2_ref(bridge, -1);
+			return NULL;
+		}
+	}
 
 	/* Pass off the bridge to the technology to manipulate if needed */
 	ast_debug(1, "Giving bridge technology %s the bridge structure %p to setup\n",
@@ -975,9 +1005,9 @@
 int ast_bridge_destroy(struct ast_bridge *bridge)
 {
 	ast_debug(1, "Telling all channels in bridge %p to leave the party\n", bridge);
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	bridge_force_out_all(bridge);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	ao2_ref(bridge, -1);
 
@@ -1266,12 +1296,10 @@
  */
 static void bridge_channel_suspend_nolock(struct ast_bridge_channel *bridge_channel)
 {
-	ao2_lock(bridge_channel);
 	bridge_channel->suspended = 1;
 	if (bridge_channel->in_bridge) {
 		--bridge_channel->bridge->num_active;
 	}
-	ao2_unlock(bridge_channel);
 
 	/* Get technology bridge threads off of the channel. */
 	if (bridge_channel->bridge->technology->suspend) {
@@ -1291,7 +1319,7 @@
 {
 	ast_bridge_channel_lock_bridge(bridge_channel);
 	bridge_channel_suspend_nolock(bridge_channel);
-	ao2_unlock(bridge_channel->bridge);
+	ast_bridge_unlock(bridge_channel->bridge);
 }
 
 /*!
@@ -1306,20 +1334,20 @@
  */
 static void bridge_channel_unsuspend_nolock(struct ast_bridge_channel *bridge_channel)
 {
-	ao2_lock(bridge_channel);
 	bridge_channel->suspended = 0;
 	if (bridge_channel->in_bridge) {
 		++bridge_channel->bridge->num_active;
 	}
 
-	/* Wake suspended channel. */
-	ast_cond_signal(&bridge_channel->cond);
-	ao2_unlock(bridge_channel);
-
 	/* Wake technology bridge threads to take care of channel again. */
 	if (bridge_channel->bridge->technology->unsuspend) {
 		bridge_channel->bridge->technology->unsuspend(bridge_channel->bridge, bridge_channel);
 	}
+
+	/* Wake suspended channel. */
+	ast_bridge_channel_lock(bridge_channel);
+	ast_cond_signal(&bridge_channel->cond);
+	ast_bridge_channel_unlock(bridge_channel);
 }
 
 /*!
@@ -1334,7 +1362,7 @@
 {
 	ast_bridge_channel_lock_bridge(bridge_channel);
 	bridge_channel_unsuspend_nolock(bridge_channel);
-	ao2_unlock(bridge_channel->bridge);
+	ast_bridge_unlock(bridge_channel->bridge);
 }
 
 /*! \brief Internal function that activates interval hooks on a bridge channel */
@@ -1592,7 +1620,7 @@
 	struct ast_frame *fr;
 	char nudge;
 
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 	if (read(bridge_channel->alert_pipe[0], &nudge, sizeof(nudge)) < 0) {
 		if (errno != EINTR && errno != EAGAIN) {
 			ast_log(LOG_WARNING, "read() failed for alert pipe on bridge channel %p(%s): %s\n",
@@ -1600,7 +1628,7 @@
 		}
 	}
 	fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list);
-	ao2_unlock(bridge_channel);
+	ast_bridge_channel_unlock(bridge_channel);
 	if (!fr) {
 		return;
 	}
@@ -1669,9 +1697,10 @@
 	struct ast_channel *chan;
 
 	/* Wait for data to either come from the channel or us to be signaled */
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 	if (bridge_channel->state != AST_BRIDGE_CHANNEL_STATE_WAIT) {
 	} else if (bridge_channel->suspended) {
+/* BUGBUG the external party use of suspended will go away as will these references because this is the bridge channel thread */
 		ast_debug(1, "Going into a signal wait for bridge channel %p(%s) of bridge %p\n",
 			bridge_channel, ast_channel_name(bridge_channel->chan),
 			bridge_channel->bridge);
@@ -1680,7 +1709,7 @@
 		ast_debug(10, "Going into a waitfor for bridge channel %p(%s) of bridge %p\n",
 			bridge_channel, ast_channel_name(bridge_channel->chan),
 			bridge_channel->bridge);
-		ao2_unlock(bridge_channel);
+		ast_bridge_channel_unlock(bridge_channel);
 		outfd = -1;
 /* BUGBUG need to make the next expiring active interval setup ms timeout rather than holding up the chan reads. */
 		chan = ast_waitfor_nandfds(&bridge_channel->chan, 1,
@@ -1696,7 +1725,7 @@
 		}
 		return;
 	}
-	ao2_unlock(bridge_channel);
+	ast_bridge_channel_unlock(bridge_channel);
 }
 
 /*! \brief Join a channel to a bridge and handle anything the bridge may want us to do */
@@ -1714,7 +1743,7 @@
 	 * Directly locking the bridge is safe here because nobody else
 	 * knows about this bridge_channel yet.
 	 */
-	ao2_lock(bridge_channel->bridge);
+	ast_bridge_lock(bridge_channel->bridge);
 
 	if (!bridge_channel->bridge->callid) {
 		bridge_channel->bridge->callid = ast_read_threadstorage_callid();
@@ -1724,7 +1753,7 @@
 	ast_bridge_reconfigured(bridge_channel->bridge);
 
 	/* Wait for something to do. */
-	ao2_unlock(bridge_channel->bridge);
+	ast_bridge_unlock(bridge_channel->bridge);
 	while (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
 		/* Update bridge pointer on channel */
 		ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
@@ -1745,15 +1774,15 @@
 		break;
 	}
 
-	ao2_unlock(bridge_channel->bridge);
+	ast_bridge_unlock(bridge_channel->bridge);
 
 	/* Flush any unhandled frames. */
-	ao2_lock(bridge_channel);
+	ast_bridge_channel_lock(bridge_channel);
 /* BUGBUG need to destroy unused bridge action frames specially since they can have referenced pointers. */
 	while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->wr_queue, frame_list))) {
 		ast_frfree(fr);
 	}
-	ao2_unlock(bridge_channel);
+	ast_bridge_channel_unlock(bridge_channel);
 
 /* BUGBUG Revisit in regards to moving channels between bridges and local channel optimization. */
 	/* Complete any partial DTMF digit before exiting the bridge. */
@@ -2362,17 +2391,17 @@
 {
 	struct ast_bridge_channel *bridge_channel;
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 
 	/* Try to find the channel that we want to remove */
 	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		return -1;
 	}
 
 	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
 
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	return 0;
 }
@@ -2409,9 +2438,9 @@
 
 		/* Point to new bridge.*/
 		ao2_ref(bridge1, +1);
-		ao2_lock(bridge_channel);
+		ast_bridge_channel_lock(bridge_channel);
 		bridge_channel->bridge = bridge1;
-		ao2_unlock(bridge_channel);
+		ast_bridge_channel_unlock(bridge_channel);
 		ao2_ref(bridge2, -1);
 
 		ast_bridge_channel_push(bridge_channel);
@@ -2428,11 +2457,11 @@
 
 	/* Deadlock avoidance. */
 	for (;;) {
-		ao2_lock(bridge1);
-		if (!ao2_trylock(bridge2)) {
+		ast_bridge_lock(bridge1);
+		if (!ast_bridge_trylock(bridge2)) {
 			break;
 		}
-		ao2_unlock(bridge1);
+		ast_bridge_unlock(bridge1);
 		sched_yield();
 	}
 
@@ -2459,8 +2488,8 @@
 		res = 0;
 	}
 
-	ao2_unlock(bridge2);
-	ao2_unlock(bridge1);
+	ast_bridge_unlock(bridge2);
+	ast_bridge_unlock(bridge1);
 	return res;
 }
 
@@ -2468,13 +2497,13 @@
 {
 	int new_request;
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	new_request = bridge->inhibit_merge + request;
 	if (new_request < 0) {
 		new_request = 0;
 	}
 	bridge->inhibit_merge = new_request;
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 int ast_bridge_suspend(struct ast_bridge *bridge, struct ast_channel *chan)
@@ -2484,16 +2513,16 @@
 /* BUGBUG suspend/unsuspend needs to be rethought. The caller must block until it has successfully suspended the channel for temporary control. */
 /* BUGBUG external suspend/unsuspend needs to be eliminated. The channel may be playing a file at the time and stealing it then is not good. */
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 
 	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		return -1;
 	}
 
 	bridge_channel_suspend_nolock(bridge_channel);
 
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	return 0;
 }
@@ -2503,16 +2532,16 @@
 	struct ast_bridge_channel *bridge_channel;
 /* BUGBUG the case of a disolved bridge while channel is suspended is not handled. */
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 
 	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
-		ao2_unlock(bridge);
+		ast_bridge_unlock(bridge);
 		return -1;
 	}
 
 	bridge_channel_unsuspend_nolock(bridge_channel);
 
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	return 0;
 }
@@ -2866,7 +2895,7 @@
 		.data.ptr = (char *) dtmf,
 	};
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 
 	AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
 		if (bridge_channel->chan == chan) {
@@ -2875,24 +2904,23 @@
 		ast_bridge_channel_queue_frame(bridge_channel, &action);
 	}
 
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 
 	return 0;
 }
 
 void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
 {
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	bridge->internal_mixing_interval = mixing_interval;
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
 {
-
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	bridge->internal_sample_rate = sample_rate;
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 static void cleanup_video_mode(struct ast_bridge *bridge)
@@ -2918,22 +2946,22 @@
 
 void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_channel *video_src_chan)
 {
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	cleanup_video_mode(bridge);
 	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
 	bridge->video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
 	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %d\r\nVideo Channel: %s", bridge->video_mode.mode, ast_channel_name(video_src_chan));
 	ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
 {
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	cleanup_video_mode(bridge);
 	bridge->video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
 	ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %d", bridge->video_mode.mode);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
@@ -2944,7 +2972,7 @@
 		return;
 	}
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	data = &bridge->video_mode.mode_data.talker_src_data;
 
 	if (data->chan_vsrc == chan) {
@@ -2972,14 +3000,14 @@
 		data->chan_old_vsrc = ast_channel_ref(chan);
 		ast_indicate(chan, AST_CONTROL_VIDUPDATE);
 	}
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 int ast_bridge_number_video_src(struct ast_bridge *bridge)
 {
 	int res = 0;
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	switch (bridge->video_mode.mode) {
 	case AST_BRIDGE_VIDEO_MODE_NONE:
 		break;
@@ -2996,7 +3024,7 @@
 			res++;
 		}
 	}
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 	return res;
 }
 
@@ -3004,7 +3032,7 @@
 {
 	int res = 0;
 
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	switch (bridge->video_mode.mode) {
 	case AST_BRIDGE_VIDEO_MODE_NONE:
 		break;
@@ -3021,13 +3049,13 @@
 		}
 
 	}
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 	return res;
 }
 
 void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
 {
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	switch (bridge->video_mode.mode) {
 	case AST_BRIDGE_VIDEO_MODE_NONE:
 		break;
@@ -3054,7 +3082,7 @@
 			bridge->video_mode.mode_data.talker_src_data.chan_old_vsrc = NULL;
 		}
 	}
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 /*!
@@ -3068,14 +3096,14 @@
  */
 static void bridge_manager_service(struct ast_bridge *bridge)
 {
-	ao2_lock(bridge);
+	ast_bridge_lock(bridge);
 	if (bridge->callid) {
 		ast_callid_threadassoc_change(bridge->callid);
 	}
 
 	/* Do any pending bridge actions. */
 	bridge_handle_actions(bridge);
-	ao2_unlock(bridge);
+	ast_bridge_unlock(bridge);
 }
 
 /*!

Modified: team/rmudgett/bridge_tasks/main/features.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_tasks/main/features.c?view=diff&rev=383438&r1=383437&r2=383438
==============================================================================
--- team/rmudgett/bridge_tasks/main/features.c (original)
+++ team/rmudgett/bridge_tasks/main/features.c Wed Mar 20 11:55:13 2013
@@ -4535,8 +4535,9 @@
 	}
 
 	/* Create bridge */
+/* BUGBUG need to create the basic bridge personality that will manage the DTMF feature hooks. */
 	bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_MULTIMIX,
-		AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_SMART);
+		AST_BRIDGE_FLAG_DISSOLVE_HANGUP | AST_BRIDGE_FLAG_SMART, NULL);
 	if (!bridge) {
 		ast_bridge_features_destroy(peer_features);
 		ast_bridge_features_cleanup(&chan_features);




More information about the asterisk-commits mailing list