|
|
|
@ -15,6 +15,24 @@ |
|
|
|
|
|
|
|
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) { |
|
|
|
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); |
|
|
|
@ -182,27 +200,97 @@ static void dtmf_end_event(struct call_media *media, unsigned int event, unsigne |
|
|
|
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; |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
// 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); |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
@ -218,79 +306,126 @@ static void dtmf_trigger_unset(call_t *c, void *mlp) { |
|
|
|
} |
|
|
|
|
|
|
|
// 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; |
|
|
|
|
|
|
|
// 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? |
|
|
|
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 |
|
|
|
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? |
|
|
|
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] |
|
|
|
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; |
|
|
|
// 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 |
|
|
|
ml->dtmf_trigger_match = next_match_idx; |
|
|
|
return; |
|
|
|
state->matched = next_match_idx; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
// 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 |
|
|
|
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 |
|
|
|
|