diff --git a/README.md b/README.md index 76cb9436a..c21d40f66 100644 --- a/README.md +++ b/README.md @@ -1521,12 +1521,14 @@ is the one generating the DTMF event, not the one receiving it. The dictionary key `code` must be present in the message, indicating the DTMF event to be generated. It can be either an integer with values 0-15, or a string containing a single character (`0` - `9`, `*`, `#`, `A` - `D`). Additional optional dictionary keys are: `duration` indicating the duration -of the event in milliseconds (defaults to 250 ms, with a minimum of 100 and a maximum of 5000); and +of the event in milliseconds (defaults to 250 ms, with a minimum of 100 and a maximum of 5000); `volume` indicating the volume in absolute decibels (defaults to -8 dB, with 0 being the maximum volume and -positive integers being interpreted as negative). +positive integers being interpreted as negative); and `pause` indicating the pause in between consecutive +DTMF events in milliseconds (defaults to 100 ms, with a minimum of 100 and a maximum of 5000). This message can be used to implement `application/dtmf-relay` or `application/dtmf` payloads carried -in SIP INFO messages. +in SIP INFO messages. Multiple DTMF events can be queued up by issuing multiple consecutive +`play DTMF` messages. If the destination participant supports the `telephone-event` RTP payload type, then it will be used to send the DTMF event. Otherwise a PCM DTMF tone will be inserted into the audio stream. Audio samples diff --git a/daemon/codec.c b/daemon/codec.c index 25ce6b37d..1704cb5d7 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -1299,6 +1299,13 @@ void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, ui g_queue_push_tail(&ch->dtmf_events, ev); } +uint64_t codec_last_dtmf_event(struct codec_ssrc_handler *ch) { + struct dtmf_event *ev = g_queue_peek_tail(&ch->dtmf_events); + if (!ev) + return 0; + return ev->ts; +} + uint64_t codec_encoder_pts(struct codec_ssrc_handler *ch) { return ch->encoder->fifo_pts; } @@ -1308,6 +1315,13 @@ void codec_decoder_skip_pts(struct codec_ssrc_handler *ch, uint64_t pts) { ch->skip_pts += pts; } +uint64_t codec_decoder_unskip_pts(struct codec_ssrc_handler *ch) { + uint64_t prev = ch->skip_pts; + ilog(LOG_DEBUG, "Un-skipping next %" PRIu64 " samples", prev); + ch->skip_pts = 0; + return prev; +} + static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) { struct codec_handler *h = p; diff --git a/daemon/dtmf.c b/daemon/dtmf.c index d32067343..783d555dd 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -217,6 +217,7 @@ static const char *dtmf_inject_pcm(struct call_media *media, struct call_monolog // keep track of how much PCM we've generated uint64_t encoder_pts = codec_encoder_pts(csh); + uint64_t skip_pts = codec_decoder_unskip_pts(csh); // reset to zero to take up our new samples media->dtmf_injector->func(media->dtmf_injector, &packet); @@ -229,7 +230,8 @@ static const char *dtmf_inject_pcm(struct call_media *media, struct call_monolog // skip generated samples uint64_t pts_offset = codec_encoder_pts(csh) - encoder_pts; - codec_decoder_skip_pts(csh, av_rescale(pts_offset, ch->dest_pt.clock_rate, ch->source_pt.clock_rate)); + skip_pts += av_rescale(pts_offset, ch->dest_pt.clock_rate, ch->source_pt.clock_rate); + codec_decoder_skip_pts(csh, skip_pts); // ready packets for send // XXX handle encryption? @@ -281,8 +283,14 @@ const char *dtmf_inject(struct call_media *media, int code, int volume, int dura // synthesise start and stop events uint64_t num_samples = duration * ch->dest_pt.clock_rate / 1000; - codec_add_dtmf_event(csh, dtmf_code_to_char(code), volume, codec_encoder_pts(csh)); - codec_add_dtmf_event(csh, 0, 0, codec_encoder_pts(csh) + num_samples); + uint64_t start_pts = codec_encoder_pts(csh); + uint64_t last_end_pts = codec_last_dtmf_event(csh); + if (last_end_pts) { + // shift this new event past the end of the last event plus a pause + start_pts = last_end_pts + pause * ch->dest_pt.clock_rate / 1000; + } + codec_add_dtmf_event(csh, dtmf_code_to_char(code), volume, start_pts); + codec_add_dtmf_event(csh, 0, 0, start_pts + num_samples); obj_put_o((struct obj *) csh); return NULL; diff --git a/include/codec.h b/include/codec.h index 6b88bc5bb..17e0bc019 100644 --- a/include/codec.h +++ b/include/codec.h @@ -75,8 +75,10 @@ void __rtp_payload_type_add_send(struct call_media *other_media, struct rtp_payl void codec_handlers_update(struct call_media *receiver, struct call_media *sink, const struct sdp_ng_flags *); void codec_add_dtmf_event(struct codec_ssrc_handler *ch, int code, int level, uint64_t ts); +uint64_t codec_last_dtmf_event(struct codec_ssrc_handler *ch); uint64_t codec_encoder_pts(struct codec_ssrc_handler *ch); void codec_decoder_skip_pts(struct codec_ssrc_handler *ch, uint64_t); +uint64_t codec_decoder_unskip_pts(struct codec_ssrc_handler *ch); #else diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 2427866f2..7320f6843 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -809,6 +809,219 @@ rcv($sock_a, $port_b, rtpm(0, 4014, 10240, $ssrc, "\xff" x 80 . "\x00" x 80)); +# multiple consecutive DTMF events + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 6024)], [qw(198.51.100.3 6026)]); + +($port_a) = offer('multiple consecutive DTMF events', + { ICE => 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < $ft, code => 'C', volume => 5, duration => 100 }); +$resp = rtpe_req('play DTMF', 'inject DTMF towards B', + { 'from-tag' => $ft, code => '4', volume => 5, duration => 100 }); + +snd($sock_a, $port_b, rtp(0, 1002, 3320, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1002, 3320, $ssrc, "\xff\x93\x94\xbc\x2e\x56\xbf\x2b\x13\x1b\xa7\x8e\x98\x47\x25\x41\xe2\x24\x16\x2b\x99\x8e\x9f\x28\x1e\x3d\x5b\x23\x1c\xdf\x92\x8f\xb6\x1c\x1c\x40\x5d\x26\x25\xaa\x8f\x95\x3b\x15\x1d\x5e\xde\x2c\x38\x9d\x8f\x9e\x1f\x11\x20\xc0\xc1\x37\xdd\x99\x92\xb7\x15\x10\x2c\xac\xb5\x49\xb8\x97\x99\x37\x0f\x13\x58\xa0\xae\x67\xae\x99\xa4\x1f\x0d\x1a\xae\x9b\xad\x7b\xad\x9d\xbf\x16\x0e\x27\x9d\x98\xb0\x55\xb1\xa6\x3a\x11\x11\x63\x95\x98\xbf\x3e\xbb\xb4\x26\x10\x1a\xa9\x90\x9a\x4e\x30\xce\xd4\x1e\x12\x29\x99\x8e\xa1\x2d\x29\x6d\x4b\x1c\x18\xef\x91\x8f\xb6\x1f\x24\x57\x3e\x1d\x20\xa9\x8e\x95\x3e\x19\x23\x67\x3e\x21\x31\x9c\x8e\x9e\x22\x14\x26\xcd\x4a")); +snd($sock_a, $port_b, rtp(0, 1003, 3480, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1003, 3480, $ssrc, "\x2a\xdf\x96\x90\xb5\x17\x13\x2f\xb6\xf5\x36\xb1\x93\x96\x39\x10\x15\x55\xaa\xc8\x4c\xa7\x95\xa0\x1f\x0e\x1b\xb4\xa1\xbd\xed\xa4\x99\xbb\x15\x0e\x27\xa0\x9d\xbd\xda\xa4\x9f\x39\x10\x11\x58\x98\x9c\xc8\xf9\xa9\xac\x23\x0e\x19\xab\x92\x9e\x59\x4c\xb0\xca\x1b\x10\x27\x9a\x90\xa5\x35\x3a\xbe\x43\x18\x15\x6c\x92\x91\xb7\x26\x30\xd6\x32\x18\x1d\xa9\x8e\x96\x44\x1d\x2d\xfc\x2e\x1b\x2d\x9a\x8d\x9e\x25\x19\x2d\xe7\x2f\x20\xea\x94\x8f\xb3\x19\x17\x36\xc8\x36\x2c\xae\x90\x95\x3b\x12\x18\x55\xb7\x43\x3e\xa1\x91\x9e\x1f\x0f\x1d\xba\xac\x64\xe8\x9d\x95\xb7\x15\x0e\x29\xa6\xa6\xda\xc3\x9d\x9b\x39\x0f\x11\x51\x9c\xa2\xd8\xbe\x9f\xa7\x21\x0e\x18\xad")); +snd($sock_a, $port_b, rtp(0, 1004, 3640, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1004, 3640, $ssrc, "\x96\xa3\x68\xc4\xa5\xc2\x19\x0e\x26\x9c\x93\xa9\x3f\xdb\xae\x3e\x14\x12\x5b\x93\x93\xb9\x2e\x51\xbe\x2c\x14\x1b\xa9\x8f\x97\x4c\x25\x3f\xde\x25\x16\x2a\x9a\x8e\x9e\x29\x1e\x3b\x5e\x24\x1b\x7b\x92\x8f\xb2\x1c\x1c\x3e\x61\x27\x25\xac\x8f\x94\x3e\x15\x1c\x59\xdb\x2d\x37\x9e\x8f\x9d\x20\x11\x1f\xc2\xbf\x38\xea\x99\x92\xb4\x16\x10\x2b\xad\xb4\x49\xba\x98\x98\x3a\x0f\x12\x4e\xa1\xad\x68\xaf\x99\xa3\x20\x0d\x19\xb0\x9b\xac\x7b\xae\x9d\xbc\x17\x0e\x25\x9e\x98\xaf\x55\xb2\xa6\x3d\x12\x11\x52\x96\x97\xbd\x3e\xbc\xb3\x28\x10\x19\xab\x90\x9a\x54\x2f\xd0\xcf\x1f\x12\x27\x9a\x8e\xa0\x2e\x28\x66\x4e\x1d\x18\x62\x92\x8f\xb2\x20\x23\x53\x3f\x1d\x1f")); +snd($sock_a, $port_b, rtp(0, 1005, 3800, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1005, 3800, $ssrc, "\xab\x8e\x94\x44\x19\x22\x61\x40\x21\x2f\x9c\x8e\x9d\x23\x14\x25\xce\x4d\x2a\xf7\x96\x8f\xb1\x18\x13\x2e\xb7\xe8\x36\xb3\x94\x96\x3c\x10\x15\x4d\xaa\xc5\x4b\xa8\x95\x9f\x20\x0e\x1a\xb6\xa0\xbc\xf5\xa4\x99\xb8\x16\x0e\x26\xa1\x9d\xbb\xdd\xa5\x9f\x3c\x10\x10\x4c\x99\x9b\xc5\x78\xaa\xac\x24\x0f\x18\xac\x93\x9d\x5f\x4a\xb1\xc7\x1c\x0f\x25\x9b\x90\xa3\x36\x39\xbf\x47\x18\x14\x56\x92\x90\xb4\x27\x2f\xd7\x34\x18\x1c\xab\x8e\x95\x4b\x1d\x2c\xfe\x2f\x1b\x2c\x9b\x8d\x9d\x27\x19\x2c\xe7\x30\x20\x6d\x94\x8f\xaf\x1a\x17\x34\xc8\x37\x2b\xaf\x91\x94\x3f\x12\x18\x4e\xb6\x45\x3d\xa3\x91\x9e\x20\x0f\x1c\xbc\xab\x6c\xf5\x9e\x95\xb3\x16\x0e\x27\xa7\xa5")); +snd($sock_a, $port_b, rtp(0, 1006, 3960, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1006, 3960, $ssrc, "\xd6\xc6\x9d\x9b\x3d\x0f\x11\x49\x9c\xa1\xd4\xbf\x9f\xa6\x22\x0e\x18\xaf\x96\xa2\x6e\xc6\xa5\xbe\x19\x0e\x24\x9d\x93\xa8\x40\xe1\xae\x42\x15\x12\x4e\x94\x93\xb7\x2e\x4e\xbe\x2d\x14\x1a\xab\x8f\x97\x52\x25\x3e\xdc\x26\x16\x28\x9b\x8e\x9e\x2b\x1e\x3a\x61\x25\x1b\x5d\x93\x8f\xaf\x1d\x1c\x3d\x67\x27\x24\xad\x8f\x93\x45\x15\x1c\x53\xd7\x2d\x35\x9f\x8f\x9c\x22\x11\x1f\xc5\xbe\x38\x7a\x9a\x91\xb0\x17\x10\x29\xad\xb3\x4a\xbc\x98\x98\x3e\x10\x12\x48\xa1\xad\x6a\xb1\x9a\xa1\x21\x0e\x18\xb3\x9b\xab\x7d\xaf\x9d\xb9\x18\x0e\x23\x9f\x97\xae\x55\xb4\xa5\x40\x12\x10\x49\x96\x97\xbb\x3d\xbd\xb2\x29\x10\x18\xac\x90\x99\x5d\x2f\xd4\xcd\x1f\x12\x25\x9b")); +# pause +snd($sock_a, $port_b, rtp(0, 1007, 4120, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1007, 4120, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1008, 4280, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1008, 4280, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1009, 4440, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1009, 4440, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1010, 4600, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1010, 4600, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1011, 4760, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1011, 4760, $ssrc, "\xff" x 160)); +# next event +snd($sock_a, $port_b, rtp(0, 1012, 4920, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1012, 4920, $ssrc, "\xff\x96\x8e\x99\xdd\x1f\x1d\x2c\x69\xe9\x39\x2d\x50\xa3\x95\x97\xbd\x1a\x0e\x12\x38\x9d\x93\x9b\xbf\x30\x2f\x4f\xdc\x39\x20\x1d\x33\xa2\x90\x91\xaa\x1f\x0f\x12\x2c\xa9\x9c\xa3\xc2\x55\xc9\xaf\xb8\x30\x18\x14\x24\xa7\x8f\x8e\xa0\x2a\x14\x16\x2a\xbc\xac\xbc\x61\xcf\xa8\x9d\xa6\x36\x15\x0f\x1b\xb4\x92\x8f\x9d\x3e\x1d\x1e\x31\xe0\xfe\x36\x30\xd3\x9e\x94\x9b\x50\x15\x0d\x17\xe3\x99\x93\x9e\xee\x2c\x30\x5b\xf0\x32\x1f\x1f\x4c\x9c\x8f\x94\xbe\x19\x0e\x15\x3d\xa2\x9b\xa7\xd2\x52\xc3\xaf\xc4\x29\x16\x16\x2f\x9e\x8e\x90\xac\x20\x13\x18\x34\xb2\xac\xc0\x61\xc2\xa5\x9d\xad\x29\x12\x10\x23\xa5\x8f\x90\xa5\x2d\x1b\x1f\x39\xd1\x65\x34\x36\xbb\x9b")); +snd($sock_a, $port_b, rtp(0, 1013, 5080, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1013, 5080, $ssrc, "\x94\x9f\x2f\x11\x0e\x1c\xb3\x95\x94\xa4\x45\x2a\x33\x69\x60\x2d\x1e\x23\xc7\x98\x8f\x98\x49\x15\x0e\x1a\xda\x9d\x9c\xab\x7d\x53\xbe\xb1\xe8\x22\x15\x19\x4d\x98\x8d\x94\xc3\x1b\x12\x1b\x48\xac\xac\xc7\x69\xba\xa2\x9f\xbb\x1f\x10\x12\x2f\x9c\x8e\x93\xb0\x25\x1a\x22\x44\xcb\x57\x34\x3d\xae\x99\x96\xa9\x23\x0f\x0f\x24\xa6\x93\x96\xac\x36\x29\x37\x7c\x4e\x29\x1e\x29\xb0\x94\x8f\x9e\x2d\x11\x0f\x1f\xb6\x9b\x9d\xb0\x55\x58\xbc\xb5\x49\x1e\x15\x1d\xbe\x94\x8e\x99\x45\x17\x12\x1f\xd9\xa9\xad\xce\xfa\xb3\xa0\xa2\xef\x1b\x0f\x16\x52\x97\x8e\x96\xcb\x1e\x1a\x26\x59\xc8\x4e\x35\x4d\xa8\x97\x98\xb8\x1c\x0e\x11\x31\x9d\x91\x98\xb9\x2d\x29\x3b\xf5")); +snd($sock_a, $port_b, rtp(0, 1014, 5240, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1014, 5240, $ssrc, "\x43\x27\x1f\x32\xa6\x92\x91\xa7\x21\x0f\x10\x28\xa9\x99\x9e\xba\x49\x60\xba\xbb\x38\x1b\x16\x23\xab\x90\x8e\x9e\x2d\x14\x13\x26\xbc\xa7\xaf\xd8\xde\xae\xa0\xa7\x3d\x17\x0f\x1a\xba\x93\x8e\x9b\x44\x1b\x1b\x2b\xe0\xc8\x48\x37\xe4\xa2\x96\x9b\x6f\x17\x0e\x15\x5d\x99\x91\x9c\xd7\x29\x29\x3f\xf8\x3c\x24\x21\x46\x9e\x90\x94\xb8\x1a\x0e\x14\x37\xa1\x99\xa1\xc8\x43\x76\xba\xc5\x2d\x19\x17\x2d\xa0\x8f\x8f\xa8\x21\x11\x16\x2e\xaf\xa6\xb2\xe5\xcf\xab\xa0\xad\x2d\x14\x10\x20\xa8\x90\x8f\xa1\x2e\x19\x1c\x31\xcb\xc9\x44\x3b\xc2\x9e\x96\x9f\x36\x13\x0e\x1a\xb8\x95\x92\xa0\x48\x26\x2a\x48\x73\x36\x23\x25\xd4\x9a\x90\x98\x5c\x15\x0e\x18\x72\x9c\x99")); +snd($sock_a, $port_b, rtp(0, 1015, 5400, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1015, 5400, $ssrc, "\xa6\xe8\x3f\xe7\xba\xdd\x27\x18\x1a\x43\x9a\x8e\x93\xbb\x1b\x10\x19\x3e\xaa\xa5\xb7\xf4\xc6\xa9\xa2\xba\x23\x12\x12\x2c\x9e\x8e\x91\xac\x25\x18\x1e\x3c\xc1\xcd\x41\x40\xb5\x9c\x97\xa8\x27\x10\x0f\x21\xa8\x92\x93\xa8\x35\x24\x2c\x50\x61\x30\x23\x2b\xb7\x97\x90\x9d\x31\x11\x0e\x1c\xb9\x9a\x9a\xab\x52\x3f\xd9\xbc\x54\x22\x18\x1d\xca\x96\x8e\x97\x52\x17\x10\x1c\xef\xa5\xa6\xbc\xff\xbe\xa7\xa5\xd8\x1d\x10\x16\x45\x98\x8e\x95\xbf\x1e\x17\x20\x4d\xbc\xd2\x3f\x4e\xad\x9a\x99\xb4\x1e\x0e\x10\x2d\x9e\x90\x96\xb2\x2c\x22\x2f\x5c\x54\x2d\x24\x32\xaa\x94\x91\xa5\x24\x0f\x0f\x24\xaa\x98\x9b\xb2\x43\x3f\xcf\xc0\x3e\x1e\x18\x23\xaf\x92\x8e\x9c\x2f")); +snd($sock_a, $port_b, rtp(0, 1016, 5560, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1016, 5560, $ssrc, "\x13\x11\x21\xbd\xa2\xa8\xc3\xfa\xb9\xa6\xa9\x45\x19\x10\x1a\xc2\x94\x8e\x99\x4e\x1a\x18\x26\xeb\xba\xdd\x40\x7d\xa7\x99\x9c\xda\x19\x0e\x14\x4a\x99\x90\x99\xc9\x26\x23\x34\x6d\x4b\x2b\x25\x41\xa1\x92\x94\xb3\x1c\x0e\x12\x30\xa0\x96\x9d\xbe\x3b\x41\xcc\xc9\x34\x1c\x19\x2c\xa3\x8f\x8f\xa5\x23\x10\x13\x2a\xaf\xa0\xaa\xcd\xeb\xb4\xa6\xae\x31\x16\x11\x1f\xab\x90\x8e\x9e\x30\x18\x19\x2c\xc8\xb9\xf0\x43\xcc\xa2\x99\x9f\x3c\x14\x0e\x19\xbe\x95\x90\x9d\x4e\x22\x24\x3a\xfa\x43\x2a\x28\xec\x9d\x91\x98\xe4\x16\x0d\x16\x51\x9c\x96\xa0\xd7\x37\x45\xca\xda\x2c\x1b\x1b\x3d\x9c\x8e\x92\xb4\x1c\x0f\x16\x38\xa8\xa0\xad\xda\xdd\xb0\xa7\xb9\x28\x14\x13")); +# pause +snd($sock_a, $port_b, rtp(0, 1017, 5720, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1017, 5720, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1018, 5880, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1018, 5880, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1019, 6040, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1019, 6040, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1020, 6200, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1020, 6200, $ssrc, "\xff" x 160)); +snd($sock_a, $port_b, rtp(0, 1021, 6360, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1021, 6360, $ssrc, "\xff" x 160)); +# resume +snd($sock_a, $port_b, rtp(0, 1022, 6520, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1022, 6520, $ssrc, "\x00" x 160)); + + + + +# RFC payload type present + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 6010)], [qw(198.51.100.3 6012)]); + +($port_a) = offer('multi- no transcoding, RFC payload type present', + { ICE => 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < $ft, code => '0', volume => 10, duration => 100 }); +$resp = rtpe_req('play DTMF', 'inject DTMF towards B', + { 'from-tag' => $ft, code => '1', volume => 6, duration => 100 }); + +snd($sock_a, $port_b, rtp(0, 1002, 3320, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96 | 0x80, 1002, 3320, $ssrc, "\x00\x0a\x00\xa0")); +snd($sock_a, $port_b, rtp(0, 1003, 3480, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1003, 3320, $ssrc, "\x00\x0a\x01\x40")); +snd($sock_a, $port_b, rtp(0, 1004, 3640, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1004, 3320, $ssrc, "\x00\x0a\x01\xe0")); +snd($sock_a, $port_b, rtp(0, 1005, 3800, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1005, 3320, $ssrc, "\x00\x0a\x02\x80")); +snd($sock_a, $port_b, rtp(0, 1006, 3960, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1006, 3320, $ssrc, "\x00\x0a\x03\x20")); +snd($sock_a, $port_b, rtp(0, 1007, 4120, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1007, 3320, $ssrc, "\x00\x8a\x03\xc0")); +rcv($sock_b, $port_a, rtpm(96, 1008, 3320, $ssrc, "\x00\x8a\x03\xc0")); +rcv($sock_b, $port_a, rtpm(96, 1009, 3320, $ssrc, "\x00\x8a\x03\xc0")); +snd($sock_a, $port_b, rtp(0, 1008, 4280, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1010, 4280, $ssrc, "\x00" x 160)); +snd($sock_a, $port_b, rtp(0, 1009, 4440, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1011, 4440, $ssrc, "\x00" x 160)); +snd($sock_a, $port_b, rtp(0, 1010, 4600, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1012, 4600, $ssrc, "\x00" x 160)); +snd($sock_a, $port_b, rtp(0, 1011, 4760, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1013, 4760, $ssrc, "\x00" x 160)); +snd($sock_a, $port_b, rtp(0, 1012, 4920, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96 | 0x80, 1014, 4920, $ssrc, "\x01\x06\x00\xa0")); +snd($sock_a, $port_b, rtp(0, 1013, 5080, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1015, 4920, $ssrc, "\x01\x06\x01\x40")); +snd($sock_a, $port_b, rtp(0, 1014, 5240, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1016, 4920, $ssrc, "\x01\x06\x01\xe0")); +snd($sock_a, $port_b, rtp(0, 1015, 5400, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1017, 4920, $ssrc, "\x01\x06\x02\x80")); +snd($sock_a, $port_b, rtp(0, 1016, 5560, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1018, 4920, $ssrc, "\x01\x06\x03\x20")); +snd($sock_a, $port_b, rtp(0, 1017, 5720, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(96, 1019, 4920, $ssrc, "\x01\x86\x03\xc0")); +rcv($sock_b, $port_a, rtpm(96, 1020, 4920, $ssrc, "\x01\x86\x03\xc0")); +rcv($sock_b, $port_a, rtpm(96, 1021, 4920, $ssrc, "\x01\x86\x03\xc0")); +snd($sock_a, $port_b, rtp(0, 1018, 5880, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1022, 5880, $ssrc, "\x00" x 160)); + + + + + # SDP in/out tests, various ICE options new_call;