--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jlibrtp/RTPSession.java Sat Mar 14 22:15:41 2009 +0100
@@ -0,0 +1,1053 @@
+/**
+ * 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.DatagramSocket;
+import java.net.MulticastSocket;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Iterator;
+import java.util.concurrent.locks.*;
+import java.util.Random;
+import java.util.Enumeration;
+/**
+ * The RTPSession object is the core of jlibrtp.
+ *
+ * One should be instantiated for every communication channel, i.e. if you send voice and video, you should create one for each.
+ *
+ * The instance holds a participant database, as well as other information about the session. When the application registers with the session, the necessary threads for receiving and processing RTP packets are spawned.
+ *
+ * RTP Packets are sent synchronously, all other operations are asynchronous.
+ *
+ * @author Arne Kepp
+ */
+public class RTPSession {
+ /**
+ * The debug level is final to avoid compilation of if-statements.</br>
+ * 0 provides no debugging information, 20 provides everything </br>
+ * Debug output is written to System.out</br>
+ * Debug level for RTP related things.
+ */
+ final static public int rtpDebugLevel = 0;
+ /**
+ * The debug level is final to avoid compilation of if-statements.</br>
+ * 0 provides no debugging information, 20 provides everything </br>
+ * Debug output is written to System.out</br>
+ * Debug level for RTCP related things.
+ */
+ final static public int rtcpDebugLevel = 0;
+
+ /** RTP unicast socket */
+ protected DatagramSocket rtpSock = null;
+ /** RTP multicast socket */
+ protected MulticastSocket rtpMCSock = null;
+ /** RTP multicast group */
+ protected InetAddress mcGroup = null;
+
+ // Internal state
+ /** Whether this session is a multicast session or not */
+ protected boolean mcSession = false;
+ /** Current payload type, can be changed by application */
+ protected int payloadType = 0;
+ /** SSRC of this session */
+ protected long ssrc;
+ /** The last timestamp when we sent something */
+ protected long lastTimestamp = 0;
+ /** Current sequence number */
+ protected int seqNum = 0;
+ /** Number of packets sent by this session */
+ protected int sentPktCount = 0;
+ /** Number of octets sent by this session */
+ protected int sentOctetCount = 0;
+
+ /** The random seed */
+ protected Random random = null;
+
+ /** Session bandwidth in BYTES per second */
+ protected int bandwidth = 8000;
+
+ /** By default we do not return packets from strangers in unicast mode */
+ protected boolean naiveReception = false;
+
+ /** Should the library attempt frame reconstruction? */
+ protected boolean frameReconstruction = true;
+
+ /** Maximum number of packets used for reordering */
+ protected int pktBufBehavior = 3;
+
+ /** Participant database */
+ protected ParticipantDatabase partDb = new ParticipantDatabase(this);
+ /** Handle to application interface for RTP */
+ protected RTPAppIntf appIntf = null;
+ /** Handle to application interface for RTCP (optional) */
+ protected RTCPAppIntf rtcpAppIntf = null;
+ /** Handle to application interface for AVPF, RFC 4585 (optional) */
+ protected RTCPAVPFIntf rtcpAVPFIntf = null;
+ /** Handle to application interface for debugging */
+ protected DebugAppIntf debugAppIntf = null;
+
+ /** The RTCP session associated with this RTP Session */
+ protected RTCPSession rtcpSession = null;
+ /** The thread for receiving RTP packets */
+ protected RTPReceiverThread recvThrd = null;
+ /** The thread for invoking callbacks for RTP packets */
+ protected AppCallerThread appCallerThrd = null;
+
+ /** Lock to protect the packet buffers */
+ final protected Lock pktBufLock = new ReentrantLock();
+ /** Condition variable, to tell the */
+ final protected Condition pktBufDataReady = pktBufLock.newCondition();
+
+ /** Enough is enough, set to true when you want to quit. */
+ protected boolean endSession = false;
+ /** Only one registered application, please */
+ protected boolean registered = false;
+ /** We're busy resolving a SSRC conflict, please try again later */
+ protected boolean conflict = false;
+ /** Number of conflicts observed, exessive number suggests loop in network */
+ protected int conflictCount = 0;
+
+ /** SDES CNAME */
+ protected String cname = null;
+ /** SDES The participant's real name */
+ public String name = null;
+ /** SDES The participant's email */
+ public String email = null;
+ /** SDES The participant's phone number */
+ public String phone = null;
+ /** SDES The participant's location*/
+ public String loc = null;
+ /** SDES The tool the participants is using */
+ public String tool = null;
+ /** SDES A note */
+ public String note = null;
+ /** SDES A priv string, loosely defined */
+ public String priv = null;
+
+ // RFC 4585 stuff. This should live on RTCPSession, but we need to have this
+ // infromation ready by the time the RTCP Session starts
+ // 0 = RFC 3550 , -1 = ACK , 1 = Immediate feedback, 2 = Early RTCP,
+ protected int rtcpMode = 0;
+ protected int fbEarlyThreshold = -1; // group size, immediate -> early transition point
+ protected int fbRegularThreshold = -1; // group size, early -> regular transition point
+ protected int minInterval = 5000; // minimum interval
+ protected int fbMaxDelay = 1000; // how long the information is useful
+ // RTCP bandwidth
+ protected int rtcpBandwidth = -1;
+
+
+ /**
+ * Returns an instance of a <b>unicast</b> RTP session.
+ * Following this you should adjust any settings and then register your application.
+ *
+ * The sockets should have external ip addresses, else your CNAME automatically
+ * generated CNAMe will be bad.
+ *
+ * @param rtpSocket UDP socket to receive RTP communication on
+ * @param rtcpSocket UDP socket to receive RTCP communication on, null if none.
+ */
+ public RTPSession(DatagramSocket rtpSocket, DatagramSocket rtcpSocket) {
+ mcSession = false;
+ rtpSock = rtpSocket;
+ this.generateCNAME();
+ this.generateSsrc();
+ this.rtcpSession = new RTCPSession(this,rtcpSocket);
+
+ // The sockets are not always imediately available?
+ try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); }
+ }
+
+ /**
+ * Returns an instance of a <b>multicast</b> RTP session.
+ * Following this you should register your application.
+ *
+ * The sockets should have external ip addresses, else your CNAME automatically
+ * generated CNAMe will be bad.
+ *
+ * @param rtpSock a multicast socket to receive RTP communication on
+ * @param rtcpSock a multicast socket to receive RTP communication on
+ * @param multicastGroup the multicast group that we want to communicate with.
+ */
+ public RTPSession(MulticastSocket rtpSock, MulticastSocket rtcpSock, InetAddress multicastGroup) throws Exception {
+ mcSession = true;
+ rtpMCSock =rtpSock;
+ mcGroup = multicastGroup;
+ rtpMCSock.joinGroup(mcGroup);
+ rtcpSock.joinGroup(mcGroup);
+ this.generateCNAME();
+ this.generateSsrc();
+ this.rtcpSession = new RTCPSession(this,rtcpSock,mcGroup);
+
+ // The sockets are not always imediately available?
+ try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println("RTPSession sleep failed"); }
+ }
+
+ /**
+ * Registers an application (RTPAppIntf) with the RTP session.
+ * The session will call receiveData() on the supplied instance whenever data has been received.
+ *
+ * Following this you should set the payload type and add participants to the session.
+ *
+ * @param rtpApp an object that implements the RTPAppIntf-interface
+ * @param rtcpApp an object that implements the RTCPAppIntf-interface (optional)
+ * @return -1 if this RTPSession-instance already has an application registered.
+ */
+ public int RTPSessionRegister(RTPAppIntf rtpApp, RTCPAppIntf rtcpApp, DebugAppIntf debugApp) {
+ if(registered) {
+ System.out.println("RTPSessionRegister(): Can\'t register another application!");
+ return -1;
+ } else {
+ registered = true;
+ generateSeqNum();
+ if(RTPSession.rtpDebugLevel > 0) {
+ System.out.println("-> RTPSessionRegister");
+ }
+ this.appIntf = rtpApp;
+ this.rtcpAppIntf = rtcpApp;
+ this.debugAppIntf = debugApp;
+
+ recvThrd = new RTPReceiverThread(this);
+ appCallerThrd = new AppCallerThread(this, rtpApp);
+ recvThrd.start();
+ appCallerThrd.start();
+ rtcpSession.start();
+ return 0;
+ }
+ }
+
+ /**
+ * Send data to all participants registered as receivers, using the current timeStamp,
+ * dynamic sequence number and the current payload type specified for the session.
+ *
+ * @param buf A buffer of bytes, less than 1496 bytes
+ * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise
+ */
+ public long[] sendData(byte[] buf) {
+ byte[][] tmp = {buf};
+ long[][] ret = this.sendData(tmp, null, null, -1, null);
+
+ if(ret != null)
+ return ret[0];
+
+ return null;
+ }
+
+ /**
+ * Send data to all participants registered as receivers, using the specified timeStamp,
+ * sequence number and the current payload type specified for the session.
+ *
+ * @param buf A buffer of bytes, less than 1496 bytes
+ * @param rtpTimestamp the RTP timestamp to be used in the packet
+ * @param seqNum the sequence number to be used in the packet
+ * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise
+ */
+ public long[] sendData(byte[] buf, long rtpTimestamp, long seqNum) {
+ byte[][] tmp = {buf};
+ long[][] ret = this.sendData(tmp, null, null, -1, null);
+
+ if(ret != null)
+ return ret[0];
+
+ return null;
+ }
+
+ /**
+ * Send data to all participants registered as receivers, using the current timeStamp and
+ * payload type. The RTP timestamp will be the same for all the packets.
+ *
+ * @param buffers A buffer of bytes, should not bed padded and less than 1500 bytes on most networks.
+ * @param csrcArray an array with the SSRCs of contributing sources
+ * @param markers An array indicating what packets should be marked. Rarely anything but the first one
+ * @param rtpTimestamp The RTP timestamp to be applied to all packets
+ * @param seqNumbers An array with the sequence number associated with each byte[]
+ * @return null if there was a problem sending the packets, 2-dim array with {RTP Timestamp, Sequence number}
+ */
+ public long[][] sendData(byte[][] buffers, long[] csrcArray, boolean[] markers, long rtpTimestamp, long[] seqNumbers) {
+ if(RTPSession.rtpDebugLevel > 5) {
+ System.out.println("-> RTPSession.sendData(byte[])");
+ }
+
+ // Same RTP timestamp for all
+ if(rtpTimestamp < 0)
+ rtpTimestamp = System.currentTimeMillis();
+
+ // Return values
+ long[][] ret = new long[buffers.length][2];
+
+ for(int i=0; i<buffers.length; i++) {
+ byte[] buf = buffers[i];
+
+ boolean marker = false;
+ if(markers != null)
+ marker = markers[i];
+
+ if(buf.length > 1500) {
+ System.out.println("RTPSession.sendData() called with buffer exceeding 1500 bytes ("+buf.length+")");
+ }
+
+ // Get the return values
+ ret[i][0] = rtpTimestamp;
+ if(seqNumbers == null) {
+ ret[i][1] = getNextSeqNum();
+ } else {
+ ret[i][1] = seqNumbers[i];
+ }
+ // Create a new RTP Packet
+ RtpPkt pkt = new RtpPkt(rtpTimestamp,this.ssrc,(int) ret[i][1],this.payloadType,buf);
+
+ if(csrcArray != null)
+ pkt.setCsrcs(csrcArray);
+
+ pkt.setMarked(marker);
+
+ // Creates a raw packet
+ byte[] pktBytes = pkt.encode();
+
+ //System.out.println(Integer.toString(StaticProcs.bytesToUIntInt(pktBytes, 2)));
+
+ // Pre-flight check, are resolving an SSRC conflict?
+ if(this.conflict) {
+ System.out.println("RTPSession.sendData() called while trying to resolve conflict.");
+ return null;
+ }
+
+
+ if(this.mcSession) {
+ DatagramPacket packet = null;
+
+
+ try {
+ packet = new DatagramPacket(pktBytes,pktBytes.length,this.mcGroup,this.rtpMCSock.getPort());
+ } catch (Exception e) {
+ System.out.println("RTPSession.sendData() packet creation failed.");
+ e.printStackTrace();
+ return null;
+ }
+
+ try {
+ rtpMCSock.send(packet);
+ //Debug
+ if(this.debugAppIntf != null) {
+ this.debugAppIntf.packetSent(1, (InetSocketAddress) packet.getSocketAddress(),
+ new String("Sent multicast RTP packet of size " + packet.getLength() +
+ " to " + packet.getSocketAddress().toString() + " via "
+ + rtpMCSock.getLocalSocketAddress().toString()));
+ }
+ } catch (Exception e) {
+ System.out.println("RTPSession.sendData() multicast failed.");
+ e.printStackTrace();
+ return null;
+ }
+
+ } else {
+ // Loop over recipients
+ Iterator<Participant> iter = partDb.getUnicastReceivers();
+ while(iter.hasNext()) {
+ InetSocketAddress receiver = iter.next().rtpAddress;
+ DatagramPacket packet = null;
+
+ if(RTPSession.rtpDebugLevel > 15) {
+ System.out.println(" Sending to " + receiver.toString());
+ }
+
+ try {
+ packet = new DatagramPacket(pktBytes,pktBytes.length,receiver);
+ } catch (Exception e) {
+ System.out.println("RTPSession.sendData() packet creation failed.");
+ e.printStackTrace();
+ return null;
+ }
+
+ //Actually send the packet
+ try {
+ rtpSock.send(packet);
+ //Debug
+ if(this.debugAppIntf != null) {
+ this.debugAppIntf.packetSent(0, (InetSocketAddress) packet.getSocketAddress(),
+ new String("Sent unicast RTP packet of size " + packet.getLength() +
+ " to " + packet.getSocketAddress().toString() + " via "
+ + rtpSock.getLocalSocketAddress().toString()));
+ }
+ } catch (Exception e) {
+ System.out.println("RTPSession.sendData() unicast failed.");
+ e.printStackTrace();
+ return null;
+ }
+ }
+ }
+
+ //Update our stats
+ this.sentPktCount++;
+ this.sentOctetCount++;
+
+ if(RTPSession.rtpDebugLevel > 5) {
+ System.out.println("<- RTPSession.sendData(byte[]) " + pkt.getSeqNumber());
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Send RTCP App packet to receiver specified by ssrc
+ *
+ *
+ *
+ * Return values:
+ * 0 okay
+ * -1 no RTCP session established
+ * -2 name is not byte[4];
+ * -3 data is not byte[x], where x = 4*y for syme y
+ * -4 type is not a 5 bit unsigned integer
+ *
+ * Note that a return value of 0 does not guarantee delivery.
+ * The participant must also exist in the participant database,
+ * otherwise the message will eventually be deleted.
+ *
+ * @param ssrc of the participant you want to reach
+ * @param type the RTCP App packet subtype, default 0
+ * @param name the ASCII (in byte[4]) representation
+ * @param data the data itself
+ * @return 0 if okay, negative value otherwise (see above)
+ */
+
+ public int sendRTCPAppPacket(long ssrc, int type, byte[] name, byte[] data) {
+ if(this.rtcpSession == null)
+ return -1;
+
+ if(name.length != 4)
+ return -2;
+
+ if(data.length % 4 != 0)
+ return -3;
+
+ if(type > 63 || type < 0 )
+ return -4;
+
+ RtcpPktAPP pkt = new RtcpPktAPP(ssrc, type, name, data);
+ this.rtcpSession.addToAppQueue(ssrc, pkt);
+
+ return 0;
+ }
+ /**
+ * Add a participant object to the participant database.
+ *
+ * If packets have already been received from this user, we will try to update the automatically inserted participant with the information provided here.
+ *
+ * @param p A participant.
+ */
+ public int addParticipant(Participant p) {
+ //For now we make all participants added this way persistent
+ p.unexpected = false;
+ return this.partDb.addParticipant(0, p);
+ }
+
+ /**
+ * Remove a participant from the database. All buffered packets will be destroyed.
+ *
+ * @param p A participant.
+ */
+ public void removeParticipant(Participant p) {
+ partDb.removeParticipant(p);
+ }
+
+ public Iterator<Participant> getUnicastReceivers() {
+ return partDb.getUnicastReceivers();
+ }
+
+ public Enumeration<Participant> getParticipants() {
+ return partDb.getParticipants();
+ }
+
+ /**
+ * End the RTP Session. This will halt all threads and send bye-messages to other participants.
+ *
+ * RTCP related threads may require several seconds to wake up and terminate.
+ */
+ public void endSession() {
+ this.endSession = true;
+
+ // No more RTP packets, please
+ if(this.mcSession) {
+ this.rtpMCSock.close();
+ } else {
+ this.rtpSock.close();
+ }
+
+ // Signal the thread that pushes data to application
+ this.pktBufLock.lock();
+ try { this.pktBufDataReady.signalAll(); } finally {
+ this.pktBufLock.unlock();
+ }
+ // Interrupt what may be sleeping
+ this.rtcpSession.senderThrd.interrupt();
+
+ // Give things a chance to cool down.
+ try { Thread.sleep(50); } catch (Exception e){ };
+
+ this.appCallerThrd.interrupt();
+
+ // Give things a chance to cool down.
+ try { Thread.sleep(50); } catch (Exception e){ };
+
+ if(this.rtcpSession != null) {
+ // No more RTP packets, please
+ if(this.mcSession) {
+ this.rtcpSession.rtcpMCSock.close();
+ } else {
+ this.rtcpSession.rtcpSock.close();
+ }
+ }
+ }
+
+
+ /**
+ * Check whether this session is ending.
+ *
+ * @return true if session and associated threads are terminating.
+ */
+ boolean isEnding() {
+ return this.endSession;
+ }
+
+ /**
+ * Overrides CNAME, used for outgoing RTCP packets.
+ *
+ * @param cname a string, e.g. username@hostname. Must be unique for session.
+ */
+ public void CNAME(String cname) {
+ this.cname = cname;
+ }
+
+ /**
+ * Get the current CNAME, used for outgoing SDES packets
+ */
+ public String CNAME() {
+ return this.cname;
+ }
+
+ public long getSsrc() {
+ return this.ssrc;
+ }
+
+ private void generateCNAME() {
+ String hostname;
+
+ if(this.mcSession) {
+ hostname = this.rtpMCSock.getLocalAddress().getCanonicalHostName();
+ } else {
+ hostname = this.rtpSock.getLocalAddress().getCanonicalHostName();
+ }
+
+ //if(hostname.equals("0.0.0.0") && System.getenv("HOSTNAME") != null) {
+ // hostname = System.getenv("HOSTNAME");
+ //}
+
+ cname = System.getProperty("user.name") + "@" + hostname;
+ }
+
+ /**
+ * Change the RTP socket of the session.
+ * Peers must be notified through SIP or other signalling protocol.
+ * Only valid if this is a unicast session to begin with.
+ *
+ * @param newSock integer for new port number, check it is free first.
+ */
+ public int updateRTPSock(DatagramSocket newSock) {
+ if(!mcSession) {
+ rtpSock = newSock;
+ return 0;
+ } else {
+ System.out.println("Can't switch from multicast to unicast.");
+ return -1;
+ }
+ }
+
+ /**
+ * Change the RTCP socket of the session.
+ * Peers must be notified through SIP or other signalling protocol.
+ * Only valid if this is a unicast session to begin with.
+ *
+ * @param newSock the new unicast socket for RTP communication.
+ */
+ public int updateRTCPSock(DatagramSocket newSock) {
+ if(!mcSession) {
+ this.rtcpSession.rtcpSock = newSock;
+ return 0;
+ } else {
+ System.out.println("Can't switch from multicast to unicast.");
+ return -1;
+ }
+ }
+
+ /**
+ * Change the RTP multicast socket of the session.
+ * Peers must be notified through SIP or other signalling protocol.
+ * Only valid if this is a multicast session to begin with.
+ *
+ * @param newSock the new multicast socket for RTP communication.
+ */
+ public int updateRTPSock(MulticastSocket newSock) {
+ if(mcSession) {
+ this.rtpMCSock = newSock;
+ return 0;
+ } else {
+ System.out.println("Can't switch from unicast to multicast.");
+ return -1;
+ }
+ }
+
+ /**
+ * Change the RTCP multicast socket of the session.
+ * Peers must be notified through SIP or other signalling protocol.
+ * Only valid if this is a multicast session to begin with.
+ *
+ * @param newSock the new multicast socket for RTCP communication.
+ */
+ public int updateRTCPSock(MulticastSocket newSock) {
+ if(mcSession) {
+ this.rtcpSession.rtcpMCSock = newSock;
+ return 0;
+ } else {
+ System.out.println("Can't switch from unicast to multicast.");
+ return -1;
+ }
+ }
+
+ /**
+ * Update the payload type used for the session. It is represented as a 7 bit integer, whose meaning must be negotiated elsewhere (see IETF RFCs <a href="http://www.ietf.org/rfc/rfc3550.txt">3550</a> and <a href="http://www.ietf.org/rfc/rfc3550.txt">3551</a>)
+ *
+ * @param payloadT an integer representing the payload type of any subsequent packets that are sent.
+ */
+ public int payloadType(int payloadT) {
+ if(payloadT > 128 || payloadT < 0) {
+ return -1;
+ } else {
+ this.payloadType = payloadT;
+ return this.payloadType;
+ }
+ }
+
+ /**
+ * Get the payload type that is currently used for outgoing RTP packets.
+ *
+ * @return payload type as integer
+ */
+ public int payloadType() {
+ return this.payloadType;
+ }
+
+ /**
+ * Should packets from unknown participants be returned to the application? This can be dangerous.
+ *
+ * @param doAccept packets from participants not added by the application.
+ */
+ public void naivePktReception(boolean doAccept) {
+ naiveReception = doAccept;
+ }
+
+ /**
+ * Are packets from unknown participants returned to the application?
+ *
+ * @return whether we accept packets from participants not added by the application.
+ */
+ public boolean naivePktReception() {
+ return naiveReception;
+ }
+
+ /**
+ * Set the number of RTP packets that should be buffered when a packet is
+ * missing or received out of order. Setting this number high increases
+ * the chance of correctly reordering packets, but increases latency when
+ * a packet is dropped by the network.
+ *
+ * Packets that arrive in order are not affected, they are passed straight
+ * to the application.
+ *
+ * The maximum delay is numberofPackets * packet rate , where the packet rate
+ * depends on the codec and profile used by the sender.
+ *
+ * Valid values:
+ * >0 - The maximum number of packets (based on RTP Timestamp) that may accumulate
+ * 0 - All valid packets received in order will be given to the application
+ * -1 - All valid packets will be given to the application
+ *
+ * @param behavior the be
+ * @return the behavior set, unchanged in the case of a erroneous value
+ */
+ public int packetBufferBehavior(int behavior) {
+ if(behavior > -2) {
+ this.pktBufBehavior = behavior;
+ // Signal the thread that pushes data to application
+ this.pktBufLock.lock();
+ try { this.pktBufDataReady.signalAll(); } finally {
+ this.pktBufLock.unlock();
+ }
+ return this.pktBufBehavior;
+ } else {
+ return this.pktBufBehavior;
+ }
+ }
+
+ /**
+ * The number of RTP packets that should be buffered when a packet is
+ * missing or received out of order. A high number increases the chance
+ * of correctly reordering packets, but increases latency when a packet is
+ * dropped by the network.
+ *
+ * A negative value disables the buffering, out of order packets will simply be dropped.
+ *
+ * @return the maximum number of packets that can accumulate before the first is returned
+ */
+ public int packetBufferBehavior() {
+ return this.pktBufBehavior;
+ }
+
+ /**
+ * Set whether the stack should operate in RFC 4585 mode.
+ *
+ * This will automatically call adjustPacketBufferBehavior(-1),
+ * i.e. disable all RTP packet buffering in jlibrtp,
+ * and disable frame reconstruction
+ *
+ * @param rtcpAVPFIntf the in
+ */
+ public int registerAVPFIntf(RTCPAVPFIntf rtcpAVPFIntf, int maxDelay, int earlyThreshold, int regularThreshold ) {
+ if(this.rtcpSession != null) {
+ this.packetBufferBehavior(-1);
+ this.frameReconstruction = false;
+ this.rtcpAVPFIntf = rtcpAVPFIntf;
+ this.fbEarlyThreshold = earlyThreshold;
+ this.fbRegularThreshold = regularThreshold;
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Unregisters the RTCP AVPF interface, thereby going from
+ * RFC 4585 mode to RFC 3550
+ *
+ * You still have to adjust packetBufferBehavior() and
+ * frameReconstruction.
+ *
+ */
+ public void unregisterAVPFIntf() {
+ this.fbEarlyThreshold = -1;
+ this.fbRegularThreshold = -1;
+ this.rtcpAVPFIntf = null;
+ }
+
+ /**
+ * Enable / disable frame reconstruction in the packet buffers.
+ * This is only relevant if getPacketBufferBehavior > 0;
+ *
+ * Default is true.
+ */
+ public void frameReconstruction(boolean toggle) {
+ this.frameReconstruction = toggle;
+ }
+
+ /**
+ * Whether the packet buffer will attempt to reconstruct
+ * packet automatically.
+ *
+ * @return the status
+ */
+ public boolean frameReconstruction() {
+ return this.frameReconstruction;
+ }
+
+ /**
+ * The bandwidth currently allocated to the session,
+ * in bytes per second. The default is 8000.
+ *
+ * This value is not enforced and currently only
+ * used to calculate the RTCP interval to ensure the
+ * control messages do not exceed 5% of the total bandwidth
+ * described here.
+ *
+ * Since the actual value may change a conservative
+ * estimate should be used to avoid RTCP flooding.
+ *
+ * see rtcpBandwidth(void)
+ *
+ * @return current bandwidth setting
+ */
+ public int sessionBandwidth() {
+ return this.bandwidth;
+ }
+
+ /**
+ * Set the bandwidth of the session.
+ *
+ * See sessionBandwidth(void) for details.
+ *
+ * @param bandwidth the new value requested, in bytes per second
+ * @return the actual value set
+ */
+ public int sessionBandwidth(int bandwidth) {
+ if(bandwidth < 1) {
+ this.bandwidth = 8000;
+ } else {
+ this.bandwidth = bandwidth;
+ }
+ return this.bandwidth;
+ }
+
+
+ /**
+ * RFC 3550 dictates that 5% of the total bandwidth,
+ * as set by sessionBandwidth, should be dedicated
+ * to RTCP traffic. This
+ *
+ * This should normally not be done, but is permissible in
+ * conjunction with feedback (RFC 4585) and possibly
+ * other profiles.
+ *
+ * Also see sessionBandwidth(void)
+ *
+ * @return current RTCP bandwidth setting, -1 means not in use
+ */
+ public int rtcpBandwidth() {
+ return this.rtcpBandwidth;
+ }
+
+ /**
+ * Set the RTCP bandwidth, see rtcpBandwidth(void) for details.
+ *
+ * This function must be
+ *
+ * @param bandwidth the new value requested, in bytes per second or -1 to disable
+ * @return the actual value set
+ */
+ public int rtcpBandwidth(int bandwidth) {
+ if(bandwidth < -1) {
+ this.rtcpBandwidth = -1;
+ } else {
+ this.rtcpBandwidth = bandwidth;
+ }
+ return this.rtcpBandwidth;
+ }
+
+ /********************************************* Feedback message stuff ***************************************/
+
+ /**
+ * Adds a Picture Loss Indication to the feedback queue
+ *
+ * @param ssrcMediaSource
+ * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
+ */
+ public int fbPictureLossIndication(long ssrcMediaSource) {
+ int ret = 0;
+
+ if(this.rtcpAVPFIntf == null)
+ return -1;
+
+ RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
+ pkt.makePictureLossIndication();
+ ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
+ if(ret == 0)
+ this.rtcpSession.wakeSenderThread(ssrcMediaSource);
+ return ret;
+ }
+
+ /**
+ * Adds a Slice Loss Indication to the feedback queue
+ *
+ * @param ssrcMediaSource
+ * @param sliFirst macroblock (MB) address of the first lost macroblock
+ * @param sliNumber number of lost macroblocks
+ * @param sliPictureId six least significant bits of the codec-specific identif
+ * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
+ */
+ public int fbSlicLossIndication(long ssrcMediaSource, int[] sliFirst, int[] sliNumber, int[] sliPictureId) {
+ int ret = 0;
+ if(this.rtcpAVPFIntf == null)
+ return -1;
+
+ RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
+ pkt.makeSliceLossIndication(sliFirst, sliNumber, sliPictureId);
+
+ ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
+ if(ret == 0)
+ this.rtcpSession.wakeSenderThread(ssrcMediaSource);
+ return ret;
+ }
+
+ /**
+ * Adds a Reference Picture Selection Indication to the feedback queue
+ *
+ * @param ssrcMediaSource
+ * @param bitPadding number of padded bits at end of bitString
+ * @param payloadType RTP payload type for codec
+ * @param bitString RPSI information as natively defined by the video codec
+ * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
+ */
+ public int fbRefPictureSelIndic(long ssrcMediaSource, int bitPadding, int payloadType, byte[] bitString) {
+ int ret = 0;
+
+ if(this.rtcpAVPFIntf == null)
+ return -1;
+
+ RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
+ pkt.makeRefPictureSelIndic(bitPadding, payloadType, bitString);
+ ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
+ if(ret == 0)
+ this.rtcpSession.wakeSenderThread(ssrcMediaSource);
+ return ret;
+ }
+
+ /**
+ * Adds a Picture Loss Indication to the feedback queue
+ *
+ * @param ssrcMediaSource
+ * @param bitString the original application message
+ * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
+ */
+ public int fbAppLayerFeedback(long ssrcMediaSource, byte[] bitString) {
+ int ret = 0;
+
+ if(this.rtcpAVPFIntf == null)
+ return -1;
+
+ RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
+ pkt.makeAppLayerFeedback(bitString);
+ ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
+ if(ret == 0)
+ this.rtcpSession.wakeSenderThread(ssrcMediaSource);
+ return ret;
+ }
+
+
+ /**
+ * Adds a RTP Feedback packet to the feedback queue.
+ *
+ * These are mostly used for NACKs.
+ *
+ * @param ssrcMediaSource
+ * @param FMT the Feedback Message Subtype
+ * @param PID RTP sequence numbers of lost packets
+ * @param BLP bitmask of following lost packets, shared index with PID
+ * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
+ */
+ public int fbPictureLossIndication(long ssrcMediaSource, int FMT, int[] PID, int[] BLP) {
+ int ret = 0;
+
+ if(this.rtcpAVPFIntf == null)
+ return -1;
+
+ RtcpPktRTPFB pkt = new RtcpPktRTPFB(this.ssrc, ssrcMediaSource, FMT, PID, BLP);
+ ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
+ if(ret == 0)
+ this.rtcpSession.wakeSenderThread(ssrcMediaSource);
+ return ret;
+ }
+
+ /**
+ * Fetches the next sequence number for RTP packets.
+ * @return the next sequence number
+ */
+ private int getNextSeqNum() {
+ seqNum++;
+ // 16 bit number
+ if(seqNum > 65536) {
+ seqNum = 0;
+ }
+ return seqNum;
+ }
+
+ /**
+ * Initializes a random variable
+ *
+ */
+ private void createRandom() {
+ this.random = new Random(System.currentTimeMillis() + Thread.currentThread().getId()
+ - Thread.currentThread().hashCode() + this.cname.hashCode());
+ }
+
+
+ /**
+ * Generates a random sequence number
+ */
+ private void generateSeqNum() {
+ if(this.random == null)
+ createRandom();
+
+ seqNum = this.random.nextInt();
+ if(seqNum < 0)
+ seqNum = -seqNum;
+ while(seqNum > 65535) {
+ seqNum = seqNum / 10;
+ }
+ }
+
+ /**
+ * Generates a random SSRC
+ */
+ private void generateSsrc() {
+ if(this.random == null)
+ createRandom();
+
+ // Set an SSRC
+ this.ssrc = this.random.nextInt();
+ if(this.ssrc < 0) {
+ this.ssrc = this.ssrc * -1;
+ }
+ }
+
+ /**
+ * Resolve an SSRC conflict.
+ *
+ * Also increments the SSRC conflict counter, after 5 conflicts
+ * it is assumed there is a loop somewhere and the session will
+ * terminate.
+ *
+ */
+ protected void resolveSsrcConflict() {
+ System.out.println("!!!!!!! Beginning SSRC conflict resolution !!!!!!!!!");
+ this.conflictCount++;
+
+ if(this.conflictCount < 5) {
+ //Don't send any more regular packets out until we have this sorted out.
+ this.conflict = true;
+
+ //Send byes
+ rtcpSession.sendByes();
+
+ //Calculate the next delay
+ rtcpSession.calculateDelay();
+
+ //Generate a new Ssrc for ourselves
+ generateSsrc();
+
+ //Get the SDES packets out faster
+ rtcpSession.initial = true;
+
+ this.conflict = false;
+ System.out.println("SSRC conflict resolution complete");
+
+ } else {
+ System.out.println("Too many conflicts. There is probably a loop in the network.");
+ this.endSession();
+ }
+ }
+}