/* * 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 static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.apache.commons.net.ntp.TimeStamp; import org.junit.Test; import org.restcomm.media.rtcp.RtcpBye; import org.restcomm.media.rtcp.RtcpPacket; import org.restcomm.media.rtcp.RtcpPacketFactory; import org.restcomm.media.rtcp.RtcpPacketType; import org.restcomm.media.rtcp.RtcpSdes; import org.restcomm.media.rtcp.RtcpSdesChunk; import org.restcomm.media.rtcp.RtcpSdesItem; import org.restcomm.media.rtcp.RtcpSenderReport; 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; import org.restcomm.media.rtp.statistics.RtpStatistics; /** * * @author Henrique Rosa * */ public class RtpStatisticsTest { private final String CNAME = "127.0.0.1"; private final long SSRC = 123456789L; private final MockWallClock wallClock; private final RtpClock rtpClock; public RtpStatisticsTest() { wallClock = new MockWallClock(); rtpClock = new RtpClock(wallClock); rtpClock.setClockRate(8000); } @Test public void testInitialization() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); // then assertTrue(stats.getSsrc() > 0); assertEquals(0, stats.getRtpPacketsReceived()); assertEquals(0, stats.getRtpOctetsReceived()); assertEquals(0, stats.getRtpPacketsSent()); assertEquals(0, stats.getRtpOctetsSent()); assertEquals(0, stats.getSenders()); assertFalse(stats.isSender(stats.getSsrc())); assertEquals(1, stats.getPmembers()); assertEquals(1, stats.getMembers()); assertEquals(1, stats.getMembers()); assertNotNull(stats.getMember(stats.getSsrc())); assertTrue(stats.getRtcpAvgSize() > 0); } @Test public void testOnRtpSent() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, stats.getSsrc(), new byte[160], 0, 160); p2.wrap(false, 8, 1, 160 * 2, stats.getSsrc(), new byte[160], 0, 160); // when stats.onRtpSent(p1); wallClock.tick(20000000L); stats.onRtpSent(p2); // then assertEquals(2, stats.getRtpPacketsSent()); assertEquals(p1.getPayloadLength() + p2.getPayloadLength(), stats.getRtpOctetsSent()); assertEquals(wallClock.getCurrentTime(), stats.getRtpSentOn()); assertTrue(stats.hasSent()); assertTrue(stats.isSender(stats.getSsrc())); assertEquals(1, stats.getSenders()); } @Test public void testOnRtpReceive() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); RtpPacket p1 = new RtpPacket(172, false); RtpPacket p2 = new RtpPacket(172, false); p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); p2.wrap(false, 8, 1, 160 * 2, 456, new byte[160], 0, 160); // when stats.onRtpReceive(p1); wallClock.tick(20000000L); stats.onRtpReceive(p2); // then assertEquals(2, stats.getRtpPacketsReceived()); assertEquals(p1.getPayloadLength() + p2.getPayloadLength(), stats.getRtpOctetsReceived()); assertEquals(wallClock.getTime(), stats.getRtpReceivedOn()); assertFalse(stats.hasSent()); assertEquals(2, stats.getSenders()); assertTrue(stats.isSender(p1.getSyncSource())); assertTrue(stats.isMember(p1.getSyncSource())); assertNotNull(stats.getMember(p1.getSyncSource())); assertTrue(stats.isSender(p2.getSyncSource())); assertTrue(stats.isMember(p2.getSyncSource())); assertNotNull(stats.getMember(p2.getSyncSource())); assertEquals(3, stats.getMembers()); assertEquals(1, stats.getPmembers()); } @Test public void testOnRtcpSent() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); RtcpPacket p1 = RtcpPacketFactory.buildReport(stats); RtcpPacket p2 = RtcpPacketFactory.buildReport(stats); double initialAvgSize = stats.getRtcpAvgSize(); // when // Need to encode the packets first (as required per sending over datagram channel) // so that the RtcpPacket.size variable is initialized! p1.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); p2.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); stats.onRtcpSent(p1); wallClock.tick(20000000L); stats.onRtcpSent(p2); // then double avgSize = calculateAvgSize(initialAvgSize, p1.getSize(), p2.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); } @Test public void testOnRtcpReceiveReport() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); double initialAvgSize = stats.getRtcpAvgSize(); stats.setRtcpPacketType(RtcpPacketType.RTCP_REPORT); RtpPacket rtp1 = new RtpPacket(172, false); RtpPacket rtp2 = new RtpPacket(172, false); rtp1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); rtp2.wrap(false, 8, 1, 160 * 2, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(System.currentTimeMillis()); RtcpSenderReport sr = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 1, 5, 5 * 160); RtcpSdes sdes = new RtcpSdes(false); RtcpSdesChunk sdesChunk = new RtcpSdesChunk(123); sdesChunk.addRtcpSdesItem(new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, CNAME)); sdes.addRtcpSdesChunk(sdesChunk); RtcpPacket rtcp1 = new RtcpPacket(sr, sdes); // Need to encode the packets first (as required per sending over the network) // so that the RtcpPacket.size variable is initialized! rtcp1.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); // when stats.onRtpReceive(rtp1); wallClock.tick(20000000L); stats.onRtpReceive(rtp2); wallClock.tick(20000000L); stats.onRtcpReceive(rtcp1); // then RtpMember member = stats.getMember(123); assertTrue(stats.isMember(sr.getSsrc())); assertNotNull(member); assertEquals(rtp2.getSeqNumber(), member.getExtHighSequence()); assertEquals(0, member.getReceivedSinceSR()); double avgSize = calculateAvgSize(initialAvgSize, rtcp1.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); } @Test public void testOnRtcpReceiveReportWithByeScheduled() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); double initialAvgSize = stats.getRtcpAvgSize(); stats.setRtcpPacketType(RtcpPacketType.RTCP_BYE); RtpPacket rtp1 = new RtpPacket(172, false); RtpPacket rtp2 = new RtpPacket(172, false); rtp1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); rtp2.wrap(false, 8, 1, 160 * 2, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(System.currentTimeMillis()); RtcpSenderReport sr = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 1, 5, 5 * 160); RtcpSdes sdes = new RtcpSdes(false); RtcpSdesChunk sdesChunk = new RtcpSdesChunk(123); sdesChunk.addRtcpSdesItem(new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, CNAME)); sdes.addRtcpSdesChunk(sdesChunk); RtcpPacket rtcp1 = new RtcpPacket(sr, sdes); // Need to encode the packets first (as required per sending over the network) // so that the RtcpPacket.size variable is initialized! rtcp1.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); // when stats.onRtpReceive(rtp1); wallClock.tick(20000000L); stats.onRtpReceive(rtp2); wallClock.tick(20000000L); stats.onRtcpReceive(rtcp1); // then RtpMember member = stats.getMember(123); assertFalse(stats.isMember(sr.getSsrc())); assertNull(member); double avgSize = calculateAvgSize(initialAvgSize, rtcp1.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); } private double calculateAvgSize(double initialSize, int ...packetSizes) { double avgSize = initialSize; for (int size : packetSizes) { avgSize = (1.0 / 16.0) * size + (15.0 / 16.0) * avgSize; } return avgSize; } @Test public void testOnRtcpReceiveBye() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); double initialAvgSize = stats.getRtcpAvgSize(); stats.setRtcpPacketType(RtcpPacketType.RTCP_REPORT); RtpPacket rtp1 = new RtpPacket(172, false); RtpPacket rtp2 = new RtpPacket(172, false); RtpPacket rtp3 = new RtpPacket(172, false); rtp1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); rtp2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160); rtp3.wrap(false, 8, 3, 160 * 2, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(System.currentTimeMillis()); RtcpSenderReport sr = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 1, 5, 5 * 160); RtcpSdes sdes = new RtcpSdes(false); RtcpSdesChunk sdesChunk = new RtcpSdesChunk(123); sdesChunk.addRtcpSdesItem(new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, CNAME)); sdes.addRtcpSdesChunk(sdesChunk); RtcpPacket rtcp1 = new RtcpPacket(sr, sdes); RtcpBye bye = new RtcpBye(false); RtcpPacket rtcp2 = new RtcpPacket(sr, sdes, bye); // Need to encode the packets first (as required per sending over the network) // so that the RtcpPacket.size variable is initialized! rtcp1.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); // when (1) - receive RTP and RTCP SR stats.onRtpReceive(rtp1); // Adds to the senders and members list wallClock.tick(20000000L); stats.onRtcpReceive(rtcp1); wallClock.tick(20000000L); stats.onRtpReceive(rtp2); wallClock.tick(20000000L); stats.onRtpReceive(rtp3); // then (1) - sender 123 is registered RtpMember member = stats.getMember(123); assertTrue(stats.isMember(sr.getSsrc())); assertNotNull(member); assertEquals(rtp3.getSeqNumber(), member.getExtHighSequence()); assertEquals(1, member.getReceivedSinceSR()); double avgSize = calculateAvgSize(initialAvgSize, rtcp1.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); // when (2) - receive RTCP BYE wallClock.tick(30000000L); stats.onRtcpReceive(rtcp2); // then (2) - sender is deregistered member = stats.getMember(123); assertFalse(stats.isMember(sr.getSsrc())); assertNull(member); assertFalse(stats.isSender(sr.getSsrc())); avgSize = calculateAvgSize(avgSize, rtcp2.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); } @Test public void testOnRtcpReceiveByeWithByeScheduled() { // given RtpStatistics stats = new RtpStatistics(rtpClock, SSRC, CNAME); double initialAvgSize = stats.getRtcpAvgSize(); stats.setRtcpPacketType(RtcpPacketType.RTCP_REPORT); RtpPacket rtp1 = new RtpPacket(172, false); RtpPacket rtp2 = new RtpPacket(172, false); RtpPacket rtp3 = new RtpPacket(172, false); rtp1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160); rtp2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160); rtp3.wrap(false, 8, 3, 160 * 2, 123, new byte[160], 0, 160); TimeStamp ntp = new TimeStamp(System.currentTimeMillis()); RtcpSenderReport sr = new RtcpSenderReport(false, 123, ntp.getSeconds(), ntp.getFraction(), 160 * 1, 5, 5 * 160); RtcpSdes sdes = new RtcpSdes(false); RtcpSdesChunk sdesChunk = new RtcpSdesChunk(123); sdesChunk.addRtcpSdesItem(new RtcpSdesItem(RtcpSdesItem.RTCP_SDES_CNAME, CNAME)); sdes.addRtcpSdesChunk(sdesChunk); RtcpPacket rtcp1 = new RtcpPacket(sr, sdes); RtcpBye bye = new RtcpBye(false); RtcpPacket rtcp2 = new RtcpPacket(sr, sdes, bye); // Need to encode the packets first (as required per sending over the network) // so that the RtcpPacket.size variable is initialized! rtcp1.encode(new byte[RtpPacket.RTP_PACKET_MAX_SIZE], 0); // when (1) - receive RTP and RTCP SR stats.onRtpReceive(rtp1); // Adds to the senders and members list wallClock.tick(20000000L); stats.onRtcpReceive(rtcp1); wallClock.tick(20000000L); stats.onRtpReceive(rtp2); wallClock.tick(20000000L); stats.onRtpReceive(rtp3); // then (1) - sender 123 is registered int memberCount = stats.getMembers(); RtpMember member = stats.getMember(123); assertTrue(stats.isMember(sr.getSsrc())); assertNotNull(member); assertEquals(rtp3.getSeqNumber(), member.getExtHighSequence()); assertEquals(1, member.getReceivedSinceSR()); double avgSize = calculateAvgSize(initialAvgSize, rtcp1.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); // when (2) - receive RTCP BYE // notice RTCP BYE is scheduled in the meanwhile stats.setRtcpPacketType(RtcpPacketType.RTCP_BYE); wallClock.tick(30000000L); stats.onRtcpReceive(rtcp2); // then (2) - sender is kept but members is updated member = stats.getMember(123); assertTrue(stats.isMember(sr.getSsrc())); assertNotNull(member); assertTrue(stats.isSender(sr.getSsrc())); assertEquals(memberCount + 1, stats.getMembers()); avgSize = calculateAvgSize(avgSize, rtcp2.getSize()); assertEquals(avgSize, stats.getRtcpAvgSize(), 0); } }