diff --git a/README.md b/README.md index 5037de97b..d92dd0db4 100644 --- a/README.md +++ b/README.md @@ -1527,7 +1527,24 @@ Optionally included keys are: audio; `tone` which replaces DTMF events with a single sine wave tone; `random` which replaces DTMF events with random other DTMF events (both in-band DTMF audio tones and RFC event packets); `zero` which is - similar to `random` except that a zero event is always used. + 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. + +* `frequency` + + Sets the tone frequency for `DTMF-security=tone` in Hertz. The default + is 400 Hz. + +* `volume` + + Sets the tone volume for `DTMF-security` modes `tone`, `zero, `DTMF`, + and `random` in negative dB. The default is -10 dB. The highest + possible volume is 0 dB and the lowest possible volume is -63 dB. + +* `digit` + + Sets the replacement digit for `DTMF-security=DTMF`. * `delay-buffer` diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 4ca01f534..a40c52ef5 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -30,6 +30,7 @@ #include "media_player.h" #include "dtmf.h" #include "codec.h" +#include "dtmf.h" struct fragment_key { @@ -958,6 +959,8 @@ void call_ng_flags_init(struct sdp_ng_flags *out, enum call_opmode opmode) { out->el_option = rtpe_config.endpoint_learning; out->tos = 256; out->delay_buffer = -1; + out->volume = 9999; + out->digit = -1; } static void call_ng_dict_iter(struct sdp_ng_flags *out, bencode_item_t *input, @@ -1314,6 +1317,17 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_ break; } break; + case CSH_LOOKUP("frequency"): + out->frequency = bencode_get_integer_str(value, out->frequency); + break; + case CSH_LOOKUP("volume"): + out->volume = bencode_get_integer_str(value, out->volume); + break; + case CSH_LOOKUP("digit"): + out->digit = bencode_get_integer_str(value, out->digit); + if (s.len == 1) + out->digit = s.s[0]; + break; #ifdef WITH_TRANSCODING case CSH_LOOKUP("T38"): case CSH_LOOKUP("T.38"): @@ -1341,6 +1355,10 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_ 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)); @@ -2357,6 +2375,25 @@ static void call_monologue_set_block_mode(struct call_monologue *ml, struct sdp_ } } ml->detect_dtmf = flags->detect_dtmf; + + if (flags->volume >= 0 && flags->volume <= 63) + ml->tone_vol = flags->volume; + else if (flags->volume < 0 && flags->volume >= -63) + ml->tone_vol = -1 * flags->volume; + + if (flags->frequency > 0) + ml->tone_freq = flags->frequency; + + if (flags->block_dtmf_mode == BLOCK_DTMF_ZERO) + ml->dtmf_digit = '0'; + else { + char digit = dtmf_code_to_char(flags->digit); + if (digit) + ml->dtmf_digit = digit; + else if (dtmf_code_from_char(flags->digit) != -1) + ml->dtmf_digit = flags->digit; + } + codec_update_all_handlers(ml); } const char *call_block_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { diff --git a/daemon/codec.c b/daemon/codec.c index e7f75f297..b79d6f548 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -2468,7 +2468,8 @@ static void delay_frame_manipulate(struct delay_frame *dframe) { 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); + struct call_monologue *ml = media->monologue; + enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, ml); switch (mode) { case BLOCK_DTMF_SILENCE: @@ -2476,17 +2477,19 @@ static void delay_frame_manipulate(struct delay_frame *dframe) { break; case BLOCK_DTMF_TONE: frame_fill_tone_samples(frame->format, frame->extended_data[0], dframe->ts, - frame->nb_samples, 400, 10, frame->sample_rate, frame->channels); + frame->nb_samples, ml->tone_freq ? : 400, + ml->tone_vol ? : 10, frame->sample_rate, frame->channels); break; case BLOCK_DTMF_ZERO: + case BLOCK_DTMF_DTMF: // if we have DTMF output, use silence, otherwise use a DTMF zero if (dframe->ch->handler->dtmf_payload_type != -1) memset(frame->extended_data[0], 0, frame->linesize[0]); else frame_fill_dtmf_samples(frame->format, frame->extended_data[0], dframe->ts, - frame->nb_samples, 0, - 10, frame->sample_rate, + frame->nb_samples, dtmf_code_from_char(ml->dtmf_digit), + ml->tone_vol ? : 10, frame->sample_rate, frame->channels); break; case BLOCK_DTMF_RANDOM: @@ -2516,7 +2519,8 @@ static void delay_packet_manipulate(struct delay_frame *dframe) { if (!dframe->handler->source_pt.codec_def || !dframe->handler->source_pt.codec_def->dtmf) return; - enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, media->monologue); + struct call_monologue *ml = media->monologue; + enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, ml); // this can be a "raw" or "packet" - get the appropriate payload str *payload = &mp->raw; @@ -2529,7 +2533,8 @@ static void delay_packet_manipulate(struct delay_frame *dframe) { switch (mode) { case BLOCK_DTMF_ZERO: - dtmf->event = 0; + case BLOCK_DTMF_DTMF: + dtmf->event = dtmf_code_from_char(ml->dtmf_digit); break; default: break; diff --git a/daemon/dtmf.c b/daemon/dtmf.c index 4b9ef7ef1..24fe4e4a8 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -29,7 +29,7 @@ static unsigned int dtmf_volume_from_dsp(int vol) { else return 63; } -static char dtmf_code_to_char(int code) { +char dtmf_code_to_char(int code) { static const char codes[] = "0123456789*#ABCD"; if (code < 0 || code > 15) return 0; diff --git a/include/call.h b/include/call.h index 21e421630..3039cf6e5 100644 --- a/include/call.h +++ b/include/call.h @@ -212,7 +212,8 @@ enum block_dtmf_mode { BLOCK_DTMF___PCM_REPLACE_END = 4, // block modes that replace DTMF events with other DTMF events if possible BLOCK_DTMF_ZERO = 5, - BLOCK_DTMF___REPLACE_END = 5, + BLOCK_DTMF_DTMF = 6, + BLOCK_DTMF___REPLACE_END = 6, }; @@ -463,7 +464,12 @@ struct call_monologue { char *sdp_username; char *sdp_session_name; struct ssrc_hash *ssrc_hash; + + // DTMF blocking/replacement stuff: enum block_dtmf_mode block_dtmf; + unsigned int tone_freq; + unsigned int tone_vol; + char dtmf_digit; unsigned int block_media:1; unsigned int silence_media:1; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index 4f84b57d6..105fa19e7 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -78,6 +78,9 @@ struct sdp_ng_flags { enum endpoint_learning el_option; enum block_dtmf_mode block_dtmf_mode; int delay_buffer; + int frequency; + int volume; + char digit; unsigned int asymmetric:1, protocol_accept:1, no_redis_update:1, diff --git a/include/dtmf.h b/include/dtmf.h index f4d61c83e..9945edfe4 100644 --- a/include/dtmf.h +++ b/include/dtmf.h @@ -26,6 +26,7 @@ int dtmf_event_packet(struct media_packet *, str *, int, uint64_t ts); // 0 = ok int dtmf_event_payload(str *, uint64_t *, uint64_t, struct dtmf_event *, GQueue *); void dtmf_event_free(void *); int dtmf_code_from_char(char); +char dtmf_code_to_char(int code); const char *dtmf_inject(struct call_media *media, int code, int volume, int duration, int pause, struct call_media *sink); bool dtmf_do_logging(void); diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client index 1ec79cf04..a265bbd3c 100755 --- a/utils/rtpengine-ng-client +++ b/utils/rtpengine-ng-client @@ -106,13 +106,16 @@ GetOptions( 'from-tags=s@' => \$options{'from-tags'}, 'DTMF-security=s' => \$options{'DTMF-security'}, 'delay-buffer=i' => \$options{'delay-buffer'}, + 'frequency=i' => \$options{'frequency'}, + 'volume=i' => \$options{'volume'}, + 'digit=s' => \$options{'digit'}, ) 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')) { +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')) { if (defined($options{$x})) { if (!$options{json}) { $packet{$x} = \$options{$x}; @@ -122,7 +125,7 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address, } } } -for my $x (split(/,/, 'TOS,delete-delay,delay-buffer')) { +for my $x (split(/,/, 'TOS,delete-delay,delay-buffer,volume,frequency')) { 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')) {