diff --git a/daemon/.gitignore b/daemon/.gitignore index 2ee5f696c..231c2c500 100644 --- a/daemon/.gitignore +++ b/daemon/.gitignore @@ -15,3 +15,4 @@ fix_frame_channel_layout.h socket.c streambuf.c ssllib.c +dtmflib.c diff --git a/daemon/Makefile b/daemon/Makefile index 86b47ecb1..79d26a7eb 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -126,7 +126,7 @@ SRCS= main.c kernel.c poller.c aux.c control_tcp.c call.c control_udp.c redis.c crypto.c rtp.c call_interfaces.strhash.c dtls.c log.c cli.c graphite.c ice.c \ media_socket.c homer.c recording.c statistics.c cdr.c ssrc.c iptables.c tcp_listener.c \ codec.c load.c dtmf.c timerthread.c media_player.c -LIBSRCS= loglib.c auxlib.c rtplib.c str.c socket.c streambuf.c ssllib.c +LIBSRCS= loglib.c auxlib.c rtplib.c str.c socket.c streambuf.c ssllib.c dtmflib.c ifeq ($(with_transcoding),yes) LIBSRCS+= codeclib.c resample.c endif diff --git a/daemon/codec.c b/daemon/codec.c index 31b72dd14..123f4dd94 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -128,22 +128,27 @@ static struct codec_handler *__handler_new(struct rtp_payload_type *pt) { static void __make_passthrough(struct codec_handler *handler) { __handler_shutdown(handler); - handler->func = handler_func_passthrough; - handler->kernelize = 1; + ilog(LOG_DEBUG, "Using passthrough handler for " STR_FORMAT, + STR_FMT(&handler->source_pt.encoding_with_params)); + if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf) + handler->func = handler_func_dtmf; + else { + handler->func = handler_func_passthrough; + handler->kernelize = 1; + } handler->dest_pt = handler->source_pt; handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler); } static void __make_passthrough_ssrc(struct codec_handler *handler) { __handler_shutdown(handler); - handler->func = handler_func_passthrough_ssrc; - handler->kernelize = 1; - handler->dest_pt = handler->source_pt; - handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler); -} - -static void __make_dtmf(struct codec_handler *handler) { - __handler_shutdown(handler); - handler->func = handler_func_dtmf; + ilog(LOG_DEBUG, "Using passthrough handler with new SSRC for " STR_FORMAT, + STR_FMT(&handler->source_pt.encoding_with_params)); + if (handler->source_pt.codec_def && handler->source_pt.codec_def->dtmf) + handler->func = handler_func_dtmf; + else { + handler->func = handler_func_passthrough_ssrc; + handler->kernelize = 1; + } handler->dest_pt = handler->source_pt; handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler); } @@ -222,7 +227,7 @@ static void __ensure_codec_def(struct rtp_payload_type *pt, struct call_media *m pt->codec_def = codec_find(&pt->encoding, media->type_id); if (!pt->codec_def) return; - if (!pt->codec_def->pseudocodec && (!pt->codec_def->support_encoding || !pt->codec_def->support_decoding)) + if (!pt->codec_def->support_encoding || !pt->codec_def->support_decoding) pt->codec_def = NULL; } @@ -231,6 +236,12 @@ static GList *__delete_send_codec(struct call_media *sender, GList *link) { &sender->codecs_prefs_send); } +// only called from codec_handlers_update() +static void __make_passthrough_gsl(struct codec_handler *handler, GSList **handlers) { + __make_passthrough(handler); + *handlers = g_slist_prepend(*handlers, handler); +} + // call must be locked in W void codec_handlers_update(struct call_media *receiver, struct call_media *sink, const struct sdp_ng_flags *flags) @@ -254,7 +265,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, for (GList *l = sink->codecs_prefs_send.head; l; l = l->next) { struct rtp_payload_type *pt = l->data; __ensure_codec_def(pt, sink); - if (!pt->codec_def || pt->codec_def->pseudocodec) // not supported, next + if (!pt->codec_def) // not supported, next continue; // fix up ptime @@ -263,7 +274,7 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, if (sink->ptime) pt->ptime = sink->ptime; - if (!pref_dest_codec) { + if (!pref_dest_codec && !pt->codec_def->supplemental) { ilog(LOG_DEBUG, "Default sink codec is " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); pref_dest_codec = pt; } @@ -281,13 +292,13 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // similarly, if the sink can receive a codec that the receiver can't send, it's also transcoding // XXX these blocks should go into their own functions - if (MEDIA_ISSET(sink, TRANSCODE) && !sink_transcoding) { + if (MEDIA_ISSET(sink, TRANSCODE)) { for (GList *l = sink->codecs_prefs_recv.head; l; l = l->next) { struct rtp_payload_type *pt = l->data; GQueue *recv_pts = g_hash_table_lookup(receiver->codec_names_recv, &pt->encoding); if (!recv_pts) { sink_transcoding = 1; - goto done; + continue; } // even if the receiver can receive the same codec that the sink can @@ -295,18 +306,17 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, // always-transcode in the offer for (GList *k = recv_pts->head; k; k = k->next) { // XXX codec_handlers can be converted to g_direct_hash table - int pt = GPOINTER_TO_INT(k->data); + int pt_num = GPOINTER_TO_INT(k->data); struct codec_handler *ch_recv = - g_hash_table_lookup(sink->codec_handlers, &pt); + g_hash_table_lookup(sink->codec_handlers, &pt_num); if (!ch_recv) continue; if (ch_recv->transcoder) { sink_transcoding = 1; - goto done; + break; } } } -done:; } // stop transcoding if we've determined that we don't need it @@ -343,11 +353,9 @@ done:; continue; } - if (!pt->codec_def->pseudocodec) { - ilog(LOG_DEBUG, "Accepting offered codec " STR_FORMAT " due to transcoding", - STR_FMT(&pt->encoding_with_params)); - MEDIA_SET(receiver, TRANSCODE); - } + ilog(LOG_DEBUG, "Accepting offered codec " STR_FORMAT " due to transcoding", + STR_FMT(&pt->encoding_with_params)); + MEDIA_SET(receiver, TRANSCODE); // we need a new pt entry pt = __rtp_payload_type_copy(pt); @@ -386,6 +394,8 @@ done:; // payload type to keep track of this. GHashTable *output_transcoders = g_hash_table_new(g_direct_hash, g_direct_equal); + int transcode_dtmf = 0; // is one of our destination codecs DTMF? + for (GList *l = receiver->codecs_prefs_recv.head; l; ) { struct rtp_payload_type *pt = l->data; @@ -416,14 +426,9 @@ done:; } // check our own support for this codec - if (!pt->codec_def || pt->codec_def->pseudocodec) { - // not supported, or not a real audio codec - if (pt->codec_def && pt->codec_def->dtmf) - __make_dtmf(handler); - else { - __make_passthrough(handler); - passthrough_handlers = g_slist_prepend(passthrough_handlers, handler); - } + if (!pt->codec_def) { + // not supported + __make_passthrough_gsl(handler, &passthrough_handlers); goto next; } @@ -440,15 +445,12 @@ done:; if (!pref_dest_codec) { ilog(LOG_DEBUG, "No known/supported sink codec for " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); - __make_passthrough(handler); - passthrough_handlers = g_slist_prepend(passthrough_handlers, handler); + __make_passthrough_gsl(handler, &passthrough_handlers); goto next; } struct rtp_payload_type *dest_pt; // transcode to this - // in case of ptime mismatch, we transcode - //struct rtp_payload_type *dest_pt = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); GQueue *dest_codecs = NULL; if (!flags || !flags->always_transcode) dest_codecs = g_hash_table_lookup(sink->codec_names_send, &pt->encoding); @@ -479,8 +481,7 @@ done:; // XXX check format parameters as well ilog(LOG_DEBUG, "Sink supports codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); - __make_passthrough(handler); - passthrough_handlers = g_slist_prepend(passthrough_handlers, handler); + __make_passthrough_gsl(handler, &passthrough_handlers); goto next; } @@ -488,6 +489,8 @@ unsupported: // the sink does not support this codec -> transcode ilog(LOG_DEBUG, "Sink does not support codec " STR_FORMAT, STR_FMT(&pt->encoding_with_params)); dest_pt = pref_dest_codec; + if (pt->codec_def->dtmf) + transcode_dtmf = 1; transcode:; // look up the reverse side of this payload type, which is the decoder to our // encoder. if any codec options such as bitrate were set during an offer, @@ -533,7 +536,13 @@ next: // must substitute the SSRC while (passthrough_handlers) { struct codec_handler *handler = passthrough_handlers->data; - __make_passthrough_ssrc(handler); + // if the sink does not support DTMF but we can receive it, we must transcode + // DTMF event packets to PCM. this requires all codecs to be transcoded to the + // sink's preferred destination codec. + if (!transcode_dtmf || !pref_dest_codec) + __make_passthrough_ssrc(handler); + else + __make_transcoder(handler, pref_dest_codec, output_transcoders); passthrough_handlers = g_slist_delete_link(passthrough_handlers, passthrough_handlers); } diff --git a/daemon/dtmf.c b/daemon/dtmf.c index fc2e71425..6019bcd21 100644 --- a/daemon/dtmf.c +++ b/daemon/dtmf.c @@ -2,24 +2,7 @@ #include "media_socket.h" #include "log.h" #include "call.h" - - -struct telephone_event_payload { - uint8_t event; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - unsigned volume:6; - unsigned r:1; - unsigned end:1; -#elif G_BYTE_ORDER == G_BIG_ENDIAN - unsigned end:1; - unsigned r:1; - unsigned volume:6; -#else -#error "byte order unknown" -#endif - uint16_t duration; -} __attribute__ ((packed)); - +#include "dtmflib.h" diff --git a/daemon/media_player.c b/daemon/media_player.c index e8dd70037..22d0cb000 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -248,7 +248,7 @@ static int __ensure_codec_handler(struct media_player *mp, AVStream *avs) { struct rtp_payload_type *dst_pt; for (GList *l = mp->media->codecs_prefs_send.head; l; l = l->next) { dst_pt = l->data; - if (dst_pt->codec_def && !dst_pt->codec_def->pseudocodec) + if (dst_pt->codec_def && !dst_pt->codec_def->supplemental) goto found; } dst_pt = NULL; diff --git a/include/dtmf.h b/include/dtmf.h index 9a94feebc..935514212 100644 --- a/include/dtmf.h +++ b/include/dtmf.h @@ -1,9 +1,6 @@ #ifndef _DTMF_H_ #define _DTMF_H_ -#include -#include -#include #include "str.h" diff --git a/lib/codeclib.c b/lib/codeclib.c index 7f9382b86..296f6def5 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef HAVE_BCG729 #include #include @@ -14,6 +15,7 @@ #include "resample.h" #include "rtplib.h" #include "bitstr.h" +#include "dtmflib.h" @@ -53,6 +55,9 @@ static void avc_encoder_close(encoder_t *enc); static int amr_decoder_input(decoder_t *dec, const str *data, GQueue *out); +static const char *dtmf_decoder_init(decoder_t *, const str *); +static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out); + @@ -74,6 +79,10 @@ static const codec_type_t codec_type_amr = { .encoder_input = avc_encoder_input, .encoder_close = avc_encoder_close, }; +static const codec_type_t codec_type_dtmf = { + .decoder_init = dtmf_decoder_init, + .decoder_input = dtmf_decoder_input, +}; #ifdef HAVE_BCG729 static packetizer_f packetizer_g729; // aggregate some frames into packets @@ -334,16 +343,19 @@ static codec_def_t __codec_defs[] = { .set_enc_options = amr_set_enc_options, .set_dec_options = amr_set_dec_options, }, - // pseudo-codecs { .rtpname = "telephone-event", .avcodec_id = -1, .packetizer = packetizer_passthrough, .media_type = MT_AUDIO, - .pseudocodec = 1, + .supplemental = 1, .dtmf = 1, .default_clockrate = 8000, .default_channels = 1, + .default_fmtp = "0-15", + .codec_type = &codec_type_dtmf, + .support_encoding = 1, + .support_decoding = 1, }, // for file reading and writing { @@ -635,6 +647,7 @@ int decoder_input_data(decoder_t *dec, const str *data, unsigned long ts, "%lu to %lu", dec->rtp_ts, ts); // XXX handle lost packets here if timestamps don't line up? + // XXX actually set the timestamp to the newly received one? dec->pts += dec->in_format.clockrate; } else @@ -760,9 +773,6 @@ void codeclib_init(int print) { if (def->codec_type && def->codec_type->def_init) def->codec_type->def_init(def); - if (def->pseudocodec) - continue; - if (print) { if (def->support_encoding && def->support_decoding) { if (def->default_channels > 0 && def->default_clockrate >= 0) @@ -1214,10 +1224,6 @@ static int encoder_fifo_flush(encoder_t *enc, int encoder_input_fifo(encoder_t *enc, AVFrame *frame, int (*callback)(encoder_t *, void *u1, void *u2), void *u1, void *u2) { - // fix up output pts - if (av_audio_fifo_size(enc->fifo) == 0) - enc->fifo_pts = frame->pts; - if (av_audio_fifo_write(enc->fifo, (void **) frame->extended_data, frame->nb_samples) < 0) return -1; @@ -1717,3 +1723,65 @@ static int packetizer_g729(AVPacket *pkt, GString *buf, str *input_output, encod return buf->len >= 2 ? 1 : 0; } #endif + + +static const char *dtmf_decoder_init(decoder_t *dec, const str *fmtp) { + dec->u.dtmf.event = -1; + return NULL; +} + +static int dtmf_decoder_input(decoder_t *dec, const str *data, GQueue *out) { + struct telephone_event_payload *dtmf; + if (data->len < sizeof(*dtmf)) { + ilog(LOG_WARN | LOG_FLAG_LIMIT, "Short DTMF event packet (len %u)", data->len); + return -1; + } + dtmf = (void *) data->s; + + // init if we need to + if (dtmf->event != dec->u.dtmf.event || dec->rtp_ts != dec->u.dtmf.start_ts) { + ZERO(dec->u.dtmf); + dec->u.dtmf.event = dtmf->event; + dec->u.dtmf.start_ts = dec->rtp_ts; + ilog(LOG_DEBUG, "New DTMF event starting: %u at TS %lu", dtmf->event, dec->rtp_ts); + } + + unsigned long duration = ntohs(dtmf->duration); + unsigned long frame_ts = dec->rtp_ts - dec->u.dtmf.start_ts + dec->u.dtmf.duration; + long num_samples = duration - dec->u.dtmf.duration; + + ilog(LOG_DEBUG, "Generate DTMF samples for event %u, start TS %lu, TS now %lu, frame TS %lu, " + "duration %lu, " + "old duration %lu, num samples %li", + dtmf->event, dec->u.dtmf.start_ts, dec->rtp_ts, frame_ts, + duration, dec->u.dtmf.duration, num_samples); + + if (num_samples <= 0) + return 0; + if (num_samples > dec->in_format.clockrate) { + ilog(LOG_ERR, "Cannot generate %li DTMF samples (clock rate %u)", num_samples, + dec->in_format.clockrate); + return -1; + } + + // synthesise PCM + // first get our frame and figure out how many samples we need, and the start offset + AVFrame *frame = av_frame_alloc(); + frame->nb_samples = num_samples; + frame->format = AV_SAMPLE_FMT_S16; + frame->sample_rate = dec->in_format.clockrate; + frame->channel_layout = AV_CH_LAYOUT_MONO; + frame->pts = frame_ts; + if (av_frame_get_buffer(frame, 0) < 0) + abort(); + + // fill samples + dtmf_samples(frame->extended_data[0], frame_ts, frame->nb_samples, dtmf->event, + dtmf->volume, dec->in_format.clockrate); + + g_queue_push_tail(out, frame); + + dec->u.dtmf.duration = duration; + + return 0; +} diff --git a/lib/codeclib.h b/lib/codeclib.h index 2a30a8891..91bc700cc 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -104,7 +104,7 @@ struct codec_def_s { support_decoding:1; // flags - int pseudocodec:1, + int supplemental:1, dtmf:1; // special case const codec_type_t *codec_type; @@ -141,6 +141,11 @@ struct decoder_s { #ifdef HAVE_BCG729 bcg729DecoderChannelContextStruct *bcg729; #endif + struct { + unsigned long start_ts; + unsigned int event; + unsigned long duration; + } dtmf; } u; unsigned long rtp_ts; diff --git a/lib/dtmflib.c b/lib/dtmflib.c new file mode 100644 index 000000000..5e6909b36 --- /dev/null +++ b/lib/dtmflib.c @@ -0,0 +1,65 @@ +#include "dtmflib.h" +#include +#include "compat.h" +#include "log.h" + +struct dtmf_freq { + unsigned int prim, + sec; +}; + +static const struct dtmf_freq dtmf_freqs[] = { + { 941, 1336 }, /* 0 */ + { 697, 1209 }, /* 1 */ + { 697, 1336 }, /* 2 */ + { 697, 1477 }, /* 3 */ + { 770, 1209 }, /* 4 */ + { 770, 1336 }, /* 5 */ + { 770, 1477 }, /* 6 */ + { 852, 1209 }, /* 7 */ + { 852, 1336 }, /* 8 */ + { 852, 1477 }, /* 9 */ + { 941, 1209 }, /* 10 = * */ + { 941, 1477 }, /* 11 = # */ + { 697, 1633 }, /* 12 = A */ + { 770, 1633 }, /* 13 = B */ + { 852, 1633 }, /* 14 = C */ + { 941, 1633 }, /* 15 = D */ +}; + + +INLINE double freq2iter(unsigned int hz, unsigned int sample_rate) { + double ret = hz; + ret *= 2 * M_PI; + ret /= sample_rate; + return ret; +} + +void dtmf_samples(void *buf, unsigned long offset, unsigned long num, unsigned int event, unsigned int volume, + unsigned int sample_rate) +{ + int16_t *samples = buf; + const struct dtmf_freq *df; + + if (event > G_N_ELEMENTS(dtmf_freqs)) { + ilog(LOG_WARN | LOG_FLAG_LIMIT, "Unsupported DTMF event %u", event); + memset(buf, 0, num * 2); + return; + } + df = &dtmf_freqs[event]; + + // XXX initialise/save these when the DTMF event starts + double vol = pow(1.122018, volume) * 2.0; + + double prim_freq = freq2iter(df->prim, sample_rate); + double sec_freq = freq2iter(df->sec, sample_rate); + + num += offset; // end here + while (offset < num) { + double prim = sin(prim_freq * offset) / vol; + double sec = sin(sec_freq * offset) / vol; + int16_t sample = prim * 32767.0 + sec * 32767.0; + *samples++ = sample; + offset++; + } +} diff --git a/lib/dtmflib.h b/lib/dtmflib.h new file mode 100644 index 000000000..4d1b08e03 --- /dev/null +++ b/lib/dtmflib.h @@ -0,0 +1,29 @@ +#ifndef _DTMFLIB_H_ +#define _DTMFLIB_H_ + +#include +#include + + +struct telephone_event_payload { + uint8_t event; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + unsigned volume:6; + unsigned r:1; + unsigned end:1; +#elif G_BYTE_ORDER == G_BIG_ENDIAN + unsigned end:1; + unsigned r:1; + unsigned volume:6; +#else +#error "byte order unknown" +#endif + uint16_t duration; +} __attribute__ ((packed)); + + +void dtmf_samples(void *buf, unsigned long offset, unsigned long num, unsigned int event, unsigned int volume, + unsigned int sample_rate); + + +#endif diff --git a/recording-daemon/.gitignore b/recording-daemon/.gitignore index 560af1052..37fdfb5af 100644 --- a/recording-daemon/.gitignore +++ b/recording-daemon/.gitignore @@ -14,3 +14,4 @@ fix_frame_channel_layout.h socket.c streambuf.c ssllib.c +dtmflib.c diff --git a/recording-daemon/Makefile b/recording-daemon/Makefile index ca6dbd81a..0bb5ca725 100644 --- a/recording-daemon/Makefile +++ b/recording-daemon/Makefile @@ -26,7 +26,8 @@ LDLIBS+= $(shell pkg-config --libs openssl) SRCS= epoll.c garbage.c inotify.c main.c metafile.c stream.c recaux.c packet.c \ decoder.c output.c mix.c db.c log.c forward.c tag.c poller.c -LIBSRCS= loglib.c auxlib.c rtplib.c codeclib.c resample.c str.c socket.c streambuf.c ssllib.c +LIBSRCS= loglib.c auxlib.c rtplib.c codeclib.c resample.c str.c socket.c streambuf.c ssllib.c \ + dtmflib.c OBJS= $(SRCS:.c=.o) $(LIBSRCS:.c=.o) include ../lib/common.Makefile diff --git a/t/.gitignore b/t/.gitignore index bab3807d2..cef8a1293 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -48,3 +48,4 @@ const_str_hash-test.strhash tests-preload.so timerthread.c media_player.c +dtmflib.c diff --git a/t/Makefile b/t/Makefile index 3b98df0e5..253330b9f 100644 --- a/t/Makefile +++ b/t/Makefile @@ -64,7 +64,7 @@ SRCS+= transcode-test.c ifeq ($(with_amr_tests),yes) SRCS+= amr-decode-test.c amr-encode-test.c endif -LIBSRCS+= codeclib.c resample.c socket.c streambuf.c +LIBSRCS+= codeclib.c resample.c socket.c streambuf.c dtmflib.c DAEMONSRCS+= codec.c call.c ice.c kernel.c media_socket.c stun.c bencode.c poller.c \ dtls.c recording.c statistics.c rtcp.c redis.c iptables.c graphite.c \ cookie_cache.c udp_listener.c homer.c load.c cdr.c dtmf.c timerthread.c \ @@ -109,9 +109,9 @@ daemon-tests: tests-preload.so bitstr-test: bitstr-test.o -amr-decode-test: amr-decode-test.o $(COMMONOBJS) codeclib.o resample.o +amr-decode-test: amr-decode-test.o $(COMMONOBJS) codeclib.o resample.o dtmflib.o -amr-encode-test: amr-encode-test.o $(COMMONOBJS) codeclib.o resample.o +amr-encode-test: amr-encode-test.o $(COMMONOBJS) codeclib.o resample.o dtmflib.o aes-crypt: aes-crypt.o $(COMMONOBJS) crypto.o @@ -120,10 +120,10 @@ transcode-test: transcode-test.o $(COMMONOBJS) codeclib.o resample.o codec.o ssr rtcp.o redis.o iptables.o graphite.o call_interfaces.strhash.o sdp.strhash.o rtp.o crypto.o \ control_ng.strhash.o \ streambuf.o cookie_cache.o udp_listener.o homer.o load.o cdr.o dtmf.o timerthread.o \ - media_player.o + media_player.o dtmflib.o payload-tracker-test: payload-tracker-test.o $(COMMONOBJS) ssrc.o aux.o auxlib.o rtp.o crypto.o codeclib.o \ - resample.o + resample.o dtmflib.o const_str_hash-test.strhash: const_str_hash-test.strhash.o $(COMMONOBJS) diff --git a/t/auto-daemon-tests.pl b/t/auto-daemon-tests.pl index 123ad3432..00b4f221b 100755 --- a/t/auto-daemon-tests.pl +++ b/t/auto-daemon-tests.pl @@ -52,6 +52,7 @@ sub new_call { $cid = $tag_iter++ . "-test-callID"; $ft = $tag_iter++ . "-test-fromtag"; $tt = $tag_iter++ . "-test-totag"; + print("new call $cid\n"); for my $p (@ports) { my ($addr, $port) = @{$p}; my $s = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp', @@ -2915,6 +2916,236 @@ rcv($sock_c, $port_a, qr/^\x00\x00\x01\x00\x00\x01\x01\x00$/s); +($sock_a, $sock_b) = new_call([qw(198.51.100.1 7010)], [qw(198.51.100.3 7012)]); + +($port_a) = offer('PCM to RFC DTMF transcoding', { ICE => 'remove', replace => ['origin'], + codec => { transcode => ['telephone-event'] }}, < ['origin'] }, < 'remove', replace => ['origin'], + codec => { transcode => ['PCMA', 'telephone-event'] }}, < ['origin'] }, < 'remove', replace => ['origin'], + codec => { transcode => ['telephone-event'] }}, < ['origin'] }, <