Browse Source

MT#59038 generalise DTMF trigger handling

Change-Id: I08ce67529f79021d04de9e7353f8bc649d9edfd7
pull/1802/head
Richard Fuchs 2 years ago
parent
commit
337c73ecc8
5 changed files with 243 additions and 75 deletions
  1. +2
    -3
      daemon/call_interfaces.c
  2. +1
    -1
      daemon/codec.c
  3. +196
    -61
      daemon/dtmf.c
  4. +13
    -10
      include/call.h
  5. +31
    -0
      include/dtmf.h

+ 2
- 3
daemon/call_interfaces.c View File

@ -2958,10 +2958,9 @@ static void call_monologue_set_block_mode(struct call_monologue *ml, sdp_ng_flag
ml->dtmf_digit = flags->digit; 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);
dtmf_trigger_set(ml, DTMF_TRIGGER_BLOCK, &flags->trigger, false);
dtmf_trigger_set(ml, DTMF_TRIGGER_UNBLOCK, &flags->trigger_end, false);
ml->block_dtmf_trigger = flags->block_dtmf_mode_trigger; ml->block_dtmf_trigger = flags->block_dtmf_mode_trigger;
ml->dtmf_trigger_match = 0;
ml->dtmf_trigger_digits = flags->trigger_end_digits; ml->dtmf_trigger_digits = flags->trigger_end_digits;
ml->block_dtmf_trigger_end_ms = flags->trigger_end_ms; ml->block_dtmf_trigger_end_ms = flags->trigger_end_ms;
ml->dtmf_delay = flags->dtmf_delay; ml->dtmf_delay = flags->dtmf_delay;


+ 1
- 1
daemon/codec.c View File

@ -1131,7 +1131,7 @@ void __codec_handlers_update(struct call_media *receiver, struct call_media *sin
do_pcm_dtmf_blocking = true; do_pcm_dtmf_blocking = true;
bool do_dtmf_detect = false; bool do_dtmf_detect = false;
if (monologue->dtmf_trigger.len)
if (monologue->num_dtmf_triggers)
do_dtmf_detect = true; do_dtmf_detect = true;
if (a.flags && a.flags->inject_dtmf) if (a.flags && a.flags->inject_dtmf)


+ 196
- 61
daemon/dtmf.c View File

@ -15,6 +15,24 @@
static socket_t dtmf_log_sock; static socket_t dtmf_log_sock;
static void dtmf_trigger_block_action(struct call_media *, struct call_monologue *);
static void dtmf_trigger_block_digit(struct call_media *, struct call_monologue *);
static void dtmf_trigger_unblock_action(struct call_media *, struct call_monologue *);
struct dtmf_trigger_action dtmf_trigger_actions[__NUM_DTMF_TRIGGERS] = {
[DTMF_TRIGGER_BLOCK] = {
.matched = dtmf_trigger_block_action,
.repeatable = false,
.digit = dtmf_trigger_block_digit,
},
[DTMF_TRIGGER_UNBLOCK] = {
.matched = dtmf_trigger_unblock_action,
.repeatable = false,
},
};
bool dtmf_init(void) { bool dtmf_init(void) {
ilog(LOG_DEBUG, "log dtmf over ng %d", rtpe_config.dtmf_via_ng); ilog(LOG_DEBUG, "log dtmf over ng %d", rtpe_config.dtmf_via_ng);
ilog(LOG_DEBUG, "no log injected dtmf %d", rtpe_config.dtmf_no_log_injects); ilog(LOG_DEBUG, "no log injected dtmf %d", rtpe_config.dtmf_no_log_injects);
@ -182,27 +200,97 @@ static void dtmf_end_event(struct call_media *media, unsigned int event, unsigne
g_string_free(buf, TRUE); g_string_free(buf, TRUE);
} }
static void dtmf_trigger_set(call_t *c, void *mlp) {
static struct dtmf_trigger_state *dtmf_get_trigger_state(struct call_monologue *ml, enum dtmf_trigger_type type)
{
// Look up entry in ->dtmf_triger_state. If trigger is set already, its index
// is stored in dtmf_trigger_index. If it isn't, grab a new entry.
// The index must be less then num_triggers and the type of the entry pointed
// to by the index must match the requested type. Everything else is invalid
// and requires a new entry.
// This keeps all set triggers at the front of the list and doesn't pollute
// the list with unset entries, while still allowing quick lookup.
// trigger_state[trigger_index[type]].type == type
// trigger_index[trigger_state[idx].type] == idx
unsigned int idx = ml->dtmf_trigger_index[type];
if (idx >= ml->num_dtmf_triggers)
return NULL;
struct dtmf_trigger_state *state = &ml->dtmf_trigger_state[idx];
if (state->type != type)
return NULL;
return state;
}
void dtmf_trigger_set(struct call_monologue *ml, enum dtmf_trigger_type trigger_type,
const str *s, bool inactive)
{
struct dtmf_trigger_state *state = dtmf_get_trigger_state(ml, trigger_type);
if (!state) {
// Trigger doesn't exist yet. Do we actually want to set a trigger?
if (s->len == 0)
return; // nothing to do
// fill in a new entry
assert(ml->num_dtmf_triggers < __NUM_DTMF_TRIGGERS);
state = &ml->dtmf_trigger_state[ml->num_dtmf_triggers];
ml->dtmf_trigger_index[trigger_type] = ml->num_dtmf_triggers;
state->type = trigger_type;
ml->num_dtmf_triggers++;
// Trigger is set below
}
else {
// Trigger is already set. Do we want to delete it?
if (s->len == 0) {
// Shift down remaining items and adjust indexes
unsigned int idx = state - ml->dtmf_trigger_state;
for (unsigned int i = idx; i < ml->num_dtmf_triggers - 1; i++) {
assert(ml->dtmf_trigger_index[ml->dtmf_trigger_state[i].type] == i);
assert(ml->dtmf_trigger_index[ml->dtmf_trigger_state[i + 1].type] == i + 1);
ml->dtmf_trigger_state[i] = ml->dtmf_trigger_state[i + 1];
ml->dtmf_trigger_index[ml->dtmf_trigger_state[i].type] = i;
}
ml->num_dtmf_triggers--;
return;
}
// Replace existing trigger below
}
call_str_cpy(ml->call, &state->trigger, s);
state->matched = 0;
state->inactive = inactive;
}
static void dtmf_trigger_set_block(call_t *c, void *mlp) {
struct call_monologue *ml = mlp; struct call_monologue *ml = mlp;
rwlock_lock_w(&c->master_lock); 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));
struct dtmf_trigger_state *end_trigger = dtmf_get_trigger_state(ml, DTMF_TRIGGER_UNBLOCK);
if (end_trigger)
ilog(LOG_INFO, "Setting DTMF block mode to %i and enabling end trigger '" STR_FORMAT "'",
ml->block_dtmf_trigger, STR_FMT(&end_trigger->trigger));
else
ilog(LOG_INFO, "Setting DTMF block mode to %i",
ml->block_dtmf_trigger);
ml->block_dtmf = ml->block_dtmf_trigger; 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
// enable end trigger
if (end_trigger) {
end_trigger->inactive = false;
ml->dtmf_trigger_digits *= -1; // negative means it's active
}
codec_update_all_handlers(ml); codec_update_all_handlers(ml);
rwlock_unlock_w(&c->master_lock); rwlock_unlock_w(&c->master_lock);
} }
static void dtmf_trigger_unset(call_t *c, void *mlp) {
static void dtmf_trigger_unset_block(call_t *c, void *mlp) {
struct call_monologue *ml = mlp; struct call_monologue *ml = mlp;
ilog(LOG_INFO, "Setting DTMF block mode to %i", ml->block_dtmf_trigger_end); ilog(LOG_INFO, "Setting DTMF block mode to %i", ml->block_dtmf_trigger_end);
@ -210,7 +298,7 @@ static void dtmf_trigger_unset(call_t *c, void *mlp) {
rwlock_lock_w(&c->master_lock); rwlock_lock_w(&c->master_lock);
ml->block_dtmf = ml->block_dtmf_trigger_end; ml->block_dtmf = ml->block_dtmf_trigger_end;
ml->dtmf_trigger = STR_NULL;
dtmf_trigger_set(ml, DTMF_TRIGGER_BLOCK, NULL, false);
codec_update_all_handlers(ml); codec_update_all_handlers(ml);
@ -218,79 +306,126 @@ static void dtmf_trigger_unset(call_t *c, void *mlp) {
} }
// dtmf_lock must be held // dtmf_lock must be held
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?
static void dtmf_trigger_block_digit(struct call_media *media, struct call_monologue *ml) {
if (ml->dtmf_trigger_digits >= 0)
return; return;
// check delay from previous event
// dtmf_lock already held
struct dtmf_event *last_ev = t_queue_peek_tail(&media->dtmf_recv);
if (last_ev) {
uint32_t ts_diff = ts - last_ev->ts;
uint64_t ts_diff_ms = (uint64_t) ts_diff * 1000 / clockrate;
if (ts_diff_ms > rtpe_config.dtmf_digit_delay) {
// delay too long: restart event trigger
ml->dtmf_trigger_match = 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_block, ml, 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);
}
}
// dtmf_lock must be held
static void dtmf_trigger_block_action(struct call_media *media, struct call_monologue *ml) {
ilog(LOG_INFO, "DTMF trigger matched, setting block mode to %i",
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_block, ml, 0);
// set up unblock triggers
if (ml->block_dtmf_trigger_end_ms)
codec_timer_callback(ml->call, dtmf_trigger_unset_block, ml,
ml->block_dtmf_trigger_end_ms * 1000);
}
// dtmf_lock must be held
static void dtmf_trigger_unblock_action(struct call_media *media, struct call_monologue *ml) {
ilog(LOG_INFO, "DTMF trigger matched, setting block mode to %i",
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_unset_block, ml, 0);
}
// dtmf_lock must be held
static bool dtmf_check_1_trigger(struct call_media *media, struct call_monologue *ml,
char event, uint64_t ts, int clockrate, unsigned int i)
{
struct dtmf_trigger_state *state = &ml->dtmf_trigger_state[i];
struct dtmf_trigger_action *action = &dtmf_trigger_actions[state->type];
if (state->matched >= state->trigger.len) // is the trigger done already?
return false;
if (action->digit)
action->digit(media, ml);
// is the new event a match? // 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) {
if (state->trigger.s[state->matched] == event) {
state->matched++;
if (state->matched == state->trigger.len) {
// trigger is finished // trigger is finished
ml->dtmf_trigger_match = 0; // reset
state->matched = 0; // reset
ilog(LOG_INFO, "DTMF trigger ('" STR_FORMAT "') matched, setting block mode to %i",
STR_FMT(&ml->dtmf_trigger), ml->block_dtmf_trigger);
action->matched(media, ml);
// 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);
if (!action->repeatable)
dtmf_trigger_set(ml, state->type, NULL, false);
// 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 true;
} }
return;
return false;
} }
// can we do a partial match? // can we do a partial match?
for (size_t off = 1; off < ml->dtmf_trigger_match; off++) {
for (size_t off = 1; off < state->matched; off++) {
// look for repeating prefix: trigger "ABCABD", matched 5, prefix at offset 3: [AB]C[AB] // 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))
if (memcmp(state->trigger.s + off, state->trigger.s, state->matched - off))
continue; continue;
// is the new event a match? // 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) {
unsigned int next_match_idx = state->trigger.len - off;
if (state->trigger.s[next_match_idx] == event) {
// got a partial match // got a partial match
ml->dtmf_trigger_match = next_match_idx;
return;
state->matched = next_match_idx;
return false;
} }
} }
// no partial match... reset completely // no partial match... reset completely
if (event == ml->dtmf_trigger.s[0])
ml->dtmf_trigger_match = 1;
if (event == state->trigger.s[0])
state->matched = 1;
else else
ml->dtmf_trigger_match = 0;
state->matched = 0;
return false;
}
// dtmf_lock must be held
static void dtmf_check_trigger(struct call_media *media, char event, uint64_t ts, int clockrate) {
if (!clockrate)
clockrate = 8000;
struct call_monologue *ml = media->monologue;
if (!ml->num_dtmf_triggers)
return; // nothing to do
// check delay from previous event
bool reset = false;
struct dtmf_event *last_ev = t_queue_peek_tail(&media->dtmf_recv);
if (last_ev) {
uint32_t ts_diff = ts - last_ev->ts;
uint64_t ts_diff_ms = (uint64_t) ts_diff * 1000 / clockrate;
if (ts_diff_ms > rtpe_config.dtmf_digit_delay) {
// delay too long: restart event trigger
reset = true;
}
}
for (unsigned int i = 0; i < ml->num_dtmf_triggers; i++) {
if (reset)
ml->dtmf_trigger_state[i].matched = 0;
if (dtmf_check_1_trigger(media, ml, event, ts, clockrate, i))
break; // triggers should be unique, so only act on one
}
} }
// media->dtmf_lock must be held // media->dtmf_lock must be held


+ 13
- 10
include/call.h View File

@ -268,6 +268,7 @@ enum block_dtmf_mode {
#include "crypto.h" #include "crypto.h"
#include "dtls.h" #include "dtls.h"
#include "sdp.h" #include "sdp.h"
#include "dtmf.h"
struct control_stream; struct control_stream;
@ -560,19 +561,21 @@ struct call_monologue {
str metadata; str metadata;
struct janus_session *janus_session; struct janus_session *janus_session;
// DTMF triggers, MUST be set via dtmf_trigger_set() only
struct dtmf_trigger_state dtmf_trigger_state[__NUM_DTMF_TRIGGERS];
uint8_t dtmf_trigger_index[__NUM_DTMF_TRIGGERS];
unsigned int num_dtmf_triggers;
unsigned int dtmf_delay;
// DTMF blocking/replacement stuff: // DTMF blocking/replacement stuff:
enum block_dtmf_mode block_dtmf;
enum block_dtmf_mode block_dtmf; // current block mode
GArray *tone_freqs; GArray *tone_freqs;
unsigned int tone_vol; 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 dtmf_delay;
char dtmf_digit; // replacement digit
enum block_dtmf_mode block_dtmf_trigger; // to enable when trigger detected
int dtmf_trigger_digits; // unblock after this many digits
enum block_dtmf_mode block_dtmf_trigger_end; // to enable when trigger detected
unsigned int block_dtmf_trigger_end_ms; // unblock after this many ms
/* carry `sdp_session` attributes into resulting call monologue SDP */ /* carry `sdp_session` attributes into resulting call monologue SDP */
sdp_attr_q sdp_attributes; sdp_attr_q sdp_attributes;


+ 31
- 0
include/dtmf.h View File

@ -8,6 +8,33 @@
#include "str.h" #include "str.h"
#include "socket.h" #include "socket.h"
struct call_media;
struct call_monologue;
enum dtmf_trigger_type {
DTMF_TRIGGER_BLOCK = 0,
DTMF_TRIGGER_UNBLOCK,
__NUM_DTMF_TRIGGERS,
};
struct dtmf_trigger_state {
enum dtmf_trigger_type type; // points to matching action
str trigger; // string to look for
unsigned int matched; // how many digits matched so far
bool inactive; // ignore even if set
};
struct dtmf_trigger_action {
void (*matched)(struct call_media *, struct call_monologue *); // run when the trigger is found
bool repeatable; // reset after a match or not
void (*digit)(struct call_media *, struct call_monologue *); // run when any digit is found
};
#include "call.h" #include "call.h"
struct media_packet; struct media_packet;
@ -39,5 +66,9 @@ bool is_pcm_dtmf_block_mode(enum block_dtmf_mode mode);
bool is_dtmf_replace_mode(enum block_dtmf_mode mode); bool is_dtmf_replace_mode(enum block_dtmf_mode mode);
struct dtmf_event *is_in_dtmf_event(dtmf_event_q *, uint32_t ts, int clockrate, unsigned int head, struct dtmf_event *is_in_dtmf_event(dtmf_event_q *, uint32_t ts, int clockrate, unsigned int head,
unsigned int trail); unsigned int trail);
void dtmf_trigger_set(struct call_monologue *ml, enum dtmf_trigger_type,
const str *s, bool inactive);
extern struct dtmf_trigger_action dtmf_trigger_actions[__NUM_DTMF_TRIGGERS];
#endif #endif

Loading…
Cancel
Save