TT#38350 implement sending DTMF events to syslog

Change-Id: I82fbdc7da6cbe2505ef1c98dd3c45b63c4461994
changes/63/22263/8
Richard Fuchs 8 years ago
parent dd34574669
commit a9ec666cb4

@ -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

@ -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

@ -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);
}

@ -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;
}

@ -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);

@ -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);

@ -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;

@ -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);

@ -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;
};

@ -0,0 +1,15 @@
#ifndef _DTMF_H_
#define _DTMF_H_
#include <inttypes.h>
#include <glib.h>
#include <sys/types.h>
#include "str.h"
struct media_packet;
int dtmf_event(struct media_packet *, str *, int);
#endif

@ -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);

@ -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"',

@ -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

@ -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 *);

1
t/.gitignore vendored

@ -43,3 +43,4 @@ stun.c
transcode-test
udp_listener.c
payload-tracker-test
dtmf.c

@ -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

@ -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

@ -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;
}

Loading…
Cancel
Save