/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip;
/*
* Packet for Receiving RTP data
*/
public class RtpReceiverPacket extends RtpPacket {
String id;
private long previousRtpTimestamp;
/*
* Statistics
*/
private long packetsDropped = 0;
private int outOfSequencePackets;
private int shortPackets = 0;
private int wrongRtpTimestamp = 0;
private int sequenceOffset = 0;
public RtpReceiverPacket(String id, int encoding, int sampleRate,
int channels, int bufferSize) {
super(encoding, sampleRate, channels, bufferSize);
this.id = id; // for Logging
}
public RtpReceiverPacket(String id, int encoding, int sampleRate,
int channels) {
super(encoding, sampleRate, channels);
this.id = id; // for Logging
}
public void setId(String id) {
this.id = id;
}
/**
* Update RTP receiver information. Keep statistics on packet arrival
* times and out-of-sequence packets.
* Returns offset of packet sequence relative to the correct sequence
* e.g. 0 means packet is in order, -1 means packet seq is one less
* than expected, +1 means packet seq is one higher than expected.
*/
public void updateRtpHeader(int length) {
long packetRtpTimestamp = getRtpTimestamp();
short packetRtpSequenceNumber = getRtpSequenceNumber();
boolean outOfSequence = false;
sequenceOffset = 0;
if (isMarkSet()) {
/*
* The MARK_BIT is supposed to be set in the first packet
* and also the first packet with PCMU_PAYLOAD after COMFORT_PAYLOAD
* has been received.
*
* When the MARK_BIT is set, we accept the sequence number
* and timestamp in the packet.
*
* Calculate what we expect the next packet to have.
*/
setMark(); // make sure mark is set;
rtpSequenceNumber = (short) (packetRtpSequenceNumber + 1);
rtpTimestamp = packetRtpTimestamp + length;
} else {
/*
* Normal packet without the MARK_BIT set.
* Verify the sequenceNumber and timestamp
*
* Set what we expect next packet to have
*/
if (packetRtpSequenceNumber == rtpSequenceNumber) {
rtpSequenceNumber = ++rtpSequenceNumber;
} else {
outOfSequencePackets++;
outOfSequence = true;
sequenceOffset = packetRtpSequenceNumber - rtpSequenceNumber;
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.writeFile(id + ": PACKET OUT OF SEQUENCE! "
+ "seq expected 0x"
+ Integer.toHexString(rtpSequenceNumber)
+ ", got 0x"
+ Integer.toHexString(packetRtpSequenceNumber)
+ ", total out of seq " + outOfSequencePackets
+ ", payload " + getRtpPayload()
+ ", length " + getLength());
}
/*
* Set the sequence number to what we expect next
* but never lower the sequence number.
* We have to be careful here because after
* 0x7fff the next sequence number is negative.
*/
if ((packetRtpSequenceNumber > 0 && rtpSequenceNumber > 0) ||
(packetRtpSequenceNumber < 0 && rtpSequenceNumber < 0)) {
if (packetRtpSequenceNumber > rtpSequenceNumber) {
rtpSequenceNumber = (short)
(packetRtpSequenceNumber + 1); // reset seq number
}
} else {
rtpSequenceNumber = (short)
(packetRtpSequenceNumber + 1); // reset seq number
}
/*
* XXX We may want to insert packets which are missing.
* One approach (Norco does this) is to duplicate
* the last packet received. Maybe inserting a packet
* of silence would be ok too.
* This is handled by the ConferenceMember.
*/
}
if (rtpTimestamp == packetRtpTimestamp) {
rtpTimestamp += length;
} else {
if (outOfSequence == false) {
/*
* Don't report wrong timestamp if packet is out of sequence.
*/
wrongRtpTimestamp++;
if ((wrongRtpTimestamp % 1000) == 0) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.writeFile(id + " Bad packet received: len "
+ length
+ ", ts off by "
+ (long)(packetRtpTimestamp - rtpTimestamp)
+ ", total wrong ts " + wrongRtpTimestamp
+ ", seq " + packetRtpSequenceNumber);
}
}
}
/*
* XXX Should we reset the timestamp to what we just got?
* Seems we have to...
* It's possible some packets were lost and we're
* never going to get the timestamp we'd like.
*/
if (packetRtpTimestamp > rtpTimestamp) {
rtpTimestamp = packetRtpTimestamp + length;
}
}
}
rtpSequenceNumber &= 0xffff;
rtpTimestamp &= 0xffffffff;
previousRtpTimestamp = packetRtpTimestamp;
}
public long getRtpTimestampDiff() {
return rtpTimestamp - previousRtpTimestamp;
}
public void incrementShortPackets() {
shortPackets++;
}
public int getShortPackets() {
return shortPackets;
}
public void incrementOutOfSequencePackets() {
outOfSequencePackets++;
}
public int getOutOfSequencePackets() {
return outOfSequencePackets;
}
public void incrementWrongRtpTimestamp() {
wrongRtpTimestamp++;
}
public int getWrongRtpTimestamp() {
return wrongRtpTimestamp;
}
public int getSequenceOffset() {
return sequenceOffset;
}
/*
* Make sure sequence number wraps properly.
*/
public static void main(String[] args) {
RtpReceiverPacket packet = new RtpReceiverPacket(
"Test", PCM_ENCODING, 8000, 1);
Logger.logLevel = 5;
short expected = packet.rtpSequenceNumber;
while (true) {
packet.updateRtpHeader(180);
if (packet.rtpSequenceNumber != expected) {
Logger.println("expected " + expected
+ " got " + packet.rtpSequenceNumber);
}
expected++;
packet.buffer[2] = (byte) ((packet.rtpSequenceNumber >> 8) & 0xff);
packet.buffer[3] = (byte) (packet.rtpSequenceNumber & 0xff);
}
}
}