From a9ec666cb432f785585c0c81513f844a37dec904 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Tue, 19 Jun 2018 11:11:14 -0400 Subject: [PATCH] TT#38350 implement sending DTMF events to syslog Change-Id: I82fbdc7da6cbe2505ef1c98dd3c45b63c4461994 --- README.md | 7 + daemon/Makefile | 2 +- daemon/codec.c | 299 ++++++++++++++++++++++++++--------- daemon/dtmf.c | 90 +++++++++++ daemon/log.c | 10 +- daemon/log.h | 2 + daemon/main.c | 10 ++ daemon/media_socket.c | 10 +- include/codec.h | 2 +- include/dtmf.h | 15 ++ kernel-module/xt_RTPENGINE.c | 8 +- lib/.ycm_extra_conf.py | 1 + lib/codeclib.c | 14 +- lib/codeclib.h | 10 +- t/.gitignore | 1 + t/Makefile | 4 +- t/log.h | 11 ++ t/transcode-test.c | 163 ++++++++++++++++++- 18 files changed, 556 insertions(+), 103 deletions(-) create mode 100644 daemon/dtmf.c create mode 100644 include/dtmf.h diff --git a/README.md b/README.md index ba369999c..58be0284f 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ option and which are reproduced below: --log-facility=daemon|local0|... Syslog facility to use for logging --log-facility-cdr=local0|... Syslog facility to use for logging CDRs --log-facility-rtcp=local0|... Syslog facility to use for logging RTCP data (take care of traffic amount) + --log-facility-dtmf=local0|... Syslog facility to use for logging DTMF --log-format=default|parsable Log prefix format -E, --log-stderr Log on stderr instead of syslog -x, --xmlrpc-format=INT XMLRPC timeout request format to use. 0: SEMS DI, 1: call-id only @@ -380,6 +381,12 @@ The options are described in more detail below. Same as --log-facility with the difference that only RTCP data is written to this log facility. Be careful with this parameter since there may be a lot of information written to it. +* --log-facilty-dtmf=daemon|local0|...|local7|... + + Same as --log-facility with the difference that only DTMF events are written to this log facility. + DTMF events are extracted from RTP packets conforming to RFC 4733, are encoded in JSON format, + and written as soon as the end of an event is detected. + * --log-format=default|parsable Selects between multiple log output styles. The default is to prefix log lines with a description diff --git a/daemon/Makefile b/daemon/Makefile index 838f7692c..347e15724 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -116,7 +116,7 @@ SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_u bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c stun.c rtcp.c \ crypto.c rtp.c call_interfaces.c dtls.c log.c cli.c graphite.c ice.c socket.c \ media_socket.c homer.c recording.c statistics.c cdr.c ssrc.c iptables.c tcp_listener.c \ - codec.c load.c + codec.c load.c dtmf.c LIBSRCS= loglib.c auxlib.c rtplib.c str.c ifeq ($(with_transcoding),yes) LIBSRCS+= codeclib.c resample.c diff --git a/daemon/codec.c b/daemon/codec.c index 59a282ed8..abb89a410 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -10,6 +10,7 @@ #include "ssrc.h" #include "rtcp.h" #include "call_interfaces.h" +#include "dtmf.h" @@ -24,7 +25,7 @@ static void __rtp_payload_type_add_name(GHashTable *, struct rtp_payload_type *p static struct codec_handler codec_handler_stub = { .source_pt.payload_type = -1, .func = handler_func_passthrough, - .passthrough = 1, + .kernelize = 1, }; @@ -60,6 +61,7 @@ struct codec_ssrc_handler { format_t encoder_format; int ptime; int bytes_per_packet; + unsigned long ts_in; // for DTMF dupe detection unsigned long ts_out; u_int16_t seq_out; GString *sample_buffer; @@ -68,12 +70,19 @@ struct transcode_packet { seq_packet_t p; // must be first unsigned long ts; str *payload; + struct codec_handler *handler; // optional different handler (for DTMF) + int marker:1, + ignore_seq:1; + int (*func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *); + void (*dup_func)(struct codec_ssrc_handler *, struct transcode_packet *, struct media_packet *); }; static codec_handler_func handler_func_passthrough_ssrc; static codec_handler_func handler_func_transcode; +static codec_handler_func handler_func_dtmf; +static struct ssrc_entry *__ssrc_handler_transcode_new(void *p); static struct ssrc_entry *__ssrc_handler_new(void *p); static void __free_ssrc_handler(void *); @@ -83,14 +92,14 @@ static void __transcode_packet_free(struct transcode_packet *); static struct codec_handler codec_handler_stub_ssrc = { .source_pt.payload_type = -1, .func = handler_func_passthrough_ssrc, - .passthrough = 1, + .kernelize = 1, }; static void __handler_shutdown(struct codec_handler *handler) { free_ssrc_hash(&handler->ssrc_hash); - handler->passthrough = 0; + handler->kernelize = 0; } static void __codec_handler_free(void *pp) { @@ -108,13 +117,23 @@ 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->passthrough = 1; + 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->passthrough = 1; + 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; + handler->dest_pt = handler->source_pt; + handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler); } static void __make_transcoder(struct codec_handler *handler, struct rtp_payload_type *source, @@ -145,7 +164,7 @@ reset: handler->dest_pt = *dest; handler->func = handler_func_transcode; - handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_new, handler); + handler->ssrc_hash = create_ssrc_hash_full(__ssrc_handler_transcode_new, handler); ilog(LOG_DEBUG, "Created transcode context for " STR_FORMAT " -> " STR_FORMAT "", STR_FMT(&source->encoding_with_params), @@ -330,8 +349,12 @@ void codec_handlers_update(struct call_media *receiver, struct call_media *sink, if (!pt->codec_def || pt->codec_def->pseudocodec) { // not supported, or not a real audio codec - __make_passthrough(handler); - passthrough_handlers = g_slist_prepend(passthrough_handlers, handler); + if (pt->codec_def && pt->codec_def->dtmf) + __make_dtmf(handler); + else { + __make_passthrough(handler); + passthrough_handlers = g_slist_prepend(passthrough_handlers, handler); + } goto next; } @@ -497,6 +520,176 @@ static int handler_func_passthrough(struct codec_handler *h, struct media_packet return 0; } +static int __handler_func_sequencer(struct codec_handler *h, struct media_packet *mp, + struct transcode_packet *packet) +{ + struct codec_ssrc_handler *ch = get_ssrc(mp->rtp->ssrc, h->ssrc_hash); + if (G_UNLIKELY(!ch)) + return 0; + + atomic64_inc(&mp->ssrc_in->packets); + atomic64_add(&mp->ssrc_in->octets, mp->payload.len); + + packet->p.seq = ntohs(mp->rtp->seq_num); + packet->payload = str_dup(&mp->payload); + packet->ts = ntohl(mp->rtp->timestamp); + packet->marker = (mp->rtp->m_pt & 0x80) ? 1 : 0; + + // how should we retrieve packets from the sequencer? + void *(*seq_next_packet)(packet_sequencer_t *) = packet_sequencer_next_packet; + if (packet->ignore_seq) + seq_next_packet = packet_sequencer_force_next_packet; + + mutex_lock(&ch->lock); + + if (packet_sequencer_insert(&ch->sequencer, &packet->p)) { + // dupe + if (packet->dup_func) + packet->dup_func(ch, packet, mp); + else + ilog(LOG_DEBUG, "Ignoring duplicate RTP packet"); + mutex_unlock(&ch->lock); + obj_put(&ch->h); + __transcode_packet_free(packet); + atomic64_inc(&mp->ssrc_in->duplicates); + return 0; + } + + // got a new packet, run decoder + + while (1) { + packet = seq_next_packet(&ch->sequencer); + if (G_UNLIKELY(!packet)) + break; + + atomic64_set(&mp->ssrc_in->packets_lost, ch->sequencer.lost_count); + atomic64_set(&mp->ssrc_in->last_seq, ch->sequencer.ext_seq); + + ilog(LOG_DEBUG, "Decoding RTP packet: seq %u, TS %lu", + packet->p.seq, packet->ts); + + if (packet->func(ch, packet, mp)) + ilog(LOG_WARN, "Decoder error while processing RTP packet"); + __transcode_packet_free(packet); + } + + mutex_unlock(&ch->lock); + obj_put(&ch->h); + + return 0; +} + +static void __output_rtp(struct media_packet *mp, struct codec_ssrc_handler *ch, + struct codec_handler *handler, // normally == ch->handler except for DTMF + char *buf, // malloc'd, room for rtp_header + filled-in payload + unsigned int payload_len, + unsigned int payload_ts, + int marker, int seq, int seq_inc) +{ + struct rtp_header *rh = (void *) buf; + // reconstruct RTP header + unsigned int ts = payload_ts + ch->ts_out; + ZERO(*rh); + rh->v_p_x_cc = 0x80; + rh->m_pt = handler->dest_pt.payload_type | (marker ? 0x80 : 0); + if (seq != -1) + rh->seq_num = htons(seq); + else + rh->seq_num = htons(ch->seq_out += seq_inc); + rh->timestamp = htonl(ts); + rh->ssrc = htonl(mp->ssrc_in->ssrc_map_out); + + // add to output queue + struct codec_packet *p = g_slice_alloc(sizeof(*p)); + p->s.s = buf; + p->s.len = payload_len + sizeof(struct rtp_header); + payload_tracker_add(&mp->ssrc_out->tracker, handler->dest_pt.payload_type); + p->free_func = free; + g_queue_push_tail(&mp->packets_out, p); + + atomic64_inc(&mp->ssrc_out->packets); + atomic64_add(&mp->ssrc_out->octets, payload_len); + atomic64_set(&mp->ssrc_out->last_ts, ts); +} + +static void packet_dtmf_fwd(struct codec_ssrc_handler *ch, struct transcode_packet *packet, + struct media_packet *mp, int seq_inc) +{ + char *buf = malloc(packet->payload->len + sizeof(struct rtp_header)); + memcpy(buf + sizeof(struct rtp_header), packet->payload->s, packet->payload->len); + if (packet->ignore_seq) // inject original seq + __output_rtp(mp, ch, packet->handler ? : ch->handler, buf, packet->payload->len, packet->ts, + packet->marker, packet->p.seq, -1); + else // use our own sequencing + __output_rtp(mp, ch, packet->handler ? : ch->handler, buf, packet->payload->len, packet->ts, + packet->marker, -1, seq_inc); +} +static int packet_dtmf(struct codec_ssrc_handler *ch, struct transcode_packet *packet, struct media_packet *mp) +{ + if (ch->ts_in != packet->ts) { // ignore already processed events + int ret = dtmf_event(mp, packet->payload, ch->encoder_format.clockrate); + if (G_UNLIKELY(ret == -1)) // error + return -1; + if (ret == 1) { + // END event + ch->ts_in = packet->ts; + } + } + + packet_dtmf_fwd(ch, packet, mp, 1); + return 0; +} +static void packet_dtmf_dup(struct codec_ssrc_handler *ch, struct transcode_packet *packet, + struct media_packet *mp) +{ + packet_dtmf_fwd(ch, packet, mp, 0); +} + +static int handler_func_dtmf(struct codec_handler *h, struct media_packet *mp) { + if (G_UNLIKELY(!mp->rtp)) + return handler_func_passthrough(h, mp); + + assert((mp->rtp->m_pt & 0x7f) == h->source_pt.payload_type); + + // create new packet and insert it into sequencer queue + + ilog(LOG_DEBUG, "Received DTMF RTP packet: SSRC %" PRIx32 ", PT %u, seq %u, TS %u, len %i", + ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num), + ntohl(mp->rtp->timestamp), mp->payload.len); + + // determine the primary audio codec used by this SSRC, as the sequence numbers + // and timing info is shared with it. we'll need to use the same sequencer + + struct codec_handler *sequencer_h = h; // handler that contains the appropriate sequencer + if (mp->ssrc_in) { + for (int i = 0; i < mp->ssrc_in->tracker.most_len; i++) { + int prim_pt = mp->ssrc_in->tracker.most[i]; + if (prim_pt == 255) + continue; + + sequencer_h = codec_handler_get(mp->media, prim_pt); + if (sequencer_h == h) + continue; + ilog(LOG_DEBUG, "Primary RTP payload type for handling DTMF event is %i", prim_pt); + break; + } + } + + struct transcode_packet *packet = g_slice_alloc0(sizeof(*packet)); + packet->func = packet_dtmf; + packet->dup_func = packet_dtmf_dup; + packet->handler = h; // original handler for output RTP options (payload type) + + if (sequencer_h->kernelize) { + // this sequencer doesn't actually keep track of RTP seq properly. instruct + // the sequencer not to wait for the next in-seq packet but always return + // them immediately + packet->ignore_seq = 1; + } + + return __handler_func_sequencer(sequencer_h, mp, packet); +} + void codec_packet_free(void *pp) { @@ -616,6 +809,17 @@ static void __transcode_packet_free(struct transcode_packet *p) { } static struct ssrc_entry *__ssrc_handler_new(void *p) { + // XXX combine with __ssrc_handler_transcode_new + struct codec_handler *h = p; + struct codec_ssrc_handler *ch = obj_alloc0("codec_ssrc_handler", sizeof(*ch), __free_ssrc_handler); + ch->handler = h; + mutex_init(&ch->lock); + // needed for DTMF processing + packet_sequencer_init(&ch->sequencer, (GDestroyNotify) __transcode_packet_free); + return &ch->h; +} + +static struct ssrc_entry *__ssrc_handler_transcode_new(void *p) { struct codec_handler *h = p; ilog(LOG_DEBUG, "Creating SSRC transcoder from %s/%u/%i to " @@ -688,7 +892,8 @@ static void __free_ssrc_handler(void *chp) { } while (going); encoder_free(ch->encoder); } - g_string_free(ch->sample_buffer, TRUE); + if (ch->sample_buffer) + g_string_free(ch->sample_buffer, TRUE); } static int __packet_encoded(encoder_t *enc, void *u1, void *u2) { @@ -707,7 +912,6 @@ static int __packet_encoded(encoder_t *enc, void *u1, void *u2) { unsigned int pkt_len = sizeof(struct rtp_header) + payload_len + RTP_BUFFER_TAIL_ROOM; // prepare our buffers char *buf = malloc(pkt_len); - struct rtp_header *rh = (void *) buf; char *payload = buf + sizeof(struct rtp_header); // tell our packetizer how much we want str inout; @@ -725,26 +929,8 @@ static int __packet_encoded(encoder_t *enc, void *u1, void *u2) { } ilog(LOG_DEBUG, "Received packet of %i bytes from packetizer", inout.len); - // reconstruct RTP header - unsigned int ts = enc->avpkt.pts / enc->def->clockrate_mult + ch->ts_out; - ZERO(*rh); - rh->v_p_x_cc = 0x80; - rh->m_pt = ch->handler->dest_pt.payload_type; - rh->seq_num = htons(ch->seq_out++); - rh->timestamp = htonl(ts); - rh->ssrc = htonl(mp->ssrc_in->ssrc_map_out); - - // add to output queue - struct codec_packet *p = g_slice_alloc(sizeof(*p)); - p->s.s = buf; - p->s.len = inout.len + sizeof(struct rtp_header); - payload_tracker_add(&mp->ssrc_out->tracker, ch->handler->dest_pt.payload_type); - p->free_func = free; - g_queue_push_tail(&mp->packets_out, p); - - atomic64_inc(&mp->ssrc_out->packets); - atomic64_add(&mp->ssrc_out->octets, inout.len); - atomic64_set(&mp->ssrc_out->last_ts, ts); + __output_rtp(mp, ch, ch->handler, buf, inout.len, enc->avpkt.pts / enc->def->clockrate_mult, + 0, -1, 1); if (ret == 0) { // no more to go @@ -770,6 +956,11 @@ static int __packet_decoded(decoder_t *decoder, AVFrame *frame, void *u1, void * return 0; } +static int packet_decode(struct codec_ssrc_handler *ch, struct transcode_packet *packet, struct media_packet *mp) +{ + return decoder_input_data(ch->decoder, packet->payload, packet->ts, __packet_decoded, ch, mp); +} + static int handler_func_transcode(struct codec_handler *h, struct media_packet *mp) { if (G_UNLIKELY(!mp->rtp)) return handler_func_passthrough(h, mp); @@ -782,52 +973,10 @@ static int handler_func_transcode(struct codec_handler *h, struct media_packet * ntohl(mp->rtp->ssrc), mp->rtp->m_pt, ntohs(mp->rtp->seq_num), ntohl(mp->rtp->timestamp), mp->payload.len); - struct codec_ssrc_handler *ch = get_ssrc(mp->rtp->ssrc, h->ssrc_hash); - if (G_UNLIKELY(!ch)) - return 0; - - atomic64_inc(&mp->ssrc_in->packets); - atomic64_add(&mp->ssrc_in->octets, mp->payload.len); - struct transcode_packet *packet = g_slice_alloc0(sizeof(*packet)); - packet->p.seq = ntohs(mp->rtp->seq_num); - packet->payload = str_dup(&mp->payload); - packet->ts = ntohl(mp->rtp->timestamp); - - mutex_lock(&ch->lock); - - if (packet_sequencer_insert(&ch->sequencer, &packet->p)) { - // dupe - mutex_unlock(&ch->lock); - obj_put(&ch->h); - __transcode_packet_free(packet); - ilog(LOG_DEBUG, "Ignoring duplicate RTP packet"); - atomic64_inc(&mp->ssrc_in->duplicates); - return 0; - } - - // got a new packet, run decoder - - while (1) { - packet = packet_sequencer_next_packet(&ch->sequencer); - if (G_UNLIKELY(!packet)) - break; + packet->func = packet_decode; - atomic64_set(&mp->ssrc_in->packets_lost, ch->sequencer.lost_count); - atomic64_set(&mp->ssrc_in->last_seq, ch->sequencer.ext_seq); - - ilog(LOG_DEBUG, "Decoding RTP packet: seq %u, TS %lu", - packet->p.seq, packet->ts); - - if (decoder_input_data(ch->decoder, packet->payload, packet->ts, __packet_decoded, ch, mp)) - ilog(LOG_WARN, "Decoder error while processing RTP packet"); - __transcode_packet_free(packet); - } - - mutex_unlock(&ch->lock); - obj_put(&ch->h); - - return 0; + return __handler_func_sequencer(h, mp, packet); } diff --git a/daemon/dtmf.c b/daemon/dtmf.c new file mode 100644 index 000000000..fc2e71425 --- /dev/null +++ b/daemon/dtmf.c @@ -0,0 +1,90 @@ +#include "dtmf.h" +#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)); + + + + +static GString *dtmf_json_print(struct media_packet *mp, + struct telephone_event_payload *dtmf, int clockrate) +{ + if (!dtmf->end) + return NULL; + + GString *buf = g_string_new(""); + + if (!clockrate) + clockrate = 8000; + + g_string_append_printf(buf, "{" + "\"callid\":\"" STR_FORMAT "\"," + "\"source_tag\":\"" STR_FORMAT "\"," + "\"tags\":[", + STR_FMT(&mp->call->callid), + STR_FMT(&mp->media->monologue->tag)); + + GList *tag_values = g_hash_table_get_values(mp->call->tags); + int i = 0; + for (GList *tag_it = tag_values; tag_it; tag_it = tag_it->next) { + struct call_monologue *ml = tag_it->data; + if (i != 0) + g_string_append(buf, ","); + g_string_append_printf(buf, "\"" STR_FORMAT "\"", + STR_FMT(&ml->tag)); + i++; + } + g_list_free(tag_values); + + g_string_append_printf(buf, "]," + "\"type\":\"DTMF\",\"timestamp\":%lu,\"source_ip\":\"%s\"," + "\"event\":%u,\"duration\":%u,\"volume\":%u}", + (unsigned long) rtpe_now.tv_sec, + sockaddr_print_buf(&mp->fsin.address), + (unsigned int) dtmf->event, + (ntohs(dtmf->duration) * (1000000 / clockrate)) / 1000, + (unsigned int) dtmf->volume); + + return buf; +} + +int dtmf_event(struct media_packet *mp, str *payload, int clockrate) { + struct telephone_event_payload *dtmf; + if (payload->len < sizeof(*dtmf)) { + ilog(LOG_WARN | LOG_FLAG_LIMIT, "Short DTMF event packet (len %u)", payload->len); + return -1; + } + dtmf = (void *) payload->s; + + ilog(LOG_DEBUG, "DTMF event: event %u, volume %u, end %u, duration %u", + dtmf->event, dtmf->volume, dtmf->end, dtmf->duration); + + int ret = 0; + + if (_log_facility_dtmf) { + GString *buf = dtmf_json_print(mp, dtmf, clockrate); + if (buf) { + dtmflog(buf); + ret = 1; // END event + } + } + + return ret; +} diff --git a/daemon/log.c b/daemon/log.c index 6f2038c73..8ab647ce0 100644 --- a/daemon/log.c +++ b/daemon/log.c @@ -17,6 +17,7 @@ struct log_info __thread log_info; int _log_facility_cdr = 0; int _log_facility_rtcp = 0; +int _log_facility_dtmf = 0; typedef void (ilog_prefix_func)(char *prefix, size_t prefix_len); @@ -106,7 +107,7 @@ void __ilog(int prio, const char *fmt, ...) { } void log_format(enum log_format f) { - if (f < 0 || f >= __LF_LAST) + if (f >= __LF_LAST) die("Invalid log format enum"); ilog_prefix = ilog_prefix_funcs[f]; if (!ilog_prefix) @@ -119,6 +120,13 @@ void cdrlog(const char* cdrbuffer) { } } +void dtmflog(GString *s) { + if (_log_facility_dtmf) { + syslog(LOG_INFO | _log_facility_dtmf, "%s", s->str); + } + g_string_free(s, TRUE); +} + void rtcplog(const char* cdrbuffer) { syslog(LOG_INFO | _log_facility_rtcp, "%s", cdrbuffer); diff --git a/daemon/log.h b/daemon/log.h index 626f41373..e8864ca07 100644 --- a/daemon/log.h +++ b/daemon/log.h @@ -33,6 +33,7 @@ struct log_info { extern int _log_facility_cdr; extern int _log_facility_rtcp; +extern int _log_facility_dtmf; extern struct log_info __thread log_info; @@ -41,6 +42,7 @@ extern struct log_info __thread log_info; void cdrlog(const char* cdrbuffer); void rtcplog(const char* cdrbuffer); +void dtmflog(GString *s); void log_format(enum log_format); diff --git a/daemon/main.c b/daemon/main.c index 565df4cb4..da548aaad 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -295,6 +295,7 @@ static void options(int *argc, char ***argv) { char *redisps_write = NULL; char *log_facility_cdr_s = NULL; char *log_facility_rtcp_s = NULL; + char *log_facility_dtmf_s = NULL; char *log_format = NULL; int sip_source = 0; char *homerp = NULL; @@ -336,6 +337,7 @@ static void options(int *argc, char ***argv) { { "b2b-url", 'b', 0, G_OPTION_ARG_STRING, &rtpe_config.b2b_url, "XMLRPC URL of B2B UA" , "STRING" }, { "log-facility-cdr",0, 0, G_OPTION_ARG_STRING, &log_facility_cdr_s, "Syslog facility to use for logging CDRs", "daemon|local0|...|local7"}, { "log-facility-rtcp",0, 0, G_OPTION_ARG_STRING, &log_facility_rtcp_s, "Syslog facility to use for logging RTCP", "daemon|local0|...|local7"}, + { "log-facility-dtmf",0, 0, G_OPTION_ARG_STRING, &log_facility_dtmf_s, "Syslog facility to use for logging DTMF", "daemon|local0|...|local7"}, { "log-format", 0, 0, G_OPTION_ARG_STRING, &log_format, "Log prefix format", "default|parsable"}, { "xmlrpc-format",'x', 0, G_OPTION_ARG_INT, &rtpe_config.fmt, "XMLRPC timeout request format to use. 0: SEMS DI, 1: call-id only", "INT" }, { "num-threads", 0, 0, G_OPTION_ARG_INT, &rtpe_config.num_threads, "Number of worker threads to create", "INT" }, @@ -476,6 +478,7 @@ static void options(int *argc, char ***argv) { if (rtpe_config.fmt > 1) die("Invalid XMLRPC format"); + // XXX unify the log facility options if (log_facility_cdr_s) { if (!parse_log_facility(log_facility_cdr_s, &_log_facility_cdr)) { print_available_log_facilities(); @@ -490,6 +493,13 @@ static void options(int *argc, char ***argv) { } } + if (log_facility_dtmf_s) { + if (!parse_log_facility(log_facility_dtmf_s, &_log_facility_dtmf)) { + print_available_log_facilities(); + die ("Invalid log facility for DTMF '%s' (--log-facility-dtmf)n", log_facility_dtmf_s); + } + } + if (log_format) { if (!strcmp(log_format, "default")) rtpe_config.log_format = LF_DEFAULT; diff --git a/daemon/media_socket.c b/daemon/media_socket.c index 92b4cfcbf..ca132cea0 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1055,12 +1055,10 @@ void kernelize(struct packet_stream *stream) { break; } rs = l->data; - if (MEDIA_ISSET(stream->media, TRANSCODE)) { - // only add payload types that are passthrough - struct codec_handler *ch = codec_handler_get(stream->media, rs->payload_type); - if (!ch->passthrough) - continue; - } + // only add payload types that are passthrough + struct codec_handler *ch = codec_handler_get(stream->media, rs->payload_type); + if (!ch->kernelize) + continue; reti.payload_types[reti.num_payload_types++] = rs->payload_type; } g_list_free(values); diff --git a/include/codec.h b/include/codec.h index 1bb708abe..aeea490d2 100644 --- a/include/codec.h +++ b/include/codec.h @@ -23,7 +23,7 @@ struct codec_handler { struct rtp_payload_type source_pt; // source_pt.payload_type = hashtable index struct rtp_payload_type dest_pt; codec_handler_func *func; - int passthrough; + int kernelize:1; struct ssrc_hash *ssrc_hash; }; diff --git a/include/dtmf.h b/include/dtmf.h new file mode 100644 index 000000000..9a94feebc --- /dev/null +++ b/include/dtmf.h @@ -0,0 +1,15 @@ +#ifndef _DTMF_H_ +#define _DTMF_H_ + +#include +#include +#include +#include "str.h" + + +struct media_packet; + +int dtmf_event(struct media_packet *, str *, int); + + +#endif diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index d1f445751..d89911ee0 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -3792,10 +3792,8 @@ static inline int rtp_payload_type(const struct rtp_header *hdr, const struct rt pt = hdr->m_pt & 0x7f; match = bsearch(&pt, tg->payload_types, tg->num_payload_types, sizeof(pt), rtp_payload_match); - if (!match) { - log_err("RTP payload type %u not found", (unsigned int) pt); + if (!match) return -1; - } return match - tg->payload_types; } #endif @@ -3944,8 +3942,8 @@ src_check_ok: if (unlikely((g->target.ssrc) && (g->target.ssrc != rtp.header->ssrc))) goto skip_error; - // if transcoding, only forward packets of passthrough payload types - if (g->target.transcoding && rtp_pt_idx < 0) + // if RTP, only forward packets of known/passthrough payload types + if (g->target.rtp && rtp_pt_idx < 0) goto skip1; pkt_idx = packet_index(&g->decrypt, &g->target.decrypt, rtp.header); diff --git a/lib/.ycm_extra_conf.py b/lib/.ycm_extra_conf.py index 6e2d633f5..566f433a9 100644 --- a/lib/.ycm_extra_conf.py +++ b/lib/.ycm_extra_conf.py @@ -23,6 +23,7 @@ flags = [ '-D__DEBUG=1', '-D__YCM=1', '-I../daemon', + '-I../include', '-I/home/dfx/src/bcg729/include', '-DRTPENGINE_VERSION="dummy"', '-DRE_PLUGIN_DIR="/usr/lib/rtpengine"', diff --git a/lib/codeclib.c b/lib/codeclib.c index aade19483..f1396dc2c 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -338,10 +338,12 @@ static codec_def_t __codec_defs[] = { { .rtpname = "telephone-event", .avcodec_id = -1, - .avcodec_name = NULL, .packetizer = packetizer_passthrough, .media_type = MT_AUDIO, .pseudocodec = 1, + .dtmf = 1, + .default_clockrate = 8000, + .default_channels = 1, }, // for file writing { @@ -806,7 +808,7 @@ static int packet_tree_search(const void *testseq_p, const void *ts_p) { return -1; } // caller must take care of locking -void *packet_sequencer_next_packet(packet_sequencer_t *ps) { +static void *__packet_sequencer_next_packet(packet_sequencer_t *ps, int num_wait) { // see if we have a packet with the correct seq nr in the queue seq_packet_t *packet = g_tree_lookup(ps->packets, GINT_TO_POINTER(ps->seq)); if (G_LIKELY(packet != NULL)) { @@ -820,7 +822,7 @@ void *packet_sequencer_next_packet(packet_sequencer_t *ps) { dbg("packet queue empty"); return NULL; } - if (G_LIKELY(nnodes < 10)) { // XXX arbitrary value + if (G_LIKELY(nnodes < num_wait)) { dbg("only %i packets in queue - waiting for more", nnodes); return NULL; // need to wait for more } @@ -867,6 +869,12 @@ out: return packet; } +void *packet_sequencer_next_packet(packet_sequencer_t *ps) { + return __packet_sequencer_next_packet(ps, 10); // arbitrary value +} +void *packet_sequencer_force_next_packet(packet_sequencer_t *ps) { + return __packet_sequencer_next_packet(ps, 0); +} int packet_sequencer_insert(packet_sequencer_t *ps, seq_packet_t *p) { // check seq for dupes diff --git a/lib/codeclib.h b/lib/codeclib.h index f87f796fc..1261c29e8 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -100,9 +100,12 @@ struct codec_def_s { // filled in by codeclib_init() str rtpname_str; int rfc_payload_type; - int support_encoding, - support_decoding, - pseudocodec; + int support_encoding:1, + support_decoding:1; + + // flags + int pseudocodec:1, + dtmf:1; // special case const codec_type_t *codec_type; @@ -218,6 +221,7 @@ int encoder_input_fifo(encoder_t *enc, AVFrame *frame, void packet_sequencer_init(packet_sequencer_t *ps, GDestroyNotify); void packet_sequencer_destroy(packet_sequencer_t *ps); void *packet_sequencer_next_packet(packet_sequencer_t *ps); +void *packet_sequencer_force_next_packet(packet_sequencer_t *ps); int packet_sequencer_insert(packet_sequencer_t *ps, seq_packet_t *); diff --git a/t/.gitignore b/t/.gitignore index b14d5778d..0b00585b5 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -43,3 +43,4 @@ stun.c transcode-test udp_listener.c payload-tracker-test +dtmf.c diff --git a/t/Makefile b/t/Makefile index 1d70fcb84..9d53e4775 100644 --- a/t/Makefile +++ b/t/Makefile @@ -61,7 +61,7 @@ DAEMONSRCS= crypto.c ifeq ($(with_transcoding),yes) DAEMONSRCS+= codec.c ssrc.c call.c ice.c aux.c kernel.c media_socket.c stun.c bencode.c socket.c poller.c \ dtls.c recording.c statistics.c rtcp.c redis.c iptables.c graphite.c call_interfaces.c sdp.c \ - rtp.c control_ng.c streambuf.c cookie_cache.c udp_listener.c homer.c load.c cdr.c + rtp.c control_ng.c streambuf.c cookie_cache.c udp_listener.c homer.c load.c cdr.c dtmf.c endif OBJS= $(SRCS:.c=.o) $(LIBSRCS:.c=.o) $(DAEMONSRCS:.c=.o) @@ -94,6 +94,6 @@ aes-crypt: aes-crypt.o $(COMMONOBJS) crypto.o transcode-test: transcode-test.o $(COMMONOBJS) codeclib.o resample.o codec.o ssrc.o call.o ice.o aux.o \ kernel.o media_socket.o stun.o bencode.o socket.o poller.o dtls.o recording.o statistics.o \ rtcp.o redis.o iptables.o graphite.o call_interfaces.o sdp.o rtp.o crypto.o control_ng.o \ - streambuf.o cookie_cache.o udp_listener.o homer.o load.o cdr.o + streambuf.o cookie_cache.o udp_listener.o homer.o load.o cdr.o dtmf.o payload-tracker-test: payload-tracker-test.o $(COMMONOBJS) ssrc.o aux.o auxlib.o rtp.o crypto.o diff --git a/t/log.h b/t/log.h index 8ef7a2482..2ebaa381b 100644 --- a/t/log.h +++ b/t/log.h @@ -10,5 +10,16 @@ INLINE void cdrlog(const char *x) { } extern int _log_facility_rtcp; extern int _log_facility_cdr; +extern int _log_facility_dtmf; +extern GString *dtmf_logs; + +INLINE void dtmflog(GString *s) { + if (!dtmf_logs) + dtmf_logs = g_string_new(""); + if (dtmf_logs->len > 0) + g_string_append(dtmf_logs, "\n"); + g_string_append_len(dtmf_logs, s->str, s->len); + g_string_free(s, TRUE); +} #endif diff --git a/t/transcode-test.c b/t/transcode-test.c index ceb645045..7ed93c505 100644 --- a/t/transcode-test.c +++ b/t/transcode-test.c @@ -7,8 +7,10 @@ int _log_facility_rtcp; int _log_facility_cdr; +int _log_facility_dtmf; struct rtpengine_config rtpe_config; struct poller *rtpe_poller; +GString *dtmf_logs; static str *sdup(char *s) { str *r = g_slice_alloc(sizeof(*r)); @@ -35,6 +37,8 @@ static struct call call; static struct sdp_ng_flags flags; static struct call_media *media_A; static struct call_media *media_B; +struct call_monologue ml_A; +struct call_monologue ml_B; static GQueue rtp_types; #define start() __start(__FILE__, __LINE__) @@ -47,10 +51,18 @@ static void __start(const char *file, int line) { ssrc_B = 2345; call = (struct call) {{0,},}; call.ssrc_hash = create_ssrc_hash_call(); + call.tags = g_hash_table_new(g_str_hash, g_str_equal); + str_init(&call.callid, "test-call"); flags = (struct sdp_ng_flags) {0,}; bencode_buffer_init(&call.buffer); media_A = call_media_new(&call); // originator media_B = call_media_new(&call); // output destination + ml_A = (struct call_monologue) {0,}; + str_init(&ml_A.tag, "tag_A"); + media_A->monologue = &ml_A; + ml_B = (struct call_monologue) {0,}; + str_init(&ml_B.tag, "tag_B"); + media_B->monologue = &ml_B; g_queue_init(&rtp_types); // parsed from received SDP flags.codec_strip = g_hash_table_new_full(str_hash, str_equal, str_slice_free, NULL); flags.codec_mask = g_hash_table_new_full(str_hash, str_equal, str_slice_free, NULL); @@ -105,21 +117,34 @@ static void __expect(const char *file, int line, GQueue *dumper, const char *cod #define packet_seq_ts(side, pt_in, pload, rtp_ts, rtp_seq, pt_out, pload_exp, ts_exp, fatal) \ __packet_seq_ts( __FILE__, __LINE__, media_ ## side, pt_in, (str) STR_CONST_INIT(pload), \ (str) STR_CONST_INIT(pload_exp), ssrc_ ## side, rtp_ts, rtp_seq, pt_out, \ - ts_exp, fatal) + ts_exp, 1, fatal) + +#define packet_seq_exp(side, pt_in, pload, rtp_ts, rtp_seq, pt_out, pload_exp, ts_diff_exp) \ + __packet_seq_ts( __FILE__, __LINE__, media_ ## side, pt_in, (str) STR_CONST_INIT(pload), \ + (str) STR_CONST_INIT(pload_exp), ssrc_ ## side, rtp_ts, rtp_seq, pt_out, \ + -1, ts_diff_exp, 1) static void __packet_seq_ts(const char *file, int line, struct call_media *media, long long pt_in, str pload, str pload_exp, uint32_t ssrc, long long rtp_ts, long long rtp_seq, long long pt_out, - long long ts_exp, int fatal) + long long ts_exp, int seq_diff_exp, int fatal) { printf("running test %s:%i\n", file, line); - struct codec_handler *h = codec_handler_get(media, pt_in); + struct codec_handler *h = codec_handler_get(media, pt_in & 0x7f); str pl = pload; str pl_exp = pload_exp; + + // from media_packet_rtp() struct media_packet mp = { + .call = &call, .media = media, .ssrc_in = get_ssrc_ctx(ssrc, call.ssrc_hash, SSRC_DIR_INPUT), }; + // from __stream_ssrc() + if (!MEDIA_ISSET(media, TRANSCODE)) + mp.ssrc_in->ssrc_map_out = ntohl(ssrc); mp.ssrc_out = get_ssrc_ctx(mp.ssrc_in->ssrc_map_out, call.ssrc_hash, SSRC_DIR_OUTPUT); + payload_tracker_add(&mp.ssrc_in->tracker, pt_in & 0x7f); + int packet_len = sizeof(struct rtp_header) + pl.len; char *packet = malloc(packet_len); struct rtp_header *rtp = (void *) packet; @@ -166,8 +191,8 @@ static void __packet_seq_ts(const char *file, int line, struct call_media *media uint32_t ts = ntohl(rtp->timestamp); uint16_t seq = ntohs(rtp->seq_num); uint32_t ssrc = ntohl(rtp->ssrc); - uint32_t ssrc_pt = ssrc ^ pt_out; - ssrc_pt ^= pt_in << 8; /* XXX this is actually wrong and should be removed. it's a workaround for a bug */ + uint32_t ssrc_pt = ssrc ^ (pt_out & 0x7f); + ssrc_pt ^= (pt_in & 0x7f) << 8; /* XXX this is actually wrong and should be removed. it's a workaround for a bug */ printf("RTP SSRC %x seq %u TS %u PT %u\n", (unsigned int) ssrc, (unsigned int) seq, (unsigned int) ts, (unsigned int) rtp->m_pt); if (g_hash_table_contains(rtp_ts_ht, GUINT_TO_POINTER(ssrc_pt))) { @@ -184,7 +209,7 @@ static void __packet_seq_ts(const char *file, int line, struct call_media *media GUINT_TO_POINTER(ssrc_pt))); uint16_t diff = seq - old_seq; printf("RTP seq diff: %u\n", (unsigned int) diff); - assert(diff == 1); + assert(diff == seq_diff_exp); } g_hash_table_insert(rtp_seq_ht, GUINT_TO_POINTER(ssrc_pt), GUINT_TO_POINTER(seq)); if (str_shift(&cp->s, sizeof(struct rtp_header))) @@ -212,6 +237,24 @@ static void end() { g_hash_table_destroy(rtp_seq_ht); } +static void dtmf(const char *s) { + if (!dtmf_logs) { + if (strlen(s) != 0) + abort(); + return; + } + if (strlen(s) != dtmf_logs->len) { + printf("DTMF mismatch: \"%s\" != \"%s\"\n", s, dtmf_logs->str); + abort(); + } + if (memcmp(s, dtmf_logs->str, dtmf_logs->len) != 0) { + printf("DTMF mismatch: \"%s\" != \"%s\"\n", s, dtmf_logs->str); + abort(); + } + printf("DTMF log ok; contents: \"%s\"\n", dtmf_logs->str); + g_string_assign(dtmf_logs, ""); +} + #define PCMU_payload "\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00" #define PCMA_payload "\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a\x2b\x2a" #define G722_payload "\x23\x84\x20\x84\x20\x84\x04\x84\x04\x04\x84\x04\x84\x04\x84\x05\x85\x46\x87\x48\xc8\x48\x88\x48\xc8\x49\x8a\x4b\xcc\x4c\x8c\x4c\xcc\x4c\x8c\x4d\xce\x50\xcf\x51\x90\x50\xcf\x12\xd1\x52\xd2\x54\x91\x52\xd2\x54\x92\x54\xd3\x56\x93\xd6\x94\xd4\x93\xd7\xd5\x55\x94\x55\xd5\x55\xd4\x56\xd5\x17\xd7\x5a\x95\xd7\x97\xd9\xd4\x16\x58\x57\x98\xd5\xd7\x5b\x96\xda\xd6\x1b\x57\x5a\xd6\x1a\x57\x5b\x98\xd6\xd8\x56\x98\xd7\xd9\x5a\x95\xdb\xd6\x1c\x52\x5e\xd7\x5c\x93\xdf\x99\xd5\xd7\x5f\xd9\x14\x56\x7f\x92\xda\xd9\x5c\x92\xdd\xd7\x5d\x92\xff\xd6\x5a\x96\xdc\xd5\x18\x56\x7e\xd2\x5e\x96\xde\x94\xd8\xd8\x58\xd3\x79\x93\xfb\x90\xdc\xd6\x5b\xdd\x58\x96\xff" @@ -220,6 +263,7 @@ static void end() { int main() { codeclib_init(0); + srandom(time(NULL)); // plain start(); @@ -603,5 +647,112 @@ int main() { expect(B, send, "9/G722/8000 8/PCMA/8000"); end(); + _log_facility_dtmf = 1; // dummy enabler + + // plain DTMF passthrough w/o transcoding + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + offer(); + expect(A, recv, ""); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, ""); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, send, "8/PCMA/8000 101/telephone-event/8000"); + packet_seq(A, 8, PCMA_payload, 1000000, 200, 8, PCMA_payload); + // start with marker + packet_seq(A, 101 | 0x80, "\x08\x0a\x00\xa0", 1000160, 201, 101 | 0x80, "\x08\x0a\x00\xa0"); + dtmf(""); + // continuous event with increasing length + // XXX check output ts, seq, ssrc + packet_seq(A, 101, "\x08\x0a\x01\x40", 1000160, 202, 101, "\x08\x0a\x01\x40"); + packet_seq(A, 101, "\x08\x0a\x01\xe0", 1000160, 203, 101, "\x08\x0a\x01\xe0"); + packet_seq(A, 101, "\x08\x0a\x02\x80", 1000160, 204, 101, "\x08\x0a\x02\x80"); + dtmf(""); + // end + packet_seq(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":8,\"duration\":100,\"volume\":10}"); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + dtmf(""); + // send some more audio + packet_seq_exp(A, 8, PCMA_payload, 1000960, 206, 8, PCMA_payload, 6); // expected seq is 200+6 for PT 8 + packet_seq(A, 8, PCMA_payload, 1001120, 207, 8, PCMA_payload); + // start with marker + packet_seq_exp(A, 101 | 0x80, "\x05\x0a\x00\xa0", 1001280, 208, 101 | 0x80, "\x05\x0a\x00\xa0", 3); // expected seq is 205+3 for PT 101 + dtmf(""); + // continuous event with increasing length + packet_seq(A, 101, "\x05\x0a\x01\x40", 1001280, 209, 101, "\x05\x0a\x01\x40"); + packet_seq(A, 101, "\x05\x0a\x01\xe0", 1001280, 210, 101, "\x05\x0a\x01\xe0"); + dtmf(""); + // end + packet_seq(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":5,\"duration\":80,\"volume\":10}"); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80", 0); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80", 0); + dtmf(""); + // final audio RTP test + packet_seq_exp(A, 8, PCMA_payload, 1000960, 212, 8, PCMA_payload, 5); // expected seq is 207+5 for PT 8 + end(); + + // DTMF passthrough w/ transcoding + start(); + sdp_pt(8, PCMA, 8000); + sdp_pt(101, telephone-event, 8000); + transcode(PCMU); + offer(); + expect(A, recv, ""); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "8/PCMA/8000 101/telephone-event/8000 0/PCMU/8000"); + expect(B, send, ""); + sdp_pt(0, PCMU, 8000); + sdp_pt(101, telephone-event, 8000); + answer(); + expect(A, recv, "8/PCMA/8000 101/telephone-event/8000"); + expect(A, send, "8/PCMA/8000 101/telephone-event/8000"); + expect(B, recv, "101/telephone-event/8000 0/PCMU/8000"); + expect(B, send, "0/PCMU/8000 101/telephone-event/8000"); + packet_seq(A, 8, PCMA_payload, 1000000, 200, 0, PCMU_payload); + // start with marker + packet_seq(A, 101 | 0x80, "\x08\x0a\x00\xa0", 1000160, 201, 101 | 0x80, "\x08\x0a\x00\xa0"); + dtmf(""); + // continuous event with increasing length + // XXX check output ts, seq, ssrc + packet_seq(A, 101, "\x08\x0a\x01\x40", 1000160, 202, 101, "\x08\x0a\x01\x40"); + packet_seq(A, 101, "\x08\x0a\x01\xe0", 1000160, 203, 101, "\x08\x0a\x01\xe0"); + packet_seq(A, 101, "\x08\x0a\x02\x80", 1000160, 204, 101, "\x08\x0a\x02\x80"); + dtmf(""); + // end + packet_seq(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":8,\"duration\":100,\"volume\":10}"); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + packet_seq_exp(A, 101, "\x08\x8a\x03\x20", 1000160, 205, 101, "\x08\x8a\x03\x20", 0); + dtmf(""); + // send some more audio + packet_seq_exp(A, 8, PCMA_payload, 1000960, 206, 0, PCMU_payload, 6); // expected seq is 200+6 for PT 8 + packet_seq(A, 8, PCMA_payload, 1001120, 207, 0, PCMU_payload); + // start with marker + packet_seq_exp(A, 101 | 0x80, "\x05\x0a\x00\xa0", 1001280, 208, 101 | 0x80, "\x05\x0a\x00\xa0", 3); // expected seq is 205+3 for PT 101 + dtmf(""); + // continuous event with increasing length + packet_seq(A, 101, "\x05\x0a\x01\x40", 1001280, 209, 101, "\x05\x0a\x01\x40"); + packet_seq(A, 101, "\x05\x0a\x01\xe0", 1001280, 210, 101, "\x05\x0a\x01\xe0"); + dtmf(""); + // end + packet_seq(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80"); + dtmf("{\"callid\":\"test-call\",\"source_tag\":\"tag_A\",\"tags\":[],\"type\":\"DTMF\",\"timestamp\":0,\"source_ip\":\"(null)\",\"event\":5,\"duration\":80,\"volume\":10}"); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80", 0); + packet_seq_exp(A, 101, "\x05\x8a\x02\x80", 1001280, 211, 101, "\x05\x8a\x02\x80", 0); + dtmf(""); + // final audio RTP test + packet_seq_exp(A, 8, PCMA_payload, 1000960, 212, 0, PCMU_payload, 5); // expected seq is 207+5 for PT 8 + end(); + return 0; }