src/jlibrtp/Participant.java
changeset 13 e684f11070d5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jlibrtp/Participant.java	Sat Mar 14 22:15:41 2009 +0100
@@ -0,0 +1,437 @@
+/**
+ * Java RTP Library (jlibrtp)
+ * Copyright (C) 2006 Arne Kepp
+ * 
+ * This library 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 library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+package jlibrtp;
+
+import java.net.InetSocketAddress;
+
+/**
+ * A participant represents a peer in an RTPSession. Based on the information stored on 
+ * these objects, packets are processed and statistics generated for RTCP.
+ */
+public class Participant {
+	/** Whether the participant is unexpected, e.g. arrived through unicast with SDES */
+	protected boolean unexpected = false;
+	/** Where to send RTP packets (unicast)*/
+	protected InetSocketAddress rtpAddress = null; 
+	/** Where to send RTCP packets (unicast) */
+	protected InetSocketAddress rtcpAddress = null;
+	/** Where the first RTP packet was received from */
+	protected InetSocketAddress rtpReceivedFromAddress = null;
+	/** Where the first RTCP packet was received from */
+	protected InetSocketAddress rtcpReceivedFromAddress = null;
+	
+	/** SSRC of participant */
+	protected long ssrc = -1;
+	/** SDES CNAME */
+	protected String cname = null;
+	/** SDES The participant's real name */
+	protected String name = null;
+	/** SDES The participant's email */
+	protected String email = null;
+	/** SDES The participant's phone number */
+	protected String phone = null;
+	/** SDES The participant's location*/
+	protected String loc = null;
+	/** SDES The tool the participants is using */
+	protected String tool = null;
+	/** SDES A note */
+	protected String note = null;
+	/** SDES A priv string, loosely defined */
+	protected String priv = null;
+
+	// Receiver Report Items
+	/** RR First sequence number */
+	protected int firstSeqNumber = -1;
+	/** RR Last sequence number */
+	protected int lastSeqNumber = 0;
+	/** RR Number of times sequence number has rolled over */
+	protected long seqRollOverCount = 0;
+	/** RR Number of packets received */
+	protected long receivedPkts = 0;
+	/** RR Number of octets received */
+	protected long receivedOctets = 0;
+	/** RR Number of packets received since last SR */
+	protected int receivedSinceLastSR = 0;
+	/** RR Sequence number associated with last SR */
+	protected int lastSRRseqNumber = 0;
+	/** RR Interarrival jitter */
+	protected double interArrivalJitter = -1.0;
+	/** RR Last received RTP Timestamp */
+	protected long lastRtpTimestamp = 0;
+	
+	/** RR Middle 32 bits of the NTP timestamp in the last SR */
+	protected long timeStampLSR = 0;
+	/** RR The time when we actually got the last SR */
+	protected long timeReceivedLSR = 0;
+	
+	/** Gradient where UNIX timestamp = ntpGradient*RTPTimestamp * ntpOffset */
+	protected double ntpGradient = -1;
+	/** Offset where UNIX timestamp = ntpGradient*RTPTimestamp * ntpOffset */
+	protected long ntpOffset = -1;
+	/** Last NTP received in SR packet, MSB */
+	protected long lastNtpTs1 = 0; //32 bits
+	/** Last NTP received in SR packet, LSB */
+	protected long lastNtpTs2 = 0; //32 bits
+	/** RTP Timestamp in last SR packet */
+	protected long lastSRRtpTs = 0; //32 bits
+	
+	/** UNIX time when a BYE was received from this participant, for pruning */
+	protected long timestampBYE = -1;	// The user said BYE at this time
+	
+	/** Store the packets received from this participant */
+	protected PktBuffer pktBuffer = null;
+
+	/** UNIX time of last RTP packet, to check whether this participant has sent anything recently */
+	protected long lastRtpPkt = -1; //Time of last RTP packet
+	/** UNIX time of last RTCP packet, to check whether this participant has sent anything recently */
+	protected long lastRtcpPkt = -1; //Time of last RTCP packet
+	/** UNIX time this participant was added by application, to check whether we ever heard back */
+	protected long addedByApp = -1; //Time the participant was added by application
+	/** UNIX time of last time we sent an RR to this user */
+	protected long lastRtcpRRPkt = -1; //Timestamp of last time we sent this person an RR packet
+	/** Unix time of second to last time we sent and RR to this user */
+	protected long secondLastRtcpRRPkt = -1; //Timestamp of 2nd to last time we sent this person an RR Packet
+		
+	/**
+	 * Create a basic participant. If this is a <b>unicast</b> session you must provide network address (ipv4 or ipv6) and ports for RTP and RTCP, 
+	 * as well as a cname for this contact. These things should be negotiated through SIP or a similar protocol.
+	 * 
+	 * jlibrtp will listen for RTCP packets to obtain a matching SSRC for this participant, based on cname.
+	 * @param networkAddress string representation of network address (ipv4 or ipv6). Use "127.0.0.1" for multicast session.
+	 * @param rtpPort port on which peer expects RTP packets. Use 0 if this is a sender-only, or this is a multicast session.
+	 * @param rtcpPort port on which peer expects RTCP packets. Use 0 if this is a sender-only, or this is a multicast session.
+	 */
+	public Participant(String networkAddress, int rtpPort, int rtcpPort) {
+		if(RTPSession.rtpDebugLevel > 6) {
+			System.out.println("Creating new participant: " + networkAddress);
+		}
+		
+		// RTP
+		if(rtpPort > 0) {
+			try {
+				rtpAddress = new InetSocketAddress(networkAddress, rtpPort);
+			} catch (Exception e) {
+				System.out.println("Couldn't resolve " + networkAddress);
+			}
+			//isReceiver = true;
+		}
+		
+		// RTCP 
+		if(rtcpPort > 0) {
+			try {
+				rtcpAddress = new InetSocketAddress(networkAddress, rtcpPort);
+			} catch (Exception e) {
+				System.out.println("Couldn't resolve " + networkAddress);
+			}
+		}
+		
+		//By default this is a sender
+		//isSender = true;
+	}
+	
+	// We got a packet, but we don't know this person yet.
+	protected Participant(InetSocketAddress rtpAdr, InetSocketAddress rtcpAdr, long SSRC) {
+		rtpReceivedFromAddress = rtpAdr;
+		rtcpReceivedFromAddress = rtcpAdr;
+		ssrc = SSRC;
+		unexpected = true;
+	}
+	
+	// Dummy constructor to ease testing
+	protected Participant() {
+		System.out.println("Don't use the Participan(void) Constructor!");
+	}
+	
+	/**
+	 * RTP Address registered with this participant.
+	 * 
+	 * @return address of participant
+	 */
+	InetSocketAddress getRtpSocketAddress() {
+		return rtpAddress;
+	}
+	
+	
+	/**
+	 * RTCP Address registered with this participant.
+	 * 
+	 * @return address of participant
+	 */
+	InetSocketAddress getRtcpSocketAddress() {
+		return rtcpAddress;
+	}
+
+	/**
+	 * InetSocketAddress this participant has used to
+	 * send us RTP packets.
+	 * 
+	 * @return address of participant
+	 */
+	InetSocketAddress getRtpReceivedFromAddress() {
+		return rtpAddress;
+	}
+
+	
+	
+	/**
+	 * InetSocketAddress this participant has used to
+	 * send us RTCP packets.
+	 * 
+	 * @return address of participant
+	 */
+	InetSocketAddress getRtcpReceivedFromAddress() {
+		return rtcpAddress;
+	}
+	
+	
+	/**
+	 * CNAME registered for this participant.
+	 * 
+	 * @return the cname
+	 */
+	public String getCNAME() {
+		return cname;
+	}
+	
+	
+	/**
+	 * NAME registered for this participant.
+	 * 
+	 * @return the name
+	 */
+	public String getNAME() {
+		return name;
+	}
+	
+	/**
+	 * EMAIL registered for this participant.
+	 * 
+	 * @return the email address
+	 */
+	public String getEmail() {
+		return email;
+	}
+	
+	/**
+	 * PHONE registered for this participant.
+	 * 
+	 * @return the phone number
+	 */
+	public String getPhone() {
+		return phone;
+	}
+	
+	/**
+	 * LOCATION registered for this participant.
+	 * 
+	 * @return the location
+	 */
+	public String getLocation() {
+		return loc;
+	}
+	
+	/**
+	 * NOTE registered for this participant.
+	 * 
+	 * @return the note
+	 */
+	public String getNote() {
+		return note;
+	}
+	
+	/**
+	 * PRIVATE something registered for this participant.
+	 * 
+	 * @return the private-string
+	 */
+	public String getPriv() {
+		return priv;
+	}
+	
+	/**
+	 * TOOL something registered for this participant.
+	 * 
+	 * @return the tool
+	 */
+	public String getTool() {
+		return tool;
+	}
+		
+	/**
+	 * SSRC for participant, determined through RTCP SDES
+	 * 
+	 * @return SSRC (32 bit unsigned integer as long)
+	 */
+	public long getSSRC() {
+		return this.ssrc;
+	}
+	
+	/** 
+	 * Updates the participant with information for receiver reports.
+	 * 
+	 * @param packetLength to keep track of received octets
+	 * @param pkt the most recently received packet
+	 */
+	protected void updateRRStats(int packetLength, RtpPkt pkt) {
+		int curSeqNum = pkt.getSeqNumber();
+		
+		if(firstSeqNumber < 0) {
+			firstSeqNumber = curSeqNum;
+		}
+		
+		receivedOctets += packetLength;
+		receivedSinceLastSR++;
+		receivedPkts++;
+		
+		long curTime =  System.currentTimeMillis();
+		
+		if( this.lastSeqNumber < curSeqNum ) {
+			//In-line packet, best thing you could hope for
+			this.lastSeqNumber = curSeqNum;
+						
+		} else if(this.lastSeqNumber - this.lastSeqNumber < -100) {
+			//Sequence counter rolled over
+			this.lastSeqNumber = curSeqNum;
+			seqRollOverCount++;
+			
+		} else {
+			//This was probably a duplicate or a late arrival.
+		}
+		
+		// Calculate jitter
+		if(this.lastRtpPkt > 0) {
+			
+			long D = (pkt.getTimeStamp() - curTime) - (this.lastRtpTimestamp - this.lastRtpPkt);
+			if(D < 0)
+				D = (-1)*D;
+			
+			this.interArrivalJitter += ((double)D - this.interArrivalJitter) / 16.0;
+		}
+
+		lastRtpPkt = curTime;
+		lastRtpTimestamp = pkt.getTimeStamp();
+	}
+	
+	/**
+	 * Calculates the extended highest sequence received by adding 
+	 * the last sequence number to 65536 times the number of times 
+	 * the sequence counter has rolled over.
+	 * 
+	 * @return extended highest sequence
+	 */
+	protected long getExtHighSeqRecv() {
+		return (65536*seqRollOverCount + lastSeqNumber);
+	}
+	
+	/**
+	 * Get the fraction of lost packets, calculated as described
+	 * in RFC 3550 as a fraction of 256.
+	 * 
+	 * @return the fraction of lost packets since last SR received
+	 */
+	protected int getFractionLost() {
+		int expected = (lastSeqNumber - lastSRRseqNumber);
+		if(expected < 0)
+			expected = 65536 + expected;
+                
+		int fraction = 256 * (expected - receivedSinceLastSR);
+		if(expected > 0) {
+			fraction = (fraction / expected);
+		} else {
+			fraction = 0;
+		}
+		
+		//Clear counters 
+		receivedSinceLastSR = 0;
+		lastSRRseqNumber = lastSeqNumber;
+		
+		return fraction;
+	}
+	
+	/**
+	 * The total number of packets lost during the session.
+	 * 
+	 * Returns zero if loss is negative, i.e. duplicates have been received.
+	 * 
+	 * @return number of lost packets, or zero.
+	 */
+	protected long getLostPktCount() {
+		long lost = (this.getExtHighSeqRecv() - this.firstSeqNumber) - receivedPkts;
+		
+		if(lost < 0)
+			lost = 0;
+		return lost;
+	}
+	
+	/** 
+	 * 
+	 * @return the interArrivalJitter, calculated continuously
+	 */
+	protected double getInterArrivalJitter() {
+		return this.interArrivalJitter;
+	}
+	
+	/**
+	 * Set the timestamp for last sender report
+	 * 
+	 * @param ntp1 high order bits
+	 * @param ntp2 low order bits
+	 */
+	protected void setTimeStampLSR(long ntp1, long ntp2) {
+		// Use what we've got
+		byte[] high = StaticProcs.uIntLongToByteWord(ntp1);
+		byte[] low = StaticProcs.uIntLongToByteWord(ntp2);
+		low[3] = low[1];
+		low[2] = low[0];
+		low[1] = high[3];
+		low[0] = high[2];
+		
+		this.timeStampLSR = StaticProcs.bytesToUIntLong(low, 0);
+	}
+	
+	/**
+	 * Calculate the delay between the last received sender report
+	 * and now.
+	 * 
+	 * @return the delay in units of 1/65.536ms
+	 */
+	protected long delaySinceLastSR() {
+		if(this.timeReceivedLSR < 1) 
+			return 0;
+			
+		long delay = System.currentTimeMillis() - this.timeReceivedLSR;
+		
+		//Convert ms into 1/65536s = 1/65.536ms
+		return (long) ((double)delay * 65.536);
+	}
+	
+	/**
+	 * Only for debugging purposes
+	 */
+	public void debugPrint() {
+		System.out.print(" Participant.debugPrint() SSRC:"+this.ssrc+" CNAME:"+this.cname);
+		if(this.rtpAddress != null)
+			System.out.print(" RTP:"+this.rtpAddress.toString());
+		if(this.rtcpAddress != null)
+			System.out.print(" RTCP:"+this.rtcpAddress.toString());
+		System.out.println("");
+		
+		System.out.println("                          Packets received:"+this.receivedPkts);
+	}
+}