Browse Source

TT#136957 support DTMF triggers for blocking

Change-Id: I32e0d02e739ceb5a34aaf187b1ea086482e6d73e
pull/1430/head
Richard Fuchs 4 years ago
parent
commit
2ca64340e1
10 changed files with 291 additions and 33 deletions
  1. +33
    -1
      README.md
  2. +75
    -25
      daemon/call_interfaces.c
  3. +44
    -2
      daemon/codec.c
  4. +114
    -3
      daemon/dtmf.c
  5. +2
    -0
      daemon/main.c
  6. +7
    -0
      include/call.h
  7. +6
    -0
      include/call_interfaces.h
  8. +1
    -0
      include/codec.h
  9. +1
    -0
      include/main.h
  10. +8
    -2
      utils/rtpengine-ng-client

+ 33
- 1
README.md View File

@ -1529,7 +1529,39 @@ Optionally included keys are:
in-band DTMF audio tones and RFC event packets); `zero` which is
similar to `random` except that a zero event is always used; `DTMF`
which is similar to `zero` except that a different DTMF digit can be
specified.
specified; `off` to disable DTMF blocking.
* `DTMF-security-trigger`
Blocking mode to enable when the DTMF `trigger` (see below) is detected.
* `DTMF-security-trigger-end`
Blocking mode to enable when the DTMF `end trigger` (see below) is
detected.
* `trigger`
A string of DTMF digits that enable a DTMF blocking mode when detected.
* `end trigger` or `trigger-end`
A string of DTMF digits that disable DTMF blocking or enable a
different DTMF blocking mode when detected, but only after the initial
enabling `trigger` has been detected.
* `trigger-end-time`
Time in milliseconds that a DTMF blocking mode enabled by the `trigger`
should remain active the most. After the time has expired, the blocking
mode is switched to the `trigger-end` mode.
* `trigger-end-digits`
Number of DTMF digits that a DTMF blocking mode enabled by the
`trigger` should remain active the most. After this number of DTMF
digits has been detected, the blocking mode is switched to the
`trigger-end` mode.
* `frequency`


+ 75
- 25
daemon/call_interfaces.c View File

@ -1018,6 +1018,35 @@ static void call_ng_codec_flags(struct sdp_ng_flags *out, str *key, bencode_item
}
#endif
}
static void call_ng_parse_block_mode(str *s, enum block_dtmf_mode *output) {
switch (__csh_lookup(s)) {
case CSH_LOOKUP("off"):
*output = BLOCK_DTMF_OFF;
break;
case CSH_LOOKUP("drop"):
*output = BLOCK_DTMF_DROP;
break;
case CSH_LOOKUP("silence"):
*output = BLOCK_DTMF_SILENCE;
break;
case CSH_LOOKUP("tone"):
*output = BLOCK_DTMF_TONE;
break;
case CSH_LOOKUP("random"):
*output = BLOCK_DTMF_RANDOM;
break;
case CSH_LOOKUP("zero"):
*output = BLOCK_DTMF_ZERO;
break;
case CSH_LOOKUP("DTMF"):
case CSH_LOOKUP("dtmf"):
*output = BLOCK_DTMF_DTMF;
break;
default:
ilog(LOG_WARN, "Unknown DTMF block mode encountered: '" STR_FORMAT "'",
STR_FMT(s));
}
}
static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_t *value) {
str s = STR_NULL;
bencode_item_t *it;
@ -1328,6 +1357,27 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_
if (s.len == 1)
out->digit = s.s[0];
break;
case CSH_LOOKUP("trigger"):
out->trigger = s;
break;
case CSH_LOOKUP("trigger-end"):
case CSH_LOOKUP("trigger end"):
case CSH_LOOKUP("end trigger"):
case CSH_LOOKUP("end-trigger"):
out->trigger_end = s;
break;
case CSH_LOOKUP("trigger-end-time"):
case CSH_LOOKUP("trigger end time"):
case CSH_LOOKUP("end-trigger-time"):
case CSH_LOOKUP("end trigger time"):
out->trigger_end_ms = bencode_get_integer_str(value, out->trigger_end_ms);
break;
case CSH_LOOKUP("trigger-end-digits"):
case CSH_LOOKUP("trigger end digits"):
case CSH_LOOKUP("end-trigger-digits"):
case CSH_LOOKUP("end trigger digits"):
out->trigger_end_digits = bencode_get_integer_str(value, out->trigger_end_digits);
break;
#ifdef WITH_TRANSCODING
case CSH_LOOKUP("T38"):
case CSH_LOOKUP("T.38"):
@ -1339,30 +1389,19 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_
case CSH_LOOKUP("dtmf-security"):
case CSH_LOOKUP("DTMF security"):
case CSH_LOOKUP("dtmf security"):
switch (__csh_lookup(&s)) {
case CSH_LOOKUP("drop"):
out->block_dtmf_mode = BLOCK_DTMF_DROP;
break;
case CSH_LOOKUP("silence"):
out->block_dtmf_mode = BLOCK_DTMF_SILENCE;
break;
case CSH_LOOKUP("tone"):
out->block_dtmf_mode = BLOCK_DTMF_TONE;
break;
case CSH_LOOKUP("random"):
out->block_dtmf_mode = BLOCK_DTMF_RANDOM;
break;
case CSH_LOOKUP("zero"):
out->block_dtmf_mode = BLOCK_DTMF_ZERO;
break;
case CSH_LOOKUP("DTMF"):
case CSH_LOOKUP("dtmf"):
out->block_dtmf_mode = BLOCK_DTMF_DTMF;
break;
default:
ilog(LOG_WARN, "Unknown 'DTMF-security' flag encountered: '" STR_FORMAT "'",
STR_FMT(&s));
}
call_ng_parse_block_mode(&s, &out->block_dtmf_mode);
break;
case CSH_LOOKUP("DTMF-security-trigger"):
case CSH_LOOKUP("dtmf-security-trigger"):
case CSH_LOOKUP("DTMF security trigger"):
case CSH_LOOKUP("dtmf security trigger"):
call_ng_parse_block_mode(&s, &out->block_dtmf_mode_trigger);
break;
case CSH_LOOKUP("DTMF-security-trigger-end"):
case CSH_LOOKUP("dtmf-security-trigger-end"):
case CSH_LOOKUP("DTMF security trigger end"):
case CSH_LOOKUP("dtmf security trigger end"):
call_ng_parse_block_mode(&s, &out->block_dtmf_mode_trigger_end);
break;
case CSH_LOOKUP("delay-buffer"):
case CSH_LOOKUP("delay buffer"):
@ -1371,6 +1410,7 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_
#endif
}
}
static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *input, enum call_opmode opmode) {
call_ng_flags_init(out, opmode);
call_ng_dict_iter(out, input, call_ng_main_flags);
@ -2394,6 +2434,13 @@ static void call_monologue_set_block_mode(struct call_monologue *ml, struct sdp_
ml->dtmf_digit = flags->digit;
}
call_str_cpy(ml->call, &ml->dtmf_trigger, &flags->trigger);
call_str_cpy(ml->call, &ml->dtmf_trigger_end, &flags->trigger_end);
ml->block_dtmf_trigger = flags->block_dtmf_mode_trigger;
ml->dtmf_trigger_match = 0;
ml->dtmf_trigger_digits = flags->trigger_end_digits;
ml->block_dtmf_trigger_end_ms = flags->trigger_end_ms;
codec_update_all_handlers(ml);
}
const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) {
@ -2407,6 +2454,9 @@ const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) {
return errstr;
enum block_dtmf_mode mode = BLOCK_DTMF_DROP;
// special case default: if there's a trigger, default block mode is none
if (flags.block_dtmf_mode_trigger || flags.trigger.len)
mode = BLOCK_DTMF_OFF;
if (flags.block_dtmf_mode)
mode = flags.block_dtmf_mode;
@ -2420,7 +2470,7 @@ const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) {
call->block_dtmf = mode;
}
if (is_dtmf_replace_mode(mode) || flags.delay_buffer >= 0) {
if (is_dtmf_replace_mode(mode) || flags.delay_buffer >= 0 || flags.trigger.len) {
if (monologue)
call_monologue_set_block_mode(monologue, &flags);
else {


+ 44
- 2
daemon/codec.c View File

@ -32,6 +32,12 @@ struct mqtt_timer {
struct call *call;
struct call_media *media;
};
struct timer_callback {
struct codec_timer ct;
void (*func)(struct call *, void *);
struct call *call;
void *ptr;
};
typedef void (*raw_input_func_t)(struct media_packet *mp, unsigned int);
@ -1006,6 +1012,10 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink,
bool do_pcm_dtmf_blocking = is_pcm_dtmf_block_mode(dtmf_block_mode);
bool do_dtmf_blocking = is_dtmf_replace_mode(dtmf_block_mode);
bool do_dtmf_detect = false;
if (receiver->monologue->dtmf_trigger.len)
do_dtmf_detect = true;
// do we have to force everything through the transcoding engine even if codecs match?
bool force_transcoding = do_pcm_dtmf_blocking || do_dtmf_blocking;
if (sink->monologue->inject_dtmf)
@ -1136,13 +1146,19 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink,
if (!recv_dtmf_pt)
pcm_dtmf_detect = true;
}
else if (do_dtmf_blocking) {
else if (do_dtmf_blocking && !pcm_dtmf_detect) {
// we only need the DSP if there's no DTMF payload present, as otherwise
// we expect DTMF event packets
if (!recv_dtmf_pt)
pcm_dtmf_detect = true;
}
// same logic if we need to detect DTMF
if (do_dtmf_detect && !pcm_dtmf_detect) {
if (!recv_dtmf_pt)
pcm_dtmf_detect = true;
}
if (pcm_dtmf_detect) {
if (sink_dtmf_pt)
ilogs(codec, LOG_DEBUG, "Enabling PCM DTMF detection from " STR_FORMAT
@ -1906,7 +1922,7 @@ static int packet_dtmf_event(struct codec_ssrc_handler *ch, struct codec_ssrc_ha
LOCK(&mp->media->dtmf_lock);
if (mp->media->dtmf_ts != packet->ts) { // ignore already processed events
int ret = dtmf_event_packet(mp, packet->payload, ch->encoder_format.clockrate, packet->ts);
int ret = dtmf_event_packet(mp, packet->payload, ch->handler->source_pt.clock_rate, packet->ts);
if (G_UNLIKELY(ret == -1)) // error
return -1;
if (ret == 1) {
@ -4640,6 +4656,32 @@ bool codec_store_is_full_answer(const struct codec_store *src, const struct code
return true;
}
static void __codec_timer_callback_free(void *p) {
struct timer_callback *cb = p;
if (cb->call)
obj_put(cb->call);
}
static void __codec_timer_callback_fire(struct codec_timer *ct) {
struct timer_callback *cb = (void *) ct;
log_info_call(cb->call);
cb->func(cb->call, cb->ptr);
codec_timer_stop(&ct);
log_info_clear();
}
void codec_timer_callback(struct call *c, void (*func)(struct call *, void *), void *p, uint64_t delay) {
struct timer_callback *cb = obj_alloc0("codec_timer_callback", sizeof(*cb), __codec_timer_callback_free);
cb->ct.tt_obj.tt = &codec_timers_thread;
cb->call = obj_get(c);
cb->func = func;
cb->ptr = p;
cb->ct.func = __codec_timer_callback_fire;
cb->ct.next = rtpe_now;
timeval_add_usec(&cb->ct.next, delay);
timerthread_obj_schedule_abs(&cb->ct.tt_obj, &cb->ct.next);
}
static void codec_timers_run(void *p) {
struct codec_timer *ct = p;
ct->func(ct);


+ 114
- 3
daemon/dtmf.c View File

@ -151,12 +151,123 @@ static void dtmf_end_event(struct call_media *media, unsigned int event, unsigne
g_string_free(buf, TRUE);
}
static void dtmf_code_event(struct call_media *media, char event, uint64_t ts) {
static void dtmf_trigger_set(struct call *c, void *mlp) {
struct call_monologue *ml = mlp;
rwlock_lock_w(&c->master_lock);
ilog(LOG_INFO, "Setting DTMF block mode to %i and setting new trigger to '" STR_FORMAT "'",
ml->block_dtmf_trigger, STR_FMT(&ml->dtmf_trigger_end));
ml->block_dtmf = ml->block_dtmf_trigger;
// switch trigger to end trigger
ml->block_dtmf_trigger = ml->block_dtmf_trigger_end;
ml->dtmf_trigger = ml->dtmf_trigger_end;
ml->dtmf_trigger_end = STR_NULL;
ml->dtmf_trigger_digits *= -1; // negative means it's active
codec_update_all_handlers(ml);
rwlock_unlock_w(&c->master_lock);
}
static void dtmf_trigger_unset(struct call *c, void *mlp) {
struct call_monologue *ml = mlp;
ilog(LOG_INFO, "Setting DTMF block mode to %i", ml->block_dtmf_trigger_end);
rwlock_lock_w(&c->master_lock);
ml->block_dtmf = ml->block_dtmf_trigger_end;
ml->dtmf_trigger = STR_NULL;
codec_update_all_handlers(ml);
rwlock_unlock_w(&c->master_lock);
}
static void dtmf_check_trigger(struct call_media *media, char event, uint64_t ts, int clockrate) {
struct call_monologue *ml = media->monologue;
if (!clockrate)
clockrate = 8000;
if (!ml->dtmf_trigger.len) // do we have a trigger?
return;
if (ml->dtmf_trigger_match >= ml->dtmf_trigger.len) // is the trigger done already?
return;
// check delay from previous event
if (media->dtmf_start) {
uint32_t ts_diff = ts - media->dtmf_start;
uint64_t ts_diff_ms = ts_diff * 1000 / clockrate;
if (ts_diff_ms > rtpe_config.dtmf_digit_delay) {
// delay too long: restart event trigger
ml->dtmf_trigger_match = 0;
}
}
if (ml->dtmf_trigger_digits < 0) {
// end trigger is active
ml->dtmf_trigger_digits++;
if (ml->dtmf_trigger_digits == 0) {
// got all digits
codec_timer_callback(ml->call, dtmf_trigger_unset, ml, 0);
}
}
// is the new event a match?
if (ml->dtmf_trigger.s[ml->dtmf_trigger_match] == event) {
ml->dtmf_trigger_match++;
if (ml->dtmf_trigger_match == ml->dtmf_trigger.len) {
// trigger is finished
ml->dtmf_trigger_match = 0; // reset
ilog(LOG_INFO, "DTMF trigger ('" STR_FORMAT "') matched, setting block mode to %i",
STR_FMT(&ml->dtmf_trigger), ml->block_dtmf_trigger);
// We only hold a read-lock on the call here and cannot switch to a write-lock
// easily, which is needed to reset the codec handlers. Therefore we do this
// asynchronously:
codec_timer_callback(ml->call, dtmf_trigger_set, ml, 0);
// set up unblock triggers
if (ml->block_dtmf_trigger_end_ms)
codec_timer_callback(ml->call, dtmf_trigger_unset, ml,
ml->block_dtmf_trigger_end_ms * 1000);
}
return;
}
// can we do a partial match?
for (size_t off = 1; off < ml->dtmf_trigger_match; off++) {
// look for repeating prefix: trigger "ABCABD", matched 5, prefix at offset 3: [AB]C[AB]
if (memcmp(ml->dtmf_trigger.s + off, ml->dtmf_trigger.s, ml->dtmf_trigger_match - off))
continue;
// is the new event a match?
unsigned int next_match_idx = ml->dtmf_trigger.len - off;
if (ml->dtmf_trigger.s[next_match_idx] == event) {
// got a partial match
ml->dtmf_trigger_match = next_match_idx;
return;
}
}
// no partial match... reset completely
if (event == ml->dtmf_trigger.s[0])
ml->dtmf_trigger_match = 1;
else
ml->dtmf_trigger_match = 0;
}
static void dtmf_code_event(struct call_media *media, char event, uint64_t ts, int clockrate) {
if (media->dtmf_code == event) // old/ongoing event
return;
// start of new event
// check trigger before setting new dtmf_start
dtmf_check_trigger(media, event, ts, clockrate);
media->dtmf_code = event;
media->dtmf_start = ts;
media->dtmf_end = 0;
@ -204,7 +315,7 @@ int dtmf_event_packet(struct media_packet *mp, str *payload, int clockrate, uint
dtmf->event, dtmf->volume, dtmf->end, ntohs(dtmf->duration));
if (!dtmf->end) {
dtmf_code_event(mp->media, dtmf_code_to_char(dtmf->event), ts);
dtmf_code_event(mp->media, dtmf_code_to_char(dtmf->event), ts, clockrate);
return 0;
}
@ -251,7 +362,7 @@ void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_e
new_event->code, new_event->volume, duration);
int code = dtmf_code_from_char(new_event->code); // for validation
if (code != -1)
dtmf_code_event(media, (char) new_event->code, ts);
dtmf_code_event(media, (char) new_event->code, ts, clockrate);
}
}


+ 2
- 0
daemon/main.c View File

@ -97,6 +97,7 @@ struct rtpengine_config rtpe_config = {
.mqtt_port = 1883,
.mqtt_keepalive = 30,
.mqtt_publish_interval = 5000,
.dtmf_digit_delay = 2500,
.common = {
.log_levels = {
[log_level_index_internals] = -1,
@ -491,6 +492,7 @@ static void options(int *argc, char ***argv) {
{ "dtmf-log-dest", 0,0, G_OPTION_ARG_STRING, &dtmf_udp_ep, "Destination address for DTMF logging via UDP", "IP46|HOSTNAME:PORT" },
{ "dtmf-log-ng-tcp", 0,0, G_OPTION_ARG_NONE, &rtpe_config.dtmf_via_ng, "DTMF logging via TCP NG protocol", NULL },
{ "dtmf-no-suppress", 0,0,G_OPTION_ARG_NONE, &rtpe_config.dtmf_no_suppress, "Disable audio suppression during DTMF events", NULL },
{ "dtmf-digit-delay", 0,0,G_OPTION_ARG_INT, &rtpe_config.dtmf_digit_delay, "Delay in ms between DTMF digit for trigger detection", NULL },
#endif
{ "log-format", 0, 0, G_OPTION_ARG_STRING, &log_format, "Log prefix format", "default|parsable"},
{ "xmlrpc-format",'x', 0, G_OPTION_ARG_INT, &rtpe_config.fmt, "XMLRPC timeout request format to use. 0: SEMS DI, 1: call-id only, 2: Kamailio", "INT" },


+ 7
- 0
include/call.h View File

@ -470,6 +470,13 @@ struct call_monologue {
unsigned int tone_freq;
unsigned int tone_vol;
char dtmf_digit;
str dtmf_trigger;
unsigned int dtmf_trigger_match;
enum block_dtmf_mode block_dtmf_trigger;
str dtmf_trigger_end;
int dtmf_trigger_digits;
enum block_dtmf_mode block_dtmf_trigger_end;
unsigned int block_dtmf_trigger_end_ms;
unsigned int block_media:1;
unsigned int silence_media:1;


+ 6
- 0
include/call_interfaces.h View File

@ -81,6 +81,12 @@ struct sdp_ng_flags {
int frequency;
int volume;
char digit;
str trigger;
enum block_dtmf_mode block_dtmf_mode_trigger;
str trigger_end;
enum block_dtmf_mode block_dtmf_mode_trigger_end;
int trigger_end_digits;
int trigger_end_ms;
unsigned int asymmetric:1,
protocol_accept:1,
no_redis_update:1,


+ 1
- 0
include/codec.h View File

@ -79,6 +79,7 @@ void codecs_init(void);
void codecs_cleanup(void);
void codec_timers_loop(void *);
void rtcp_timer_stop(struct rtcp_timer **);
void codec_timer_callback(struct call *, void (*)(struct call *, void *), void *, uint64_t delay);
void mqtt_timer_stop(struct mqtt_timer **);
void mqtt_timer_start(struct mqtt_timer **mqtp, struct call *call, struct call_media *media);


+ 1
- 0
include/main.h View File

@ -100,6 +100,7 @@ struct rtpengine_config {
endpoint_t dtmf_udp_ep;
int dtmf_via_ng;
int dtmf_no_suppress;
int dtmf_digit_delay;
enum endpoint_learning endpoint_learning;
int jb_length;
int jb_clock_drift;


+ 8
- 2
utils/rtpengine-ng-client View File

@ -105,17 +105,23 @@ GetOptions(
'to-label=s' => \$options{'to-label'},
'from-tags=s@' => \$options{'from-tags'},
'DTMF-security=s' => \$options{'DTMF-security'},
'DTMF-security-trigger=s' => \$options{'DTMF-security-trigger'},
'DTMF-security-trigger-end=s' => \$options{'DTMF-security-trigger-end'},
'delay-buffer=i' => \$options{'delay-buffer'},
'frequency=i' => \$options{'frequency'},
'volume=i' => \$options{'volume'},
'digit=s' => \$options{'digit'},
'trigger=s' => \$options{'trigger'},
'trigger-end=s' => \$options{'trigger-end'},
'trigger-end-digits=i' => \$options{'trigger-end-digits'},
'trigger-end-time=i' => \$options{'trigger-end-time'},
) or die;
my $cmd = shift(@ARGV) or die;
my %packet = (command => $cmd);
for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file,db-id,code,DTLS-fingerprint,ICE-lite,media echo,label,set-label,from-label,to-label,DTMF-security,digit')) {
for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,ICE,address family,DTLS,via-branch,media address,ptime,xmlrpc-callback,metadata,address,file,db-id,code,DTLS-fingerprint,ICE-lite,media echo,label,set-label,from-label,to-label,DTMF-security,digit,DTMF-security-trigger,DTMF-security-trigger-end,trigger,trigger-end')) {
if (defined($options{$x})) {
if (!$options{json}) {
$packet{$x} = \$options{$x};
@ -125,7 +131,7 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,
}
}
}
for my $x (split(/,/, 'TOS,delete-delay,delay-buffer,volume,frequency')) {
for my $x (split(/,/, 'TOS,delete-delay,delay-buffer,volume,frequency,trigger-end-time,trigger-end-digits')) {
defined($options{$x}) and $packet{$x} = $options{$x};
}
for my $x (split(/,/, 'trust address,symmetric,asymmetric,unidirectional,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,full rtcp attribute,loop protect,record call,always transcode,all,SIPREC,pad crypto,generate mid,fragment,original sendrecv,symmetric codecs,asymmetric codecs,inject DTMF,detect DTMF,generate RTCP,single codec,reorder codecs,pierce NAT,SIP-source-address,allow transcoding')) {


Loading…
Cancel
Save