/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.restcomm.media.rtp.jitter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import org.junit.Before;
import org.junit.Test;
import org.restcomm.media.rtp.MockWallClock;
import org.restcomm.media.rtp.RtpClock;
import org.restcomm.media.rtp.RtpPacket;
import org.restcomm.media.sdp.format.AVProfile;
import org.restcomm.media.spi.memory.Frame;
/**
*
* @author kulikov
*/
public class FixedJitterBufferTest {
private MockWallClock wallClock = new MockWallClock();
private RtpClock rtpClock = new RtpClock(wallClock);
private int period = 20;
private int jitter = 40;
private FixedJitterBuffer jitterBuffer = new FixedJitterBuffer(rtpClock, jitter);
@Before
public void setUp() {
rtpClock.setClockRate(8000);
jitterBuffer.reset();
}
@Test
public void testNormalReadWrite() throws Exception {
RtpPacket[] stream = createStream(100);
Frame[] media = new Frame[stream.length];
for (int i = 0; i < stream.length; i++) {
wallClock.tick(20000000L);
jitterBuffer.write(stream[i], AVProfile.audio.find(8));
media[i] = jitterBuffer.read(wallClock.getTime());
}
this.checkSequence(media);
assertEquals(0, 0);
}
@Test
public void testInnerSort() throws Exception {
RtpPacket p1 = new RtpPacket(172, false);
p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160);
RtpPacket p2 = new RtpPacket(172, false);
p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160);
RtpPacket p3 = new RtpPacket(172, false);
p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160);
RtpPacket p4 = new RtpPacket(172, false);
p4.wrap(false, 8, 4, 160 * 4, 123, new byte[160], 0, 160);
RtpPacket p5 = new RtpPacket(172, false);
p5.wrap(false, 8, 5, 160 * 5, 123, new byte[160], 0, 160);
jitterBuffer.write(p1, AVProfile.audio.find(8));
jitterBuffer.write(p2, AVProfile.audio.find(8));
jitterBuffer.write(p4, AVProfile.audio.find(8));
jitterBuffer.write(p3, AVProfile.audio.find(8));
Frame buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(1, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(2, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(3, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(4, buffer.getSequenceNumber());
}
@Test
public void testOutstanding() throws Exception {
RtpPacket p1 = new RtpPacket(172, false);
p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160);
RtpPacket p2 = new RtpPacket(172, false);
p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160);
RtpPacket p3 = new RtpPacket(172, false);
p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160);
RtpPacket p4 = new RtpPacket(172, false);
p4.wrap(false, 8, 4, 160 * 4, 123, new byte[160], 0, 160);
RtpPacket p5 = new RtpPacket(172, false);
p5.wrap(false, 8, 5, 160 * 5, 123, new byte[160], 0, 160);
jitterBuffer.write(p1, AVProfile.audio.find(8));
jitterBuffer.write(p3, AVProfile.audio.find(8));
jitterBuffer.write(p5, AVProfile.audio.find(8));
assertEquals(0, jitterBuffer.getDropped());
// 60ms + 40ms
wallClock.tick(100000000L);
Frame buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(1, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(3, buffer.getSequenceNumber());
jitterBuffer.write(p2, AVProfile.audio.find(8));
assertEquals(1, jitterBuffer.getDropped());
// buffer = jitterBuffer.read(wallClock.getTime());
// assertEquals(3, buffer.getSequenceNumber());
// buffer = jitterBuffer.read(wallClock.getTime());
// assertEquals(null, buffer);
}
@Test
public void testEmpty() throws Exception {
RtpPacket p1 = new RtpPacket(172, false);
p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160);
RtpPacket p2 = new RtpPacket(172, false);
p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160);
RtpPacket p3 = new RtpPacket(172, false);
p3.wrap(false, 8, 3, 160 * 3, 123, new byte[160], 0, 160);
jitterBuffer.write(p1, AVProfile.audio.find(8));
jitterBuffer.write(p2, AVProfile.audio.find(8));
jitterBuffer.write(p3, AVProfile.audio.find(8));
Frame buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(1, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(2, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(3, buffer.getSequenceNumber());
buffer = jitterBuffer.read(wallClock.getTime());
assertEquals(null, buffer);
}
@Test
public void testOverflow() {
RtpPacket[] stream = createStream(5);
for (int i = 0; i < stream.length; i++) {
jitterBuffer.write(stream[i], AVProfile.audio.find(8));
}
Frame data = jitterBuffer.read(wallClock.getTime());
assertEquals(1, data.getSequenceNumber());
}
@Test
/**
*
* Test that network jitter for RTP packets is estimated correctly
*
* http://tools.ietf.org/html/rfc3550#appendix-A.8
*/
public void testJitter() {
// the timestamp for each packet increases by 10ms=160 timestamp units for sampling rate 8KHz
RtpPacket p1 = new RtpPacket(172, false);
p1.wrap(false, 8, 1, 160 * 1, 123, new byte[160], 0, 160);
RtpPacket p2 = new RtpPacket(172, false);
p2.wrap(false, 8, 2, 160 * 2, 123, new byte[160], 0, 160);
RtpPacket p3 = new RtpPacket(172, false);
p3.wrap(false, 8, 2, 160 * 3, 123, new byte[160], 0, 160);
RtpPacket p4 = new RtpPacket(172, false);
p4.wrap(false, 8, 3, 160 * 4, 123, new byte[160], 0, 160);
RtpPacket p5 = new RtpPacket(172, false);
p5.wrap(false, 8, 3, 160 * 5, 123, new byte[160], 0, 160);
long jitterDeltaLimit = 1; // 1 sampling units delta for timing and rounding errors , i.e. 1/8ms
// write first packet, expected jitter = 0
jitterBuffer.write(p1, AVProfile.audio.find(8));
assertEquals(0, jitterBuffer.getEstimatedJitter(), 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);
jitterBuffer.write(p2, AVProfile.audio.find(8));
assertEquals(0, jitterBuffer.getEstimatedJitter(), 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);
jitterBuffer.write(p3, AVProfile.audio.find(8));
assertEquals(5, jitterBuffer.getEstimatedJitter(), 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);
jitterBuffer.write(p4, AVProfile.audio.find(8));
assertEquals(4, jitterBuffer.getEstimatedJitter(), 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);
jitterBuffer.write(p5, AVProfile.audio.find(8));
assertEquals(9, jitterBuffer.getEstimatedJitter(), jitterDeltaLimit);
}
private RtpPacket[] createStream(int size) {
RtpPacket[] stream = new RtpPacket[size];
int it = 12345;
for (int i = 0; i < stream.length; i++) {
stream[i] = new RtpPacket(172, false);
stream[i].wrap(false, 8, i + 1, 160 * (i + 1) + it, 123, new byte[160], 0, 160);
}
return stream;
}
private void checkSequence(Frame[] media) throws Exception {
boolean res = true;
for (int i = 0; i < media.length - 1; i++) {
if (media[i] == null) {
throw new Exception("Null data at position: " + i);
}
if (media[i + 1] == null) {
throw new Exception("Null data at position: " + (i + 1));
}
res &= (media[i + 1].getSequenceNumber() - media[i].getSequenceNumber() == 1);
}
assertTrue("Wrong sequence ", res);
}
private void shuffle(RtpPacket[] stream) {
Random rnd = new Random();
for (int k = 0; k < 5; k++) {
int i = rnd.nextInt(stream.length - 1);
RtpPacket tmp = stream[i];
stream[i] = stream[i + 1];
stream[i + 1] = tmp;
}
}
}