diff --git a/README.md b/README.md index 413aca5d3..66f60ec66 100644 --- a/README.md +++ b/README.md @@ -1587,6 +1587,14 @@ Optionally included keys are: setting this value to zero. The delay buffer setting is honoured in all messages that set up codec handlers, such as `block DTMF`. +* `DTMF-delay` + + Time in milliseconds to delay DTMF events (both RFC event packets and + DTMF tones) for. With this option enabled (set to non-zero), DTMF + events are initially replaced by silence and then subsequently + reproduced after the given delay. DTMF blocking modes are honoured at + the time when the DTMF events are reproduced. + 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 79f1a6ba4..625041dfd 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -1378,6 +1378,12 @@ static void call_ng_main_flags(struct sdp_ng_flags *out, str *key, bencode_item_ case CSH_LOOKUP("end trigger digits"): out->trigger_end_digits = bencode_get_integer_str(value, out->trigger_end_digits); break; + case CSH_LOOKUP("DTMF-delay"): + case CSH_LOOKUP("DTMF delay"): + case CSH_LOOKUP("dtmf-delay"): + case CSH_LOOKUP("dtmf delay"): + out->dtmf_delay = bencode_get_integer_str(value, out->dtmf_delay); + break; #ifdef WITH_TRANSCODING case CSH_LOOKUP("T38"): case CSH_LOOKUP("T.38"): @@ -2440,6 +2446,7 @@ static void call_monologue_set_block_mode(struct call_monologue *ml, struct sdp_ ml->dtmf_trigger_match = 0; ml->dtmf_trigger_digits = flags->trigger_end_digits; ml->block_dtmf_trigger_end_ms = flags->trigger_end_ms; + ml->dtmf_delay = flags->dtmf_delay; codec_update_all_handlers(ml); } diff --git a/daemon/codec.c b/daemon/codec.c index 5aeab5cbb..215bd29a4 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -1014,6 +1014,9 @@ 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); + if (receiver->monologue->dtmf_delay) // received DTMF must be replaced by silence initially, therefore: + do_pcm_dtmf_blocking = true; + bool do_dtmf_detect = false; if (receiver->monologue->dtmf_trigger.len) do_dtmf_detect = true; @@ -1115,6 +1118,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, sink_pt->clock_rate, "telephone-event"); struct rtp_payload_type *sink_cn_pt = __supp_payload_type(supplemental_sinks, sink_pt->clock_rate, "CN"); + struct rtp_payload_type *real_sink_dtmf_pt = NULL; // for DTMF delay // XXX synthesise missing supp codecs according to codec tracker XXX needed? @@ -1142,6 +1146,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // special mode for DTMF blocking if (do_pcm_dtmf_blocking) { + real_sink_dtmf_pt = sink_dtmf_pt; // remember for DTMF delay sink_dtmf_pt = NULL; // always transcode DTMF to PCM // enable DSP if we expect DTMF to be carried as PCM @@ -1256,6 +1261,11 @@ transcode:; __make_transcoder(handler, sink_pt, output_transcoders, sink_dtmf_pt ? sink_dtmf_pt->payload_type : -1, pcm_dtmf_detect, sink_cn_pt ? sink_cn_pt->payload_type : -1); + // for DTMF delay: we pretend that there is no output DTMF payload type (sink_dtmf_pt == NULL) + // so that DTMF is converted to audio (so it can be replaced with silence). we still want + // to output DTMF event packets when we can though, so we need to remember the DTMF payload + // type here. + handler->real_dtmf_payload_type = real_sink_dtmf_pt ? real_sink_dtmf_pt->payload_type : -1; __check_dtmf_injector(receiver, sink, handler, output_transcoders); next: @@ -2206,6 +2216,7 @@ void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, ui ts + ch->first_ts); // add to queue if we're doing PCM -> DTMF event conversion + // this does not capture events when doing DTMF delay (dtmf_payload_type == -1) if (ch->handler && ch->handler->dtmf_payload_type != -1) { struct dtmf_event *ev = g_slice_alloc(sizeof(*ev)); *ev = new_ev; @@ -2514,23 +2525,74 @@ static void delay_frame_manipulate(struct delay_frame *dframe) { struct call_monologue *ml = media->monologue; enum block_dtmf_mode mode = dtmf_get_block_mode(dframe->mp.call, ml); - if (mode == BLOCK_DTMF_OFF) + if (mode == BLOCK_DTMF_OFF && media->monologue->dtmf_delay == 0) return; mutex_lock(&media->dtmf_lock); struct dtmf_event *dtmf_recv = is_in_dtmf_event(&media->dtmf_recv, dframe->ts, frame->sample_rate, media->buffer_delay, media->buffer_delay); struct dtmf_event *dtmf_send = is_in_dtmf_event(&media->dtmf_send, dframe->ts, frame->sample_rate, - media->buffer_delay, media->buffer_delay); + 0, 0); mutex_unlock(&media->dtmf_lock); - if (!dtmf_send) { - if (!dtmf_recv) - return; - mode = BLOCK_DTMF_SILENCE; + if (mode == BLOCK_DTMF_OFF) { + if (!dtmf_send) { + mode = BLOCK_DTMF_SILENCE; + + if (dframe->ch->handler->real_dtmf_payload_type != -1) { + // add end event to queue + if (dframe->ch->dtmf_event.code) { + struct dtmf_event *ev = g_slice_alloc0(sizeof(*ev)); + uint64_t ts = dframe->ch->encoder ? dframe->ch->encoder->next_pts + : dframe->ts; + *ev = (struct dtmf_event) { .code = 0, .volume = 0, .ts = ts }; + g_queue_push_tail(&dframe->ch->dtmf_events, ev); + } + } + + if (!dtmf_recv) + return; + } + else + mode = dtmf_send->block_dtmf; } + else if (!dtmf_recv) + return; + + // XXX this should be used for DTMF injection instead of a separate codec handler switch (mode) { + case BLOCK_DTMF_OFF: + // DTMF delay mode: play original DTMF + if (dframe->ch->handler->real_dtmf_payload_type != -1) { + // add event to handler queue so the packet can be translated + // to DTMF event packet. + memset(frame->extended_data[0], 0, frame->linesize[0]); + // XXX quite some redundant operations here: first the incoming + // DTMF event is decoded to audio, which is then later (maybe) replaced + // by silence. when the delayed DTMF is reproduced, the frame samples + // are first filled with silence, and then replaced + // by the DTMF event packet in packet_encoded_rtp(). + if (dframe->ch->dtmf_event.code != dtmf_send->code) { + // XXX this should be switched to proper state tracking instead + // of using start/stop events + struct dtmf_event *ev = g_slice_alloc0(sizeof(*ev)); + uint64_t ts = dframe->ch->encoder ? dframe->ch->encoder->next_pts + : dframe->ts; + *ev = (struct dtmf_event) { .code = dtmf_send->code, + .volume = -1 * dtmf_send->volume, + .ts = ts }; + g_queue_push_tail(&dframe->ch->dtmf_events, ev); + } + } + else { + // fill with DTMF PCM + frame_fill_dtmf_samples(frame->format, frame->extended_data[0], dframe->ts, + frame->nb_samples, dtmf_code_from_char(dtmf_send->code), + dtmf_send->volume, frame->sample_rate, + frame->channels); + } + break; case BLOCK_DTMF_SILENCE: memset(frame->extended_data[0], 0, frame->linesize[0]); break; @@ -3335,6 +3397,8 @@ static int packet_encoded_rtp(encoder_t *enc, void *u1, void *u2) { unsigned int repeats = 0; int payload_type = -1; int dtmf_pt = ch->handler->dtmf_payload_type; + if (dtmf_pt == -1) + dtmf_pt = ch->handler->real_dtmf_payload_type; int is_dtmf = 0; if (dtmf_pt != -1) @@ -3439,6 +3503,12 @@ static int packet_decoded_common(decoder_t *decoder, AVFrame *frame, void *u1, v if (!new_ch->first_ts) new_ch->first_ts = ch->first_ts; + if (decoder->def->supplemental) { + // supp codecs return bogus timestamps. Adjust the frame's TS to be in + // line with the primary decoder + frame->pts -= new_ch->first_ts; + } + ch = new_ch; } @@ -3474,7 +3544,7 @@ 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; - uint32_t ts = mp->rtp ? ntohl(mp->rtp->timestamp) : frame->pts; + uint32_t ts = frame->pts + ch->first_ts; __buffer_delay_frame(h->input_handler ? h->input_handler->delay_buffer : h->delay_buffer, ch, input_func, frame, mp, ts); frame = NULL; // consumed @@ -3698,12 +3768,6 @@ static int handler_func_transcode(struct codec_handler *h, struct media_packet * packet->rtp = *mp->rtp; packet->handler = h; - if (h->source_pt.codec_def->dtmf && h->dest_pt.codec_def->dtmf) { - // DTMF scaler - packet->func = packet_dtmf; - packet->dup_func = packet_dtmf_dup; - } - int ret = __handler_func_sequencer(mp, packet); return ret; diff --git a/daemon/dtmf.c b/daemon/dtmf.c index 0d82272cc..fa1867781 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -68,7 +68,7 @@ static void dtmf_bencode_and_notify(struct call_media *media, unsigned int event bencode_dictionary_add_string(data, "source_ip", sockaddr_print_buf(&fsin->address)); bencode_dictionary_add_integer(data, "timestamp", rtpe_now.tv_sec); bencode_dictionary_add_integer(data, "event", event); - bencode_dictionary_add_integer(data, "duration", ((long long) ntohs(duration) * (1000000LL / clockrate)) / 1000LL); + bencode_dictionary_add_integer(data, "duration", ((long long) duration * (1000000LL / clockrate)) / 1000LL); bencode_dictionary_add_integer(data, "volume", volume); bencode_collapse_str(notify, &encoded_data); @@ -113,7 +113,7 @@ static GString *dtmf_json_print(struct call_media *media, unsigned int event, un (unsigned long) rtpe_now.tv_sec, sockaddr_print_buf(&fsin->address), (unsigned int) event, - (ntohs(duration) * (1000000 / clockrate)) / 1000, + (duration * (1000000 / clockrate)) / 1000, (unsigned int) volume); return buf; @@ -137,7 +137,8 @@ static void dtmf_end_event(struct call_media *media, unsigned int event, unsigne g_queue_push_tail(&media->dtmf_recv, ev); ev = g_slice_alloc0(sizeof(*ev)); - *ev = (struct dtmf_event) { .code = 0, .ts = ts, .volume = 0 }; + *ev = (struct dtmf_event) { .code = 0, .ts = ts + media->monologue->dtmf_delay * clockrate / 1000, + .volume = 0, .block_dtmf = media->monologue->block_dtmf }; g_queue_push_tail(&media->dtmf_send, ev); if (!dtmf_do_logging()) @@ -284,7 +285,9 @@ static void dtmf_code_event(struct call_media *media, char event, uint64_t ts, i g_queue_push_tail(&media->dtmf_recv, ev); ev = g_slice_alloc0(sizeof(*ev)); - *ev = (struct dtmf_event) { .code = event, .ts = ts, .volume = volume, + *ev = (struct dtmf_event) { .code = event, .ts = ts + media->monologue->dtmf_delay * clockrate / 1000, + .volume = volume, + .block_dtmf = media->monologue->block_dtmf, .rand_code = '0' + (ssl_random() % 10) }; g_queue_push_tail(&media->dtmf_send, ev); @@ -292,7 +295,7 @@ static void dtmf_code_event(struct call_media *media, char event, uint64_t ts, i } -struct dtmf_event *is_in_dtmf_event(GQueue *events, uint32_t ts, int clockrate, unsigned int head, +struct dtmf_event *is_in_dtmf_event(GQueue *events, uint32_t this_ts, int clockrate, unsigned int head, unsigned int trail) { if (!clockrate) @@ -300,8 +303,8 @@ struct dtmf_event *is_in_dtmf_event(GQueue *events, uint32_t ts, int clockrate, uint32_t cutoff = clockrate * 10; uint32_t neg = ~(clockrate * 100); - uint32_t start_ts = ts + head * clockrate / 1000; - uint32_t end_ts = ts - trail * clockrate / 1000; + uint32_t start_ts = this_ts + head * clockrate / 1000; + uint32_t end_ts = this_ts - trail * clockrate / 1000; // go backwards through our list of DTMF events for (GList *l = events->tail; l; l = l->prev) { @@ -345,16 +348,18 @@ int dtmf_event_packet(struct media_packet *mp, str *payload, int clockrate, uint return -1; } dtmf = (void *) payload->s; + uint16_t duration = ntohs(dtmf->duration); ilog(LOG_DEBUG, "DTMF event packet: event %u, volume %u, end %u, duration %u", - dtmf->event, dtmf->volume, dtmf->end, ntohs(dtmf->duration)); + dtmf->event, dtmf->volume, dtmf->end, duration); if (!dtmf->end) { dtmf_code_event(mp->media, dtmf_code_to_char(dtmf->event), ts, clockrate, dtmf->volume); return 0; } - dtmf_end_event(mp->media, dtmf->event, dtmf->volume, dtmf->duration, &mp->fsin, clockrate, true, ts); + dtmf_end_event(mp->media, dtmf->event, dtmf->volume, duration, + &mp->fsin, clockrate, true, ts + duration - 1); return 1; } @@ -383,7 +388,7 @@ void dtmf_dsp_event(const struct dtmf_event *new_event, struct dtmf_event *cur_e // we don't have a real fsin so just use the stream address struct packet_stream *ps = media->streams.head->data; - unsigned int duration = cur_event.ts - new_event->ts; + unsigned int duration = new_event->ts - cur_event.ts; LOCK(&media->dtmf_lock); @@ -399,7 +404,8 @@ 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, clockrate, new_event->volume); + dtmf_code_event(media, (char) new_event->code, ts, clockrate, + dtmf_volume_from_dsp(new_event->volume)); } } diff --git a/include/call.h b/include/call.h index d7eca02cf..e78a85b25 100644 --- a/include/call.h +++ b/include/call.h @@ -477,6 +477,7 @@ struct call_monologue { int dtmf_trigger_digits; enum block_dtmf_mode block_dtmf_trigger_end; unsigned int block_dtmf_trigger_end_ms; + unsigned int dtmf_delay; unsigned int block_media:1; unsigned int silence_media:1; diff --git a/include/call_interfaces.h b/include/call_interfaces.h index e346c717c..235c0d110 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -87,6 +87,7 @@ struct sdp_ng_flags { enum block_dtmf_mode block_dtmf_mode_trigger_end; int trigger_end_digits; int trigger_end_ms; + int dtmf_delay; unsigned int asymmetric:1, protocol_accept:1, no_redis_update:1, diff --git a/include/codec.h b/include/codec.h index a4501d41b..f8bbb6121 100644 --- a/include/codec.h +++ b/include/codec.h @@ -36,6 +36,7 @@ struct codec_handler { struct rtp_payload_type source_pt; // source_pt.payload_type = hashtable index struct rtp_payload_type dest_pt; int dtmf_payload_type; + int real_dtmf_payload_type; int cn_payload_type; codec_handler_func *func; unsigned int passthrough:1; diff --git a/include/dtmf.h b/include/dtmf.h index 2bf1c1587..0b1c79315 100644 --- a/include/dtmf.h +++ b/include/dtmf.h @@ -7,6 +7,7 @@ #include #include "str.h" #include "socket.h" +#include "call.h" struct media_packet; @@ -20,6 +21,7 @@ struct dtmf_event { int volume; uint64_t ts; int rand_code; // state for random replace mode + enum block_dtmf_mode block_dtmf; // block mode at the time of the event }; void dtmf_init(void); diff --git a/lib/codeclib.c b/lib/codeclib.c index f856a0fe6..68b438be7 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -2597,6 +2597,7 @@ static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out) { AVFrame *frame = dtmf_frame_int16_t_mono(frame_ts, num_samples, dtmf->event, dtmf->volume, dec->in_format.clockrate); + frame->pts += dec->u.dtmf.start_ts; g_queue_push_tail(out, frame); dec->u.dtmf.duration = duration; diff --git a/t/auto-daemon-tests-delay-buffer.pl b/t/auto-daemon-tests-delay-buffer.pl index 316bee258..2891e2811 100755 --- a/t/auto-daemon-tests-delay-buffer.pl +++ b/t/auto-daemon-tests-delay-buffer.pl @@ -24,6 +24,541 @@ my ($sock_a, $sock_b, $sock_c, $sock_d, $port_a, $port_b, $ssrc, $ssrc_b, $resp, +($sock_a, $sock_b) = new_call([qw(198.51.100.1 4000)], [qw(198.51.100.1 5000)]); + +($port_a) = offer('DTMF delay - RFC', { }, < ft(), 'trigger' => '##', 'trigger-end' => '#', 'DTMF-security-trigger' => 'silence', + 'delay-buffer' => 1, 'DTMF-delay' => 60 }); + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x01\xe0")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$rseq = $seq; + +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x02\x80")); +rcv($sock_b, $port_a, rtpm(101 | 0x80, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x00\xa0")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x03\x20")); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x01\x40")); +Time::HiRes::usleep(18000); $seq++; +# end +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x8a\x03\xc0")); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x01\xe0")); +Time::HiRes::usleep(18000); $seq++; + +# back to PCM + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x02\x80")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x03\x20")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x03\xc0")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x04\x60")); +rcv($sock_b, $port_a, rtpm(101, 1001 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x04\x60")); +rcv($sock_b, $port_a, rtpm(101, 1002 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x04\x60")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 4004)], [qw(198.51.100.1 5004)]); + +($port_a) = offer('DTMF delay - PCM', { }, < ft(), 'trigger' => '##', 'trigger-end' => '#', 'DTMF-security-trigger' => 'silence', + 'delay-buffer' => 1, 'DTMF-delay' => 60 }); + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x40\xe0\xb3\xad\xbd\x3f\x2c\x2f\x54\xbb\xb5\xc4\x6b\x5d\xde\xd9\x4e\x37\x35\x58\xb5\xab\xb4\x52\x2f\x2f\x47\xca\xbf\xd0\xfe\xd8\xc1\xc3\x57\x32\x2e\x40\xbc\xab\xb0\xe0\x39\x35\x46\xe3\xdb\x61\x5d\xcc\xb7\xb7\xe8\x33\x2b\x37\xcb\xae\xb0\xcb\x46\x3f\x50\x7e\x58\x41\x46\xcf\xb1\xae\xc6\x39\x2b\x31\x7d\xb7\xb5\xc8\x5d\x58\xe5\xe1\x4a\x37\x38\xf2\xb1\xab\xba\x44\x2e\x30\x4f\xc3\xbe\xd1\x7d\xd8\xc3\xc9\x4b\x30\x2f\x4c\xb6\xab\xb3\x61\x35\x35\x4b\xd8\xd6\x68\x68\xc8\xb7\xba\x5d\x30\x2c\x3c\xbf\xad\xb1\xd8\x40\x3e\x52\xfb\x58\x44\x4c\xc8\xb0\xb0\xd6\x34\x2b\x35\xd5\xb3\xb5\xcd\x54\x54\xec\xef\x47\x37\x3c\xd3\xaf\xac\xc0\x3c\x2d\x33\x63\xbe")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x40\xe0\xb3\xad\xbd\x3f\x2c\x2f\x54\xbb\xb5\xc4\x6b\x5d\xde\xd9\x4e\x37\x35\x58\xb5\xab\xb4\x52\x2f\x2f\x47\xca\xbf\xd0\xfe\xd8\xc1\xc3\x57\x32\x2e\x40\xbc\xab\xb0\xe0\x39\x35\x46\xe3\xdb\x61\x5d\xcc\xb7\xb7\xe8\x33\x2b\x37\xcb\xae\xb0\xcb\x46\x3f\x50\x7e\x58\x41\x46\xcf\xb1\xae\xc6\x39\x2b\x31\x7d\xb7\xb5\xc8\x5d\x58\xe5\xe1\x4a\x37\x38\xf2\xb1\xab\xba\x44\x2e\x30\x4f\xc3\xbe\xd1\x7d\xd8\xc3\xc9\x4b\x30\x2f\x4c\xb6\xab\xb3\x61\x35\x35\x4b\xd8\xd6\x68\x68\xc8\xb7\xba\x5d\x30\x2c\x3c\xbf\xad\xb1\xd8\x40\x3e\x52\xfb\x58\x44\x4c\xc8\xb0\xb0\xd6\x34\x2b\x35\xd5\xb3\xb5\xcd\x54\x54\xec\xef\x47\x37\x3c\xd3\xaf\xac\xc0\x3c\x2d\x33\x63\xbe")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xbd\xd3\x77\xd9\xc5\xd0\x44\x30\x32\x65\xb2\xab\xb8\x4c\x32\x35\x50\xcf\xd2\x70\x7a\xc6\xb8\xbe\x4c\x2e\x2d\x45\xb9\xac\xb4\xfd\x3c\x3d\x55\xf2\x5a\x47\x56\xc1\xb0\xb4\x71\x30\x2b\x3a\xc7\xb0\xb6\xd7\x4d\x50\xf6\x78\x45\x38\x41\xc7\xae\xae\xcc\x37\x2c\x36\xe5\xbb\xbd\xd7\x6d\xdb\xc9\xdd\x3f\x30\x36\xdc\xae\xab\xbd\x41\x2f\x37\x5d\xcb\xcf\x7b\xef\xc4\xb9\xc6\x42\x2d\x2e\x55\xb4\xac\xb8\x58\x39\x3d\x59\xea\x5c\x4a\x66\xbd\xb0\xb8\x50\x2e\x2c\x40\xbd\xaf\xb8\xe8\x48\x4e\x7d\x6b\x43\x3a\x4a\xbf\xad\xaf\xe4\x32\x2c\x3a\xcf\xb8\xbd\xdc\x66\xde\xcc\xf5\x3c\x30\x3b\xca\xad\xac\xc6\x3b\x2e\x39\x7c\xc6\xcd\xfa\xe7\xc3\xbb\xce\x3c\x2d\x31\xf2")); +# replaced by silence +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xc0\xe9\xfe\xcc\xc6\x66\x35\x2d\x3b\xbe\xa9\xac\xce\x36\x2e\x3b\xe4\xc8\xda\x68\xd7\xbb\xb7\xd6\x35\x29\x31\xd2\xac\xab\xbf\x43\x37\x41\x6e\xfb\x4d\x48\xdb\xb4\xad\xbf\x3b\x29\x2c\x5a\xb3\xae\xbd\x5b\x46\x59\xec\x54\x3b\x38\x63\xb3\xaa\xb4\x4a\x2b\x2c\x44\xbf\xb6\xc2\xf8\x75\xce\xcb\x54\x33\x2e\x44\xb8\xa9\xae\xf0\x31\x2e\x3e\xd4\xc4\xd8\x70\xd2\xbb\xba\x7c\x31\x2a\x37\xc3\xab\xac\xca\x3c\x35\x43\xfa\xef\x4f\x4e\xce\xb2\xaf\xca\x35\x28\x2f\xe3\xaf\xae\xc1\x4e\x43\x57\xf6\x50\x3b\x3c\xe1\xaf\xaa\xba\x3e\x2a\x2d\x50\xba\xb4\xc4\x6e\x6b\xd1\xd1\x4b\x32\x30\x52\xb2\xa8\xb1\x53\x2e\x2e\x45\xca\xc1\xd6\x7a\xcf\xbc\xbe\x56\x2f\x2b\x3d\xba")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xa9\xae\xdb\x38\x34\x46\xe7\xe7\x54\x56\xc8\xb2\xb2\xdf\x30\x28\x33\xca\xad\xae\xc9\x47\x40\x55\xfe\x4f\x3c\x40\xce\xae\xac\xc1\x37\x29\x2f\x70\xb6\xb4\xc8\x5f\x62\xd6\xdb\x46\x32\x34\xfd\xae\xa9\xb6\x44\x2c\x2f\x4d\xc3\xbf\xd6\xfe\xce\xbd\xc3\x49\x2d\x2c\x48\xb4\xa9\xb0\x65\x34\x34\x4a\xdb\xdf\x59\x60\xc3\xb2\xb6\x5e\x2d\x29\x39\xbe\xac\xaf\xd5\x40\x3e\x56\x7d\x4e\x3d\x47\xc4\xad\xad\xd0\x31\x28\x32\xd6\xb2\xb4\xcd\x57\x5c\xdb\xe9\x41\x32\x38\xd2\xad\xaa\xbd\x3b\x2b\x30\x5e\xbe\xbe\xd7\xfe\xce\xbe\xcb\x40\x2d\x2e\x5d\xaf\xa9\xb5\x4c\x30\x34\x4f\xd2\xda\x5e\x74\xc0\xb3\xbb\x4a\x2c\x2a\x41\xb8\xab\xb2\xf2\x3c\x3d\x58\xfe\x4e\x3f\x4f")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + + + + + + + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 4008)], [qw(198.51.100.1 5008)]); + +($port_a) = offer('DTMF trigger', { }, < ft(), 'trigger' => '##', 'trigger-end' => '#', 'DTMF-security-trigger' => 'tone', + 'delay-buffer' => 1, 'DTMF-delay' => 100 }); + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x01\xe0")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x02\x80")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x03\x20")); +rcv($sock_b, $port_a, rtpm(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$rseq = $seq; + +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x03\xc0")); +rcv($sock_b, $port_a, rtpm(101 | 0x80, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x00\xa0")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x0a\x04\x60")); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x01\x40")); +Time::HiRes::usleep(18000); $seq++; +# end +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x05\x8a\x05\x00")); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x01\xe0")); +Time::HiRes::usleep(18000); $seq++; + +# back to PCM + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x02\x80")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x03\x20")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x03\xc0")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x04\x60")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x0a\x05\x00")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(101, 1000 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x05\xa0")); +rcv($sock_b, $port_a, rtpm(101, 1001 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x05\xa0")); +rcv($sock_b, $port_a, rtpm(101, 1002 + $seq, 3000 + 160 * $rseq, 0x1234, "\x05\x8a\x05\xa0")); + +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +# trigger + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x8a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x0a\x00\xa0")); +# trigger matched, now tone +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x8a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x01\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x01\x8a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x02\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x02\x8a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; + +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + +# end trigger + +$sseq = $seq; + +snd($sock_a, $port_b, rtp(101 | 0x80, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x0a\x00\xa0")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(101, 1000 + $seq, 3000 + 160 * $sseq, 0x1234, "\x0b\x8a\x01\x40")); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35\xff\xb5\xa7\x9f\x9c\x9b\x9c\x9f\xa7\xb5\xff\x35\x27\x1f\x1c\x1b\x1c\x1f\x27\x35")); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\xff" x 160)); +Time::HiRes::usleep(18000); $seq++; +snd($sock_a, $port_b, rtp(0, 1000 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002 + $seq, 3000 + 160 * $seq, 0x1234, "\x00" x 160)); +Time::HiRes::usleep(18000); $seq++; + + + + + + + + + ($sock_a, $sock_b) = new_call([qw(198.51.100.1 2000)], [qw(198.51.100.1 3000)]); ($port_a) = offer('PCM DTMF block', { }, <