diff --git a/README.md b/README.md index d309f175f..76cb9436a 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ the following additional features are available: - Recording of media streams, decrypted if possible - Transcoding and repacketization - Transcoding between RFC 2833/4733 DTMF event packets and in-band DTMF tones (and vice versa) +- Injection of DTMF events or PCM DTMF tones into running audio streams - Playback of pre-recorded streams/announcements *Rtpengine* does not (yet) support: @@ -511,6 +512,7 @@ a string and determines the type of message. Currently the following commands ar * stop forwarding * play media * stop media +* play DTMF The response dictionary must contain at least one key called `result`. The value can be either `ok` or `error`. For the `ping` command, the additional value `pong` is allowed. If the result is `error`, then another key @@ -714,6 +716,13 @@ Optionally included keys are: own version of them based on other media parameters (e.g. a media section with a zero IP address would come out as `sendonly` or `inactive`). + - `inject DTMF` + + Signals to *rtpengine* that the audio streams involved in this `offer` or `answer` + (the flag should be present in both of them) are to be made available for DTMF + injection via the `play DTMF` control message. See `play DTMF` below for additional + information. + * `replace` Similar to the `flags` list. Controls which parts of the SDP body should be rewritten. @@ -1501,3 +1510,28 @@ the media file could be determined. The duration is given as in integer represen Stops the playback previously started by a `play media` message. Media playback stops automatically when the end of the media file is reached, so this message is only useful for prematurely stopping playback. The same participant selection keys as for the `play media` message can and must be used. + +`play DTMF` Message +------------------- + +Instructs *rtpengine* to inject a DTMF tone or event into a running audio stream. A call participant must +be selected in the same way as described under the `block DTMF` message above. The selected call participant +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 +`volume` indicating the volume in absolute decibels (defaults to -8 dB, with 0 being the maximum volume and +positive integers being interpreted as negative). + +This message can be used to implement `application/dtmf-relay` or `application/dtmf` payloads carried +in SIP INFO 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 +received during a generated DTMF event will be suppressed. + +The call must be marked for DTMF injection using the `inject DTMF` flag used in both `offer` and `answer` +messages. Enabling this flag forces all audio to go through the transcoding engine, even if input and output +codecs are the same (similar to DTMF transcoding, see above). diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index 1323f6d72..c0fbdcc5d 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -28,6 +28,7 @@ #include "main.h" #include "load.h" #include "media_player.h" +#include "dtmf.h" static pcre *info_re; @@ -695,6 +696,9 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) { case CSH_LOOKUP("asymmetric-codecs"): out->asymmetric_codecs = 1; break; + case CSH_LOOKUP("inject-DTMF"): + out->inject_dtmf = 1; + break; case CSH_LOOKUP("pad-crypto"): out->pad_crypto = 1; break; @@ -1838,6 +1842,83 @@ out: } +const char *call_play_dtmf_ng(bencode_item_t *input, bencode_item_t *output) { +#ifdef WITH_TRANSCODING + struct call *call; + struct call_monologue *monologue; + str str; + const char *err = NULL; + + err = play_media_select_party(&call, &monologue, input); + if (err) + goto out; + + // validate input parameters + + long long duration = bencode_dictionary_get_int_str(input, "duration", 250); + if (duration < 100) { + duration = 100; + ilog(LOG_WARN, "Invalid duration (%lli ms) specified, using 100 ms instead", duration); + } + else if (duration > 5000) { + duration = 5000; + ilog(LOG_WARN, "Invalid duration (%lli ms) specified, using 5000 ms instead", duration); + } + + long long code = bencode_dictionary_get_int_str(input, "code", -1); + err = "Out of range 'code' specified"; + if (code == -1) { + // try a string code + err = "No valid 'code' specified"; + if (!bencode_dictionary_get_str(input, "code", &str)) + goto out; + err = "Given 'code' is not a single digit"; + if (str.len != 1) + goto out; + code = dtmf_code_from_char(str.s[0]); + err = "Invalid 'code' character"; + if (code == -1) + goto out; + } + else if (code < 0) + goto out; + else if (code > 15) + goto out; + + long long volume = bencode_dictionary_get_int_str(input, "volume", 8); + if (volume > 0) + volume *= -1; + + // find a usable output media + struct call_media *media; + for (GList *l = monologue->medias.head; l; l = l->next) { + media = l->data; + if (media->type_id != MT_AUDIO) + continue; + if (!media->dtmf_injector) + continue; + goto found; + } + + err = "Monologue has no media capable of DTMF injection"; + // XXX fall back to generating a secondary stream + goto out; + +found:; + err = dtmf_inject(media, code, volume, duration); + +out: + if (call) { + rwlock_unlock_w(&call->master_lock); + obj_put(call); + } + return err; +#else + return "unsupported"; +#endif +} + + int call_interfaces_init() { const char *errptr; int erroff; diff --git a/daemon/codec.c b/daemon/codec.c index 2da00f67c..786684514 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -103,6 +103,7 @@ struct transcode_packet { static codec_handler_func handler_func_passthrough_ssrc; static codec_handler_func handler_func_transcode; static codec_handler_func handler_func_playback; +static codec_handler_func handler_func_inject_dtmf; static codec_handler_func handler_func_dtmf; static struct ssrc_entry *__ssrc_handler_transcode_new(void *p); @@ -348,7 +349,7 @@ static struct rtp_payload_type *__check_dest_codecs(struct call_media *receiver, } } } - else if (flags && flags->always_transcode) { + else if (flags && (flags->always_transcode || flags->inject_dtmf)) { // with always-transcode, we must keep track of potential output DTMF payload // types as well if (pt->codec_def && pt->codec_def->dtmf) { @@ -372,7 +373,7 @@ static void __check_send_codecs(struct call_media *receiver, struct call_media * struct rtp_payload_type *pt = l->data; struct rtp_payload_type *recv_pt = g_hash_table_lookup(receiver->codecs_send, &pt->payload_type); - if (!recv_pt || rtp_payload_type_cmp(pt, recv_pt)) { + if (!recv_pt || rtp_payload_type_cmp(pt, recv_pt) || (flags && flags->inject_dtmf)) { *sink_transcoding = 1; // can the sink receive RFC DTMF but the receiver can't send it? if (pt->codec_def && pt->codec_def->dtmf) { @@ -487,6 +488,43 @@ static void __eliminate_rejected_codecs(struct call_media *receiver, struct call } } +static void __check_dtmf_injector(const struct sdp_ng_flags *flags, struct call_media *receiver, + struct rtp_payload_type *pref_dest_codec, GHashTable *output_transcoders, + int dtmf_payload_type) +{ + if (!flags || !flags->inject_dtmf) + return; + if (receiver->dtmf_injector) { + // is this still valid? + if (!rtp_payload_type_cmp(pref_dest_codec, &receiver->dtmf_injector->dest_pt)) + return; + + codec_handler_free(receiver->dtmf_injector); + receiver->dtmf_injector = NULL; + } + + // synthesise input rtp payload type + struct rtp_payload_type src_pt = { + .payload_type = -1, + .clock_rate = pref_dest_codec->clock_rate, + .channels = pref_dest_codec->channels, + }; + str_init(&src_pt.encoding, "DTMF injector"); + str_init(&src_pt.encoding_with_params, "DTMF injector"); + const str tp_event = STR_CONST_INIT("telephone-event"); + src_pt.codec_def = codec_find(&tp_event, MT_AUDIO); + if (!src_pt.codec_def) { + ilog(LOG_ERR, "RTP payload type 'telephone-event' is not defined"); + return; + } + + //receiver->dtmf_injector = codec_handler_make_playback(&src_pt, pref_dest_codec, 0); + //receiver->dtmf_injector->dtmf_payload_type = dtmf_payload_type; + receiver->dtmf_injector = __handler_new(&src_pt); + __make_transcoder(receiver->dtmf_injector, pref_dest_codec, output_transcoders, dtmf_payload_type, 0); + receiver->dtmf_injector->func = handler_func_inject_dtmf; +} + // call must be locked in W void codec_handlers_update(struct call_media *receiver, struct call_media *sink, const struct sdp_ng_flags *flags) @@ -605,7 +643,11 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, GQueue *dest_codecs = NULL; if (!flags || !flags->always_transcode) { // we ignore output codec matches if we must transcode DTMF - if (dtmf_payload_type == -1) + if (dtmf_payload_type != -1) + ; + else if (flags && flags->inject_dtmf) + ; + else dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); } else if (flags->always_transcode) { @@ -692,6 +734,8 @@ next: // we have to translate RTCP packets receiver->rtcp_handler = rtcp_transcode_handler; + __check_dtmf_injector(flags, receiver, pref_dest_codec, output_transcoders, dtmf_payload_type); + // at least some payload types will be transcoded, which will result in SSRC // change. for payload types which we don't actually transcode, we still // must substitute the SSRC @@ -756,6 +800,8 @@ void codec_handlers_free(struct call_media *m) { g_hash_table_destroy(m->codec_handlers); m->codec_handlers = NULL; m->codec_handler_cache = NULL; + if (m->dtmf_injector) + codec_handler_free(m->dtmf_injector); } @@ -1520,8 +1566,6 @@ static int handler_func_transcode(struct codec_handler *h, struct media_packet * if (mp->call->block_media || mp->media->monologue->block_media) return 0; - assert((mp->rtp->m_pt & 0x7f) == h->source_pt.payload_type); - // create new packet and insert it into sequencer queue ilog(LOG_DEBUG, "Received RTP packet: SSRC %" PRIx32 ", PT %u, seq %u, TS %u, len %i", @@ -1551,6 +1595,13 @@ static int handler_func_playback(struct codec_handler *h, struct media_packet *m return 0; } +static int handler_func_inject_dtmf(struct codec_handler *h, struct media_packet *mp) { + struct codec_ssrc_handler *ch = get_ssrc(mp->ssrc_in->parent->h.ssrc, h->ssrc_hash); + decoder_input_data(ch->decoder, &mp->payload, mp->rtp->timestamp, + __packet_decoded, ch, mp); + return 0; +} + diff --git a/daemon/control_ng.c b/daemon/control_ng.c index 27a317ed7..b96199044 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -256,6 +256,10 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin errstr = call_stop_media_ng(dict, resp); g_atomic_int_inc(&cur->stop_media); break; + case CSH_LOOKUP("play DTMF"): + errstr = call_play_dtmf_ng(dict, resp); + g_atomic_int_inc(&cur->play_dtmf); + break; default: errstr = "Unrecognized command"; } diff --git a/daemon/dtmf.c b/daemon/dtmf.c index fb3358251..646825fa0 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -4,6 +4,9 @@ #include "call.h" #include "dtmflib.h" #include "main.h" +#include "rtplib.h" +#include "codec.h" +#include "ssrc.h" @@ -162,3 +165,113 @@ int dtmf_code_from_char(char c) { return c - 'A' + 12; return -1; } + +#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]; +} + +static const char *dtmf_inject_pcm(struct call_media *media, struct call_monologue *monologue, + struct packet_stream *ps, struct ssrc_ctx *ssrc_in, struct codec_handler *ch, + struct codec_ssrc_handler *csh, + int code, int volume, int duration) +{ + struct call *call = monologue->call; + + struct ssrc_ctx *ssrc_out = get_ssrc_ctx(ssrc_in->ssrc_map_out, call->ssrc_hash, SSRC_DIR_OUTPUT); + if (!ssrc_out) + return "No output SSRC context present"; // XXX generate stream + + // we generate PCM DTMF by simulating a detected RFC event packet + // XXX this shouldn't require faking an actual RTP packet + struct telephone_event_payload tep = { + .event = code, + .volume = -1 * volume, + .end = 1, + .duration = htons(duration * ch->dest_pt.clock_rate / 1000), + }; + struct rtp_header rtp = { + .m_pt = 0xff, + .timestamp = 0, + .seq_num = htons(ssrc_in->parent->sequencer.seq), + .ssrc = htonl(ssrc_in->parent->h.ssrc), + }; + struct media_packet packet = { + .tv = rtpe_now, + .call = call, + .media = media, + .rtp = &rtp, + .ssrc_in = ssrc_in, + .ssrc_out = ssrc_out, + .raw = { (void *) &tep, sizeof(tep) }, + .payload = { (void *) &tep, sizeof(tep) }, + }; + + // keep track of how much PCM we've generated + uint64_t encoder_pts = codec_encoder_pts(csh); + + media->dtmf_injector->func(media->dtmf_injector, &packet); + + 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)); + + // ready packets for send + // XXX handle encryption? + + media_socket_dequeue(&packet, packet_stream_sink(ps)); + + return 0; +} + +const char *dtmf_inject(struct call_media *media, int code, int volume, int duration) { + struct call_monologue *monologue = media->monologue; + + if (!media->streams.head) + return "Media doesn't have an RTP stream"; + struct packet_stream *ps = media->streams.head->data; + struct ssrc_ctx *ssrc_in = ps->ssrc_in; + if (!ssrc_in) + return "No SSRC context present for DTMF injection"; // XXX fall back to generating stream + + // create RFC DTMF events. we do this by simulating a detected PCM DTMF event + // find payload type to use + int pt = -1; + for (int i = 0; i < ssrc_in->tracker.most_len; i++) { + pt = ssrc_in->tracker.most[i]; + if (pt != 255) + break; + } + if (pt < 0 || pt == 255) + return "No RTP payload type found to be in use"; // XXX generate stream + + struct codec_handler *ch = codec_handler_get(media, pt); + if (!ch) + return "No matching codec handler"; + if (ch->output_handler) // context switch if we have multiple inputs going to one output + ch = ch->output_handler; + struct codec_ssrc_handler *csh = get_ssrc(ssrc_in->parent->h.ssrc, ch->ssrc_hash); + if (!csh) + return "No matching codec SSRC handler"; + + // if we don't have a DTMF payload type, we have to generate PCM + if (media->dtmf_injector->dtmf_payload_type == -1) + return dtmf_inject_pcm(media, monologue, ps, ssrc_in, ch, csh, code, volume, duration); + + ilog(LOG_DEBUG, "Injecting RFC DTMF event #%i for %i ms (vol %i) from '" STR_FORMAT "' (media #%u) " + "into RTP PT %i, SSRC %" PRIx32, + code, duration, volume, STR_FMT(&monologue->tag), media->index, pt, + ssrc_in->parent->h.ssrc); + + // 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); + + return NULL; +} + +#endif diff --git a/include/call.h b/include/call.h index 37538405c..635ddba24 100644 --- a/include/call.h +++ b/include/call.h @@ -321,6 +321,7 @@ struct call_media { // XXX combine this with 'codecs_recv' hash table? volatile struct codec_handler *codec_handler_cache; struct rtcp_handler *rtcp_handler; + struct codec_handler *dtmf_injector; int ptime; // either from SDP or overridden diff --git a/include/call_interfaces.h b/include/call_interfaces.h index bc3e84505..f225e7450 100644 --- a/include/call_interfaces.h +++ b/include/call_interfaces.h @@ -75,6 +75,7 @@ struct sdp_ng_flags { original_sendrecv:1, always_transcode:1, asymmetric_codecs:1, + inject_dtmf:1, supports_load_limit:1, dtls_off:1, sdes_off:1, @@ -117,6 +118,7 @@ const char *call_block_media_ng(bencode_item_t *, bencode_item_t *); const char *call_unblock_media_ng(bencode_item_t *, bencode_item_t *); const char *call_play_media_ng(bencode_item_t *, bencode_item_t *); const char *call_stop_media_ng(bencode_item_t *, bencode_item_t *); +const char *call_play_dtmf_ng(bencode_item_t *, bencode_item_t *); void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output, struct call_stats *totals); diff --git a/include/control_ng.h b/include/control_ng.h index 9d6f2b7dc..c0003374b 100644 --- a/include/control_ng.h +++ b/include/control_ng.h @@ -27,6 +27,7 @@ struct control_ng_stats { int unblock_media; int play_media; int stop_media; + int play_dtmf; int errors; }; diff --git a/include/dtmf.h b/include/dtmf.h index 57717a23e..c44963ea3 100644 --- a/include/dtmf.h +++ b/include/dtmf.h @@ -8,6 +8,8 @@ struct media_packet; +struct call_media; + struct dtmf_event { int code; @@ -20,6 +22,7 @@ int dtmf_event(struct media_packet *, str *, int); int dtmf_event_payload(str *, uint64_t *, uint64_t, struct dtmf_event *, GQueue *); void dtmf_event_free(void *); int dtmf_code_from_char(char); +const char *dtmf_inject(struct call_media *media, int code, int volume, int duration); #endif diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index e8e71d276..c8b9bcf22 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -178,6 +178,431 @@ sub rtpm { ok $r->{result} eq 'pong', 'ping works, daemon operational'; } + + +my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp); + + +# DTMF injection +# +# no transcoding, 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('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 }); + +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_b, $port_a, rtp(0, 4000, 8000, 0x6543, "\x00" x 160)); +($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 8000, -1, "\x00" x 160)); +snd($sock_b, $port_a, rtp(0, 4001, 8160, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4001, 8160, $ssrc, "\x00" x 160)); + +$resp = rtpe_req('play DTMF', 'inject DTMF towards A', + { 'from-tag' => $tt, code => '*', volume => 10, duration => 100 }); + +snd($sock_b, $port_a, rtp(0, 4002, 8320, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96 | 0x80, 4002, 8320, $ssrc, "\x0a\x0a\x00\xa0")); +snd($sock_b, $port_a, rtp(0, 4003, 8480, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4003, 8320, $ssrc, "\x0a\x0a\x01\x40")); +snd($sock_b, $port_a, rtp(0, 4004, 8640, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4004, 8320, $ssrc, "\x0a\x0a\x01\xe0")); +snd($sock_b, $port_a, rtp(0, 4005, 8800, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4005, 8320, $ssrc, "\x0a\x0a\x02\x80")); +snd($sock_b, $port_a, rtp(0, 4006, 8960, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4006, 8320, $ssrc, "\x0a\x0a\x03\x20")); +snd($sock_b, $port_a, rtp(0, 4007, 9120, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4007, 8320, $ssrc, "\x0a\x8a\x03\xc0")); +rcv($sock_a, $port_b, rtpm(96, 4008, 8320, $ssrc, "\x0a\x8a\x03\xc0")); +rcv($sock_a, $port_b, rtpm(96, 4009, 8320, $ssrc, "\x0a\x8a\x03\xc0")); +snd($sock_b, $port_a, rtp(0, 4008, 9280, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4010, 9280, $ssrc, "\x00" x 160)); + + + + + +# transcoding, RFC payload type present on both sides + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 6110)], [qw(198.51.100.3 6112)]); + +($port_a) = offer('transcoding, RFC payload type present on both sides', + { ICE => 'remove', replace => ['origin'], flags => ['inject DTMF'], + codec => { transcode => ['PCMA'] }}, < 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < $ft, code => '0', volume => 10, 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(8, 1010, 4280, $ssrc, "\x2a" x 160)); + + + +snd($sock_b, $port_a, rtp(8, 4000, 8000, 0x6543, "\x2a" x 160)); +($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 8000, -1, "\x00" x 160)); +snd($sock_b, $port_a, rtp(8, 4001, 8160, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4001, 8160, $ssrc, "\x00" x 160)); + +$resp = rtpe_req('play DTMF', 'inject DTMF towards A', + { 'from-tag' => $tt, code => '#', volume => -10, duration => 100 }); + +snd($sock_b, $port_a, rtp(8, 4002, 8320, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96 | 0x80, 4002, 8320, $ssrc, "\x0b\x0a\x00\xa0")); +snd($sock_b, $port_a, rtp(8, 4003, 8480, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4003, 8320, $ssrc, "\x0b\x0a\x01\x40")); +snd($sock_b, $port_a, rtp(8, 4004, 8640, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4004, 8320, $ssrc, "\x0b\x0a\x01\xe0")); +snd($sock_b, $port_a, rtp(8, 4005, 8800, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4005, 8320, $ssrc, "\x0b\x0a\x02\x80")); +snd($sock_b, $port_a, rtp(8, 4006, 8960, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4006, 8320, $ssrc, "\x0b\x0a\x03\x20")); +snd($sock_b, $port_a, rtp(8, 4007, 9120, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(96, 4007, 8320, $ssrc, "\x0b\x8a\x03\xc0")); +rcv($sock_a, $port_b, rtpm(96, 4008, 8320, $ssrc, "\x0b\x8a\x03\xc0")); +rcv($sock_a, $port_b, rtpm(96, 4009, 8320, $ssrc, "\x0b\x8a\x03\xc0")); +snd($sock_b, $port_a, rtp(8, 4008, 9280, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4010, 9280, $ssrc, "\x00" x 160)); + + + +# no transcoding, no RFC payload type present + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 6014)], [qw(198.51.100.3 6016)]); + +($port_a) = offer('no transcoding, no RFC payload type present', + { ICE => 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < $ft, code => 'C', volume => 5, duration => 120 }); + +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")); +snd($sock_a, $port_b, rtp(0, 1007, 4120, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1007, 4120, $ssrc, "\x8e\x9f\x2f\x28\x5f\x51\x1d\x17\x52\x92\x8f\xaf\x20\x22\x50\x42\x1e\x1f\xad\x8e\x93\x4b\x19\x21\x5d\x42\x22\x2e\x9d\x8e\x9c\x25\x14\x24\xd0\x4f\x2a\x68\x97\x8f\xae\x18\x12\x2c\xb7\xdf\x36\xb6\x94\x95\x41\x11\x14\x48\xaa\xc3\x4a\xaa\x95\x9e\x21\x0e\x19\xb8\xa0\xba\xfe\xa5\x99\xb4\x17\x0e\x24\xa2\x9c\xba\xe0\xa6\x9e\x40\x10\x10\x45\x99\x9b\xc2\x6d\xaa\xab\x26\x0f\x17\xae\x93\x9c\x6a\x48\xb2\xc3\x1c\x0f\x23\x9c\x90\xa2\x37\x38\xbf\x4b\x19\x14\x4b\x93\x90\xb1\x27\x2e\xd8\x36\x19\x1c\xad\x8e\x94\x52\x1d\x2b\x7d\x30\x1b\x2a\x9c\x8d\x9c\x28\x19\x2b\xe7\x31\x20\x5a\x95\x8f\xad\x1a\x16\x32\xc8\x39\x2b\xb2\x91\x94\x46\x13\x17\x4a\xb6\x48\x3c")); +snd($sock_a, $port_b, rtp(0, 1008, 4280, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(0, 1008, 4280, $ssrc, "\x00" x 160)); + + + +snd($sock_b, $port_a, rtp(0, 4000, 8000, 0x6543, "\x00" x 160)); +($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 8000, -1, "\x00" x 160)); +snd($sock_b, $port_a, rtp(0, 4001, 8160, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4001, 8160, $ssrc, "\x00" x 160)); + +$resp = rtpe_req('play DTMF', 'inject DTMF towards A', + { 'from-tag' => $tt, code => '4', volume => 3, duration => 150 }); + +snd($sock_b, $port_a, rtp(0, 4002, 8320, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4002, 8320, $ssrc, "\xff\x90\x8a\x93\xd9\x1b\x18\x27\x65\xe5\x33\x29\x4c\x9e\x8f\x91\xb8\x15\x09\x0d\x32\x98\x8e\x96\xbb\x2c\x2b\x4c\xd8\x34\x1c\x18\x2e\x9d\x8c\x8c\xa5\x1a\x0b\x0d\x27\xa3\x97\x9e\xbd\x4f\xc4\xaa\xb2\x2c\x12\x0e\x1e\xa1\x8b\x8a\x9c\x25\x0e\x10\x25\xb7\xa7\xb7\x5e\xcb\xa2\x98\x9f\x30\x0f\x0a\x16\xae\x8d\x8a\x98\x3a\x18\x19\x2c\xdd\xfd\x30\x2b\xce\x99\x8e\x95\x4c\x0f\x09\x10\xdf\x93\x8e\x9a\xec\x28\x2c\x56\xee\x2d\x1a\x1a\x48\x97\x8b\x8e\xba\x14\x0a\x0f\x39\x9d\x96\xa1\xcd\x4e\xbe\xab\xbe\x23\x10\x10\x2b\x99\x8a\x8c\xa7\x1b\x0d\x12\x2f\xad\xa7\xbc\x5e\xbd\x9f\x99\xa8\x23\x0d\x0b\x1d\x9f\x8b\x8c\x9f\x29\x16\x1b\x34\xcd\x60\x2f\x2f\xb6\x96")); +snd($sock_b, $port_a, rtp(0, 4003, 8480, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4003, 8480, $ssrc, "\x8e\x9b\x2b\x0c\x09\x17\xae\x8f\x8e\x9e\x3f\x25\x2e\x65\x5c\x28\x1a\x1e\xc2\x92\x8a\x92\x44\x0f\x0a\x14\xd6\x99\x97\xa6\x7c\x4e\xba\xad\xe5\x1d\x0f\x13\x49\x92\x89\x8e\xbe\x15\x0d\x16\x43\xa8\xa7\xc1\x66\xb5\x9d\x9a\xb6\x1b\x0c\x0d\x2b\x98\x8a\x8d\xab\x1f\x15\x1d\x3f\xc7\x52\x2e\x39\xaa\x93\x8f\xa3\x1e\x0b\x0b\x1e\x9f\x8d\x8f\xa7\x30\x23\x31\x7c\x4a\x24\x1a\x24\xac\x8e\x8b\x99\x28\x0c\x0a\x1a\xb0\x96\x98\xac\x4f\x53\xb7\xaf\x44\x19\x0f\x18\xba\x8e\x89\x93\x3f\x10\x0d\x1a\xd5\xa3\xa8\xca\xf9\xae\x9c\x9d\xec\x16\x0b\x10\x4e\x91\x89\x90\xc6\x1a\x14\x20\x55\xc3\x4a\x2f\x49\xa2\x91\x92\xb2\x17\x09\x0c\x2d\x99\x8d\x92\xb3\x29\x23\x36\xf2")); +snd($sock_b, $port_a, rtp(0, 4004, 8640, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4004, 8640, $ssrc, "\x3e\x20\x1b\x2d\xa0\x8d\x8c\xa1\x1c\x0a\x0c\x22\xa3\x94\x9a\xb5\x44\x5c\xb5\xb6\x32\x16\x0f\x1e\xa6\x8c\x8a\x99\x28\x0e\x0e\x20\xb7\xa1\xab\xd4\xdb\xaa\x9c\xa1\x38\x11\x0b\x15\xb5\x8d\x8a\x96\x3f\x16\x15\x26\xdd\xc2\x43\x31\xdf\x9d\x90\x96\x6d\x11\x09\x0f\x5a\x93\x8c\x97\xd2\x23\x23\x3b\xf6\x37\x1f\x1d\x40\x9a\x8c\x8e\xb2\x15\x09\x0e\x31\x9c\x93\x9c\xc2\x3e\x74\xb4\xbf\x29\x14\x11\x29\x9b\x8a\x8b\xa3\x1c\x0d\x0f\x2a\xab\x9f\xad\xe0\xcc\xa6\x9c\xa9\x28\x0e\x0c\x1c\xa2\x8b\x8b\x9c\x2a\x14\x17\x2c\xc6\xc4\x3e\x36\xbd\x99\x90\x9b\x30\x0d\x09\x15\xb3\x8f\x8d\x9b\x42\x1f\x25\x42\x70\x30\x1e\x1f\xcf\x95\x8b\x92\x58\x0f\x09\x12\x6f\x98\x93")); +snd($sock_b, $port_a, rtp(0, 4005, 8800, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4005, 8800, $ssrc, "\x9f\xe5\x3b\xe2\xb5\xd9\x21\x12\x14\x3e\x95\x89\x8d\xb6\x16\x0c\x13\x3a\xa4\x9f\xb1\xf1\xc0\xa3\x9d\xb4\x1e\x0d\x0d\x27\x99\x8a\x8c\xa7\x1f\x12\x19\x37\xbc\xc8\x3c\x3c\xaf\x97\x91\xa2\x21\x0b\x0a\x1c\xa2\x8d\x8e\xa2\x2f\x1e\x28\x4c\x5d\x2c\x1e\x25\xb0\x90\x8c\x98\x2c\x0c\x0a\x18\xb4\x94\x94\xa6\x4d\x3a\xd4\xb8\x4f\x1d\x11\x18\xc5\x8f\x89\x91\x4d\x10\x0c\x17\xec\x9f\xa0\xb8\xff\xba\xa1\x9f\xd3\x19\x0c\x0f\x3f\x92\x89\x8f\xbb\x19\x11\x1c\x48\xb8\xce\x3b\x4a\xa8\x95\x93\xaf\x19\x0a\x0c\x29\x99\x8c\x8f\xad\x27\x1d\x2b\x59\x4f\x29\x1e\x2d\xa5\x8e\x8d\x9f\x1e\x0b\x0b\x1e\xa4\x91\x96\xad\x3e\x3b\xcc\xbc\x3a\x1a\x12\x1e\xaa\x8d\x8a\x98\x2b")); +snd($sock_b, $port_a, rtp(0, 4006, 8960, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4006, 8960, $ssrc, "\x0e\x0c\x1d\xb8\x9d\xa2\xbe\xf9\xb4\xa0\xa3\x3f\x14\x0c\x14\xbd\x8e\x89\x93\x49\x15\x12\x1f\xe7\xb5\xd9\x3c\x7c\xa1\x93\x97\xd5\x13\x09\x0e\x45\x93\x8b\x93\xc4\x20\x1d\x2e\x6b\x46\x26\x1f\x3d\x9d\x8d\x8e\xae\x17\x09\x0d\x2c\x9c\x90\x98\xba\x36\x3d\xc7\xc4\x2e\x17\x13\x27\x9e\x8b\x8b\x9f\x1e\x0c\x0e\x25\xaa\x9c\xa5\xc8\xe8\xae\xa0\xaa\x2d\x10\x0c\x1b\xa6\x8c\x8a\x9a\x2c\x12\x13\x27\xc3\xb3\xed\x3e\xc8\x9d\x93\x9b\x38\x0f\x09\x13\xba\x8f\x8b\x98\x4a\x1d\x1e\x34\xf9\x3e\x24\x23\xea\x98\x8c\x92\xdf\x10\x09\x0f\x4d\x97\x90\x9c\xd2\x31\x3f\xc5\xd6\x28\x16\x16\x39\x97\x8a\x8d\xaf\x17\x0b\x10\x32\xa2\x9b\xa8\xd6\xd9\xac\xa1\xb3\x22\x0e\x0e")); +snd($sock_b, $port_a, rtp(0, 4007, 9120, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4007, 9120, $ssrc, "\x24\x9b\x8a\x8b\xa2\x1f\x10\x15\x2f\xb8\xb4\x68\x43\xb8\x9a\x94\xa1\x25\x0c\x0a\x1a\xa5\x8d\x8c\x9e\x30\x1b\x1f\x3c\xee\x38\x23\x28\xb8\x93\x8d\x97\x31\x0d\x09\x15\xb9\x93\x90\xa0\x4f\x2f\x46\xc4\x5e\x21\x15\x19\xd7\x91\x89\x90\x7b\x10\x0b\x14\x5b\x9d\x9c\xad\xed\xcd\xa9\xa3\xca\x1c\x0d\x10\x38\x94\x89\x8e\xb3\x19\x0f\x18\x3e\xb0\xb5\x59\x4d\xae\x98\x95\xad\x1c\x0b\x0c\x25\x9b\x8b\x8e\xa9\x26\x1a\x22\x46\xf5\x33\x23\x2e\xaa\x90\x8d\x9e\x21\x0b\x0a\x1c\xa6\x90\x92\xa8\x3b\x2e\x4d\xc7\x43\x1e\x15\x1e\xaf\x8e\x8a\x96\x2e\x0e\x0b\x1a\xbb\x9b\x9d\xb2\x68\xc5\xa8\xa7\x4c\x17\x0d\x14\xcb\x8f\x89\x91\x5e\x14\x0f\x1c\x6e\xad\xb8\x52\x68\xa8")); +snd($sock_b, $port_a, rtp(0, 4008, 9280, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4008, 9280, $ssrc, "\x97\x98\xc7\x16\x0a\x0e\x3a\x94\x8a\x90\xbb\x1e\x1a\x27\x56\x6f\x2f\x25\x3b\xa0\x8e\x8f\xaa\x19\x09\x0c\x28\x9c\x8f\x95\xb2\x31\x2e\x59\xcc\x37\x1b\x16\x26\xa1\x8c\x8b\x9d\x1f\x0c\x0c\x20\xab\x99\x9e\xbb\x5d\xbe\xa7\xac\x32\x13\x0d\x1a\xab\x8c\x89\x97\x2e\x10\x10\x21\xc3\xab\xbc\x4f\xd4\xa2\x96\x9c\x3f\x10\x0a\x12\xc4\x8f\x8a\x95\x57\x1b\x1a\x2b\xfd\x5d\x2d\x27\x62\x9b\x8e\x92\xc9\x12\x09\x0e\x3f\x97\x8e\x98\xc6\x2c\x2f\x6b\xd9\x2e\x1a\x18\x34\x9a\x8b\x8d\xab\x18\x0a\x0e\x2d\xa1\x98\xa1\xc7\x5b\xb9\xa7\xb4\x27\x10\x0e\x22\x9d\x8a\x8b\x9f\x20\x0e\x12\x2a\xb4\xaa\xc0\x50\xc0\x9e\x97\xa1\x2a\x0e\x0a\x19\xa8\x8c\x8b\x9b\x31\x18\x1b\x31")); +snd($sock_b, $port_a, rtp(0, 4009, 9440, 0x6543, "\x00" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4009, 9440, $ssrc, "\xda\x50\x2c\x2b\xc0\x97\x8e\x97\x39\x0e\x09\x13\xbf\x92\x8e\x9c\x57\x29\x31\xef\x72\x28\x19\x1b\x6d\x94\x8a\x8f\xce\x11\x0a\x11\x48\x9c\x98\xa5\xdc\x5e\xb5\xa9\xc6\x1f\x0f\x10\x31\x96\x89\x8d\xad\x19\x0e\x15\x37\xac\xaa\xc8\x57\xb7\x9c\x98\xac\x1e\x0c\x0c\x21\x9c\x8b\x8d\xa4\x25\x17\x1d\x3b\xcf\x48\x2b\x30\xae\x93\x8e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); + + + + +# transcoding, no RFC payload type present + +($sock_a, $sock_b) = new_call([qw(198.51.100.1 6018)], [qw(198.51.100.3 6020)]); + +($port_a) = offer('transcoding, no RFC payload type present', + { ICE => 'remove', replace => ['origin'], flags => ['inject DTMF'], + codec => { transcode => ['PCMA'] } }, < 'remove', replace => ['origin'], flags => ['inject DTMF'] }, < $ft, code => 'C', volume => 5, duration => 120 }); + +snd($sock_a, $port_b, rtp(0, 1002, 3320, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1002, 3320, $ssrc, "\xd5\xb9\xbe\x97\x05\x70\xea\x01\x3e\x31\x82\xa5\xb2\x63\x0f\x69\xc1\x0f\x3d\x06\xb3\xa4\x8a\x03\x35\x14\x75\x0e\x36\xcc\xb8\xa5\x9d\x36\x36\x68\x49\x0d\x0c\x81\xa5\xbf\x16\x3f\x37\x4f\xcf\x07\x13\xb4\xa5\xb4\x0a\x3b\x0b\xeb\xe9\x12\xc9\xb3\xb8\x92\x3c\x3a\x07\x87\x9c\x61\x93\xb2\xb3\x12\x25\x39\x76\x8b\x85\x5a\x85\xb3\x8e\x35\x24\x30\x85\xb1\x87\x57\x84\xb7\xeb\x3c\x24\x0d\xb4\xb2\x9b\x70\x98\x8c\x11\x3b\x38\x41\xbf\xb2\xeb\x15\x96\x9f\x0d\x3a\x30\x83\xba\xb1\x7b\x1b\xfa\xf2\x34\x39\x03\xb0\xa5\x88\x04\x03\x5f\x67\x37\x32\xdd\xb8\xba\x9d\x35\x0e\x71\x15\x37\x0a\x80\xa4\xbf\x15\x33\x09\x45\x15\x0b\x18\xb6\xa4\xb4\x08\x3f\x0d\xe5\x66")); +snd($sock_a, $port_b, rtp(0, 1003, 3480, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1003, 3480, $ssrc, "\x00\xcd\xbc\xba\x9c\x3d\x39\x1a\x9d\xd1\x1d\x98\xbe\xbd\x10\x3a\x3f\x73\x80\xe0\x64\x82\xbf\x8b\x35\x24\x31\x9f\x8b\x94\xdf\x8e\xb3\x96\x3c\x24\x02\x8b\xb7\x94\xf4\x8f\xb5\x10\x3a\x3b\x76\xb2\xb6\xe0\xd6\x80\x87\x09\x25\x33\x81\xb9\xb4\x74\x64\x9b\xe6\x31\x3a\x0d\xb1\xba\x8f\x1c\x11\x95\x6f\x32\x3f\x5e\xb8\xbb\x92\x0d\x1a\xf0\x19\x32\x37\x83\xa4\xbc\x6d\x37\x07\xd4\x04\x31\x07\xb1\xa4\xb4\x0c\x33\x04\xc5\x05\x0b\xd8\xbe\xa5\x9e\x30\x3d\x1d\xe0\x1d\x06\x84\xbb\xbf\x16\x38\x33\x73\x92\x6f\x15\x88\xbb\xb5\x35\x25\x37\x91\x86\x46\xda\xb7\xbf\x92\x3c\x25\x03\x8d\x8c\xf4\xef\xb7\xb6\x10\x25\x3b\x7f\xb6\x89\xf6\x95\xb5\x82\x0b\x24\x33\x84")); +snd($sock_a, $port_b, rtp(0, 1004, 3640, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1004, 3640, $ssrc, "\xbd\x8e\x5a\xec\x8c\xee\x33\x24\x0c\xb6\xbe\x80\x6b\xf5\x85\x6a\x3f\x39\x4a\xbe\xbe\x90\x05\x7f\x95\x06\x3e\x31\x80\xa5\xbd\x64\x0f\x6b\xcc\x0c\x3d\x00\xb0\xa4\xb5\x00\x34\x16\x4e\x0e\x36\x57\xb9\xa5\x99\x36\x36\x6a\x43\x0d\x0f\x86\xa5\xbe\x15\x3f\x36\x77\xf5\x07\x12\xb4\xa5\xb4\x0b\x3b\x0a\xee\xeb\x13\xd8\xb0\xb8\x9f\x3c\x3a\x01\x87\x9f\x66\x91\xb2\xb3\x11\x25\x39\x7a\x8b\x84\x5b\x9a\xb0\x89\x0a\x24\x33\x9b\xb1\x87\x54\x85\xb7\x97\x3d\x24\x0c\xb4\xb2\x9a\x73\x99\x8c\x14\x38\x3b\x7c\xbc\xbd\x94\x15\x97\x9e\x02\x3a\x33\x81\xba\xb0\x73\x1a\xfe\xf9\x35\x39\x02\xb1\xa4\x8a\x05\x03\x44\x7a\x37\x32\x40\xb8\xa5\x99\x0a\x0e\x72\x6b\x34\x35")); +snd($sock_a, $port_b, rtp(0, 1005, 3800, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1005, 3800, $ssrc, "\x81\xa4\xbe\x6c\x33\x08\x43\x68\x08\x1a\xb7\xa4\xb7\x0e\x3f\x0c\xfb\x65\x00\xd1\xbd\xba\x98\x32\x39\x04\x92\xdb\x1d\x9e\xbe\xbc\x17\x3a\x3f\x65\x80\xed\x67\x83\xbf\xb5\x0a\x24\x30\x9d\x8b\x97\xd0\x8f\xb3\x93\x3c\x24\x0c\x88\xb7\x96\xc9\x8c\xb5\x17\x3a\x3a\x64\xb3\xb6\xed\x56\x80\x86\x0f\x25\x32\x87\xb9\xb7\x4d\x66\x98\xe3\x36\x3a\x0c\xb1\xba\x8e\x1d\x10\xea\x63\x33\x3f\x70\xb9\xbb\x9f\x0d\x05\xf1\x1f\x33\x36\x81\xa4\xbf\x67\x34\x06\xd5\x05\x31\x06\xb6\xa4\xb7\x0d\x33\x07\xc5\x1a\x0a\x5f\xbe\xa5\x9a\x30\x3d\x1f\xe0\x12\x06\x9a\xbb\xbf\x6b\x39\x32\x7b\x9d\x62\x14\x89\xbb\xb4\x0b\x25\x36\x97\x86\x5e\xd1\xb4\xbf\x9e\x3c\x24\x0d\x82\x8c")); +snd($sock_a, $port_b, rtp(0, 1006, 3960, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1006, 3960, $ssrc, "\xf0\xe2\xb7\xb1\x14\x3a\x3b\x61\xb6\x88\xf3\xeb\xb5\x8d\x09\x24\x32\x85\xbd\x89\x5c\xe2\x8c\x95\x30\x24\x0e\xb7\xb9\x83\x68\xc3\x85\x6e\x3f\x38\x7a\xbe\xb9\x92\x05\x7a\x95\x07\x3e\x30\x86\xa5\xbd\x7c\x0f\x15\xcb\x0d\x3d\x03\xb1\xa4\xb4\x01\x34\x11\x40\x0f\x36\x48\xb9\xa5\x85\x37\x36\x14\x45\x02\x0f\x84\xa5\xbe\x6d\x3c\x36\x7d\xf1\x04\x1c\xb5\xa5\xb7\x09\x3b\x35\xed\xea\x13\x57\xb0\xb8\x9b\x3d\x3a\x00\x84\x9e\x66\x97\xb2\xb2\x15\x3a\x38\x60\x8b\x87\x58\x98\xb0\x88\x08\x24\x32\x9e\xb1\x86\x54\x9a\xb7\x90\x32\x24\x0e\xb5\xb2\x84\x73\x9f\x8c\x68\x38\x3b\x61\xbc\xbd\x96\x14\x94\x99\x03\x3b\x32\x87\xba\xb3\x48\x1a\xf2\xe5\x0a\x39\x0c\xb1")); +snd($sock_a, $port_b, rtp(0, 1007, 4120, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1007, 4120, $ssrc, "\xa4\xb5\x1a\x02\x4c\x7f\x37\x32\x7c\xb9\xa5\x9a\x0a\x09\x7e\x6e\x34\x35\x87\xa5\xbe\x67\x33\x0b\x48\x6e\x08\x05\xb7\xa4\xb6\x0f\x3f\x0e\xfe\x79\x00\x5a\xbd\xa5\x85\x32\x39\x07\x92\xcd\x1d\x9d\xbe\xbc\x69\x3b\x3e\x60\x80\xef\x66\x80\xbf\xb5\x08\x24\x30\x90\x8b\x91\xd5\x8c\xb3\x9f\x3d\x24\x0e\x89\xb7\x91\xc2\x8c\xb5\x68\x3b\x3a\x6d\xb3\xb1\xee\x5c\x81\x81\x0c\x25\x3d\x85\xb9\xb7\x58\x60\x99\xef\x37\x3a\x0e\xb6\xba\x89\x12\x13\xeb\x67\x33\x3e\x67\xb9\xba\x98\x02\x05\xf7\x1d\x33\x36\x87\xa4\xbe\x7c\x34\x01\x54\x1a\x31\x01\xb6\xa4\xb6\x03\x33\x06\xda\x18\x0a\x75\xbf\xa5\x84\x31\x3d\x19\xe0\x10\x01\x99\xbb\xbe\x62\x39\x3d\x66\x9d\x60\x17")); +snd($sock_a, $port_b, rtp(0, 1008, 4280, 0x1234, "\x00" x 160)); +rcv($sock_b, $port_a, rtpm(8, 1008, 4280, $ssrc, "\x2a" x 160)); + + + +snd($sock_b, $port_a, rtp(8, 4000, 8000, 0x6543, "\x2a" x 160)); +($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 8000, -1, "\x00" x 160)); +snd($sock_b, $port_a, rtp(8, 4001, 8160, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4001, 8160, $ssrc, "\x00" x 160)); + +$resp = rtpe_req('play DTMF', 'inject DTMF towards A', + { 'from-tag' => $tt, code => '4', volume => 3, duration => 150 }); + +snd($sock_b, $port_a, rtp(8, 4002, 8320, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4002, 8320, $ssrc, "\xff\x90\x8a\x93\xd9\x1b\x18\x27\x65\xe5\x33\x29\x4c\x9e\x8f\x91\xb8\x15\x09\x0d\x32\x98\x8e\x96\xbb\x2c\x2b\x4c\xd8\x34\x1c\x18\x2e\x9d\x8c\x8c\xa5\x1a\x0b\x0d\x27\xa3\x97\x9e\xbd\x4f\xc4\xaa\xb2\x2c\x12\x0e\x1e\xa1\x8b\x8a\x9c\x25\x0e\x10\x25\xb7\xa7\xb7\x5e\xcb\xa2\x98\x9f\x30\x0f\x0a\x16\xae\x8d\x8a\x98\x3a\x18\x19\x2c\xdd\xfd\x30\x2b\xce\x99\x8e\x95\x4c\x0f\x09\x10\xdf\x93\x8e\x9a\xec\x28\x2c\x56\xee\x2d\x1a\x1a\x48\x97\x8b\x8e\xba\x14\x0a\x0f\x39\x9d\x96\xa1\xcd\x4e\xbe\xab\xbe\x23\x10\x10\x2b\x99\x8a\x8c\xa7\x1b\x0d\x12\x2f\xad\xa7\xbc\x5e\xbd\x9f\x99\xa8\x23\x0d\x0b\x1d\x9f\x8b\x8c\x9f\x29\x16\x1b\x34\xcd\x60\x2f\x2f\xb6\x96")); +snd($sock_b, $port_a, rtp(8, 4003, 8480, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4003, 8480, $ssrc, "\x8e\x9b\x2b\x0c\x09\x17\xae\x8f\x8e\x9e\x3f\x25\x2e\x65\x5c\x28\x1a\x1e\xc2\x92\x8a\x92\x44\x0f\x0a\x14\xd6\x99\x97\xa6\x7c\x4e\xba\xad\xe5\x1d\x0f\x13\x49\x92\x89\x8e\xbe\x15\x0d\x16\x43\xa8\xa7\xc1\x66\xb5\x9d\x9a\xb6\x1b\x0c\x0d\x2b\x98\x8a\x8d\xab\x1f\x15\x1d\x3f\xc7\x52\x2e\x39\xaa\x93\x8f\xa3\x1e\x0b\x0b\x1e\x9f\x8d\x8f\xa7\x30\x23\x31\x7c\x4a\x24\x1a\x24\xac\x8e\x8b\x99\x28\x0c\x0a\x1a\xb0\x96\x98\xac\x4f\x53\xb7\xaf\x44\x19\x0f\x18\xba\x8e\x89\x93\x3f\x10\x0d\x1a\xd5\xa3\xa8\xca\xf9\xae\x9c\x9d\xec\x16\x0b\x10\x4e\x91\x89\x90\xc6\x1a\x14\x20\x55\xc3\x4a\x2f\x49\xa2\x91\x92\xb2\x17\x09\x0c\x2d\x99\x8d\x92\xb3\x29\x23\x36\xf2")); +snd($sock_b, $port_a, rtp(8, 4004, 8640, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4004, 8640, $ssrc, "\x3e\x20\x1b\x2d\xa0\x8d\x8c\xa1\x1c\x0a\x0c\x22\xa3\x94\x9a\xb5\x44\x5c\xb5\xb6\x32\x16\x0f\x1e\xa6\x8c\x8a\x99\x28\x0e\x0e\x20\xb7\xa1\xab\xd4\xdb\xaa\x9c\xa1\x38\x11\x0b\x15\xb5\x8d\x8a\x96\x3f\x16\x15\x26\xdd\xc2\x43\x31\xdf\x9d\x90\x96\x6d\x11\x09\x0f\x5a\x93\x8c\x97\xd2\x23\x23\x3b\xf6\x37\x1f\x1d\x40\x9a\x8c\x8e\xb2\x15\x09\x0e\x31\x9c\x93\x9c\xc2\x3e\x74\xb4\xbf\x29\x14\x11\x29\x9b\x8a\x8b\xa3\x1c\x0d\x0f\x2a\xab\x9f\xad\xe0\xcc\xa6\x9c\xa9\x28\x0e\x0c\x1c\xa2\x8b\x8b\x9c\x2a\x14\x17\x2c\xc6\xc4\x3e\x36\xbd\x99\x90\x9b\x30\x0d\x09\x15\xb3\x8f\x8d\x9b\x42\x1f\x25\x42\x70\x30\x1e\x1f\xcf\x95\x8b\x92\x58\x0f\x09\x12\x6f\x98\x93")); +snd($sock_b, $port_a, rtp(8, 4005, 8800, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4005, 8800, $ssrc, "\x9f\xe5\x3b\xe2\xb5\xd9\x21\x12\x14\x3e\x95\x89\x8d\xb6\x16\x0c\x13\x3a\xa4\x9f\xb1\xf1\xc0\xa3\x9d\xb4\x1e\x0d\x0d\x27\x99\x8a\x8c\xa7\x1f\x12\x19\x37\xbc\xc8\x3c\x3c\xaf\x97\x91\xa2\x21\x0b\x0a\x1c\xa2\x8d\x8e\xa2\x2f\x1e\x28\x4c\x5d\x2c\x1e\x25\xb0\x90\x8c\x98\x2c\x0c\x0a\x18\xb4\x94\x94\xa6\x4d\x3a\xd4\xb8\x4f\x1d\x11\x18\xc5\x8f\x89\x91\x4d\x10\x0c\x17\xec\x9f\xa0\xb8\xff\xba\xa1\x9f\xd3\x19\x0c\x0f\x3f\x92\x89\x8f\xbb\x19\x11\x1c\x48\xb8\xce\x3b\x4a\xa8\x95\x93\xaf\x19\x0a\x0c\x29\x99\x8c\x8f\xad\x27\x1d\x2b\x59\x4f\x29\x1e\x2d\xa5\x8e\x8d\x9f\x1e\x0b\x0b\x1e\xa4\x91\x96\xad\x3e\x3b\xcc\xbc\x3a\x1a\x12\x1e\xaa\x8d\x8a\x98\x2b")); +snd($sock_b, $port_a, rtp(8, 4006, 8960, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4006, 8960, $ssrc, "\x0e\x0c\x1d\xb8\x9d\xa2\xbe\xf9\xb4\xa0\xa3\x3f\x14\x0c\x14\xbd\x8e\x89\x93\x49\x15\x12\x1f\xe7\xb5\xd9\x3c\x7c\xa1\x93\x97\xd5\x13\x09\x0e\x45\x93\x8b\x93\xc4\x20\x1d\x2e\x6b\x46\x26\x1f\x3d\x9d\x8d\x8e\xae\x17\x09\x0d\x2c\x9c\x90\x98\xba\x36\x3d\xc7\xc4\x2e\x17\x13\x27\x9e\x8b\x8b\x9f\x1e\x0c\x0e\x25\xaa\x9c\xa5\xc8\xe8\xae\xa0\xaa\x2d\x10\x0c\x1b\xa6\x8c\x8a\x9a\x2c\x12\x13\x27\xc3\xb3\xed\x3e\xc8\x9d\x93\x9b\x38\x0f\x09\x13\xba\x8f\x8b\x98\x4a\x1d\x1e\x34\xf9\x3e\x24\x23\xea\x98\x8c\x92\xdf\x10\x09\x0f\x4d\x97\x90\x9c\xd2\x31\x3f\xc5\xd6\x28\x16\x16\x39\x97\x8a\x8d\xaf\x17\x0b\x10\x32\xa2\x9b\xa8\xd6\xd9\xac\xa1\xb3\x22\x0e\x0e")); +snd($sock_b, $port_a, rtp(8, 4007, 9120, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4007, 9120, $ssrc, "\x24\x9b\x8a\x8b\xa2\x1f\x10\x15\x2f\xb8\xb4\x68\x43\xb8\x9a\x94\xa1\x25\x0c\x0a\x1a\xa5\x8d\x8c\x9e\x30\x1b\x1f\x3c\xee\x38\x23\x28\xb8\x93\x8d\x97\x31\x0d\x09\x15\xb9\x93\x90\xa0\x4f\x2f\x46\xc4\x5e\x21\x15\x19\xd7\x91\x89\x90\x7b\x10\x0b\x14\x5b\x9d\x9c\xad\xed\xcd\xa9\xa3\xca\x1c\x0d\x10\x38\x94\x89\x8e\xb3\x19\x0f\x18\x3e\xb0\xb5\x59\x4d\xae\x98\x95\xad\x1c\x0b\x0c\x25\x9b\x8b\x8e\xa9\x26\x1a\x22\x46\xf5\x33\x23\x2e\xaa\x90\x8d\x9e\x21\x0b\x0a\x1c\xa6\x90\x92\xa8\x3b\x2e\x4d\xc7\x43\x1e\x15\x1e\xaf\x8e\x8a\x96\x2e\x0e\x0b\x1a\xbb\x9b\x9d\xb2\x68\xc5\xa8\xa7\x4c\x17\x0d\x14\xcb\x8f\x89\x91\x5e\x14\x0f\x1c\x6e\xad\xb8\x52\x68\xa8")); +snd($sock_b, $port_a, rtp(8, 4008, 9280, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4008, 9280, $ssrc, "\x97\x98\xc7\x16\x0a\x0e\x3a\x94\x8a\x90\xbb\x1e\x1a\x27\x56\x6f\x2f\x25\x3b\xa0\x8e\x8f\xaa\x19\x09\x0c\x28\x9c\x8f\x95\xb2\x31\x2e\x59\xcc\x37\x1b\x16\x26\xa1\x8c\x8b\x9d\x1f\x0c\x0c\x20\xab\x99\x9e\xbb\x5d\xbe\xa7\xac\x32\x13\x0d\x1a\xab\x8c\x89\x97\x2e\x10\x10\x21\xc3\xab\xbc\x4f\xd4\xa2\x96\x9c\x3f\x10\x0a\x12\xc4\x8f\x8a\x95\x57\x1b\x1a\x2b\xfd\x5d\x2d\x27\x62\x9b\x8e\x92\xc9\x12\x09\x0e\x3f\x97\x8e\x98\xc6\x2c\x2f\x6b\xd9\x2e\x1a\x18\x34\x9a\x8b\x8d\xab\x18\x0a\x0e\x2d\xa1\x98\xa1\xc7\x5b\xb9\xa7\xb4\x27\x10\x0e\x22\x9d\x8a\x8b\x9f\x20\x0e\x12\x2a\xb4\xaa\xc0\x50\xc0\x9e\x97\xa1\x2a\x0e\x0a\x19\xa8\x8c\x8b\x9b\x31\x18\x1b\x31")); +snd($sock_b, $port_a, rtp(8, 4009, 9440, 0x6543, "\x2a" x 160)); +rcv($sock_a, $port_b, rtpm(0, 4009, 9440, $ssrc, "\xda\x50\x2c\x2b\xc0\x97\x8e\x97\x39\x0e\x09\x13\xbf\x92\x8e\x9c\x57\x29\x31\xef\x72\x28\x19\x1b\x6d\x94\x8a\x8f\xce\x11\x0a\x11\x48\x9c\x98\xa5\xdc\x5e\xb5\xa9\xc6\x1f\x0f\x10\x31\x96\x89\x8d\xad\x19\x0e\x15\x37\xac\xaa\xc8\x57\xb7\x9c\x98\xac\x1e\x0c\x0c\x21\x9c\x8b\x8d\xa4\x25\x17\x1d\x3b\xcf\x48\x2b\x30\xae\x93\x8e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")); + + + + + + # SDP in/out tests, various ICE options new_call; @@ -1252,9 +1677,9 @@ SDP # RTP sequencing tests -my ($sock_a, $sock_b) = new_call([qw(198.51.100.1 2010)], [qw(198.51.100.3 2012)]); +($sock_a, $sock_b) = new_call([qw(198.51.100.1 2010)], [qw(198.51.100.3 2012)]); -my ($port_a) = offer('two codecs, no transcoding', { ICE => 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < $ft, blob => $wav_file }); +$resp = rtpe_req('play media', 'media playback, offer only', { 'from-tag' => $ft, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; my ($ts, $seq); @@ -3246,7 +3671,7 @@ SDP -($sock_a, $sock_b) = new_call([qw(198.51.100.1 7050)], [qw(198.51.100.3 7052)]); +($sock_a, $sock_b) = new_call([qw(198.51.100.1 8050)], [qw(198.51.100.3 8052)]); ($port_a) = offer('reverse DTMF transcoding - no-op', { ICE => 'remove', replace => ['origin'], flags => ['always transcode'] }, <