mirror of https://github.com/sipwise/rtpengine.git
Merge branch 'jb_new' of https://github.com/balajeesv/rtpengine
Change-Id: I73fc3529938bf65e40f86a4e526c8bf77e199492pull/940/head
commit
ab53fdc6f6
@ -0,0 +1,358 @@
|
||||
#include "jitter_buffer.h"
|
||||
#include "timerthread.h"
|
||||
#include "media_socket.h"
|
||||
#include "call.h"
|
||||
#include "codec.h"
|
||||
#include "main.h"
|
||||
#include <math.h>
|
||||
|
||||
#define INITIAL_PACKETS 0x1E
|
||||
#define CONT_SEQ_COUNT 0x64
|
||||
#define CONT_MISS_COUNT 0x0A
|
||||
#define CLOCK_DRIFT_MULT 0x14
|
||||
|
||||
|
||||
static struct timerthread jitter_buffer_thread;
|
||||
|
||||
|
||||
void jitter_buffer_init(void) {
|
||||
ilog(LOG_INFO, "jitter_buffer_init");
|
||||
timerthread_init(&jitter_buffer_thread, timerthread_queue_run);
|
||||
}
|
||||
|
||||
// jb is locked
|
||||
static void reset_jitter_buffer(struct jitter_buffer *jb) {
|
||||
ilog(LOG_INFO, "reset_jitter_buffer");
|
||||
|
||||
jb->first_send_ts = 0;
|
||||
jb->first_send.tv_sec = 0;
|
||||
jb->first_send.tv_usec = 0;
|
||||
jb->first_seq = 0;
|
||||
jb->rtptime_delta = 0;
|
||||
jb->buffer_len = 0;
|
||||
jb->cont_frames = 0;
|
||||
jb->cont_miss = 0;
|
||||
jb->next_exp_seq = 0;
|
||||
jb->clock_rate = 0;
|
||||
jb->payload_type = 0;
|
||||
jb->drift_mult_factor = 0;
|
||||
jb->buf_decremented = 0;
|
||||
jb->clock_drift_val = 0;
|
||||
|
||||
jb->num_resets++;
|
||||
|
||||
//disable jitter buffer in case of more than 2 resets
|
||||
if(jb->num_resets > 2 && jb->call)
|
||||
jb->disabled = 1;
|
||||
}
|
||||
|
||||
static int get_clock_rate(struct media_packet *mp, int payload_type) {
|
||||
const struct rtp_payload_type *rtp_pt = NULL;
|
||||
struct jitter_buffer *jb = mp->stream->jb;
|
||||
int clock_rate = 0;
|
||||
|
||||
if(jb->clock_rate && jb->payload_type == payload_type)
|
||||
return jb->clock_rate;
|
||||
|
||||
struct codec_handler *transcoder = codec_handler_get(mp->media, payload_type);
|
||||
if(transcoder) {
|
||||
if(transcoder->source_pt.payload_type == payload_type)
|
||||
rtp_pt = &transcoder->source_pt;
|
||||
if(transcoder->dest_pt.payload_type == payload_type)
|
||||
rtp_pt = &transcoder->dest_pt;
|
||||
}
|
||||
|
||||
if(rtp_pt) {
|
||||
clock_rate = jb->clock_rate = rtp_pt->clock_rate;
|
||||
jb->payload_type = payload_type;
|
||||
}
|
||||
else
|
||||
ilog(LOG_DEBUG, "clock_rate not present payload_type = %d", payload_type);
|
||||
|
||||
return clock_rate;
|
||||
}
|
||||
|
||||
static struct jb_packet* get_jb_packet(struct media_packet *mp, const str *s) {
|
||||
char *buf = malloc(s->len + RTP_BUFFER_HEAD_ROOM + RTP_BUFFER_TAIL_ROOM);
|
||||
if (!buf) {
|
||||
ilog(LOG_ERROR, "Failed to allocate memory: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct jb_packet *p = g_slice_alloc0(sizeof(*p));
|
||||
|
||||
p->buf = buf;
|
||||
p->mp = *mp;
|
||||
obj_hold(p->mp.sfd);
|
||||
|
||||
str_init_len(&p->mp.raw, buf + RTP_BUFFER_HEAD_ROOM, s->len);
|
||||
memcpy(p->mp.raw.s, s->s, s->len);
|
||||
|
||||
if(rtp_payload(&p->mp.rtp, &p->mp.payload, &p->mp.raw)) {
|
||||
jb_packet_free(&p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// jb is locked
|
||||
static void check_buffered_packets(struct jitter_buffer *jb) {
|
||||
if (g_tree_nnodes(jb->ttq.entries) >= (2* rtpe_config.jb_length)) {
|
||||
ilog(LOG_DEBUG, "Jitter reset due to buffer overflow");
|
||||
reset_jitter_buffer(jb);
|
||||
}
|
||||
}
|
||||
|
||||
// jb is locked
|
||||
static int queue_packet(struct media_packet *mp, struct jb_packet *p) {
|
||||
struct jitter_buffer *jb = mp->stream->jb;
|
||||
unsigned long ts = ntohl(mp->rtp->timestamp);
|
||||
int payload_type = (mp->rtp->m_pt & 0x7f);
|
||||
int clockrate = get_clock_rate(mp, payload_type);
|
||||
|
||||
if(!clockrate || !jb->first_send.tv_sec) {
|
||||
ilog(LOG_DEBUG, "Jitter reset due to clockrate");
|
||||
reset_jitter_buffer(jb);
|
||||
return 1;
|
||||
}
|
||||
long ts_diff = (uint32_t) ts - (uint32_t) jb->first_send_ts;
|
||||
int seq_diff = ntohs(mp->rtp->seq_num) - jb->first_seq;
|
||||
if(!jb->rtptime_delta && seq_diff) {
|
||||
jb->rtptime_delta = ts_diff/seq_diff;
|
||||
}
|
||||
p->ttq_entry.when = jb->first_send;
|
||||
long long ts_diff_us =
|
||||
(long long) (ts_diff + (jb->rtptime_delta * jb->buffer_len))* 1000000 / clockrate;
|
||||
|
||||
ts_diff_us += (jb->clock_drift_val * seq_diff);
|
||||
|
||||
if(jb->buf_decremented) {
|
||||
ts_diff_us += 5000; //add 5ms delta when 2 packets are scheduled around same time
|
||||
jb->buf_decremented = 0;
|
||||
}
|
||||
timeval_add_usec(&p->ttq_entry.when, ts_diff_us);
|
||||
|
||||
ts_diff_us = timeval_diff(&p->ttq_entry.when, &rtpe_now);
|
||||
|
||||
if (ts_diff_us > 3000000) { // more than three second, can't be right
|
||||
jb->first_send.tv_sec = 0;
|
||||
jb->rtptime_delta = 0;
|
||||
}
|
||||
|
||||
timerthread_queue_push(&jb->ttq, &p->ttq_entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handle_clock_drift(struct media_packet *mp) {
|
||||
ilog(LOG_DEBUG, "handle_clock_drift");
|
||||
struct jitter_buffer *jb = mp->stream->jb;
|
||||
int seq_diff = ntohs(mp->rtp->seq_num) - jb->first_seq;
|
||||
|
||||
int mult_factor = pow(2, jb->drift_mult_factor);
|
||||
|
||||
if(seq_diff < (mult_factor * CLOCK_DRIFT_MULT))
|
||||
return;
|
||||
|
||||
unsigned long ts = ntohl(mp->rtp->timestamp);
|
||||
int payload_type = (mp->rtp->m_pt & 0x7f);
|
||||
int clockrate = get_clock_rate(mp, payload_type);
|
||||
if(!clockrate) {
|
||||
return;
|
||||
}
|
||||
long ts_diff = (uint32_t) ts - (uint32_t) jb->first_send_ts;
|
||||
long long ts_diff_us =
|
||||
(long long) (ts_diff)* 1000000 / clockrate;
|
||||
struct timeval to_send = jb->first_send;
|
||||
timeval_add_usec(&to_send, ts_diff_us);
|
||||
long long time_diff = timeval_diff(&rtpe_now, &to_send);
|
||||
|
||||
jb->clock_drift_val = time_diff/seq_diff;
|
||||
jb->drift_mult_factor++;
|
||||
}
|
||||
|
||||
int buffer_packet(struct media_packet *mp, const str *s) {
|
||||
struct jb_packet *p = NULL;
|
||||
int ret = 1; // must call stream_packet
|
||||
|
||||
mp->stream = mp->sfd->stream;
|
||||
mp->media = mp->stream->media;
|
||||
mp->call = mp->sfd->call;
|
||||
struct call *call = mp->call;
|
||||
|
||||
rwlock_lock_r(&call->master_lock);
|
||||
|
||||
struct jitter_buffer *jb = mp->stream->jb;
|
||||
if (!jb || jb->disabled)
|
||||
goto end;
|
||||
|
||||
ilog(LOG_DEBUG, "Handling JB packet on: %s:%d", sockaddr_print_buf(&mp->stream->endpoint.address),
|
||||
mp->stream->endpoint.port);
|
||||
|
||||
p = get_jb_packet(mp, s);
|
||||
if (!p)
|
||||
goto end;
|
||||
|
||||
mp = &p->mp;
|
||||
|
||||
int payload_type = (mp->rtp->m_pt & 0x7f);
|
||||
|
||||
mutex_lock(&jb->lock);
|
||||
|
||||
if(jb->clock_rate && jb->payload_type != payload_type) { //reset in case of payload change
|
||||
jb->first_send.tv_sec = 0;
|
||||
jb->rtptime_delta = 0;
|
||||
}
|
||||
|
||||
if (jb->first_send.tv_sec) {
|
||||
if(rtpe_config.jb_clock_drift)
|
||||
handle_clock_drift(mp);
|
||||
ret = queue_packet(mp,p);
|
||||
}
|
||||
else {
|
||||
// store data from first packet and use for successive packets and queue the first packet
|
||||
unsigned long ts = ntohl(mp->rtp->timestamp);
|
||||
int payload_type = (mp->rtp->m_pt & 0x7f);
|
||||
int clockrate = get_clock_rate(mp, payload_type);
|
||||
if(!clockrate){
|
||||
jb->initial_pkts++;
|
||||
if(jb->initial_pkts > INITIAL_PACKETS) { //Ignore initial Payload Type 126 if any
|
||||
reset_jitter_buffer(jb);
|
||||
}
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
p->ttq_entry.when = jb->first_send = rtpe_now;
|
||||
jb->first_send_ts = ts;
|
||||
jb->first_seq = ntohs(mp->rtp->seq_num);
|
||||
}
|
||||
|
||||
// packet consumed?
|
||||
if (ret == 0)
|
||||
p = NULL;
|
||||
|
||||
check_buffered_packets(jb);
|
||||
|
||||
end_unlock:
|
||||
mutex_unlock(&jb->lock);
|
||||
|
||||
end:
|
||||
rwlock_unlock_r(&call->master_lock);
|
||||
if (p)
|
||||
jb_packet_free(&p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void increment_buffer(struct jitter_buffer *jb) {
|
||||
if(jb->buffer_len < rtpe_config.jb_length)
|
||||
jb->buffer_len++;
|
||||
}
|
||||
|
||||
static void decrement_buffer(struct jitter_buffer *jb) {
|
||||
if(jb->buffer_len > 0) {
|
||||
jb->buffer_len--;
|
||||
jb->buf_decremented = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void set_jitter_values(struct media_packet *mp) {
|
||||
struct jitter_buffer *jb = mp->stream->jb;
|
||||
if(!jb || !mp->rtp)
|
||||
return;
|
||||
int curr_seq = ntohs(mp->rtp->seq_num);
|
||||
if(jb->next_exp_seq) {
|
||||
mutex_lock(&jb->lock);
|
||||
if(curr_seq > jb->next_exp_seq) {
|
||||
ilog(LOG_DEBUG, "missing seq exp seq =%d, received seq= %d", jb->next_exp_seq, curr_seq);
|
||||
increment_buffer(jb);
|
||||
jb->cont_frames = 0;
|
||||
jb->cont_miss++;
|
||||
}
|
||||
else if(curr_seq < jb->next_exp_seq) { //Might be duplicate or sequence already crossed
|
||||
jb->cont_frames = 0;
|
||||
jb->cont_miss++;
|
||||
}
|
||||
else {
|
||||
jb->cont_frames++;
|
||||
jb->cont_miss = 0;
|
||||
if(jb->cont_frames >= CONT_SEQ_COUNT) {
|
||||
decrement_buffer(jb);
|
||||
jb->cont_frames = 0;
|
||||
ilog(LOG_DEBUG, "Received continous frames Buffer len=%d", jb->buffer_len);
|
||||
}
|
||||
}
|
||||
|
||||
if(jb->cont_miss >= CONT_MISS_COUNT)
|
||||
reset_jitter_buffer(jb);
|
||||
mutex_unlock(&jb->lock);
|
||||
}
|
||||
if(curr_seq >= jb->next_exp_seq)
|
||||
jb->next_exp_seq = curr_seq + 1;
|
||||
}
|
||||
|
||||
static void __jb_send_later(struct timerthread_queue *ttq, void *p) {
|
||||
struct jb_packet *cp = p;
|
||||
set_jitter_values(&cp->mp);
|
||||
play_buffered(p);
|
||||
};
|
||||
// jb and call are locked
|
||||
static void __jb_send_now(struct timerthread_queue *ttq, void *p) {
|
||||
struct jitter_buffer *jb = (void *) ttq;
|
||||
|
||||
mutex_unlock(&jb->lock);
|
||||
rwlock_unlock_r(&jb->call->master_lock);
|
||||
|
||||
__jb_send_later(ttq, p);
|
||||
|
||||
rwlock_lock_r(&jb->call->master_lock);
|
||||
mutex_lock(&jb->lock);
|
||||
};
|
||||
static void __jb_free(void *p) {
|
||||
struct jitter_buffer *jb = p;
|
||||
jitter_buffer_free(&jb);
|
||||
}
|
||||
void __jb_packet_free(void *p) {
|
||||
struct jb_packet *jbp = p;
|
||||
jb_packet_free(&jbp);
|
||||
}
|
||||
|
||||
void jitter_buffer_loop(void *p) {
|
||||
ilog(LOG_DEBUG, "jitter_buffer_loop");
|
||||
timerthread_run(&jitter_buffer_thread);
|
||||
}
|
||||
|
||||
struct jitter_buffer *jitter_buffer_new(struct call *c) {
|
||||
ilog(LOG_DEBUG, "creating jitter_buffer");
|
||||
|
||||
struct jitter_buffer *jb = timerthread_queue_new("jitter_buffer", sizeof(*jb),
|
||||
&jitter_buffer_thread,
|
||||
__jb_send_now,
|
||||
__jb_send_later,
|
||||
__jb_free, __jb_packet_free);
|
||||
mutex_init(&jb->lock);
|
||||
jb->call = obj_get(c);
|
||||
return jb;
|
||||
}
|
||||
|
||||
void jitter_buffer_free(struct jitter_buffer **jbp) {
|
||||
if (!jbp || !*jbp)
|
||||
return;
|
||||
|
||||
ilog(LOG_DEBUG, "freeing jitter_buffer");
|
||||
|
||||
mutex_destroy(&(*jbp)->lock);
|
||||
if ((*jbp)->call)
|
||||
obj_put((*jbp)->call);
|
||||
}
|
||||
|
||||
void jb_packet_free(struct jb_packet **jbp) {
|
||||
if (!jbp || !*jbp)
|
||||
return;
|
||||
|
||||
free((*jbp)->buf);
|
||||
if ((*jbp)->mp.sfd)
|
||||
obj_put((*jbp)->mp.sfd);
|
||||
g_slice_free1(sizeof(**jbp), *jbp);
|
||||
*jbp = NULL;
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
#ifndef _JITTER_BUFFER_H_
|
||||
#define _JITTER_BUFFER_H_
|
||||
|
||||
#include "auxlib.h"
|
||||
#include "socket.h"
|
||||
#include "timerthread.h"
|
||||
#include "media_socket.h"
|
||||
//#include "codec.h"
|
||||
//
|
||||
//struct packet_handler_ctx;
|
||||
struct jb_packet;
|
||||
struct media_packet;
|
||||
//
|
||||
struct jb_packet {
|
||||
struct timerthread_queue_entry ttq_entry;
|
||||
char *buf;
|
||||
struct media_packet mp;
|
||||
};
|
||||
|
||||
struct jitter_buffer {
|
||||
struct timerthread_queue ttq;
|
||||
mutex_t lock;
|
||||
unsigned long first_send_ts;
|
||||
struct timeval first_send;
|
||||
unsigned int first_seq;
|
||||
unsigned int rtptime_delta;
|
||||
unsigned int next_exp_seq;
|
||||
unsigned int cont_frames;
|
||||
unsigned int cont_miss;
|
||||
unsigned int clock_rate;
|
||||
unsigned int payload_type;
|
||||
unsigned int num_resets;
|
||||
unsigned int initial_pkts;
|
||||
unsigned int drift_mult_factor;
|
||||
int buffer_len;
|
||||
int clock_drift_val;
|
||||
int buf_decremented;
|
||||
struct call *call;
|
||||
int disabled;
|
||||
};
|
||||
|
||||
void jitter_buffer_init(void);
|
||||
|
||||
struct jitter_buffer *jitter_buffer_new(struct call *);
|
||||
void jitter_buffer_free(struct jitter_buffer **);
|
||||
|
||||
int buffer_packet(struct media_packet *mp, const str *s);
|
||||
void jb_packet_free(struct jb_packet **jbp);
|
||||
|
||||
void jitter_buffer_loop(void *p);
|
||||
|
||||
INLINE void jb_put(struct jitter_buffer **jb) {
|
||||
if (!*jb)
|
||||
return;
|
||||
obj_put(&(*jb)->ttq.tt_obj);
|
||||
*jb = NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,242 @@
|
||||
package NGCP::Rtpengine::AutoTest;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use NGCP::Rtpengine::Test;
|
||||
use NGCP::Rtpclient::SRTP;
|
||||
use Test::More;
|
||||
use File::Temp;
|
||||
use IPC::Open3;
|
||||
use Time::HiRes;
|
||||
use POSIX ":sys_wait_h";
|
||||
use IO::Socket;
|
||||
use Exporter;
|
||||
|
||||
|
||||
our @ISA;
|
||||
our @EXPORT;
|
||||
|
||||
BEGIN {
|
||||
require Exporter;
|
||||
@ISA = qw(Exporter);
|
||||
our @EXPORT = qw(autotest_start new_call offer answer ft tt snd srtp_snd rtp rcv srtp_rcv
|
||||
srtp_dec escape rtpm reverse_tags new_tt crlf sdp_split rtpe_req offer_answer);
|
||||
};
|
||||
|
||||
|
||||
my $rtpe_stdout;
|
||||
my $rtpe_stderr;
|
||||
my $rtpe_pid;
|
||||
my $c;
|
||||
my ($cid, $ft, $tt, @sockets, $tag_iter);
|
||||
|
||||
|
||||
sub autotest_start {
|
||||
my (@cmdline) = @_;
|
||||
|
||||
like $ENV{LD_PRELOAD}, qr/tests-preload/, 'LD_PRELOAD present';
|
||||
is $ENV{RTPE_PRELOAD_TEST_ACTIVE}, '1', 'preload library is active';
|
||||
SKIP: {
|
||||
skip 'daemon is running externally', 1 if $ENV{RTPE_TEST_NO_LAUNCH};
|
||||
ok -x $ENV{RTPE_BIN}, 'RTPE_BIN points to executable';
|
||||
}
|
||||
|
||||
$rtpe_stdout = File::Temp::tempfile() or die;
|
||||
$rtpe_stderr = File::Temp::tempfile() or die;
|
||||
SKIP: {
|
||||
skip 'daemon is running externally', 1 if $ENV{RTPE_TEST_NO_LAUNCH};
|
||||
$rtpe_pid = open3(undef, '>&'.fileno($rtpe_stdout), '>&'.fileno($rtpe_stderr),
|
||||
$ENV{RTPE_BIN}, @cmdline);
|
||||
ok $rtpe_pid, 'daemon launched in background';
|
||||
}
|
||||
|
||||
# keep trying to connect to the control socket while daemon is starting up
|
||||
for (1 .. 300) {
|
||||
$c = NGCP::Rtpengine->new($ENV{RTPENGINE_HOST} // '127.0.0.1', $ENV{RTPENGINE_PORT} // 2223);
|
||||
last if $c->{socket};
|
||||
Time::HiRes::usleep(100000); # 100 ms x 300 = 30 sec
|
||||
}
|
||||
|
||||
1;
|
||||
$c->{socket} or die;
|
||||
|
||||
$tag_iter = 0;
|
||||
|
||||
my $r = $c->req({command => 'ping'});
|
||||
ok $r->{result} eq 'pong', 'ping works, daemon operational';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub new_call {
|
||||
my @ports = @_;
|
||||
for my $s (@sockets) {
|
||||
$s->close();
|
||||
}
|
||||
@sockets = ();
|
||||
$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',
|
||||
LocalHost => $addr, LocalPort => $port)
|
||||
or die;
|
||||
push(@sockets, $s);
|
||||
}
|
||||
return @sockets;
|
||||
}
|
||||
sub crlf {
|
||||
my ($s) = @_;
|
||||
$s =~ s/\r\n/\n/gs;
|
||||
return $s;
|
||||
}
|
||||
sub sdp_split {
|
||||
my ($s) = @_;
|
||||
return split(/--------*\n/, $s);
|
||||
}
|
||||
sub rtpe_req {
|
||||
my ($cmd, $name, $req) = @_;
|
||||
$req->{command} = $cmd;
|
||||
$req->{'call-id'} = $cid;
|
||||
my $resp = $c->req($req);
|
||||
is $resp->{result}, 'ok', "$name - '$cmd' status";
|
||||
return $resp;
|
||||
}
|
||||
sub offer_answer {
|
||||
my ($cmd, $name, $req, $sdps) = @_;
|
||||
my ($sdp_in, $exp_sdp_out) = sdp_split($sdps);
|
||||
$req->{'from-tag'} = $ft;
|
||||
$req->{sdp} = $sdp_in;
|
||||
my $resp = rtpe_req($cmd, $name, $req);
|
||||
my $regexp = "^\Q$exp_sdp_out\E\$";
|
||||
$regexp =~ s/\\\?/./gs;
|
||||
$regexp =~ s/PORT/(\\d{1,5})/gs;
|
||||
$regexp =~ s/ICEBASE/([0-9a-zA-Z]{16})/gs;
|
||||
$regexp =~ s/ICEUFRAG/([0-9a-zA-Z]{8})/gs;
|
||||
$regexp =~ s/ICEPWD/([0-9a-zA-Z]{26})/gs;
|
||||
$regexp =~ s/CRYPTO128/([0-9a-zA-Z\/+]{40})/gs;
|
||||
$regexp =~ s/CRYPTO192/([0-9a-zA-Z\/+]{51})/gs;
|
||||
$regexp =~ s/CRYPTO256/([0-9a-zA-Z\/+]{62})/gs;
|
||||
$regexp =~ s/LOOPER/([0-9a-f]{12})/gs;
|
||||
my $crlf = crlf($resp->{sdp});
|
||||
like $crlf, qr/$regexp/s, "$name - output '$cmd' SDP";
|
||||
my @matches = $crlf =~ qr/$regexp/s;
|
||||
return @matches;
|
||||
}
|
||||
sub offer {
|
||||
return offer_answer('offer', @_);
|
||||
}
|
||||
sub answer {
|
||||
my ($name, $req, $sdps) = @_;
|
||||
$req->{'to-tag'} = $tt;
|
||||
return offer_answer('answer', $name, $req, $sdps);
|
||||
}
|
||||
sub snd {
|
||||
my ($sock, $dest, $packet) = @_;
|
||||
$sock->send($packet, 0, pack_sockaddr_in($dest, inet_aton('203.0.113.1'))) or die;
|
||||
}
|
||||
sub srtp_snd {
|
||||
my ($sock, $dest, $packet, $srtp_ctx) = @_;
|
||||
if (!$srtp_ctx->{skey}) {
|
||||
my ($key, $salt) = NGCP::Rtpclient::SRTP::decode_inline_base64($srtp_ctx->{key}, $srtp_ctx->{cs});
|
||||
@$srtp_ctx{qw(skey sauth ssalt)} = NGCP::Rtpclient::SRTP::gen_rtp_session_keys($key, $salt);
|
||||
}
|
||||
my ($enc, $out_roc) = NGCP::Rtpclient::SRTP::encrypt_rtp(@$srtp_ctx{qw(cs skey ssalt sauth roc)},
|
||||
'', 0, 0, 0, $packet);
|
||||
$srtp_ctx->{roc} = $out_roc;
|
||||
$sock->send($enc, 0, pack_sockaddr_in($dest, inet_aton('203.0.113.1'))) or die;
|
||||
}
|
||||
sub rtp {
|
||||
my ($pt, $seq, $ts, $ssrc, $payload) = @_;
|
||||
print("rtp in $pt $seq $ts $ssrc\n");
|
||||
return pack('CCnNN a*', 0x80, $pt, $seq, $ts, $ssrc, $payload);
|
||||
}
|
||||
sub rcv {
|
||||
my ($sock, $port, $match, $cb, $cb_arg) = @_;
|
||||
my $p = '';
|
||||
alarm(1);
|
||||
my $addr = $sock->recv($p, 65535, 0) or die;
|
||||
alarm(0);
|
||||
my ($hdr_mark, $pt, $seq, $ts, $ssrc, $payload) = unpack('CCnNN a*', $p);
|
||||
if ($payload) {
|
||||
print("rtp recv $pt $seq $ts $ssrc " . unpack('H*', $payload) . "\n");
|
||||
}
|
||||
if ($cb) {
|
||||
$p = $cb->($hdr_mark, $pt, $seq, $ts, $ssrc, $payload, $p, $cb_arg);
|
||||
}
|
||||
like $p, $match, 'received packet matches';
|
||||
my @matches = $p =~ $match;
|
||||
for my $m (@matches) {
|
||||
if (length($m) == 2) {
|
||||
($m) = unpack('n', $m);
|
||||
}
|
||||
elsif (length($m) == 4) {
|
||||
($m) = unpack('N', $m);
|
||||
}
|
||||
}
|
||||
return @matches;
|
||||
}
|
||||
sub srtp_rcv {
|
||||
my ($sock, $port, $match, $srtp_ctx) = @_;
|
||||
return rcv($sock, $port, $match, \&srtp_dec, $srtp_ctx);
|
||||
}
|
||||
sub srtp_dec {
|
||||
my ($hdr_mark, $pt, $seq, $ts, $ssrc, $payload, $pack, $srtp_ctx) = @_;
|
||||
if (!$srtp_ctx->{skey}) {
|
||||
my ($key, $salt) = NGCP::Rtpclient::SRTP::decode_inline_base64($srtp_ctx->{key}, $srtp_ctx->{cs});
|
||||
@$srtp_ctx{qw(skey sauth ssalt)} = NGCP::Rtpclient::SRTP::gen_rtp_session_keys($key, $salt);
|
||||
}
|
||||
my ($dec, $out_roc, $tag, $hmac) = NGCP::Rtpclient::SRTP::decrypt_rtp(@$srtp_ctx{qw(cs skey ssalt sauth roc)}, $pack);
|
||||
$srtp_ctx->{roc} = $out_roc;
|
||||
is $tag, substr($hmac, 0, length($tag)), 'SRTP auth tag matches';
|
||||
return $dec;
|
||||
}
|
||||
sub escape {
|
||||
return "\Q$_[0]\E";
|
||||
}
|
||||
sub rtpm {
|
||||
my ($pt, $seq, $ts, $ssrc, $payload) = @_;
|
||||
print("rtp matcher $pt $seq $ts $ssrc " . unpack('H*', $payload) . "\n");
|
||||
my $re = '';
|
||||
$re .= escape(pack('C', 0x80));
|
||||
$re .= escape(pack('C', $pt));
|
||||
$re .= $seq >= 0 ? escape(pack('n', $seq)) : '(..)';
|
||||
$re .= $ts >= 0 ? escape(pack('N', $ts)) : '(....)';
|
||||
$re .= $ssrc >= 0 ? escape(pack('N', $ssrc)) : '(....)';
|
||||
$re .= escape($payload);
|
||||
return qr/^$re$/s;
|
||||
}
|
||||
|
||||
sub ft { return $ft; }
|
||||
sub tt { return $tt; }
|
||||
|
||||
sub reverse_tags {
|
||||
($tt, $ft) = ($ft, $tt);
|
||||
}
|
||||
sub new_tt {
|
||||
$tt = $tag_iter++ . "-test-totag";
|
||||
}
|
||||
|
||||
|
||||
|
||||
END {
|
||||
if ($rtpe_pid) {
|
||||
kill('INT', $rtpe_pid) or die;
|
||||
# wait for daemon to terminate
|
||||
my $status = -1;
|
||||
for (1 .. 50) {
|
||||
$status = waitpid($rtpe_pid, WNOHANG);
|
||||
last if $status != 0;
|
||||
Time::HiRes::usleep(100000); # 100 ms x 50 = 5 sec
|
||||
}
|
||||
kill('KILL', $rtpe_pid) if $status == 0;
|
||||
$status == $rtpe_pid or die;
|
||||
$? == 0 or die;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use NGCP::Rtpengine::Test;
|
||||
use NGCP::Rtpclient::SRTP;
|
||||
use NGCP::Rtpengine::AutoTest;
|
||||
use Test::More;
|
||||
|
||||
|
||||
autotest_start(qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1
|
||||
-n 2223 -c 12345 -f -L 7 -E -u 2222 --jitter-buffer=10))
|
||||
or die;
|
||||
|
||||
|
||||
my ($sock_a, $sock_b, $port_a, $port_b, $ssrc, $resp, $srtp_ctx_a, $srtp_ctx_b, @ret1, @ret2);
|
||||
|
||||
|
||||
|
||||
|
||||
# RTP sequencing tests
|
||||
|
||||
($sock_a, $sock_b) = new_call([qw(198.51.100.1 2010)], [qw(198.51.100.3 2012)]);
|
||||
|
||||
($port_a) = offer('two codecs, no transcoding', { ICE => 'remove', replace => ['origin'] }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.1
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 2010 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
|
||||
c=IN IP4 203.0.113.1
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=sendrecv
|
||||
a=rtcp:PORT
|
||||
SDP
|
||||
|
||||
($port_b) = answer('two codecs, no transcoding', { ICE => 'remove', replace => ['origin'] }, <<SDP);
|
||||
v=0
|
||||
o=- 1545997027 1 IN IP4 198.51.100.3
|
||||
s=tester
|
||||
t=0 0
|
||||
m=audio 2012 RTP/AVP 0 8
|
||||
c=IN IP4 198.51.100.3
|
||||
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));
|
||||
snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(0, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(0, 1001, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(0, 1001, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(0, 1010, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(0, 1010, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1000, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1001, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1001, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1010, 3000, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1010, 3000, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1011, 3160, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1011, 3160, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1012, 3320, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1012, 3320, 0x1234, "\x00" x 160));
|
||||
snd($sock_a, $port_b, rtp(8, 1013, 3480, 0x1234, "\x00" x 160));
|
||||
rcv($sock_b, $port_a, rtpm(8, 1013, 3480, 0x1234, "\x00" x 160));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
done_testing();
|
||||
Loading…
Reference in new issue