Browse Source

TT#58659 RFC DTMF to PCM transcoding

Change-Id: I32fa876940131e3a18f611e2a518f7acd1327665
changes/68/29568/17
Richard Fuchs 7 years ago
parent
commit
c0781e5193
15 changed files with 470 additions and 79 deletions
  1. +1
    -0
      daemon/.gitignore
  2. +1
    -1
      daemon/Makefile
  3. +49
    -40
      daemon/codec.c
  4. +1
    -18
      daemon/dtmf.c
  5. +1
    -1
      daemon/media_player.c
  6. +0
    -3
      include/dtmf.h
  7. +77
    -9
      lib/codeclib.c
  8. +6
    -1
      lib/codeclib.h
  9. +65
    -0
      lib/dtmflib.c
  10. +29
    -0
      lib/dtmflib.h
  11. +1
    -0
      recording-daemon/.gitignore
  12. +2
    -1
      recording-daemon/Makefile
  13. +1
    -0
      t/.gitignore
  14. +5
    -5
      t/Makefile
  15. +231
    -0
      t/auto-daemon-tests.pl

+ 1
- 0
daemon/.gitignore View File

@ -15,3 +15,4 @@ fix_frame_channel_layout.h
socket.c
streambuf.c
ssllib.c
dtmflib.c

+ 1
- 1
daemon/Makefile View File

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


+ 49
- 40
daemon/codec.c View File

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


+ 1
- 18
daemon/dtmf.c View File

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


+ 1
- 1
daemon/media_player.c View File

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


+ 0
- 3
include/dtmf.h View File

@ -1,9 +1,6 @@
#ifndef _DTMF_H_
#define _DTMF_H_
#include <inttypes.h>
#include <glib.h>
#include <sys/types.h>
#include "str.h"


+ 77
- 9
lib/codeclib.c View File

@ -4,6 +4,7 @@
#include <libavfilter/avfilter.h>
#include <libavutil/opt.h>
#include <glib.h>
#include <arpa/inet.h>
#ifdef HAVE_BCG729
#include <bcg729/encoder.h>
#include <bcg729/decoder.h>
@ -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;
}

+ 6
- 1
lib/codeclib.h View File

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


+ 65
- 0
lib/dtmflib.c View File

@ -0,0 +1,65 @@
#include "dtmflib.h"
#include <math.h>
#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++;
}
}

+ 29
- 0
lib/dtmflib.h View File

@ -0,0 +1,29 @@
#ifndef _DTMFLIB_H_
#define _DTMFLIB_H_
#include <glib.h>
#include <inttypes.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));
void dtmf_samples(void *buf, unsigned long offset, unsigned long num, unsigned int event, unsigned int volume,
unsigned int sample_rate);
#endif

+ 1
- 0
recording-daemon/.gitignore View File

@ -14,3 +14,4 @@ fix_frame_channel_layout.h
socket.c
streambuf.c
ssllib.c
dtmflib.c

+ 2
- 1
recording-daemon/Makefile View File

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


+ 1
- 0
t/.gitignore View File

@ -48,3 +48,4 @@ const_str_hash-test.strhash
tests-preload.so
timerthread.c
media_player.c
dtmflib.c

+ 5
- 5
t/Makefile View File

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


+ 231
- 0
t/auto-daemon-tests.pl View File

@ -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'] }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 7010 RTP/AVP 0
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0 96
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
a=rtcp:PORT
SDP
($port_b) = answer('PCM to RFC DTMF transcoding', { replace => ['origin'] }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 7012 RTP/AVP 0 96
c=IN IP4 198.51.100.3
a=rtpmap:0 PCMU/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160));
($seq, $ssrc) = rcv($sock_b, $port_a, rtpm(0, -1, 3000, -1, "\x00" x 160));
snd($sock_a, $port_b, rtp(0, 1001, 3160, 0x1234, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(0, $seq+1, 3160, $ssrc, "\x00" x 160));
snd($sock_b, $port_a, rtp(0, 2000, 4000, 0x5678, "\x00" x 160));
($seq, $ssrc) = rcv($sock_a, $port_b, rtpm(0, -1, 4000, -1, "\x00" x 160));
snd($sock_b, $port_a, rtp(0, 2001, 4000+160, 0x5678, "\x00" x 160));
rcv($sock_a, $port_b, rtpm(0, $seq+1, 4000+160, $ssrc, "\x00" x 160));
snd($sock_b, $port_a, rtp(96, 2002, 4000+320, 0x5678, "\x08\x10\x00\xa0"));
rcv($sock_a, $port_b, rtpm(0, $seq+2, 4000+320, $ssrc, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40"));
snd($sock_b, $port_a, rtp(96, 2003, 4000+320, 0x5678, "\x08\x10\x01\x40"));
rcv($sock_a, $port_b, rtpm(0, $seq+3, 4000+480, $ssrc, "\x40\xe0\xb3\xad\xbd\x3f\x2c\x2f\x54\xbb\xb5\xc4\x6b\x5d\xde\xd9\x4e\x37\x35\x58\xb5\xab\xb4\x52\x2f\x2f\x47\xca\xbf\xd0\xfe\xd8\xc1\xc3\x57\x32\x2e\x40\xbc\xab\xb0\xe0\x39\x35\x46\xe3\xdb\x61\x5d\xcc\xb7\xb7\xe8\x33\x2b\x37\xcb\xae\xb0\xcb\x46\x3f\x50\x7e\x58\x41\x46\xcf\xb1\xae\xc6\x39\x2b\x31\x7d\xb7\xb5\xc8\x5d\x58\xe5\xe1\x4a\x37\x38\xf2\xb1\xab\xba\x44\x2e\x30\x4f\xc3\xbe\xd1\x7d\xd8\xc3\xc9\x4b\x30\x2f\x4c\xb6\xab\xb3\x61\x35\x35\x4b\xd8\xd6\x68\x68\xc8\xb7\xba\x5d\x30\x2c\x3c\xbf\xad\xb1\xd8\x40\x3e\x52\xfb\x58\x44\x4c\xc8\xb0\xb0\xd6\x34\x2b\x35\xd5\xb3\xb5\xcd\x54\x54\xec\xef\x47\x37\x3c\xd3\xaf\xac\xc0\x3c\x2d\x33\x63\xbe"));
snd($sock_b, $port_a, rtp(96, 2004, 4000+320, 0x5678, "\x08\x10\x01\xe0"));
rcv($sock_a, $port_b, rtpm(0, $seq+4, 4000+640, $ssrc, "\xbd\xd3\x77\xd9\xc5\xd0\x44\x30\x32\x65\xb2\xab\xb8\x4c\x32\x35\x50\xcf\xd2\x70\x7a\xc6\xb8\xbe\x4c\x2e\x2d\x45\xb9\xac\xb4\xfd\x3c\x3d\x55\xf2\x5a\x47\x56\xc1\xb0\xb4\x71\x30\x2b\x3a\xc7\xb0\xb6\xd7\x4d\x50\xf6\x78\x45\x38\x41\xc7\xae\xae\xcc\x37\x2c\x36\xe5\xbb\xbd\xd7\x6d\xdb\xc9\xdd\x3f\x30\x36\xdc\xae\xab\xbd\x41\x2f\x37\x5d\xcb\xcf\x7b\xef\xc4\xb9\xc6\x42\x2d\x2e\x55\xb4\xac\xb8\x58\x39\x3d\x59\xea\x5c\x4a\x66\xbd\xb0\xb8\x50\x2e\x2c\x40\xbd\xaf\xb8\xe8\x48\x4e\x7d\x6b\x43\x3a\x4a\xbf\xad\xaf\xe4\x32\x2c\x3a\xcf\xb8\xbd\xdc\x66\xde\xcc\xf5\x3c\x30\x3b\xca\xad\xac\xc6\x3b\x2e\x39\x7c\xc6\xcd\xfa\xe7\xc3\xbb\xce\x3c\x2d\x31\xf2"));
# test out of seq
snd($sock_b, $port_a, rtp(0, 2006, 4000+160*5, 0x5678, "\x00" x 160)); # buffered
Time::HiRes::usleep(20000);
snd($sock_b, $port_a, rtp(96, 2005, 4000+320, 0x5678, "\x08\x10\x01\xe0")); # repeat, no-op, consumed
rcv($sock_a, $port_b, rtpm(0, $seq+5, 4000+160*5, $ssrc, "\x00" x 160));
# resume normal
snd($sock_b, $port_a, rtp(0, 2007, 4000+160*6, 0x5678, "\x00" x 160));
rcv($sock_a, $port_b, rtpm(0, $seq+6, 4000+160*6, $ssrc, "\x00" x 160));
# test TS reset
snd($sock_b, $port_a, rtp(0, 2008, 2000, 0x5678, "\x00" x 160));
rcv($sock_a, $port_b, rtpm(0, $seq+7, 4000+160*7, $ssrc, "\x00" x 160));
snd($sock_b, $port_a, rtp(96, 2009, 2160, 0x5678, "\x08\x10\x00\xa0"));
rcv($sock_a, $port_b, rtpm(0, $seq+8, 4000+160*8, $ssrc, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40"));
($sock_a, $sock_b) = new_call([qw(198.51.100.1 7020)], [qw(198.51.100.3 7022)]);
($port_a) = offer('PCM to RFC DTMF transcoding w/ PCM transcoding', { ICE => 'remove', replace => ['origin'],
codec => { transcode => ['PCMA', 'telephone-event'] }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 7020 RTP/AVP 0
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0 8 96
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
a=rtcp:PORT
SDP
($port_b) = answer('PCM to RFC DTMF transcoding w/ PCM transcoding', { replace => ['origin'] }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 7022 RTP/AVP 8 96
c=IN IP4 198.51.100.3
a=rtpmap:0 PCMA/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=sendrecv
a=rtcp:PORT
SDP
snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160));
($seq, $ssrc) = rcv($sock_b, $port_a, rtpm(8, -1, 3000, -1, "\x2a" x 160));
snd($sock_a, $port_b, rtp(0, 1001, 3160, 0x1234, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(8, $seq+1, 3160, $ssrc, "\x2a" x 160));
snd($sock_b, $port_a, rtp(8, 2000, 4000, 0x5678, "\x2a" x 160));
($seq, $ssrc) = rcv($sock_a, $port_b, rtpm(0, -1, 4000, -1, "\x00" x 160));
snd($sock_b, $port_a, rtp(8, 2001, 4000+160, 0x5678, "\x2a" x 160));
rcv($sock_a, $port_b, rtpm(0, $seq+1, 4000+160, $ssrc, "\x00" x 160));
snd($sock_b, $port_a, rtp(96, 2002, 4000+320, 0x5678, "\x08\x10\x00\xa0"));
rcv($sock_a, $port_b, rtpm(0, $seq+2, 4000+320, $ssrc, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40"));
snd($sock_b, $port_a, rtp(96, 2003, 4000+320, 0x5678, "\x08\x10\x01\x40"));
rcv($sock_a, $port_b, rtpm(0, $seq+3, 4000+480, $ssrc, "\x40\xe0\xb3\xad\xbd\x3f\x2c\x2f\x54\xbb\xb5\xc4\x6b\x5d\xde\xd9\x4e\x37\x35\x58\xb5\xab\xb4\x52\x2f\x2f\x47\xca\xbf\xd0\xfe\xd8\xc1\xc3\x57\x32\x2e\x40\xbc\xab\xb0\xe0\x39\x35\x46\xe3\xdb\x61\x5d\xcc\xb7\xb7\xe8\x33\x2b\x37\xcb\xae\xb0\xcb\x46\x3f\x50\x7e\x58\x41\x46\xcf\xb1\xae\xc6\x39\x2b\x31\x7d\xb7\xb5\xc8\x5d\x58\xe5\xe1\x4a\x37\x38\xf2\xb1\xab\xba\x44\x2e\x30\x4f\xc3\xbe\xd1\x7d\xd8\xc3\xc9\x4b\x30\x2f\x4c\xb6\xab\xb3\x61\x35\x35\x4b\xd8\xd6\x68\x68\xc8\xb7\xba\x5d\x30\x2c\x3c\xbf\xad\xb1\xd8\x40\x3e\x52\xfb\x58\x44\x4c\xc8\xb0\xb0\xd6\x34\x2b\x35\xd5\xb3\xb5\xcd\x54\x54\xec\xef\x47\x37\x3c\xd3\xaf\xac\xc0\x3c\x2d\x33\x63\xbe"));
snd($sock_b, $port_a, rtp(96, 2004, 4000+320, 0x5678, "\x08\x10\x01\xe0"));
rcv($sock_a, $port_b, rtpm(0, $seq+4, 4000+640, $ssrc, "\xbd\xd3\x77\xd9\xc5\xd0\x44\x30\x32\x65\xb2\xab\xb8\x4c\x32\x35\x50\xcf\xd2\x70\x7a\xc6\xb8\xbe\x4c\x2e\x2d\x45\xb9\xac\xb4\xfd\x3c\x3d\x55\xf2\x5a\x47\x56\xc1\xb0\xb4\x71\x30\x2b\x3a\xc7\xb0\xb6\xd7\x4d\x50\xf6\x78\x45\x38\x41\xc7\xae\xae\xcc\x37\x2c\x36\xe5\xbb\xbd\xd7\x6d\xdb\xc9\xdd\x3f\x30\x36\xdc\xae\xab\xbd\x41\x2f\x37\x5d\xcb\xcf\x7b\xef\xc4\xb9\xc6\x42\x2d\x2e\x55\xb4\xac\xb8\x58\x39\x3d\x59\xea\x5c\x4a\x66\xbd\xb0\xb8\x50\x2e\x2c\x40\xbd\xaf\xb8\xe8\x48\x4e\x7d\x6b\x43\x3a\x4a\xbf\xad\xaf\xe4\x32\x2c\x3a\xcf\xb8\xbd\xdc\x66\xde\xcc\xf5\x3c\x30\x3b\xca\xad\xac\xc6\x3b\x2e\x39\x7c\xc6\xcd\xfa\xe7\xc3\xbb\xce\x3c\x2d\x31\xf2"));
# test out of seq
snd($sock_b, $port_a, rtp(8, 2006, 4000+800, 0x5678, "\x2a" x 160)); # buffered
Time::HiRes::usleep(20000);
snd($sock_b, $port_a, rtp(96, 2005, 4000+320, 0x5678, "\x08\x10\x01\xe0")); # repeat, no-op, consumed
rcv($sock_a, $port_b, rtpm(0, $seq+5, 4000+800, $ssrc, "\x00" x 160));
($sock_a, $sock_b) = new_call([qw(198.51.100.1 7030)], [qw(198.51.100.3 7032)]);
($port_a) = offer('PCM to RFC DTMF transcoding w/ forced PCM transcoding', { ICE => 'remove', replace => ['origin'],
codec => { transcode => ['telephone-event'] }}, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.1
s=tester
t=0 0
m=audio 7030 RTP/AVP 0 8
c=IN IP4 198.51.100.1
a=sendrecv
----------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0 8 96
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
a=rtcp:PORT
SDP
($port_b) = answer('PCM to RFC DTMF transcoding w/ forced PCM transcoding', { replace => ['origin'] }, <<SDP);
v=0
o=- 1545997027 1 IN IP4 198.51.100.3
s=tester
t=0 0
m=audio 7032 RTP/AVP 8 96
c=IN IP4 198.51.100.3
a=rtpmap:0 PCMA/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=sendrecv
--------------------------------------
v=0
o=- 1545997027 1 IN IP4 203.0.113.1
s=tester
t=0 0
m=audio PORT RTP/AVP 0 8
c=IN IP4 203.0.113.1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=sendrecv
a=rtcp:PORT
SDP
snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160));
($seq, $ssrc) = rcv($sock_b, $port_a, rtpm(8, -1, 3000, -1, "\x2a" x 160));
snd($sock_a, $port_b, rtp(0, 1001, 3160, 0x1234, "\x00" x 160));
rcv($sock_b, $port_a, rtpm(8, $seq+1, 3160, $ssrc, "\x2a" x 160));
snd($sock_b, $port_a, rtp(8, 2000, 4000, 0x5678, "\x2a" x 160));
($seq, $ssrc) = rcv($sock_a, $port_b, rtpm(0, -1, 4000, -1, "\x00" x 160));
snd($sock_b, $port_a, rtp(8, 2001, 4000+160, 0x5678, "\x2a" x 160));
rcv($sock_a, $port_b, rtpm(0, $seq+1, 4000+160, $ssrc, "\x00" x 160));
snd($sock_b, $port_a, rtp(96, 2002, 4000+320, 0x5678, "\x08\x10\x00\xa0"));
rcv($sock_a, $port_b, rtpm(0, $seq+2, 4000+320, $ssrc, "\xff\xb0\xac\xbc\x4c\x39\x3f\x63\xee\x55\x4a\xf6\xba\xaf\xbc\x45\x2c\x2d\x4b\xba\xaf\xbb\x6e\x48\x53\xf3\x5f\x3f\x3a\x52\xba\xac\xb3\x5e\x2f\x2d\x3e\xc8\xb8\xc0\xe8\x6b\xd7\xcc\x66\x39\x30\x3f\xbf\xac\xae\xd2\x37\x2f\x3c\xe1\xc6\xd2\x77\xdd\xbf\xbb\xdc\x38\x2c\x35\xd1\xae\xad\xc2\x43\x37\x40\x6e\xe7\x58\x4e\xdd\xb8\xb1\xc3\x3d\x2b\x2f\x5e\xb5\xaf\xbe\x59\x44\x51\xfb\x5b\x3f\x3d\x6b\xb6\xac\xb8\x4a\x2d\x2d\x47\xbf\xb6\xc1\xfa\x63\xda\xd1\x57\x37\x32\x49\xba\xab\xb0\xfe\x33\x2f\x40\xd2\xc2\xd1\x7e\xda\xbf\xbe\x73\x35\x2d\x3a\xc4\xac\xae\xcd\x3d\x36\x43\xf6\xdf\x5c\x55\xd2\xb7\xb4\xce\x37\x2b\x32\xdf\xb1\xaf\xc3\x4d\x41\x50\x7e\x59\x40"));
snd($sock_b, $port_a, rtp(96, 2003, 4000+320, 0x5678, "\x08\x10\x01\x40"));
rcv($sock_a, $port_b, rtpm(0, $seq+3, 4000+480, $ssrc, "\x40\xe0\xb3\xad\xbd\x3f\x2c\x2f\x54\xbb\xb5\xc4\x6b\x5d\xde\xd9\x4e\x37\x35\x58\xb5\xab\xb4\x52\x2f\x2f\x47\xca\xbf\xd0\xfe\xd8\xc1\xc3\x57\x32\x2e\x40\xbc\xab\xb0\xe0\x39\x35\x46\xe3\xdb\x61\x5d\xcc\xb7\xb7\xe8\x33\x2b\x37\xcb\xae\xb0\xcb\x46\x3f\x50\x7e\x58\x41\x46\xcf\xb1\xae\xc6\x39\x2b\x31\x7d\xb7\xb5\xc8\x5d\x58\xe5\xe1\x4a\x37\x38\xf2\xb1\xab\xba\x44\x2e\x30\x4f\xc3\xbe\xd1\x7d\xd8\xc3\xc9\x4b\x30\x2f\x4c\xb6\xab\xb3\x61\x35\x35\x4b\xd8\xd6\x68\x68\xc8\xb7\xba\x5d\x30\x2c\x3c\xbf\xad\xb1\xd8\x40\x3e\x52\xfb\x58\x44\x4c\xc8\xb0\xb0\xd6\x34\x2b\x35\xd5\xb3\xb5\xcd\x54\x54\xec\xef\x47\x37\x3c\xd3\xaf\xac\xc0\x3c\x2d\x33\x63\xbe"));
snd($sock_b, $port_a, rtp(96, 2004, 4000+320, 0x5678, "\x08\x10\x01\xe0"));
rcv($sock_a, $port_b, rtpm(0, $seq+4, 4000+640, $ssrc, "\xbd\xd3\x77\xd9\xc5\xd0\x44\x30\x32\x65\xb2\xab\xb8\x4c\x32\x35\x50\xcf\xd2\x70\x7a\xc6\xb8\xbe\x4c\x2e\x2d\x45\xb9\xac\xb4\xfd\x3c\x3d\x55\xf2\x5a\x47\x56\xc1\xb0\xb4\x71\x30\x2b\x3a\xc7\xb0\xb6\xd7\x4d\x50\xf6\x78\x45\x38\x41\xc7\xae\xae\xcc\x37\x2c\x36\xe5\xbb\xbd\xd7\x6d\xdb\xc9\xdd\x3f\x30\x36\xdc\xae\xab\xbd\x41\x2f\x37\x5d\xcb\xcf\x7b\xef\xc4\xb9\xc6\x42\x2d\x2e\x55\xb4\xac\xb8\x58\x39\x3d\x59\xea\x5c\x4a\x66\xbd\xb0\xb8\x50\x2e\x2c\x40\xbd\xaf\xb8\xe8\x48\x4e\x7d\x6b\x43\x3a\x4a\xbf\xad\xaf\xe4\x32\x2c\x3a\xcf\xb8\xbd\xdc\x66\xde\xcc\xf5\x3c\x30\x3b\xca\xad\xac\xc6\x3b\x2e\x39\x7c\xc6\xcd\xfa\xe7\xc3\xbb\xce\x3c\x2d\x31\xf2"));
# test out of seq
snd($sock_b, $port_a, rtp(8, 2006, 4000+800, 0x5678, "\x2a" x 160)); # buffered
Time::HiRes::usleep(20000);
snd($sock_b, $port_a, rtp(96, 2005, 4000+320, 0x5678, "\x08\x10\x01\xe0")); # repeat, no-op, consumed
rcv($sock_a, $port_b, rtpm(0, $seq+5, 4000+800, $ssrc, "\x00" x 160));
END {
if ($rtpe_pid) {
kill('INT', $rtpe_pid) or die;


Loading…
Cancel
Save