/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2014, Telestax Inc and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package org.restcomm.media.rtp.statistics; import static org.junit.Assert.assertEquals; import java.util.Date; import org.apache.commons.net.ntp.TimeStamp; import org.junit.Test; import org.restcomm.media.rtcp.RtcpSenderReport; import org.restcomm.media.rtcp.ntp.NtpUtils; import org.restcomm.media.rtp.MockWallClock; import org.restcomm.media.rtp.RtpClock; import org.restcomm.media.rtp.RtpPacket; import org.restcomm.media.rtp.statistics.RtpMember; /** * Unit tests for {@link RtpMember} * * @author Henrique Rosa (henrique.rosa@telestax.com) * */ public class RtpMemberTest { private final MockWallClock wallClock; private final RtpClock rtpClock; public RtpMemberTest() { this.wallClock = new MockWallClock(); this.rtpClock = new RtpClock(this.wallClock); this.rtpClock.setClockRate(8000); } private int sumOctets(RtpPacket ...packets) { int sum = 0; for (RtpPacket packet : packets) { sum += packet.getPayloadLength(); } return sum; } private void receiveRtpPackets(RtpMember member, RtpPacket ...packets) { for (RtpPacket packet : packets) { member.onReceiveRtp(packet); wallClock.tick(20000); } } @Test public void testOnReceiveRtpPacket() { // given RtpMember member = new RtpMember(rtpClock, 123); RtpPacket p1 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); // when member.onReceiveRtp(p1); // then // packet is still in probation so all values are zero assertEquals(p1.getSyncSource(), member.getSsrc()); assertEquals(0, member.getOctetsReceived()); assertEquals(0, member.getPacketsReceived()); assertEquals(0, member.getReceivedSinceSR()); assertEquals(0, member.getPacketsLost()); assertEquals(0, member.getExtHighSequence()); assertEquals(0, member.getSequenceCycle()); assertEquals(0, member.getLastSR()); assertEquals(0, member.getLastSRdelay()); } @Test public void testOnReceiveRtpPackets() { // given RtpMember member = new RtpMember(rtpClock, 123); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); RtpPacket p3 = new RtpPacket(172, false); RtpPacket p4 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160); p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160); p4.wrap(false, 8, 3, 160 * 4, 123, new byte[160], 0, 160); // when receiveRtpPackets(member, p1, p2, p3, p4); // then assertEquals(p1.getSyncSource(), member.getSsrc()); assertEquals(sumOctets(p3, p4), member.getOctetsReceived()); assertEquals(2, member.getPacketsReceived()); assertEquals(2, member.getReceivedSinceSR()); assertEquals(-1, member.getPacketsLost()); // expected 1 packet but received 2 (with same seqnum) assertEquals(p4.getSeqNumber(), member.getExtHighSequence()); assertEquals(0, member.getSequenceCycle()); assertEquals(0, member.getLastSR()); assertEquals(0, member.getLastSRdelay()); } @Test public void testSequenceCycleIncrement() { // given RtpMember member = new RtpMember(rtpClock, 123); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); RtpPacket p3 = new RtpPacket(172, false); RtpPacket p4 = new RtpPacket(172, false); RtpPacket p5 = new RtpPacket(172, false); p1.wrap(false, 8, 100, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 101, 160 * 2, 123, new byte[160], 0, 160); p3.wrap(false, 8, 102, 160 * 3, 123, new byte[160], 0, 160); p4.wrap(false, 8, 103, 160 * 4, 123, new byte[160], 0, 160); // The 100+ sequence number gap will cause the sequence cycle to increment p5.wrap(false, 8, 50, 160 * 5, 123, new byte[160], 0, 160); // when receiveRtpPackets(member, p1, p2, p3, p4, p5); // then int expectedSeqCycle = 1; assertEquals(expectedSeqCycle, member.getSequenceCycle()); int expectedHighSequence = (expectedSeqCycle * RtpMember.RTP_SEQ_MOD) + p5.getSeqNumber(); assertEquals(expectedHighSequence, member.getExtHighSequence()); int expected = expectedHighSequence - p1.getSeqNumber() - 1; // discard last packet hence the -1 long expectedLostPackets = expected - member.getPacketsReceived(); assertEquals(expectedLostPackets, member.getPacketsLost()); } @Test public void testJitter() { /* * Given */ RtpMember member = new RtpMember(rtpClock, 123); // the timestamp for each packet increases by 10ms=160 timestamp units // for sampling rate 8KHz RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); RtpPacket p3 = new RtpPacket(172, false); RtpPacket p4 = new RtpPacket(172, false); RtpPacket p5 = new RtpPacket(172, false); RtpPacket p6 = new RtpPacket(172, false); RtpPacket p7 = new RtpPacket(172, false); // packets in probation p6.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); p7.wrap(false, 8, 3, 160 * 1, 123, new byte[160], 0, 160); // valid packets p1.wrap(false, 8, 3, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 4, 160 * 2, 123, new byte[160], 0, 160); p3.wrap(false, 8, 5, 160 * 3, 123, new byte[160], 0, 160); p4.wrap(false, 8, 6, 160 * 4, 123, new byte[160], 0, 160); p5.wrap(false, 8, 7, 160 * 5, 123, new byte[160], 0, 160); // 1 sampling units delta for timing and rounding errors , i.e. 1/8ms long jitterDeltaLimit = 1; /* * When/Then */ // send two dummy ordered packets to pass probation period member.onReceiveRtp(p6); member.onReceiveRtp(p7); // write first packet, expected jitter = 0 member.onReceiveRtp(p1); assertEquals(0, member.getJitter(), jitterDeltaLimit); // move time forward by 20ms and write the second packet // the transit time should remain approximately the same - near 0ms. // expected jitter = 0; wallClock.tick(20000000L); member.onReceiveRtp(p2); assertEquals(0, member.getJitter(), jitterDeltaLimit); // move time forward by 30ms and write the next packet // the transit time should increase by 10ms, // as suggested by the difference in the third packet timestamp (160*3) // and the 20ms delay for the server to receive the second packet // expected jitter should be close to the 10ms delay in timestamp // units/16, i.e. 80/16. wallClock.tick(30000000L); member.onReceiveRtp(p3); assertEquals(5, member.getJitter(), jitterDeltaLimit); // move time forward by 20ms and write the next packet // the transit time does not change from the previous packet. // The jitter should stay approximately the same. wallClock.tick(20000000L); member.onReceiveRtp(p4); assertEquals(4, member.getJitter(), jitterDeltaLimit); // move time forward by 30ms and write the next packet // packet was delayed 10ms again. // The estimated jitter should increase significantly, by nearly 5ms // (80/16) wallClock.tick(30000000L); member.onReceiveRtp(p5); assertEquals(9, member.getJitter(), jitterDeltaLimit); } @Test public void testOnReceiveSR() { // given RtpMember member = new RtpMember(rtpClock, 123); TimeStamp ntp = new TimeStamp(new Date()); RtcpSenderReport sendReport = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 2, 100, 100 * 130); long receiveTime = this.wallClock.getCurrentTime(); // when member.onReceiveSR(sendReport); this.wallClock.tick(20000000L); // then long expectedSrTimestamp = NtpUtils.calculateLastSrTimestamp(ntp.getSeconds(), ntp.getFraction()); assertEquals(expectedSrTimestamp, member.getLastSR()); assertEquals(0, member.getReceivedSinceSR()); long expectedDelay = (long) ((this.wallClock.getCurrentTime() - receiveTime) * 65.536); assertEquals(expectedDelay, member.getLastSRdelay()); } @Test public void testOnReceiveRtpAndSR() { // given RtpMember member = new RtpMember(rtpClock, 123); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); RtpPacket p3 = new RtpPacket(172, false); RtpPacket p4 = new RtpPacket(172, false); RtpPacket p5 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160); p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160); p4.wrap(false, 8, 4, 160 * 4, 123, new byte[160], 0, 160); p5.wrap(false, 8, 5, 160 * 5, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(new Date()); RtcpSenderReport sendReport = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 2, 100, 100 * 130); // when member.onReceiveRtp(p1); wallClock.tick(20000000L); member.onReceiveRtp(p2); wallClock.tick(20000000L); member.onReceiveRtp(p3); wallClock.tick(20000000L); member.onReceiveRtp(p4); wallClock.tick(20000000L); long receivedSrOn = this.wallClock.getCurrentTime(); member.onReceiveSR(sendReport); member.onReceiveRtp(p5); wallClock.tick(20000000L); // then assertEquals(0, member.getSequenceCycle()); long expectedHighSeq = (65536 * 0 + p5.getSeqNumber()); assertEquals(expectedHighSeq, member.getExtHighSequence()); assertEquals(3, member.getPacketsReceived()); assertEquals(sumOctets(p3, p4, p5), member.getOctetsReceived()); assertEquals(1, member.getReceivedSinceSR()); assertEquals(0, member.getPacketsLost()); assertEquals(0, member.getFractionLost()); // expected packets = received packets = no loss long expectedDelay = (long) ((wallClock.getCurrentTime() - receivedSrOn) * 65.536); assertEquals(expectedDelay, member.getLastSRdelay()); } @Test public void testLostPackets() { // given RtpMember member = new RtpMember(rtpClock, 123); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); RtpPacket p3 = new RtpPacket(172, false); RtpPacket p4 = new RtpPacket(172, false); RtpPacket p5 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160); p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160); p4.wrap(false, 8, 4, 160 * 4, 123, new byte[160], 0, 160); p5.wrap(false, 8, 24, 160 * 5, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(new Date()); RtcpSenderReport sendReport = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 2, 100, 100 * 130); // when member.onReceiveRtp(p1); wallClock.tick(20000000L); member.onReceiveRtp(p2); wallClock.tick(20000000L); member.onReceiveRtp(p3); wallClock.tick(20000000L); member.onReceiveRtp(p4); wallClock.tick(20000000L); member.onReceiveSR(sendReport); member.onReceiveRtp(p5); wallClock.tick(20000000L); // then int packetsReceived = 3; // 2 first packets were in probation period int expected = p5.getSeqNumber() - p3.getSeqNumber() + 1; // only considers valid sequence after 2 consecutive packets long expectedLostPackets = expected - packetsReceived; long lostInterval = expected - packetsReceived; long fractionLost = (lostInterval * 256) / expected; assertEquals(expectedLostPackets, member.getPacketsLost()); assertEquals(fractionLost, member.getFractionLost()); } }