[asterisk-dev] Unexpected variable-length DTMF behaviors

Paul Cadach paul at odt.east.telecom.kz
Thu Feb 8 03:49:38 MST 2007


Hi folks,

Currently I'm playing with broken DTMFs in chan_h323 and got next minds to discuss (as I remember I shown my minds about VL-DTMF somewhere already, before 1.4 was released). This is my story.


Old days we had AST_FRAME_DTMF message only, which denotes transmission of single DTMF digit regardless of its duration.

Immediately before 1.4 beta was released a code for support variable-length DTMF (VL-DTMF) which intended to transmit DTMF digits and care about its duration as accurate as possible. To support this feature, two new types of frame was added - AST_FRAME_DTMF_BEGIN and AST_FRAME_DTMF_END with meaningful names. But some channels and old versions of Asterisk don't have support for such events, and some channels have options to provide optional DTMF tone duration in single event (example of such channels includes chan_sip and chan_h323). To resolve compatibility problems, old AST_FRAME_DTMF was associated to AST_FRAME_DTMF_END (and frame code assigned to AST_FRAME_DTMF_END was equal to "old days" AST_FRAME_DTMF). To pass duration information, channel driver should translate this value to number of 8KHz samples occuped by tone and assign to .samples field. When channel which transmits DTMF provides support for VL-DTMF, Asterisk should expand single AST_FRAME_DTMF_END<duration> frame into two frames - AST_FRAME_DTMF_BEGIN and AST_FRAME_DTMF_END which distanced in time by <duration> milliseconds.

In real life first impelentation of VL-DTMF was a bit far from description shown above. Internally (within Asterisk core) only AST_FRAME_DTMF_BEGIN and AST_FRAME_DTMF_END frames are in use. If a channel which receives DTMF from endpoint and needs to pass it through Asterisk do not have <duration> information or just have single frame, AST_FRAME_DTMF, Asterisk core provides "emulation" to generate fake AST_FRAME_DTMF_BEGIN frame and delays AST_FRAME_DTMF (which is equal to AST_FRAME_DTMF_END) for <duration> (pointed by channel driver or assumed by Asterisk from default value) milliseconds. Channel driver which don't support for transmission VL-DTMF to client, uses only AST_FRAME_DTMF (=AST_FRAME_DTMF_END) event. As result, we have:
1) to provide time lag in distribution of single AST_FRAME_DTMF event between boxes (because each box will delay it for <duration> milliseconds);
2) API isn't allow to pass <duration> information on transmission of DTMF_END event;
3) applications still use AST_FRAME_DTMF (=AST_FRAME_DTMF_END) so they reacts to keypress after key unpressed.

Which sort of problems follow from above? A few:
1) we always make a delay when peer channel isn't support dual-event scheme;
2) peer channel should calculate DTMF duration itself (for SIP INFO/H.323 signal events) by remembering times of DTMF_BEGIN and DTMF_END events;
3) applications get AST_FRAME_DTMF_BEGIN transparently (because they react to AST_FRAME_DTMF only), which could cause some side effects (for example, Echo() application).

In the near time ago there was update for VL-DTMF code to support end-to-end passing of single-event DTMFs with support of passing <duration> information together with AST_FRAME_DTMF/AST_FRAME_DTMF_END event. So, positions 1) and 2) of problem list shown above should be solved, BUT we still have position 3) opened.

To resolve last issue, application should react on first DTMF event (BEGIN) only and silently ignore second event (END). This can be easily done by re-assigning AST_FRAME_DTMF from AST_FRAME_DTMF_END to AST_FRAME_DTMF_BEGIN. How it will impact existing applications? Lets examine...

When both channels support two-event scheme:
BEGIN<fake duration> -> bridge -> BEGIN<fake duration>
END<real duration> -> bridge -> END<real duration>

When transmitting channel support single-event scheme with duration:
BEGIN<fake duration> -> bridge -> /dev/null
END<real duration> -> bridge -> AST_FRAME_DTMF<real duration>

When transmitting channel support single-event scheme but don't support duration bypassing (applications are sort of such objects):
BEGIN<fake duration> -> bridge -> AST_FRAME_DTMF<fake duration>
END<real duration> -> bridge -> /dev/null

When receiving channel support single-event scheme with duration but transmitting channel support two-event scheme (bridge generates END event itself):
BEGIN<duration> -> bridge -> BEGIN<duration>
...audio...            -> bridge (generates END<duration>)-> END<duration>

When receiving channel support single-event scheme without duration but transmitting channel support two-event scheme (bridge generates END event itself too and replaces missing duration as pre-defined value, for example, 100 ms):
BEGIN<0> -> bridge (<0> -> <std duration>) -> BEGIN<std duration>
...audio...   -> bridge (generates END<std duration>) -> END<std duration>

When both channels support single-event scheme with duration:
BEGIN<duration> -> bridge -> BEGIN<duration>
...audio... -> bridge (generates END<duration>) -> /dev/null

When receiving channel support single-event scheme with duration while transmitting support single-event too but without duration:
BEGIN<duration> -> bridge -> BEGIN<duration> (transmitting channel just ignores <duration> value)
...audio... -> bridge (generates END<duration>) -> /dev/null

When transmitting channel support single-event scheme without duration while transmitting side support full single-event scheme:
BEGIN<0> -> bridge (<0> -> <std duration>) -> BEGIN<std duration>
...audio... -> bridge (generates END<std duration>) -> /dev/null

When both channels supports single-event scheme without duration:
BEGIN<0> -> bridge (<0> -> <std duration>) -> BEGIN<std duration> (<std duration> value ignored)
...audio... -> bridge (generates END<std duration>) -> /dev/null

When bridge placed into "DTMF transmission" state (before it generates END event itself) and receives another BEGIN event, it immediately generates END event for currently generating DTMF and handles new BEGIN event as usual.

<fake duration> should be very huge value (some hours, for example) to be sure that bridge will stay in "DTMF transmission" state until END event (or new BEGIN event) will be received.


Because we send DTMF event(s) as soon as we get it, there should no timing problems with IAX or other channels. One real problem is to support existing unclear implementation of VLDTMF in shipped 1.4 builds.


Functionality of the bridge could be described by next schematic code:
switch(frame.type) {
  case AST_FRAME_DTMF_BEGIN:
  {
    // Remember current time for forced END generation
    time_t now = time(NULL);
    // We should use default duration if it is not specified by receiver
    if(frame.len == 0)
      frame.len = AST_DEFAULT_DTMF_DURATION;
    if(chan.dtmf.end_sched >= 0) {    // This means we placed to "DTMF transmission" state because we always set up timer for this state
      size_t duration;
      ast_sched_destroy(&chan.sched, chan.dtmf.end_sched);
      // Just to be sure we clean-up everything after us
      chan.dtmf.end_sched = -1;
      // Calculate real DTMF duration
      duration = now - chan.dtmf.end_start_time;
      // Pass END event to transmitting channel
      peerchan->tech->send_digit_end(peerchan, chan.dtmf.digit, duration);
    }
    // Send BEGIN event to transmitting channel
    peerchan->tech->send_digit_begin(peerchan, frame.subtype, frame.len);
    // If we supposed to send END event, remember all required information and perform transition to "DTMF transmission" state
    if (peerchan->tech->send_digit_end) {
      // Add scheduler for generating END event
      chan.dtmf.end_sched = ast_sched_add(&chan.sched, frame.len, dtmf_end_generate, chan);
      // Remember parameters to generate END event correctly
      chan.dtmf.digit = frame.subtype;
      chan.dtmf.duration = frame.len;
      chan.dtmf.end_start_time = now;
    }
    break;
  case AST_FRAME_DTMF_END:
    if (chan.dtmf.end_sched) {
      // Exit from "DTMF transmission" state, abort timer
      ast_sched_destroy(&chan.sched, chan.dtmf.end_sched);
      chan.dtmf.end_sched = -1;
      // Peer supports two-event DTMF transmission, then send END event
      if (peerchan->tech->send_digit_end)
        peerchan->tech->send_digit_end(peerchan, chan.dtmf.digit, chan.dtmf.duration);
    }
    // Ignore END event if we sent it already by timer
    break;
...
}

int dtmf_end_generate(void *arg)
{
  struct ast_channel *chan = (struct ast_channel *)arg;
  struct ast_channel *peerchan = ast_bridgedpeer(chan);

  // Timer is fired, exit from "DTMF transmission" state
  chan->dtmf.end_sched = -1;

  // Send END event if peer supports for two-event DTMF transmission
  if (peerchan->tech->send_digit_end)
    peerchan->tech->send_digit_end(peerchan, chan->dtmf.digit, chan->dtmf.duration);

  // Do not re-schedule event generation
  return 0;
}


WBR,
Paul.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.digium.com/pipermail/asterisk-dev/attachments/20070208/e6e11a9d/attachment-0001.htm


More information about the asterisk-dev mailing list