<p>George Joseph <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/13342">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
Benjamin Keith Ford: Looks good to me, but someone else must approve
Kevin Harwell: Looks good to me, approved
George Joseph: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">channel.c: Resolve issue with receiving SIP INFO packets for DTMF<br><br>The problem is essentially the same as in ASTERISK~28245. Besides<br>the direct media scenario we have an additional scenario where a<br>special client is involved. This device mutes audio by default in<br>transmit direction (no rtp frames) and activates audio only by a<br>foot switch. In this situation dtmf input (pin for conferences,<br>transfer features codes , etc) using SIP INFO mode is not<br>understood properly especially when SIP INFO messages are sent<br>quickly.<br><br>This patch ensures that SIP INFO frames are properly queued and<br>processed in the above scenario. The patch also corrects situations<br>where successive dtmf events are received quicker than the<br>signalled event duration (plus minimum gap/pause) allows, i.e. DTMF<br>events have to be buffered in the ast channel read queue and<br>emulation has to be processed asynchronously at slower speed.<br><br>Reported by: Thomas Arimont<br>patches:<br> trigger_dtmf_emulation.patch submitted by Thomas Arimont (license 5525)<br><br>Change-Id: I309bf61dd065c9978c8e48f5b9a936ab47de64c2<br>---<br>M main/channel.c<br>1 file changed, 102 insertions(+), 3 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/main/channel.c b/main/channel.c</span><br><span>index f096087..c350177 100644</span><br><span>--- a/main/channel.c</span><br><span>+++ b/main/channel.c</span><br><span>@@ -2819,6 +2819,33 @@</span><br><span> return (ast_channel_get_up_time_ms(chan) / 1000);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Determine whether or not we have to trigger dtmf emulating using 50 fps timer events</span><br><span style="color: hsl(120, 100%, 40%);">+ * especially when no voice frames are received during dtmf processing (direct media or muted</span><br><span style="color: hsl(120, 100%, 40%);">+ * sender case using SIP INFO)</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static inline int should_trigger_dtmf_emulating(struct ast_channel *chan)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEFER_DTMF | AST_FLAG_EMULATE_DTMF)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* We're in the middle of emulating a digit, or DTMF has been</span><br><span style="color: hsl(120, 100%, 40%);">+ * explicitly deferred. Trigger dtmf with periodic 50 pfs timer events, then. */</span><br><span style="color: hsl(120, 100%, 40%);">+ return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_tvzero(*ast_channel_dtmf_tv(chan)) &&</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_tvdiff_ms(ast_tvnow(), *ast_channel_dtmf_tv(chan)) < 2*AST_MIN_DTMF_GAP) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * We're not in the middle of a digit, but it hasn't been long enough</span><br><span style="color: hsl(120, 100%, 40%);">+ * since the last digit, so we'll have to trigger DTMF furtheron.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Using 2 times AST_MIN_DTMF_GAP to trigger readq reading for possible</span><br><span style="color: hsl(120, 100%, 40%);">+ * buffered next dtmf event</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static void deactivate_generator_nolock(struct ast_channel *chan)</span><br><span> {</span><br><span> if (ast_channel_generatordata(chan)) {</span><br><span>@@ -2839,6 +2866,10 @@</span><br><span> {</span><br><span> ast_channel_lock(chan);</span><br><span> deactivate_generator_nolock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (should_trigger_dtmf_emulating(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* if in the middle of dtmf emulation keep 50 tick per sec timer on rolling */</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_set_rate(ast_channel_timer(chan), 50);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> ast_channel_unlock(chan);</span><br><span> }</span><br><span> </span><br><span>@@ -3508,6 +3539,7 @@</span><br><span> </span><br><span> if (ast_channel_timingfd(chan) > -1 && ast_channel_fdno(chan) == AST_TIMING_FD) {</span><br><span> enum ast_timer_event res;</span><br><span style="color: hsl(120, 100%, 40%);">+ int trigger_dtmf_emulating = should_trigger_dtmf_emulating(chan);</span><br><span> </span><br><span> ast_clear_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION);</span><br><span> </span><br><span>@@ -3535,10 +3567,26 @@</span><br><span> if (got_ref) {</span><br><span> ao2_ref(data, -1);</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (trigger_dtmf_emulating) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Since we're breaking out of this switch block and not</span><br><span style="color: hsl(120, 100%, 40%);">+ * returning, we need to re-lock the channel.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_channel_lock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ /* generate null frame to trigger dtmf emulating */</span><br><span style="color: hsl(120, 100%, 40%);">+ f = &ast_null_frame;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (trigger_dtmf_emulating) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* generate null frame to trigger dtmf emualating */</span><br><span style="color: hsl(120, 100%, 40%);">+ f = &ast_null_frame;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span> } else {</span><br><span> ast_timer_set_rate(ast_channel_timer(chan), 0);</span><br><span style="color: hsl(0, 100%, 40%);">- ast_channel_fdno_set(chan, -1);</span><br><span style="color: hsl(0, 100%, 40%);">- ast_channel_unlock(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ /* generate very last null frame to trigger dtmf emulating */</span><br><span style="color: hsl(120, 100%, 40%);">+ f = &ast_null_frame;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span> }</span><br><span> </span><br><span> /* cannot 'goto done' because the channel is already unlocked */</span><br><span>@@ -3576,6 +3624,7 @@</span><br><span> </span><br><span> /* Check for pending read queue */</span><br><span> if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int skipped_dtmf_frame = 0;</span><br><span> int skip_dtmf = should_skip_dtmf(chan);</span><br><span> </span><br><span> AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_readq(chan), f, frame_list) {</span><br><span>@@ -3584,6 +3633,7 @@</span><br><span> * some later time. */</span><br><span> </span><br><span> if ( (f->frametype == AST_FRAME_DTMF_BEGIN || f->frametype == AST_FRAME_DTMF_END) && skip_dtmf) {</span><br><span style="color: hsl(120, 100%, 40%);">+ skipped_dtmf_frame = 1;</span><br><span> continue;</span><br><span> }</span><br><span> </span><br><span>@@ -3595,7 +3645,19 @@</span><br><span> if (!f) {</span><br><span> /* There were no acceptable frames on the readq. */</span><br><span> f = &ast_null_frame;</span><br><span style="color: hsl(0, 100%, 40%);">- ast_channel_alert_write(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!skipped_dtmf_frame) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Do not trigger alert pipe if only buffered dtmf begin or end frames</span><br><span style="color: hsl(120, 100%, 40%);">+ * are left in the readq.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_channel_alert_write(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Safely disable continous timer events if only buffered dtmf begin or end</span><br><span style="color: hsl(120, 100%, 40%);">+ * frames are left in the readq.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_disable_continuous(ast_channel_timer(chan));</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> }</span><br><span> </span><br><span> /* Interpret hangup and end-of-Q frames to return NULL */</span><br><span>@@ -3801,6 +3863,16 @@</span><br><span> } else</span><br><span> ast_channel_emulate_dtmf_duration_set(chan, AST_DEFAULT_EMULATE_DTMF_DURATION);</span><br><span> ast_log(LOG_DTMF, "DTMF begin emulation of '%c' with duration %u queued on %s\n", f->subclass.integer, ast_channel_emulate_dtmf_duration(chan), ast_channel_name(chan));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Start generating 50 fps timer events (null frames) for dtmf emulating</span><br><span style="color: hsl(120, 100%, 40%);">+ * independently from any existing incoming voice frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If channel generator is already activated in regular mode use these</span><br><span style="color: hsl(120, 100%, 40%);">+ * timer events to generate null frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_channel_generator(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_set_rate(ast_channel_timer(chan), 50);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> }</span><br><span> if (ast_channel_audiohooks(chan)) {</span><br><span> struct ast_frame *old_frame = f;</span><br><span>@@ -3842,12 +3914,30 @@</span><br><span> ast_channel_emulate_dtmf_duration_set(chan, option_dtmfminduration - f->len);</span><br><span> ast_frfree(f);</span><br><span> f = &ast_null_frame;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Start generating 50 fps timer events (null frames) for dtmf emulating</span><br><span style="color: hsl(120, 100%, 40%);">+ * independently from any existing incoming voice frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If channel generator is already activated in regular mode use these</span><br><span style="color: hsl(120, 100%, 40%);">+ * timer events to generate null frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_channel_generator(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_set_rate(ast_channel_timer(chan), 50);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> } else {</span><br><span> ast_log(LOG_DTMF, "DTMF end passthrough '%c' on %s\n", f->subclass.integer, ast_channel_name(chan));</span><br><span> if (f->len < option_dtmfminduration) {</span><br><span> f->len = option_dtmfminduration;</span><br><span> }</span><br><span> ast_channel_dtmf_tv_set(chan, &now);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Start generating 50 fps timer events (null frames) for dtmf emulating</span><br><span style="color: hsl(120, 100%, 40%);">+ * independently from any existing incoming voice frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If channel generator is already activated in regular mode use these</span><br><span style="color: hsl(120, 100%, 40%);">+ * timer events to generate null frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_channel_generator(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_set_rate(ast_channel_timer(chan), 50);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> }</span><br><span> if (ast_channel_audiohooks(chan)) {</span><br><span> struct ast_frame *old_frame = f;</span><br><span>@@ -3901,6 +3991,15 @@</span><br><span> ast_frfree(old_frame);</span><br><span> }</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Start generating 50 fps timer events (null frames) for dtmf emulating</span><br><span style="color: hsl(120, 100%, 40%);">+ * independently from any existing incoming voice frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If channel generator is already activated in regular mode use these</span><br><span style="color: hsl(120, 100%, 40%);">+ * timer events to generate null frames.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_channel_generator(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_timer_set_rate(ast_channel_timer(chan), 50);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span> }</span><br><span> }</span><br><span> break;</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/13342">change 13342</a>. To unsubscribe, or for help writing mail filters, 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/c/asterisk/+/13342"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 17 </div>
<div style="display:none"> Gerrit-Change-Id: I309bf61dd065c9978c8e48f5b9a936ab47de64c2 </div>
<div style="display:none"> Gerrit-Change-Number: 13342 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </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-MessageType: merged </div>