<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5855">View Change</a></p><div style="white-space:pre-wrap">Approvals:
George Joseph: Looks good to me, but someone else must approve
Joshua Colp: Looks good to me, but someone else must approve
Kevin Harwell: Looks good to me, approved
Jenkins2: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">bridge_native_rtp: Keep rtp instance refs on bridge_channel<br><br>There have been reports of deadlocks caused by an attempt to send a frame<br>to a channel's rtp instance after the channel has left the native bridge<br>and been destroyed. This patch effectively causes the bridge channel to<br>keep a reference to the glue and both the audio and video rtp instances<br>so what gets started will get stopped.<br><br>ASTERISK-26978 #close<br>Reported-by: Ross Beer<br><br>Change-Id: I9e1ac49fa4af68d64826ccccd152593cf8cdb21a<br>---<br>M bridges/bridge_native_rtp.c<br>M include/asterisk/rtp_engine.h<br>2 files changed, 517 insertions(+), 141 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c<br>index 832e397..cf43050 100644<br>--- a/bridges/bridge_native_rtp.c<br>+++ b/bridges/bridge_native_rtp.c<br>@@ -46,76 +46,214 @@<br> #include "asterisk/frame.h"<br> #include "asterisk/rtp_engine.h"<br> <br>-/*! \brief Internal structure which contains information about bridged RTP channels */<br>-struct native_rtp_bridge_data {<br>+/*! \brief Internal structure which contains bridged RTP channel hook data */<br>+struct native_rtp_framehook_data {<br> /*! \brief Framehook used to intercept certain control frames */<br> int id;<br> /*! \brief Set when this framehook has been detached */<br> unsigned int detached;<br> };<br> <br>-/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */<br>-static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,<br>- struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,<br>- struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)<br>-{<br>- enum ast_rtp_glue_result audio_glue0_res;<br>- enum ast_rtp_glue_result video_glue0_res;<br>- enum ast_rtp_glue_result audio_glue1_res;<br>- enum ast_rtp_glue_result video_glue1_res;<br>+struct rtp_glue_stream {<br>+ /*! \brief RTP instance */<br>+ struct ast_rtp_instance *instance;<br>+ /*! \brief glue result */<br>+ enum ast_rtp_glue_result result;<br>+};<br> <br>- if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||<br>- !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {<br>- return AST_RTP_GLUE_RESULT_FORBID;<br>+struct rtp_glue_data {<br>+ /*!<br>+ * \brief glue callbacks<br>+ *<br>+ * \note The glue data is considered valid if cb is not NULL.<br>+ */<br>+ struct ast_rtp_glue *cb;<br>+ struct rtp_glue_stream audio;<br>+ struct rtp_glue_stream video;<br>+ /*! Combined glue result of both bridge channels. */<br>+ enum ast_rtp_glue_result result;<br>+};<br>+<br>+/*! \brief Internal structure which contains instance information about bridged RTP channels */<br>+struct native_rtp_bridge_channel_data {<br>+ /*! \brief Channel's hook data */<br>+ struct native_rtp_framehook_data *hook_data;<br>+ /*!<br>+ * \brief Glue callbacks to bring remote channel streams back to Asterisk.<br>+ * \note NULL if channel streams are local.<br>+ */<br>+ struct ast_rtp_glue *remote_cb;<br>+ /*! \brief Channel's cached RTP glue information */<br>+ struct rtp_glue_data glue;<br>+};<br>+<br>+static void rtp_glue_data_init(struct rtp_glue_data *glue)<br>+{<br>+ glue->cb = NULL;<br>+ glue->audio.instance = NULL;<br>+ glue->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br>+ glue->video.instance = NULL;<br>+ glue->video.result = AST_RTP_GLUE_RESULT_FORBID;<br>+ glue->result = AST_RTP_GLUE_RESULT_FORBID;<br>+}<br>+<br>+static void rtp_glue_data_destroy(struct rtp_glue_data *glue)<br>+{<br>+ if (!glue) {<br>+ return;<br>+ }<br>+ ao2_cleanup(glue->audio.instance);<br>+ ao2_cleanup(glue->video.instance);<br>+}<br>+<br>+static void rtp_glue_data_reset(struct rtp_glue_data *glue)<br>+{<br>+ rtp_glue_data_destroy(glue);<br>+ rtp_glue_data_init(glue);<br>+}<br>+<br>+static void native_rtp_bridge_channel_data_free(struct native_rtp_bridge_channel_data *data)<br>+{<br>+ ast_debug(2, "Destroying channel tech_pvt data %p\n", data);<br>+<br>+ /*<br>+ * hook_data will probably already have been unreferenced by the framehook detach<br>+ * and the pointer set to null.<br>+ */<br>+ ao2_cleanup(data->hook_data);<br>+<br>+ rtp_glue_data_reset(&data->glue);<br>+ ast_free(data);<br>+}<br>+<br>+static struct native_rtp_bridge_channel_data *native_rtp_bridge_channel_data_alloc(void)<br>+{<br>+ struct native_rtp_bridge_channel_data *data;<br>+<br>+ data = ast_calloc(1, sizeof(*data));<br>+ if (data) {<br>+ rtp_glue_data_init(&data->glue);<br>+ }<br>+ return data;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which gets all RTP information (glue and instances) relating to the given channels<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on error.<br>+ */<br>+static int rtp_glue_data_get(struct ast_channel *c0, struct rtp_glue_data *glue0,<br>+ struct ast_channel *c1, struct rtp_glue_data *glue1)<br>+{<br>+ struct ast_rtp_glue *cb0;<br>+ struct ast_rtp_glue *cb1;<br>+ enum ast_rtp_glue_result combined_result;<br>+<br>+ cb0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type);<br>+ cb1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type);<br>+ if (!cb0 || !cb1) {<br>+ /* One or both channels doesn't have any RTP glue registered. */<br>+ return -1;<br> }<br> <br>- audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);<br>- video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;<br>+ /* The glue callbacks bump the RTP instance refcounts for us. */<br> <br>- audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);<br>- video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;<br>+ glue0->cb = cb0;<br>+ glue0->audio.result = cb0->get_rtp_info(c0, &glue0->audio.instance);<br>+ glue0->video.result = cb0->get_vrtp_info<br>+ ? cb0->get_vrtp_info(c0, &glue0->video.instance) : AST_RTP_GLUE_RESULT_FORBID;<br>+<br>+ glue1->cb = cb1;<br>+ glue1->audio.result = cb1->get_rtp_info(c1, &glue1->audio.instance);<br>+ glue1->video.result = cb1->get_vrtp_info<br>+ ? cb1->get_vrtp_info(c1, &glue1->video.instance) : AST_RTP_GLUE_RESULT_FORBID;<br>+<br>+ /*<br>+ * Now determine the combined glue result.<br>+ */<br> <br> /* Apply any limitations on direct media bridging that may be present */<br>- if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {<br>- if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {<br>+ if (glue0->audio.result == glue1->audio.result && glue1->audio.result == AST_RTP_GLUE_RESULT_REMOTE) {<br>+ if (glue0->cb->allow_rtp_remote && !glue0->cb->allow_rtp_remote(c0, glue1->audio.instance)) {<br> /* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */<br>- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>- } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {<br>- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;<br>+ } else if (glue1->cb->allow_rtp_remote && !glue1->cb->allow_rtp_remote(c1, glue0->audio.instance)) {<br>+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;<br> }<br> }<br>- if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {<br>- if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {<br>+ if (glue0->video.result == glue1->video.result && glue1->video.result == AST_RTP_GLUE_RESULT_REMOTE) {<br>+ if (glue0->cb->allow_vrtp_remote && !glue0->cb->allow_vrtp_remote(c0, glue1->audio.instance)) {<br> /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */<br>- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>- } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {<br>- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;<br>+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;<br>+ } else if (glue1->cb->allow_vrtp_remote && !glue1->cb->allow_vrtp_remote(c1, glue0->audio.instance)) {<br>+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;<br> }<br> }<br> <br> /* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */<br>- if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID<br>- && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE<br>- || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {<br>- audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;<br>+ if (glue0->video.result != AST_RTP_GLUE_RESULT_FORBID<br>+ && (glue0->audio.result != AST_RTP_GLUE_RESULT_REMOTE<br>+ || glue0->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {<br>+ glue0->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br> }<br>- if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID<br>- && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE<br>- || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {<br>- audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;<br>+ if (glue1->video.result != AST_RTP_GLUE_RESULT_FORBID<br>+ && (glue1->audio.result != AST_RTP_GLUE_RESULT_REMOTE<br>+ || glue1->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {<br>+ glue1->audio.result = AST_RTP_GLUE_RESULT_FORBID;<br> }<br> <br> /* The order of preference is: forbid, local, and remote. */<br>- if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID ||<br>- audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {<br>+ if (glue0->audio.result == AST_RTP_GLUE_RESULT_FORBID<br>+ || glue1->audio.result == AST_RTP_GLUE_RESULT_FORBID) {<br> /* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */<br>- return AST_RTP_GLUE_RESULT_FORBID;<br>- } else if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL ||<br>- audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {<br>- return AST_RTP_GLUE_RESULT_LOCAL;<br>+ combined_result = AST_RTP_GLUE_RESULT_FORBID;<br>+ } else if (glue0->audio.result == AST_RTP_GLUE_RESULT_LOCAL<br>+ || glue1->audio.result == AST_RTP_GLUE_RESULT_LOCAL) {<br>+ combined_result = AST_RTP_GLUE_RESULT_LOCAL;<br> } else {<br>- return AST_RTP_GLUE_RESULT_REMOTE;<br>+ combined_result = AST_RTP_GLUE_RESULT_REMOTE;<br> }<br>+ glue0->result = combined_result;<br>+ glue1->result = combined_result;<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Get the current RTP native bridge combined glue result.<br>+ * \since 15.0.0<br>+ *<br>+ * \param c0 First bridge channel<br>+ * \param c1 Second bridge channel<br>+ *<br>+ * \note Both channels must be locked when calling this function.<br>+ *<br>+ * \return Current combined glue result.<br>+ */<br>+static enum ast_rtp_glue_result rtp_glue_get_current_combined_result(struct ast_channel *c0,<br>+ struct ast_channel *c1)<br>+{<br>+ struct rtp_glue_data glue_a;<br>+ struct rtp_glue_data glue_b;<br>+ struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br>+ enum ast_rtp_glue_result combined_result;<br>+<br>+ rtp_glue_data_init(&glue_a);<br>+ glue0 = &glue_a;<br>+ rtp_glue_data_init(&glue_b);<br>+ glue1 = &glue_b;<br>+ if (rtp_glue_data_get(c0, glue0, c1, glue1)) {<br>+ return AST_RTP_GLUE_RESULT_FORBID;<br>+ }<br>+<br>+ combined_result = glue0->result;<br>+ rtp_glue_data_destroy(glue0);<br>+ rtp_glue_data_destroy(glue1);<br>+ return combined_result;<br> }<br> <br> /*!<br>@@ -131,52 +269,91 @@<br> {<br> struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);<br> struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);<br>- enum ast_rtp_glue_result native_type = AST_RTP_GLUE_RESULT_FORBID;<br>- struct ast_rtp_glue *glue0, *glue1;<br>- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, tinstance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, tinstance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);<br>- RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);<br>+ struct native_rtp_bridge_channel_data *data0;<br>+ struct native_rtp_bridge_channel_data *data1;<br>+ struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br>+ struct ast_format_cap *cap0;<br>+ struct ast_format_cap *cap1;<br>+ enum ast_rtp_glue_result native_type;<br> <br> if (bc0 == bc1) {<br> return;<br> }<br>+ data0 = bc0->tech_pvt;<br>+ data1 = bc1->tech_pvt;<br>+ if (!data0 || !data1) {<br>+ /* Not all channels are joined with the bridge tech yet */<br>+ return;<br>+ }<br>+ glue0 = &data0->glue;<br>+ glue1 = &data1->glue;<br> <br> ast_channel_lock_both(bc0->chan, bc1->chan);<br>- if (!bc0->suspended && !bc1->suspended) {<br>- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);<br>+<br>+ if (!glue0->cb || !glue1->cb) {<br>+ /*<br>+ * Somebody doesn't have glue data so the bridge isn't running<br>+ *<br>+ * Actually neither side should have glue data.<br>+ */<br>+ ast_assert(!glue0->cb && !glue1->cb);<br>+<br>+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {<br>+ /*<br>+ * This might happen if one of the channels got masqueraded<br>+ * at a critical time. It's a bit of a stretch even then<br>+ * since the channel is in a bridge.<br>+ */<br>+ goto done;<br>+ }<br> }<br>+<br>+ ast_debug(2, "Bridge '%s'. Tech starting '%s' and '%s' with target '%s'\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),<br>+ target ? ast_channel_name(target) : "none");<br>+<br>+ native_type = glue0->result;<br> <br> switch (native_type) {<br> case AST_RTP_GLUE_RESULT_LOCAL:<br>- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {<br>- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);<br>+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {<br>+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, glue1->audio.instance);<br> }<br>- if (ast_rtp_instance_get_engine(instance1)->local_bridge) {<br>- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);<br>+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {<br>+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, glue0->audio.instance);<br> }<br>- ast_rtp_instance_set_bridged(instance0, instance1);<br>- ast_rtp_instance_set_bridged(instance1, instance0);<br>+ ast_rtp_instance_set_bridged(glue0->audio.instance, glue1->audio.instance);<br>+ ast_rtp_instance_set_bridged(glue1->audio.instance, glue0->audio.instance);<br> ast_verb(4, "Locally RTP bridged '%s' and '%s' in stack\n",<br> ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br> break;<br>-<br> case AST_RTP_GLUE_RESULT_REMOTE:<br>- if (glue0->get_codec) {<br>- glue0->get_codec(bc0->chan, cap0);<br>- }<br>- if (glue1->get_codec) {<br>- glue1->get_codec(bc1->chan, cap1);<br>+ cap0 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ cap1 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ if (!cap0 || !cap1) {<br>+ ao2_cleanup(cap0);<br>+ ao2_cleanup(cap1);<br>+ break;<br> }<br> <br>- /* If we have a target, it's the channel that received the UNHOLD or UPDATE_RTP_PEER frame and was told to resume */<br>+ if (glue0->cb->get_codec) {<br>+ glue0->cb->get_codec(bc0->chan, cap0);<br>+ }<br>+ if (glue1->cb->get_codec) {<br>+ glue1->cb->get_codec(bc1->chan, cap1);<br>+ }<br>+<br>+ /*<br>+ * If we have a target, it's the channel that received the UNHOLD or<br>+ * UPDATE_RTP_PEER frame and was told to resume<br>+ */<br> if (!target) {<br>- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);<br>- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);<br>+ /* Send both channels to remote */<br>+ data0->remote_cb = glue0->cb;<br>+ data1->remote_cb = glue1->cb;<br>+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);<br>+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);<br> ast_verb(4, "Remotely bridged '%s' and '%s' - media will flow directly between them\n",<br> ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br> } else {<br>@@ -186,51 +363,121 @@<br> * already set up to handle the new media path or will have its own set of updates independent<br> * of this pass.<br> */<br>+ ast_debug(2, "Bridge '%s'. Sending '%s' back to remote\n",<br>+ bridge->uniqueid, ast_channel_name(target));<br> if (bc0->chan == target) {<br>- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);<br>+ data0->remote_cb = glue0->cb;<br>+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);<br> } else {<br>- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);<br>+ data1->remote_cb = glue1->cb;<br>+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);<br> }<br> }<br>+<br>+ ao2_cleanup(cap0);<br>+ ao2_cleanup(cap1);<br> break;<br> case AST_RTP_GLUE_RESULT_FORBID:<br> break;<br> }<br> <br>+ if (native_type != AST_RTP_GLUE_RESULT_REMOTE) {<br>+ /* Bring any remaining channels back to us. */<br>+ if (data0->remote_cb) {<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan));<br>+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+ data0->remote_cb = NULL;<br>+ }<br>+ if (data1->remote_cb) {<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc1->chan));<br>+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+ data1->remote_cb = NULL;<br>+ }<br>+ }<br>+<br>+done:<br> ast_channel_unlock(bc0->chan);<br> ast_channel_unlock(bc1->chan);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Stop native RTP bridging of two channels<br>+ *<br>+ * \param bridge The bridge that had native RTP bridging happening on it<br>+ * \param target If remote RTP bridging, the channel that is held.<br>+ *<br>+ * \note The first channel to leave the bridge triggers the cleanup for both channels<br>+ */<br> static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel *target)<br> {<br> struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);<br> struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);<br>- enum ast_rtp_glue_result native_type;<br>- struct ast_rtp_glue *glue0, *glue1 = NULL;<br>- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>+ struct native_rtp_bridge_channel_data *data0;<br>+ struct native_rtp_bridge_channel_data *data1;<br>+ struct rtp_glue_data *glue0;<br>+ struct rtp_glue_data *glue1;<br> <br> if (bc0 == bc1) {<br> return;<br> }<br>+ data0 = bc0->tech_pvt;<br>+ data1 = bc1->tech_pvt;<br>+ if (!data0 || !data1) {<br>+ /* Not all channels are joined with the bridge tech */<br>+ return;<br>+ }<br>+ glue0 = &data0->glue;<br>+ glue1 = &data1->glue;<br>+<br>+ ast_debug(2, "Bridge '%s'. Tech stopping '%s' and '%s' with target '%s'\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),<br>+ target ? ast_channel_name(target) : "none");<br>+<br>+ if (!glue0->cb || !glue1->cb) {<br>+ /*<br>+ * Somebody doesn't have glue data so the bridge isn't running<br>+ *<br>+ * Actually neither side should have glue data.<br>+ */<br>+ ast_assert(!glue0->cb && !glue1->cb);<br>+ /* At most one channel can be left at the remote endpoint here. */<br>+ ast_assert(!data0->remote_cb || !data1->remote_cb);<br>+<br>+ /* Bring selected channel streams back to us */<br>+ if (data0->remote_cb && (!target || target == bc0->chan)) {<br>+ ast_channel_lock(bc0->chan);<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan));<br>+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+ data0->remote_cb = NULL;<br>+ ast_channel_unlock(bc0->chan);<br>+ }<br>+ if (data1->remote_cb && (!target || target == bc1->chan)) {<br>+ ast_channel_lock(bc1->chan);<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc1->chan));<br>+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+ data1->remote_cb = NULL;<br>+ ast_channel_unlock(bc1->chan);<br>+ }<br>+ return;<br>+ }<br> <br> ast_channel_lock_both(bc0->chan, bc1->chan);<br>- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);<br> <br>- switch (native_type) {<br>+ switch (glue0->result) {<br> case AST_RTP_GLUE_RESULT_LOCAL:<br>- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {<br>- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);<br>+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {<br>+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, NULL);<br> }<br>- if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {<br>- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);<br>+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {<br>+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, NULL);<br> }<br>- ast_rtp_instance_set_bridged(instance0, NULL);<br>- if (instance1) {<br>- ast_rtp_instance_set_bridged(instance1, NULL);<br>- }<br>+ ast_rtp_instance_set_bridged(glue0->audio.instance, NULL);<br>+ ast_rtp_instance_set_bridged(glue1->audio.instance, NULL);<br> break;<br> case AST_RTP_GLUE_RESULT_REMOTE:<br> if (target) {<br>@@ -238,10 +485,38 @@<br> * If a target was provided, it is being put on hold and should expect to<br> * receive media from Asterisk instead of what it was previously connected to.<br> */<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(target));<br> if (bc0->chan == target) {<br>- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+ data0->remote_cb = NULL;<br>+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br> } else {<br>- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+ data1->remote_cb = NULL;<br>+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+ }<br>+ } else {<br>+ data0->remote_cb = NULL;<br>+ data1->remote_cb = NULL;<br>+ /*<br>+ * XXX We don't want to bring back the channels if we are<br>+ * switching to T.38. We have received a reinvite on one channel<br>+ * and we will be sending a reinvite on the other to start T.38.<br>+ * If we bring the streams back now we confuse the chan_pjsip<br>+ * channel driver processing the incoming T.38 reinvite with<br>+ * reinvite glare. I think this is really a bug in chan_pjsip<br>+ * that this exception case is working around.<br>+ */<br>+ if (rtp_glue_get_current_combined_result(bc0->chan, bc1->chan)<br>+ != AST_RTP_GLUE_RESULT_FORBID) {<br>+ ast_debug(2, "Bridge '%s'. Bringing back '%s' and '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan),<br>+ ast_channel_name(bc1->chan));<br>+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>+ } else {<br>+ ast_debug(2, "Bridge '%s'. Skip bringing back '%s' and '%s' to us\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan),<br>+ ast_channel_name(bc1->chan));<br> }<br> }<br> break;<br>@@ -249,10 +524,8 @@<br> break;<br> }<br> <br>- if (!target && native_type != AST_RTP_GLUE_RESULT_FORBID) {<br>- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);<br>- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);<br>- }<br>+ rtp_glue_data_reset(glue0);<br>+ rtp_glue_data_reset(glue1);<br> <br> ast_debug(2, "Discontinued RTP bridging of '%s' and '%s' - media will flow through Asterisk core\n",<br> ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br>@@ -261,11 +534,15 @@<br> ast_channel_unlock(bc1->chan);<br> }<br> <br>-/*! \brief Frame hook that is called to intercept hold/unhold */<br>-static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)<br>+/*!<br>+ * \internal<br>+ * \brief Frame hook that is called to intercept hold/unhold<br>+ */<br>+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan,<br>+ struct ast_frame *f, enum ast_framehook_event event, void *data)<br> {<br> RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);<br>- struct native_rtp_bridge_data *native_data = data;<br>+ struct native_rtp_framehook_data *native_data = data;<br> <br> if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {<br> return f;<br>@@ -295,39 +572,49 @@<br> }<br> ast_bridge_unlock(bridge);<br> ast_channel_lock(chan);<br>-<br> }<br> <br> return f;<br> }<br> <br>-/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */<br>+/*!<br>+ * \internal<br>+ * \brief Callback function which informs upstream if we are consuming a frame of a specific type<br>+ */<br> static int native_rtp_framehook_consume(void *data, enum ast_frame_type type)<br> {<br> return (type == AST_FRAME_CONTROL ? 1 : 0);<br> }<br> <br>-/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */<br>+/*!<br>+ * \internal<br>+ * \brief Internal helper function which checks whether a channel is compatible with our native bridging<br>+ */<br> static int native_rtp_bridge_capable(struct ast_channel *chan)<br> {<br> return !ast_channel_has_hook_requiring_audio(chan);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Internal helper function which checks whether both channels are compatible with our native bridging<br>+ */<br> static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct ast_bridge_channel *bc0, struct ast_bridge_channel *bc1)<br> {<br> enum ast_rtp_glue_result native_type;<br>- struct ast_rtp_glue *glue0;<br>- struct ast_rtp_glue *glue1;<br>- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);<br> int read_ptime0;<br> int read_ptime1;<br> int write_ptime0;<br> int write_ptime1;<br>+ struct rtp_glue_data glue_a;<br>+ struct rtp_glue_data glue_b;<br>+ RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);<br>+ RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);<br>+ RAII_VAR(struct rtp_glue_data *, glue0, NULL, rtp_glue_data_destroy);<br>+ RAII_VAR(struct rtp_glue_data *, glue1, NULL, rtp_glue_data_destroy);<br>+<br>+ ast_debug(1, "Bridge '%s'. Checking compatability for channels '%s' and '%s'\n",<br>+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));<br> <br> if (!native_rtp_bridge_capable(bc0->chan)) {<br> ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",<br>@@ -341,8 +628,17 @@<br> return 0;<br> }<br> <br>- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1,<br>- &instance0, &instance1, &vinstance0, &vinstance1);<br>+ rtp_glue_data_init(&glue_a);<br>+ glue0 = &glue_a;<br>+ rtp_glue_data_init(&glue_b);<br>+ glue1 = &glue_b;<br>+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {<br>+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as could not get details\n",<br>+ bridge->uniqueid);<br>+ return 0;<br>+ }<br>+ native_type = glue0->result;<br>+<br> if (native_type == AST_RTP_GLUE_RESULT_FORBID) {<br> ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",<br> bridge->uniqueid);<br>@@ -350,25 +646,25 @@<br> }<br> <br> if (ao2_container_count(bc0->features->dtmf_hooks)<br>- && ast_rtp_instance_dtmf_mode_get(instance0)) {<br>+ && ast_rtp_instance_dtmf_mode_get(glue0->audio.instance)) {<br> ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",<br> bridge->uniqueid, ast_channel_name(bc0->chan));<br> return 0;<br> }<br> <br> if (ao2_container_count(bc1->features->dtmf_hooks)<br>- && ast_rtp_instance_dtmf_mode_get(instance1)) {<br>+ && ast_rtp_instance_dtmf_mode_get(glue1->audio.instance)) {<br> ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",<br> bridge->uniqueid, ast_channel_name(bc1->chan));<br> return 0;<br> }<br> <br> if (native_type == AST_RTP_GLUE_RESULT_LOCAL<br>- && (ast_rtp_instance_get_engine(instance0)->local_bridge<br>- != ast_rtp_instance_get_engine(instance1)->local_bridge<br>- || (ast_rtp_instance_get_engine(instance0)->dtmf_compatible<br>- && !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(bc0->chan,<br>- instance0, bc1->chan, instance1)))) {<br>+ && (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge<br>+ != ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge<br>+ || (ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible<br>+ && !ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible(bc0->chan,<br>+ glue0->audio.instance, bc1->chan, glue1->audio.instance)))) {<br> ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",<br> bridge->uniqueid);<br> return 0;<br>@@ -381,11 +677,11 @@<br> }<br> <br> /* Make sure that codecs match */<br>- if (glue0->get_codec) {<br>- glue0->get_codec(bc0->chan, cap0);<br>+ if (glue0->cb->get_codec) {<br>+ glue0->cb->get_codec(bc0->chan, cap0);<br> }<br>- if (glue1->get_codec) {<br>- glue1->get_codec(bc1->chan, cap1);<br>+ if (glue1->cb->get_codec) {<br>+ glue1->cb->get_codec(bc1->chan, cap1);<br> }<br> if (ast_format_cap_count(cap0) != 0<br> && ast_format_cap_count(cap1) != 0<br>@@ -415,6 +711,10 @@<br> return 1;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Called by the bridge core "compatible' callback<br>+ */<br> static int native_rtp_bridge_compatible(struct ast_bridge *bridge)<br> {<br> struct ast_bridge_channel *bc0;<br>@@ -439,10 +739,13 @@<br> return is_compatible;<br> }<br> <br>-/*! \brief Helper function which adds frame hook to bridge channel */<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which adds frame hook to bridge channel<br>+ */<br> static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)<br> {<br>- struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);<br>+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;<br> static struct ast_framehook_interface hook = {<br> .version = AST_FRAMEHOOK_INTERFACE_VERSION,<br> .event_cb = native_rtp_framehook,<br>@@ -451,45 +754,82 @@<br> .disable_inheritance = 1,<br> };<br> <br>- if (!data) {<br>+ ast_assert(data->hook_data == NULL);<br>+ data->hook_data = ao2_alloc_options(sizeof(*data->hook_data), NULL,<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!data->hook_data) {<br> return -1;<br> }<br>+<br>+ ast_debug(2, "Bridge '%s'. Attaching hook data %p to '%s'\n",<br>+ bridge_channel->bridge->uniqueid, data, ast_channel_name(bridge_channel->chan));<br> <br> ast_channel_lock(bridge_channel->chan);<br>- hook.data = ao2_bump(data);<br>- data->id = ast_framehook_attach(bridge_channel->chan, &hook);<br>+ /* We're giving 1 ref to the framehook and keeping the one from the alloc for ourselves */<br>+ hook.data = ao2_bump(data->hook_data);<br>+ data->hook_data->id = ast_framehook_attach(bridge_channel->chan, &hook);<br> ast_channel_unlock(bridge_channel->chan);<br>- if (data->id < 0) {<br>- /* We need to drop both the reference we hold, and the one the framehook would hold */<br>- ao2_ref(data, -2);<br>+ if (data->hook_data->id < 0) {<br>+ /*<br>+ * We need to drop both the reference we hold in data,<br>+ * and the one the framehook would hold.<br>+ */<br>+ ao2_ref(data->hook_data, -2);<br>+ data->hook_data = NULL;<br>+<br> return -1;<br> }<br>-<br>- bridge_channel->tech_pvt = data;<br> <br> return 0;<br> }<br> <br>-/*! \brief Helper function which removes frame hook from bridge channel */<br>+/*!<br>+ * \internal<br>+ * \brief Helper function which removes frame hook from bridge channel<br>+ */<br> static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)<br> {<br>- RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->tech_pvt, ao2_cleanup);<br>+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;<br> <br>- if (!data) {<br>+ if (!data || !data->hook_data) {<br> return;<br> }<br> <br>+ ast_debug(2, "Bridge '%s'. Detaching hook data %p from '%s'\n",<br>+ bridge_channel->bridge->uniqueid, data->hook_data, ast_channel_name(bridge_channel->chan));<br>+<br> ast_channel_lock(bridge_channel->chan);<br>- ast_framehook_detach(bridge_channel->chan, data->id);<br>- data->detached = 1;<br>+ ast_framehook_detach(bridge_channel->chan, data->hook_data->id);<br>+ data->hook_data->detached = 1;<br> ast_channel_unlock(bridge_channel->chan);<br>- bridge_channel->tech_pvt = NULL;<br>+ ao2_cleanup(data->hook_data);<br>+ data->hook_data = NULL;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Called by the bridge core 'join' callback for each channel joining he bridge<br>+ */<br> static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>- native_rtp_bridge_framehook_detach(bridge_channel);<br>+ ast_debug(2, "Bridge '%s'. Channel '%s' is joining bridge tech\n",<br>+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+<br>+ ast_assert(bridge_channel->tech_pvt == NULL);<br>+<br>+ if (bridge_channel->suspended) {<br>+ /* The channel will rejoin when it is unsuspended */<br>+ return 0;<br>+ }<br>+<br>+ bridge_channel->tech_pvt = native_rtp_bridge_channel_data_alloc();<br>+ if (!bridge_channel->tech_pvt) {<br>+ return -1;<br>+ }<br>+<br> if (native_rtp_bridge_framehook_attach(bridge_channel)) {<br>+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);<br>+ bridge_channel->tech_pvt = NULL;<br> return -1;<br> }<br> <br>@@ -497,15 +837,46 @@<br> return 0;<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Add the channel back into the bridge<br>+ */<br> static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>+ ast_debug(2, "Bridge '%s'. Channel '%s' is unsuspended back to bridge tech\n",<br>+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br> native_rtp_bridge_join(bridge, bridge_channel);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Leave the bridge<br>+ */<br> static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br> {<br>+ ast_debug(2, "Bridge '%s'. Channel '%s' is leaving bridge tech\n",<br>+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+<br>+ if (!bridge_channel->tech_pvt) {<br>+ return;<br>+ }<br>+<br> native_rtp_bridge_framehook_detach(bridge_channel);<br> native_rtp_bridge_stop(bridge, NULL);<br>+<br>+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);<br>+ bridge_channel->tech_pvt = NULL;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Suspend the channel from the bridge<br>+ */<br>+static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)<br>+{<br>+ ast_debug(2, "Bridge '%s'. Channel '%s' is suspending from bridge tech\n",<br>+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));<br>+ native_rtp_bridge_leave(bridge, bridge_channel);<br> }<br> <br> static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)<br>@@ -550,7 +921,7 @@<br> .join = native_rtp_bridge_join,<br> .unsuspend = native_rtp_bridge_unsuspend,<br> .leave = native_rtp_bridge_leave,<br>- .suspend = native_rtp_bridge_leave,<br>+ .suspend = native_rtp_bridge_suspend,<br> .write = native_rtp_bridge_write,<br> .compatible = native_rtp_bridge_compatible,<br> };<br>diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h<br>index 6544e70..e08738e 100644<br>--- a/include/asterisk/rtp_engine.h<br>+++ b/include/asterisk/rtp_engine.h<br>@@ -630,12 +630,13 @@<br> /*!<br> * \brief Used to prevent two channels from remotely bridging audio rtp if the channel tech has a<br> * reason for prohibiting it based on qualities that need to be compared from both channels.<br>- * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, function this is not used.<br>+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br> */<br> int (*allow_rtp_remote)(struct ast_channel *chan1, struct ast_rtp_instance *instance);<br> /*!<br> * \brief Callback for retrieving the RTP instance carrying video<br> * \note This function increases the reference count on the returned RTP instance.<br>+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br> */<br> enum ast_rtp_glue_result (*get_vrtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);<br> /*!<br>@@ -648,11 +649,15 @@<br> /*!<br> * \brief Callback for retrieving the RTP instance carrying text<br> * \note This function increases the reference count on the returned RTP instance.<br>+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br> */<br> enum ast_rtp_glue_result (*get_trtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);<br> /*! Callback for updating the destination that the remote side should send RTP to */<br> int (*update_peer)(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active);<br>- /*! Callback for retrieving codecs that the channel can do. Result returned in result_cap. */<br>+ /*!<br>+ * \brief Callback for retrieving codecs that the channel can do. Result returned in result_cap.<br>+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.<br>+ */<br> void (*get_codec)(struct ast_channel *chan, struct ast_format_cap *result_cap);<br> /*! Linked list information */<br> AST_RWLIST_ENTRY(ast_rtp_glue) entry;<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/5855">change 5855</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/5855"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 14 </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I9e1ac49fa4af68d64826ccccd152593cf8cdb21a </div>
<div style="display:none"> Gerrit-Change-Number: 5855 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>