From 38f97c2df8b4cc89909419e550487446f64f125d Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Mon, 27 Sep 2021 13:32:35 -0400 Subject: [PATCH] TT#136956 support DTMF silence replacement Change-Id: If693800a955a9ddf7245da0082426ae609deb407 --- README.md | 22 ++++++++ daemon/call_interfaces.c | 90 ++++++++++++++++++++++++++++-- daemon/codec.c | 70 +++++++++++++++++++----- daemon/dtmf.c | 112 +++++++++++++++++++++++++++++--------- include/call.h | 7 +++ include/call_interfaces.h | 3 + include/dtmf.h | 6 +- utils/rtpengine-ng-client | 9 ++- 8 files changed, 272 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 576600da2..dacec9b07 100644 --- a/README.md +++ b/README.md @@ -856,6 +856,12 @@ Optionally included keys are: injection via the `play DTMF` control message. See `play DTMF` below for additional information. + - `detect DTMF` + + When present in a message that sets up codec handlers, enables + the DSP to detect in-band DTMF audio tones even when it + wouldn't otherwise be necessary. + - `generate RTCP` Identical to setting `generate RTCP = on`. @@ -1513,6 +1519,22 @@ Optionally included keys are: Enables media echo towards both the sender and the receiver of this message. +* `DTMF-security` + + Used in the `block DTMF` message to select the DTMF blocking mode. The + default mode is `drop` which simply drops DTMF event packets. The other + supported mode is `silence` which replaces DTMF events with silence + audio. + +* `delay-buffer` + + Takes an integer as value. When set to non-zero, enables the delay + buffer when setting up codec handlers. The delay buffer delays all + media by the given number of milliseconds before passing it on. Once + the delay buffer is configured, it must explicitly be disabled again by + setting this value to zero. The delay buffer setting is honoured in all + messages that set up codec handlers, such as `block DTMF`. + An example of a complete `offer` request dictionary could be (SDP body abbreviated): { "command": "offer", "call-id": "cfBXzDSZqhYNcXM", "from-tag": "mS9rSAn0Cr", diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 049785037..0c17c6f67 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -29,6 +29,7 @@ #include "load.h" #include "media_player.h" #include "dtmf.h" +#include "codec.h" struct fragment_key { @@ -866,8 +867,13 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { out->allow_transcoding = 1; break; case CSH_LOOKUP("inject-DTMF"): + case CSH_LOOKUP("inject-dtmf"): out->inject_dtmf = 1; break; + case CSH_LOOKUP("detect-DTMF"): + case CSH_LOOKUP("detect-dtmf"): + out->detect_dtmf = 1; + break; case CSH_LOOKUP("pad-crypto"): out->sdes_pad = 1; break; @@ -951,6 +957,7 @@ void call_ng_flags_init(struct sdp_ng_flags *out, enum call_opmode opmode) { out->dtls_reverse_passive = dtls_passive_def; out->el_option = rtpe_config.endpoint_learning; out->tos = 256; + out->delay_buffer = -1; } static void call_ng_dict_iter(struct sdp_ng_flags *out, bencode_item_t *input, @@ -1314,6 +1321,26 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_ case CSH_LOOKUP("t.38"): call_ng_flags_list(out, value, ng_t38_option, NULL); break; + case CSH_LOOKUP("DTMF-security"): + 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; + default: + ilog(LOG_WARN, "Unknown 'DTMF-security' flag encountered: '" STR_FORMAT "'", + STR_FMT(&s)); + } + break; + case CSH_LOOKUP("delay-buffer"): + case CSH_LOOKUP("delay buffer"): + out->delay_buffer = bencode_get_integer_str(value, out->delay_buffer); + break; #endif } } @@ -2323,14 +2350,43 @@ const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { if (errstr) return errstr; + enum block_dtmf_mode mode = BLOCK_DTMF_DROP; + if (flags.block_dtmf_mode) + mode = flags.block_dtmf_mode; + if (monologue) { ilog(LOG_INFO, "Blocking directional DTMF (tag '" STR_FORMAT_M "')", STR_FMT_M(&monologue->tag)); - monologue->block_dtmf = BLOCK_DTMF_DROP; + monologue->block_dtmf = mode; } else { ilog(LOG_INFO, "Blocking DTMF (entire call)"); - call->block_dtmf = BLOCK_DTMF_DROP; + call->block_dtmf = mode; + } + + if (is_pcm_dtmf_block_mode(mode) || flags.delay_buffer >= 0) { + if (monologue) { + if (flags.delay_buffer >= 0) { + for (GList *l = monologue->medias.head; l; l = l->next) { + struct call_media *media = l->data; + media->buffer_delay = flags.delay_buffer; + } + } + monologue->detect_dtmf = flags.detect_dtmf; + codec_update_all_handlers(monologue); + } + else + for (GList *l = call->monologues.head; l; l = l->next) { + struct call_monologue *ml = l->data; + ml->detect_dtmf = flags.detect_dtmf; + if (flags.delay_buffer >= 0) { + for (GList *k = ml->medias.head; k; k = k->next) { + struct call_media *media = k->data; + media->buffer_delay = flags.delay_buffer; + } + } + codec_update_all_handlers(ml); + } } return NULL; @@ -2349,15 +2405,41 @@ const char *call_unblock_dtmf_ng(bencode_item_t *input, bencode_item_t *output) if (monologue) { ilog(LOG_INFO, "Unblocking directional DTMF (tag '" STR_FORMAT_M "')", STR_FMT_M(&monologue->tag)); + enum block_dtmf_mode prev_mode = monologue->block_dtmf; monologue->block_dtmf = BLOCK_DTMF_OFF; + if (is_pcm_dtmf_block_mode(prev_mode) || flags.delay_buffer >= 0) { + if (flags.delay_buffer >= 0) { + for (GList *l = monologue->medias.head; l; l = l->next) { + struct call_media *media = l->data; + media->buffer_delay = flags.delay_buffer; + } + } + monologue->detect_dtmf = flags.detect_dtmf; + codec_update_all_handlers(monologue); + } } else { ilog(LOG_INFO, "Unblocking DTMF (entire call)"); + enum block_dtmf_mode prev_mode = call->block_dtmf; call->block_dtmf = BLOCK_DTMF_OFF; - if (flags.all) { + if (flags.all || is_pcm_dtmf_block_mode(prev_mode) || flags.delay_buffer >= 0) { for (GList *l = call->monologues.head; l; l = l->next) { monologue = l->data; - monologue->block_dtmf = BLOCK_DTMF_OFF; + enum block_dtmf_mode prev_ml_mode = BLOCK_DTMF_OFF; + if (flags.all) { + prev_ml_mode = monologue->block_dtmf; + monologue->block_dtmf = BLOCK_DTMF_OFF; + } + if (flags.delay_buffer >= 0) { + for (GList *k = monologue->medias.head; k; k = k->next) { + struct call_media *media = k->data; + media->buffer_delay = flags.delay_buffer; + } + } + monologue->detect_dtmf = flags.detect_dtmf; + if (is_pcm_dtmf_block_mode(prev_ml_mode) || is_pcm_dtmf_block_mode(prev_mode) + || flags.delay_buffer >= 0) + codec_update_all_handlers(monologue); } } } diff --git a/daemon/codec.c b/daemon/codec.c index 34599ba0a..0f69e53ad 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -36,7 +36,7 @@ struct mqtt_timer { typedef void (*raw_input_func_t)(struct media_packet *mp, unsigned int); static void __buffer_delay_raw(struct delay_buffer *dbuf, - raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate); + raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate, uint32_t); static codec_handler_func handler_func_passthrough; @@ -133,6 +133,7 @@ struct delay_frame { AVFrame *frame; struct media_packet mp; unsigned int clockrate; + uint32_t ts; encoder_input_func_t encoder_func; raw_input_func_t raw_func; struct codec_handler *handler; @@ -984,6 +985,9 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, AUTO_CLEANUP(GHashTable *output_transcoders, __g_hash_table_destroy) = g_hash_table_new(g_direct_hash, g_direct_equal); + enum block_dtmf_mode dtmf_block_mode = dtmf_get_block_mode(NULL, receiver->monologue); + bool do_pcm_dtmf_blocking = is_pcm_dtmf_block_mode(dtmf_block_mode); + for (GList *l = receiver->codecs.codec_prefs.head; l; ) { struct rtp_payload_type *pt = l->data; @@ -1052,6 +1056,10 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, sink_pt = pref_dest_codec; } + // ignore DTMF sink if we're blocking DTMF in PCM replacement mode + if (do_pcm_dtmf_blocking && sink_pt && sink_pt->codec_def && sink_pt->codec_def->dtmf) + sink_pt = NULL; + // still no output? pick the preferred sink codec if (!sink_pt) sink_pt = pref_dest_codec; @@ -1098,6 +1106,15 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, if (receiver->monologue->detect_dtmf) pcm_dtmf_detect = true; + // special mode for DTMF blocking + if (do_pcm_dtmf_blocking) { + sink_dtmf_pt = NULL; // always transcode DTMF to PCM + + // enable DSP if we expect DTMF to be carried as PCM + 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 @@ -1144,7 +1161,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // DTMF if (pcm_dtmf_detect) goto transcode; - if (recv_dtmf_pt && recv_dtmf_pt->for_transcoding && !sink_dtmf_pt) { + if (recv_dtmf_pt && (recv_dtmf_pt->for_transcoding || do_pcm_dtmf_blocking) && !sink_dtmf_pt) { ilogs(codec, LOG_DEBUG, "Transcoding DTMF events to PCM from " STR_FORMAT " to " STR_FORMAT, STR_FMT(&pt->encoding_with_params), @@ -1410,12 +1427,14 @@ static int handler_func_passthrough(struct codec_handler *h, struct media_packet if (!handler_silence_block(h, mp)) return 0; + uint32_t ts = 0; if (mp->rtp) { - codec_calc_jitter(mp->ssrc_in, ntohl(mp->rtp->timestamp), h->source_pt.clock_rate, &mp->tv); + ts = ntohl(mp->rtp->timestamp); + codec_calc_jitter(mp->ssrc_in, ts, h->source_pt.clock_rate, &mp->tv); codec_calc_lost(mp->ssrc_in, ntohs(mp->rtp->seq_num)); } - __buffer_delay_raw(h->delay_buffer, codec_add_raw_packet, mp, h->source_pt.clock_rate); + __buffer_delay_raw(h->delay_buffer, codec_add_raw_packet, mp, h->source_pt.clock_rate, ts); return 0; } @@ -1842,7 +1861,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); + int ret = dtmf_event_packet(mp, packet->payload, ch->encoder_format.clockrate, packet->ts); if (G_UNLIKELY(ret == -1)) // error return -1; if (ret == 1) { @@ -2074,8 +2093,10 @@ static int handler_func_passthrough_ssrc(struct codec_handler *h, struct media_p if (!handler_silence_block(h, mp)) return 0; + uint32_t ts = 0; if (mp->rtp) { - codec_calc_jitter(mp->ssrc_in, ntohl(mp->rtp->timestamp), h->source_pt.clock_rate, &mp->tv); + ts = ntohl(mp->rtp->timestamp); + codec_calc_jitter(mp->ssrc_in, ts, h->source_pt.clock_rate, &mp->tv); codec_calc_lost(mp->ssrc_in, ntohs(mp->rtp->seq_num)); } @@ -2085,7 +2106,7 @@ static int handler_func_passthrough_ssrc(struct codec_handler *h, struct media_p // keep track of other stats here? - __buffer_delay_raw(h->delay_buffer, codec_add_raw_packet, mp, h->source_pt.clock_rate); + __buffer_delay_raw(h->delay_buffer, codec_add_raw_packet, mp, h->source_pt.clock_rate, ts); return 0; } @@ -2116,7 +2137,8 @@ void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, ui struct dtmf_event new_ev = { .code = code, .volume = level, .ts = ts }; ilogs(transcoding, LOG_DEBUG, "DTMF event state change: code %i, volume %i, TS %lu", new_ev.code, new_ev.volume, (unsigned long) ts); - dtmf_dsp_event(&new_ev, &ch->dtmf_state, ch->handler->media, ch->handler->source_pt.clock_rate); + dtmf_dsp_event(&new_ev, &ch->dtmf_state, ch->handler->media, ch->handler->source_pt.clock_rate, + ts + ch->first_ts); // add to queue if we're doing PCM -> DTMF event conversion if (ch->handler && ch->handler->dtmf_payload_type != -1) { @@ -2203,7 +2225,7 @@ static int delay_frame_cmp(const void *A, const void *B, void *ptr) { // consumes frame static void __buffer_delay_frame(struct delay_buffer *dbuf, struct codec_ssrc_handler *ch, - encoder_input_func_t input_func, AVFrame *frame, struct media_packet *mp) + encoder_input_func_t input_func, AVFrame *frame, struct media_packet *mp, uint32_t ts) { if (__buffer_delay_do_direct(dbuf)) { // input now @@ -2215,6 +2237,7 @@ static void __buffer_delay_frame(struct delay_buffer *dbuf, struct codec_ssrc_ha struct delay_frame *dframe = g_slice_alloc0(sizeof(*dframe)); dframe->frame = frame; dframe->encoder_func = input_func; + dframe->ts = ts; dframe->ch = obj_get(&ch->h); media_packet_copy(&dframe->mp, mp); @@ -2226,7 +2249,7 @@ static void __buffer_delay_frame(struct delay_buffer *dbuf, struct codec_ssrc_ha } static void __buffer_delay_raw(struct delay_buffer *dbuf, - raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate) + raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate, uint32_t ts) { if (__buffer_delay_do_direct(dbuf)) { // direct passthrough @@ -2349,6 +2372,25 @@ static void dtx_buffer_stop(struct dtx_buffer **dtxbp) { } +static void delay_frame_manipulate(struct delay_frame *dframe) { + struct call_media *media = dframe->mp.media; + if (!media) + return; + + AVFrame *frame = dframe->frame; + + if (is_in_dtmf_event(media, dframe->ts, frame->sample_rate, media->buffer_delay, media->buffer_delay)) { + enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, media->monologue); + + switch (mode) { + case BLOCK_DTMF_SILENCE: + memset(frame->extended_data[0], 0, frame->linesize[0]); + break; + default: + break; + } + } +} static void __delay_frame_process(struct delay_buffer *dbuf, struct delay_frame *dframe) { if (dframe->mp.rtp) { // adjust output seq num. pushing packets into the delay buffer looks as if they @@ -2361,7 +2403,8 @@ static void __delay_frame_process(struct delay_buffer *dbuf, struct delay_frame struct codec_ssrc_handler *csh = dframe->ch; - if (csh && csh->handler && csh->encoder) { + if (csh && csh->handler && csh->encoder && dframe->encoder_func) { + delay_frame_manipulate(dframe); dframe->encoder_func(csh->encoder, dframe->frame, csh->handler->packet_encoded, csh, &dframe->mp); } @@ -3224,7 +3267,8 @@ static int packet_decoded_common(decoder_t *decoder, AVFrame *frame, void *u1, v if (mp->media_out) ch->encoder->codec_options.amr.cmr = mp->media_out->u.amr.cmr; - __buffer_delay_frame(h->delay_buffer, ch, input_func, frame, mp); + uint32_t ts = mp->rtp ? ntohl(mp->rtp->timestamp) : frame->pts; + __buffer_delay_frame(h->delay_buffer, ch, input_func, frame, mp, ts); frame = NULL; // consumed discard: @@ -3294,7 +3338,7 @@ out: // dummy/stub static void __buffer_delay_raw(struct delay_buffer *dbuf, - raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate) + raw_input_func_t input_func, struct media_packet *mp, unsigned int clockrate, uint32_t ts) { input_func(mp, clockrate); } diff --git a/daemon/dtmf.c b/daemon/dtmf.c index 54c87bb43..9ac1e2015 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -29,6 +29,13 @@ static unsigned int dtmf_volume_from_dsp(int vol) { else return 63; } +static char dtmf_code_to_char(int code) { + static const char codes[] = "0123456789*#ABCD"; + if (code < 0 || code > 15) + return 0; + return codes[code]; +} + static void dtmf_bencode_and_notify(struct call_media *media, unsigned int event, unsigned int volume, unsigned int duration, const endpoint_t *fsin, int clockrate) @@ -119,11 +126,17 @@ bool dtmf_do_logging(void) { } static void dtmf_end_event(struct call_media *media, unsigned int event, unsigned int volume, - unsigned int duration, const endpoint_t *fsin, int clockrate, bool rfc_event) + unsigned int duration, const endpoint_t *fsin, int clockrate, bool rfc_event, uint64_t ts) { if (!clockrate) clockrate = 8000; + media->dtmf_code = 0; + media->dtmf_end = ts; + + if (!dtmf_do_logging()) + return; + GString *buf = dtmf_json_print(media, event, volume, duration, fsin, clockrate); if (_log_facility_dtmf) @@ -138,8 +151,47 @@ 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) { + if (media->dtmf_code == event) // old/ongoing event + return; + + // start of new event + + media->dtmf_code = event; + media->dtmf_start = ts; + media->dtmf_end = 0; +} + + +bool is_in_dtmf_event(struct call_media *media, uint32_t ts, int clockrate, unsigned int head, + unsigned int trail) +{ + if (!clockrate) + clockrate = 8000; + + uint32_t start_ts = ts + head * clockrate / 1000; + uint32_t end_ts = ts - trail * clockrate / 1000; + + if (!media->dtmf_start) + return false; + + if (media->dtmf_code) { + // active event. is it current? + uint32_t start_diff = start_ts - media->dtmf_start; + if (start_diff > clockrate * 10) + return false; // outdated start TS + } + else { + // event has already ended. did it just end now? + uint32_t end_diff = media->dtmf_end - end_ts; + if (end_diff > clockrate * 10) + return false; // bad or old end TS + } + return true; +} + -int dtmf_event_packet(struct media_packet *mp, str *payload, int clockrate) { +int dtmf_event_packet(struct media_packet *mp, str *payload, int clockrate, uint64_t ts) { struct telephone_event_payload *dtmf; if (payload->len < sizeof(*dtmf)) { ilog(LOG_WARN | LOG_FLAG_LIMIT, "Short DTMF event packet (len %zu)", payload->len); @@ -150,19 +202,18 @@ int dtmf_event_packet(struct media_packet *mp, str *payload, int clockrate) { ilog(LOG_DEBUG, "DTMF event packet: event %u, volume %u, end %u, duration %u", dtmf->event, dtmf->volume, dtmf->end, ntohs(dtmf->duration)); - if (!dtmf->end) + if (!dtmf->end) { + dtmf_code_event(mp->media, dtmf_code_to_char(dtmf->event), ts); return 0; + } - if (!dtmf_do_logging()) - return 1; - - dtmf_end_event(mp->media, dtmf->event, dtmf->volume, dtmf->duration, &mp->fsin, clockrate, true); + dtmf_end_event(mp->media, dtmf->event, dtmf->volume, dtmf->duration, &mp->fsin, clockrate, true, ts); return 1; } void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_event_p, - struct call_media *media, int clockrate) + struct call_media *media, int clockrate, uint64_t ts) { // update state tracker regardless of outcome struct dtmf_event cur_event = *cur_event_p; @@ -170,9 +221,15 @@ void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_e if (!media) return; - // we only care for "end" events - if (cur_event.code == 0 || new_event->code != 0) - return; + + bool end_event; + if (cur_event.code != 0 && new_event->code == 0) + end_event = true; + else if (cur_event.code == 0 && new_event->code != 0) + end_event = false; // start of a new code + else + return; // don't care + if (!media->streams.length) return; @@ -181,14 +238,20 @@ void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_e unsigned int duration = cur_event.ts - new_event->ts; - ilog(LOG_DEBUG, "DTMF DSP end event: event %u, volume %u, duration %u", - cur_event.code, cur_event.volume, duration); - - if (!dtmf_do_logging()) - return; + if (end_event) { + ilog(LOG_DEBUG, "DTMF DSP end event: event %u, volume %u, duration %u", + cur_event.code, cur_event.volume, duration); - dtmf_end_event(media, dtmf_code_from_char(cur_event.code), dtmf_volume_from_dsp(cur_event.volume), - duration, &ps->endpoint, clockrate, false); + dtmf_end_event(media, dtmf_code_from_char(cur_event.code), dtmf_volume_from_dsp(cur_event.volume), + duration, &ps->endpoint, clockrate, false, ts); + } + else { + ilog(LOG_DEBUG, "DTMF DSP code event: event %u, volume %u, duration %u", + 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); + } } void dtmf_event_free(void *e) { @@ -261,13 +324,6 @@ int dtmf_code_from_char(char c) { #ifdef WITH_TRANSCODING -static char dtmf_code_to_char(int code) { - static const char codes[] = "0123456789*#ABCD"; - if (code < 0 || code > 15) - return 0; - return codes[code]; -} - // takes over the csh reference static const char *dtmf_inject_pcm(struct call_media *media, struct call_media *sink, struct call_monologue *monologue, @@ -440,3 +496,9 @@ enum block_dtmf_mode dtmf_get_block_mode(struct call *call, struct call_monologu return BLOCK_DTMF_OFF; return ml->block_dtmf; } + +bool is_pcm_dtmf_block_mode(enum block_dtmf_mode mode) { + if (mode >= BLOCK_DTMF___PCM_REPLACE_START && mode <= BLOCK_DTMF___PCM_REPLACE_END) + return true; + return false; +} diff --git a/include/call.h b/include/call.h index fbfbda8cc..9de2f8823 100644 --- a/include/call.h +++ b/include/call.h @@ -202,6 +202,10 @@ enum { enum block_dtmf_mode { BLOCK_DTMF_OFF = 0, BLOCK_DTMF_DROP = 1, + + BLOCK_DTMF___PCM_REPLACE_START = 2, + BLOCK_DTMF_SILENCE = 2, + BLOCK_DTMF___PCM_REPLACE_END = 2, }; @@ -393,6 +397,9 @@ struct call_media { mutex_t dtmf_lock; unsigned long dtmf_ts; // TS of last processed end event + uint32_t dtmf_start; + char dtmf_code; + uint32_t dtmf_end; #ifdef WITH_TRANSCODING union { struct { diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 61bc341fb..4f84b57d6 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -76,6 +76,8 @@ struct sdp_ng_flags { MEO_BOTH, } media_echo:3; enum endpoint_learning el_option; + enum block_dtmf_mode block_dtmf_mode; + int delay_buffer; unsigned int asymmetric:1, protocol_accept:1, no_redis_update:1, @@ -120,6 +122,7 @@ struct sdp_ng_flags { allow_transcoding:1, accept_any:1, inject_dtmf:1, + detect_dtmf:1, t38_decode:1, t38_force:1, t38_stop:1, diff --git a/include/dtmf.h b/include/dtmf.h index 8f0e19a5c..b487f67f7 100644 --- a/include/dtmf.h +++ b/include/dtmf.h @@ -22,7 +22,7 @@ struct dtmf_event { }; void dtmf_init(void); -int dtmf_event_packet(struct media_packet *, str *, int); // 0 = ok, 1 = end event, -1 = error +int dtmf_event_packet(struct media_packet *, str *, int, uint64_t ts); // 0 = ok, 1 = end event, -1 = error int dtmf_event_payload(str *, uint64_t *, uint64_t, struct dtmf_event *, GQueue *); void dtmf_event_free(void *); int dtmf_code_from_char(char); @@ -30,7 +30,9 @@ const char *dtmf_inject(struct call_media *media, int code, int volume, int dura struct call_media *sink); bool dtmf_do_logging(void); void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_event, - struct call_media *media, int clockrate); + struct call_media *media, int clockrate, uint64_t ts); enum block_dtmf_mode dtmf_get_block_mode(struct call *call, struct call_monologue *ml); +bool is_pcm_dtmf_block_mode(enum block_dtmf_mode mode); +bool is_in_dtmf_event(struct call_media *, uint32_t ts, int clockrate, unsigned int head, unsigned int trail); #endif diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 7c93e95ef..1ec79cf04 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -89,6 +89,7 @@ GetOptions( 'symmetric-codecs' => \$options{'symmetric codecs'}, 'asymmetric-codecs' => \$options{'asymmetric codecs'}, 'inject-DTMF' => \$options{'inject DTMF'}, + 'detect-DTMF' => \$options{'detect DTMF'}, 'DTLS-fingerprint=s' => \$options{'DTLS-fingerprint'}, 'ICE-lite=s' => \$options{'ICE-lite'}, 'no-jitter-buffer' => \$options{'no jitter buffer'}, @@ -103,13 +104,15 @@ GetOptions( 'from-label=s' => \$options{'from-label'}, 'to-label=s' => \$options{'to-label'}, 'from-tags=s@' => \$options{'from-tags'}, + 'DTMF-security=s' => \$options{'DTMF-security'}, + 'delay-buffer=i' => \$options{'delay-buffer'}, ) 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')) { +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')) { if (defined($options{$x})) { if (!$options{json}) { $packet{$x} = \$options{$x}; @@ -119,10 +122,10 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address, } } } -for my $x (split(/,/, 'TOS,delete-delay')) { +for my $x (split(/,/, 'TOS,delete-delay,delay-buffer')) { 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,generate RTCP,single codec,reorder codecs,pierce NAT,SIP-source-address,allow transcoding')) { +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')) { defined($options{$x}) and push(@{$packet{flags}}, $x); } for my $x (split(/,/, 'origin,session connection,sdp version,username,session-name,zero-address')) {